diff --git a/Assets/Scripts/WebSocketClient.cs b/Assets/Scripts/WebSocketClient.cs index 66c82da..c922f3c 100644 --- a/Assets/Scripts/WebSocketClient.cs +++ b/Assets/Scripts/WebSocketClient.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json.Linq; public class WebSocketClient : MonoBehaviour { private WebSocket websocket; - private readonly string serverUrl = "ws://localhost:8080"; + private readonly string serverUrl = "ws://localhost:8010"; async void Start() { diff --git a/CMakeLists.txt b/CMakeLists.txt index d039238..862b06d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,9 @@ set(Boost_USE_STATIC_RUNTIME OFF) set(Boost_ROOT /opt/homebrew/Cellar/boost/1.86.0_2) find_package(Boost REQUIRED COMPONENTS system) +# 添加 libcurl +find_package(CURL REQUIRED) + # 添加 nlohmann_json include(FetchContent) FetchContent_Declare( @@ -54,6 +57,8 @@ set(LIB_SOURCES src/types/VehicleData.cpp src/vehicle/ControllableVehicles.cpp src/config/SystemConfig.cpp + src/config/IntersectionConfig.cpp + src/detector/TrafficLightDetector.cpp ) # 创建主库 @@ -63,12 +68,14 @@ target_include_directories(${PROJECT_NAME}_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} + ${CURL_INCLUDE_DIRS} ) target_link_libraries(${PROJECT_NAME}_lib PUBLIC Boost::system nlohmann_json::nlohmann_json + CURL::libcurl ) # 创建主可执行文件 @@ -124,7 +131,7 @@ configure_file( ) configure_file( - ${CMAKE_SOURCE_DIR}/config/controllable_vehicles.json - ${CMAKE_BINARY_DIR}/bin/config/controllable_vehicles.json + ${CMAKE_SOURCE_DIR}/config/vehicle_speed_limits.json + ${CMAKE_BINARY_DIR}/bin/config/vehicle_speed_limits.json COPYONLY ) \ No newline at end of file diff --git a/config/intersections.json b/config/intersections.json new file mode 100644 index 0000000..276eb83 --- /dev/null +++ b/config/intersections.json @@ -0,0 +1,28 @@ +{ + "intersections": [ + { + "id": "intersection_001", + "name": "无人车与特勤车交叉路口", + "trafficLightId": "TL001", + "position": { + "longitude": 120.088003, + "latitude": 36.361999, + "altitude": 12.5 + }, + "radius": 100.0, + "stopDistance": 50.0 + }, + { + "id": "intersection_002", + "name": "无人车与飞机交叉路口", + "trafficLightId": "TL002", + "position": { + "longitude": 120.088303, + "latitude": 36.361999, + "altitude": 12.5 + }, + "radius": 100.0, + "stopDistance": 50.0 + } + ] +} \ No newline at end of file diff --git a/config/system_config.json b/config/system_config.json index 62c97ce..6fed7ae 100644 --- a/config/system_config.json +++ b/config/system_config.json @@ -10,11 +10,13 @@ }, "data_source": { "host": "localhost", - "port": 8080, + "port": 8081, "aircraft_path": "/api/getCurrentFlightPositions", "vehicle_path": "/api/getCurrentVehiclePositions", + "traffic_light_path": "/api/getTrafficLightSignals", "refresh_interval_ms": 1000, - "timeout_ms": 5000 + "timeout_ms": 5000, + "read_timeout_ms": 2000 }, "warning": { "warning_interval_ms": 1000, @@ -26,7 +28,8 @@ "ping_interval_ms": 30000, "position_update": { "aircraft_interval_ms": 300, - "vehicle_interval_ms": 500 + "vehicle_interval_ms": 500, + "traffic_light_interval_ms": 1000 } }, "collision_detection": { @@ -51,7 +54,7 @@ } }, "logging": { - "level": "info", + "level": "debug", "file": "logs/system.log", "max_size_mb": 10, "max_files": 5, diff --git a/config/controllable_vehicles.json b/config/vehicle_speed_limits.json similarity index 72% rename from config/controllable_vehicles.json rename to config/vehicle_speed_limits.json index 8333a5b..a4d0c61 100644 --- a/config/controllable_vehicles.json +++ b/config/vehicle_speed_limits.json @@ -1,17 +1,17 @@ { "vehicles": [ { - "vehicleNo": "VEH001", + "vehicleNo": "QN001", "ip": "192.168.1.101", "port": 8080 }, { - "vehicleNo": "VEH002", + "vehicleNo": "QN002", "ip": "192.168.1.102", "port": 8080 }, { - "vehicleNo": "VEH003", + "vehicleNo": "TQ001", "ip": "192.168.1.103", "port": 8080 } diff --git a/docs/traffic_light_api.md b/docs/traffic_light_api.md new file mode 100644 index 0000000..fdc2740 --- /dev/null +++ b/docs/traffic_light_api.md @@ -0,0 +1,41 @@ +# 红绿灯信号接口文档 + +## 1. 上报接口定义 + +### 1.1 信号机实时状态上报 + +#### Topic + +| Topic | cuse/v2/{tenantCode}/{deviceId}/data | +|-------|--------------------------------------| +| 消息发送方 | 设备 | +| 消息接收方 | 物联网平台 | + +#### 参数说明 + +| 参数 | 必选/可选 | 类型 | 描述 | +|------|-----------|------|------| +| topic | 必选 | String | 设备参数上报固定为"deviceReq" | +| encryptFlag | 必选 | Short | 加密方式,0-不加密 1-AES128 加密 | +| serviceId | 必选 | String | 上报服务的标识 | +| reportTime | 必选 | Long | 设备参数上报时间 | +| serviceType | 必选 | String | 服务类型(设备上报数据类型) 固定值"ras-traffic-lights" | +| serviceData | 必选 | Object | 具体上报的数据集合,若采用 SM4 加密方式,则进行数据加密 | + +#### serviceData + +| 参数 | 必选/可选 | 类型 | 描述 | +|------|-----------|------|------| +| collectTime | 必选 | Long | 上报时间 | +| deviceSn | 可选 | String | 终端设备序列号 | +| deviceName | 可选 | String | 终端设备名称 | +| manufacturer | 可选 | String | 厂商编码 | +| modelName | 可选 | String | 设备类型名称 | +| trafficLightId | 必选 | String | 红绿灯设备 ID | +| signalStatus | 必选 | String | 信号灯状态 | +| longitude | 必选 | Double | 经度坐标,14位小数 | +| latitude | 必选 | Double | 纬度坐标,14位小数 | +| altitude | 必选 | Double | 海拔高度,6位小数 | +| areaId | 必选 | String | 信号机所处区域 ID | +| intersection | 必选 | String | 信号机所处路口名称 | +| generateTime | 必选 | Long | unix 时间戳 | diff --git a/docs/vehicle_api.md b/docs/vehicle_api.md index b6ece58..2961403 100644 --- a/docs/vehicle_api.md +++ b/docs/vehicle_api.md @@ -43,3 +43,71 @@ | 4 | time | 时间戳 | double | 是 | | 5 | direction | 方向 | double | 否 | | 6 | speed | 速度 | double | 否 | + +## 第2章 无人车控制接口对接方案 + +### 2.1 无人车控制指令 + +2.1.1 接口地址: + +2.1.2 请求方法:POST + +2.1.3 请求参数: + +| 序号 | 字段名字 | 是否必选 | 类型 | 说明 | + +|-----|------|------|----------|----------| + +| 1 | messageName | 是 | string | messageName 是 EmergencyControl 固定值 | + +| 2 | messageUniqueId | 是 | string | messageUniqueId 是 string | + +| 3 | messageTimestamp | 是 | string | messageTimestamp 是 string 2023-04-24 17:15:44.276341 | + +| 4 | origin | 可选 | string | "Vehicle" | + +| 5 | vehicleID | 是 | string | 根据项目车号的格式 | + +| 6 | operationType | 是 | string | "RESUME" or "STOP" | + +| 7 | StopType | 是 | string | "jerk" : 急停 "slow" : 缓停 | + +| 8 | reason | 可选 | string | 停止/恢复的原因 | + +2.1.4 请求参数: + +``` http + +request : { + +"messageName" : "EmergencyControl", + +"messageUniqueId" : "68f79d1a-e27f-11ed-b28c-2cf05d9c2649", "messageTimestamp":"2023-04-24 17:15:44.276341", + +"origin" : "Vehicle", + +"vehicleID" : "A001", + +"operationType" : "RESUME", # RESUME | STOP + +"StopType" : "jerk" + +} + +``` + +2.1.5 返回格式:以 JSON 格式返回数据 + +``` http + +response : { + +"code" : 200, + +"msg" : "success", + +"error" : "" + +} + +``` diff --git a/docs/warning_strategy.md b/docs/warning_strategy.md new file mode 100644 index 0000000..ad4798e --- /dev/null +++ b/docs/warning_strategy.md @@ -0,0 +1,133 @@ +# 机场场景告警策略需求文档 + +## 1. 控制指令定义 + +系统对无人车的控制分为三种指令类型: + +### 1.1 信号灯指令(SIGNAL) + +- **目的**:响应交通信号灯 +- **触发条件**:接收到红绿灯状态变化 +- **执行动作**:根据信号灯状态停车或通行 +- **优先级**:高(最高优先级) + +### 1.2 告警指令(ALERT) + +- **目的**:应对紧急情况 +- **触发条件**:检测到直接碰撞风险 +- **执行动作**:强制停车或紧急避让 +- **优先级**:中 + +### 1.3 预警指令(WARNING) + +- **目的**:提前告知可能的风险 +- **触发条件**:进入监测区域但尚未到达关键区域 +- **执行动作**:降低速度,提高警惕 +- **优先级**:低 + +### 1.4 恢复指令(RESUME) + +- **目的**:恢复到正常行驶状态 +- **触发条件**:航空器或特勤车辆通过后,距离路口 50 m后 +- **执行动作**:恢复到正常行驶状态 +- **优先级**:低 + +## 2. 红绿灯条件下的告警策略 + +### 2.1 适用场景 + +- 无人车接近带有红绿灯的交叉路口时的行为控制 +- 主要针对机场内各主要道路交叉口 + +### 2.2 告警条件 + +1. **距离阈值**: + - 车辆进入路口 200 米范围内开始监测 + - 在距离路口 50 米处为停车点 + +2. **信号灯状态响应**: + - 红灯:持续发送 SIGNAL 指令,要求停车 + - 绿灯:持续发送 SIGNAL 指令,允许通过 + +## 3. 航空器或特勤车辆,和无人车垂直经过交叉路口的告警策略 + +### 3.1 适用场景 + +- 滑行道与车行道的交叉路口 +- 航空器或特勤车辆,和无人车垂直交叉,预判可能冲突的情况 + +### 3.2 告警条件 + +1. **距离监测**: + - 航空器或特勤车辆通过路口,无人车距离路口阈值:100 米 + +2. **控制策略**: + - 预警阶段(100m-50m):发送 WARNING 指令 + - 告警阶段(50m 以内):发送 ALERT 指令,要求立即停车 + - 航空器或特勤车辆通过后,距离路口大于 50 m后,发送 RESUME 指令,允许恢复正常行驶 + +3. **优先级规则**: + - 航空器或特勤车辆具有绝对优先权 + - 无人车必须避让航空器或特勤车辆 + +## 4. 航空器出库时的告警策略 + +### 4.1 适用场景 + +- 航空器从机库或维修区驶出 +- 与服务区道路的交叉路口 + +### 4.2 告警条件 + +1. **距离监测**: + - 航空器通过路口,无人车距离路口阈值:100 米 + +2. **控制策略**: + - 预警阶段(100m-50m):发送 WARNING 指令 + - 告警阶段(50m 以内):发送 ALERT 指令,要求立即停车 + - 航空器离开后,距离路口大于 50 m后,发送 RESUME 指令,允许恢复正常行驶 + +### 4.3 特殊考虑 + +1. **多航空器情况**: + - 就近原则处理 + - 使用 ALERT 指令确保安全间距 + +## 5. 通用要求 + +### 5.1 指令优先级处理 + +1. SIGNAL 指令(最高优先级) + - 立即执行 + - 覆盖其他所有指令 + - 红绿灯控制具有最高优先级,确保交通安全 + +2. ALERT 指令(中等优先级) + - 可被 SIGNAL 指令覆盖 + - 优先于 WARNING 指令 + - 用于碰撞风险等紧急情况 + +3. WARNING 指令(最低优先级) + - 可被其他指令覆盖 + - 用于预防性控制 + - 在无更高优先级指令时执行 + +4. RESUME 指令(最低优先级) + - 可被其他指令覆盖 + - 用于恢复到正常行驶状态 + +### 5.2 系统响应时间 + +- 告警信号处理延迟 < 100ms +- 控制指令执行延迟 < 200ms +- 状态反馈延迟 < 300ms + +### 5.3 降级处理 + +- 在通信中断时,无人车应自动降速或停车 +- 在传感器失效时,扩大安全距离 + +### 5.4 提前量 + +- 无人车在距离路口 110 米处开始监测,提前 10 米发送 WARNING 指令 +- 无人车在距离路口 60 米处开始监测,提前 10 米发送 ALERT 指令 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index d62de69..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# 收集源文件 -set(SOURCES - collector/DataCollector.cpp - core/System.cpp - detector/CollisionDetector.cpp - mock/MockDataService.cpp - network/HTTPDataSource.cpp - spatial/AirportBounds.cpp - spatial/CoordinateConverter.cpp - types/BasicTypes.cpp - types/VehicleData.cpp -) - -# ... 其余部分保持不变 ... \ No newline at end of file diff --git a/src/collector/DataCollector.cpp b/src/collector/DataCollector.cpp index 374269f..f21b081 100644 --- a/src/collector/DataCollector.cpp +++ b/src/collector/DataCollector.cpp @@ -1,6 +1,8 @@ #include "collector/DataCollector.h" #include "network/HTTPDataSource.h" #include "utils/Logger.h" +#include +#include DataCollector::DataCollector() : lastSuccessfulFetch_(std::chrono::steady_clock::now()) // 初始化时间戳 @@ -62,19 +64,38 @@ void DataCollector::stop() { } void DataCollector::collectLoop() { + Logger::debug("数据采集循环启动,刷新间隔: ", dataSourceConfig_.refresh_interval_ms, "ms"); + auto next_collection_time = std::chrono::steady_clock::now(); + while (running_) { loopCount_++; + Logger::debug("开始第 ", loopCount_.load(), " 次数据采集循环"); bool success = fetchData(); if (!success) { - Logger::warning("Failed to fetch data in loop ", loopCount_.load()); + Logger::warning("第 ", loopCount_.load(), " 次数据采集失败"); checkTimeout(); // 每次获取失败都检查超时 } // 即使成功获取数据,也定期检查超时状态 checkTimeout(); - std::this_thread::sleep_for(std::chrono::milliseconds(dataSourceConfig_.refresh_interval_ms)); + // 计算下一次采集时间 + next_collection_time += std::chrono::milliseconds(dataSourceConfig_.refresh_interval_ms); + auto now = std::chrono::steady_clock::now(); + + if (next_collection_time > now) { + auto wait_time = std::chrono::duration_cast( + next_collection_time - now).count(); + if (wait_time > 0) { + Logger::debug("等待 ", wait_time, "ms 进行下一次采集"); + std::this_thread::sleep_for(std::chrono::milliseconds(wait_time)); + } + } else { + // 如果已经超过了下一次采集时间,立即重置 + Logger::warning("数据采集延迟,重置采集时间"); + next_collection_time = std::chrono::steady_clock::now(); + } } } @@ -83,15 +104,22 @@ void DataCollector::checkTimeout() { auto elapsed = std::chrono::duration_cast( now - lastSuccessfulFetch_).count(); - if (elapsed > dataSourceConfig_.timeout_ms) { - // 检查是否达到告警间隔 - auto warning_elapsed = std::chrono::duration_cast( - now - last_warning_time_).count(); + // 检查是否达到告警间隔 + auto warning_elapsed = std::chrono::duration_cast( + now - last_warning_time_).count(); - if (warning_elapsed >= warnConfig_.warning_interval_ms) { + if (warning_elapsed >= warnConfig_.warning_interval_ms) { + // 如果超过了超时阈值,发送警告 + if (elapsed > dataSourceConfig_.read_timeout_ms) { sendTimeoutWarning(elapsed); last_warning_time_ = now; - Logger::warning("Data source timeout: No response for ", elapsed, "ms"); + + // 只在非测试环境中记录警告日志 + if (system_) { + Logger::warning("Data source timeout: No response for ", elapsed, "ms"); + } else { + Logger::debug("Data source timeout in test environment: ", elapsed, "ms"); + } } } } @@ -110,97 +138,102 @@ void DataCollector::resetTimeout() { } void DataCollector::sendTimeoutWarning(int64_t elapsed_ms) { - network::TimeoutWarningMessage msg; - msg.type = "data_source_timeout"; // 确保类型正确 - msg.host = dataSourceConfig_.host; - msg.port = dataSourceConfig_.port; - msg.elapsed_ms = elapsed_ms; - msg.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); - - if (System::instance()) { - System::instance()->broadcastTimeoutWarning(msg); - Logger::debug("Sent timeout warning: host=", msg.host, - ", port=", msg.port, - ", elapsed=", msg.elapsed_ms, "ms"); - } else { - Logger::error("Failed to send timeout warning: System instance not available"); + if (!system_) { + Logger::debug("System not set, skipping timeout warning"); + return; } + + network::TimeoutWarningMessage msg; + msg.source = "data_source"; + msg.elapsed_ms = elapsed_ms; + // 如果超时时间超过了读取超时阈值,就是读取超时 + msg.is_read_timeout = (elapsed_ms > dataSourceConfig_.read_timeout_ms); + + system_->broadcastTimeoutWarning(msg); + Logger::debug("Sent timeout warning: source=", msg.source, + " elapsed=", msg.elapsed_ms, "ms", + " is_read_timeout=", msg.is_read_timeout ? "true" : "false", + " read_timeout=", dataSourceConfig_.read_timeout_ms, + " connect_timeout=", dataSourceConfig_.timeout_ms); } bool DataCollector::fetchData() { + bool success = true; + std::vector newAircraft; + std::vector newVehicles; + std::vector newSignals; + + Logger::debug("开始获取数据..."); + + if (dataSource_->fetchAircraftData(newAircraft)) { + std::lock_guard lock(cacheMutex_); + // 更新时间戳为数据源中最新的时间戳 + int64_t maxTimestamp = 0; + for (const auto& aircraft : newAircraft) { + if (aircraft.timestamp > maxTimestamp) { + maxTimestamp = aircraft.timestamp; + } + } + if (maxTimestamp > 0) { + lastAircraftTimestamp_.store(maxTimestamp); + Logger::debug("更新航空器时间戳为: ", lastAircraftTimestamp_.load()); + } + aircraftCache_ = std::move(newAircraft); + Logger::debug("成功获取航空器数据: ", aircraftCache_.size(), " 架"); + } else { + Logger::warning("获取航空器数据失败"); + success = false; + } + + if (dataSource_->fetchVehicleData(newVehicles)) { + std::lock_guard lock(cacheMutex_); + // 更新时间戳为数据源中最新的时间戳 + int64_t maxTimestamp = 0; + for (const auto& vehicle : newVehicles) { + if (vehicle.timestamp > maxTimestamp) { + maxTimestamp = vehicle.timestamp; + } + } + if (maxTimestamp > 0) { + lastVehicleTimestamp_.store(maxTimestamp); + Logger::debug("更新车辆时间戳为: ", lastVehicleTimestamp_.load()); + } + vehicleCache_ = std::move(newVehicles); + Logger::debug("成功获取车辆数据: ", vehicleCache_.size(), " 辆"); + } else { + Logger::warning("获取车辆数据失败"); + success = false; + } + + if (dataSource_->fetchTrafficLightSignals(newSignals)) { + std::lock_guard lock(cacheMutex_); + trafficLightCache_ = std::move(newSignals); + Logger::debug("成功获取红绿灯数据: ", trafficLightCache_.size(), " 个"); + } else { + Logger::warning("获取红绿灯数据失败"); + success = false; + } + + if (success) { + resetTimeout(); // 重置超时计时器 + Logger::debug("数据获取完成"); + } else { + Logger::warning("数据获取过程中存在错误"); + } + + return success; +} + +std::vector DataCollector::getTrafficLightSignals() { + std::lock_guard lock(cacheMutex_); + return trafficLightCache_; +} + +bool DataCollector::fetchTrafficLightSignals(std::vector& signals) { if (!dataSource_) { return false; } - - try { - bool success = false; - - // 获取航空器数据 - std::vector aircraft; - if (dataSource_->fetchAircraftData(aircraft)) { - std::lock_guard lock(cacheMutex_); - // 更新运动信息 - for (auto& a : aircraft) { - auto it = lastAircraftPositions_.find(a.id); - if (it != lastAircraftPositions_.end()) { - Logger::debug("Updating motion for aircraft ", a.id, - ", prev pos: lat=", it->second.geo.latitude, - ", lon=", it->second.geo.longitude, - ", time=", it->second.timestamp, - ", curr pos: lat=", a.geo.latitude, - ", lon=", a.geo.longitude, - ", time=", a.timestamp); - - // 复制历史记录 - a.copyHistoryFrom(it->second); - // 更新运动信息 - a.updateMotion(a.geo, a.timestamp); - } else { - Logger::debug("First position for aircraft ", a.id); - a.updateMotion(a.geo, a.timestamp); - } - lastAircraftPositions_[a.id] = a; - } - aircraftCache_ = std::move(aircraft); - Logger::debug("Cached ", aircraftCache_.size(), " aircraft"); - success = true; - } - - // 获取车辆数据 - std::vector vehicles; - if (dataSource_->fetchVehicleData(vehicles)) { - std::lock_guard lock(cacheMutex_); - // 更新运动信息 - for (auto& v : vehicles) { - auto it = lastVehiclePositions_.find(v.id); - if (it != lastVehiclePositions_.end()) { - Logger::debug("Updating motion for vehicle ", v.id); - - // 复制历史记录 - v.copyHistoryFrom(it->second); - // 更新运动信息 - v.updateMotion(v.geo, v.timestamp); - } else { - Logger::debug("First position for vehicle ", v.id); - v.updateMotion(v.geo, v.timestamp); - } - lastVehiclePositions_[v.id] = v; - } - vehicleCache_ = std::move(vehicles); - Logger::debug("Cached ", vehicleCache_.size(), " vehicles"); - success = true; - } - - if (success) { - resetTimeout(); // 成功获取数据后重置超时 - } - - return success; - } - catch (const std::exception& e) { - Logger::error("Exception in fetchData: ", e.what()); - return false; - } + return dataSource_->fetchTrafficLightSignals(signals); } std::vector DataCollector::getAircraftData() { @@ -218,6 +251,13 @@ void DataCollector::refresh() { // 从数据源获取最新数据 std::vector newAircraft; std::vector newVehicles; + std::vector newSignals; + + // 获取红绿灯信号 + if (dataSource_->fetchTrafficLightSignals(newSignals)) { + std::lock_guard lock(cacheMutex_); + trafficLightCache_ = std::move(newSignals); + } if (dataSource_->fetchAircraftData(newAircraft)) { std::lock_guard lock(cacheMutex_); diff --git a/src/collector/DataCollector.h b/src/collector/DataCollector.h index 9b8b404..c89c819 100644 --- a/src/collector/DataCollector.h +++ b/src/collector/DataCollector.h @@ -11,6 +11,8 @@ #include "DataSourceConfig.h" #include "types/BasicTypes.h" #include "config/WarnConfig.h" +#include "types/TrafficLightTypes.h" +#include "core/System.h" class DataCollector { public: @@ -20,21 +22,25 @@ public: bool initialize(const DataSourceConfig& config, const WarnConfig& warnConfig); void start(); void stop(); + void refresh(); + + void setSystem(std::shared_ptr system) { system_ = system; } // 获取数据的接口 std::vector getAircraftData(); std::vector getVehicleData(); + std::vector getTrafficLightSignals(); + bool fetchTrafficLightSignals(std::vector& signals); // 添加设置数据源的方法(用于测试) void setDataSource(std::shared_ptr source) { dataSource_ = source; } - // 添加刷新方法 - void refresh(); private: std::shared_ptr dataSource_; + std::shared_ptr system_; DataSourceConfig dataSourceConfig_; WarnConfig warnConfig_; std::thread collectorThread_; @@ -42,14 +48,19 @@ private: std::atomic loopCount_{0}; // 缓存当前数据 - std::mutex cacheMutex_; + mutable std::mutex cacheMutex_; std::vector aircraftCache_; std::vector vehicleCache_; + std::vector trafficLightCache_; // 保存上一次位置信息(用于计算速度) std::map lastAircraftPositions_; std::map lastVehiclePositions_; + // 数据时间戳 + std::atomic lastAircraftTimestamp_{0}; + std::atomic lastVehicleTimestamp_{0}; + // 超时检测相关 std::chrono::steady_clock::time_point lastSuccessfulFetch_; std::chrono::steady_clock::time_point last_warning_time_; diff --git a/src/collector/DataSource.h b/src/collector/DataSource.h index 2386952..edc559a 100644 --- a/src/collector/DataSource.h +++ b/src/collector/DataSource.h @@ -2,6 +2,7 @@ #define AIRPORT_COLLECTOR_DATA_SOURCE_H #include "types/BasicTypes.h" +#include "types/TrafficLightTypes.h" #include #include @@ -16,6 +17,7 @@ public: // 获取数据的接口 virtual bool fetchAircraftData(std::vector& aircraft) = 0; virtual bool fetchVehicleData(std::vector& vehicles) = 0; + virtual bool fetchTrafficLightSignals(std::vector& signals) = 0; }; #endif // AIRPORT_COLLECTOR_DATA_SOURCE_H \ No newline at end of file diff --git a/src/collector/DataSourceConfig.h b/src/collector/DataSourceConfig.h index d3e6d5b..38c5ce6 100644 --- a/src/collector/DataSourceConfig.h +++ b/src/collector/DataSourceConfig.h @@ -8,8 +8,10 @@ struct DataSourceConfig { uint16_t port; std::string aircraft_path; std::string vehicle_path; + std::string traffic_light_path; int refresh_interval_ms; - int timeout_ms; + int timeout_ms; // 连接超时时间, 单位: 毫秒 + int read_timeout_ms; // 读取超时时间, 单位: 毫秒, 小于连接超时时间 }; #endif diff --git a/src/command/CommandSender.cpp b/src/command/CommandSender.cpp deleted file mode 100644 index 49486f3..0000000 --- a/src/command/CommandSender.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "CommandSender.h" -#include - -CommandSender::CommandSender() - : network_(std::make_unique()) { -} - -bool CommandSender::sendCommand(const VehicleCommand& cmd, Priority priority) { - PrioritizedCommand pcmd{ - .command = cmd, - .priority = priority, - .timestamp = std::chrono::system_clock::now().time_since_epoch().count() - }; - - cmdQueue_.push(pcmd); - processQueue(); - return true; -} - -void CommandSender::processQueue() { - while (!cmdQueue_.empty()) { - auto cmd = cmdQueue_.top(); - cmdQueue_.pop(); - - if (!network_->send(cmd.command)) { - // 发送失败,进入重试机制 - retryMechanism(cmd.command); - } - } -} - -void CommandSender::retryMechanism(const VehicleCommand& cmd) { - constexpr int MAX_RETRIES = 3; - constexpr int RETRY_DELAY_MS = 100; - - for (int i = 0; i < MAX_RETRIES; ++i) { - std::this_thread::sleep_for(std::chrono::milliseconds(RETRY_DELAY_MS)); - if (network_->send(cmd)) { - return; - } - } - - // 所有重试都失败,记录错误 - // TODO: 添加错误日志 -} \ No newline at end of file diff --git a/src/command/CommandSender.h b/src/command/CommandSender.h deleted file mode 100644 index ca94419..0000000 --- a/src/command/CommandSender.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef AIRPORT_COMMAND_COMMAND_SENDER_H -#define AIRPORT_COMMAND_COMMAND_SENDER_H - -#include -#include -#include "types/VehicleCommand.h" -#include "network/NetworkInterface.h" - -class CommandSender { -public: - enum class Priority { - EMERGENCY, - HIGH, - NORMAL, - LOW - }; - - CommandSender(); - bool sendCommand(const VehicleCommand& cmd, Priority priority = Priority::NORMAL); - -private: - struct PrioritizedCommand { - VehicleCommand command; - Priority priority; - uint64_t timestamp; - - bool operator<(const PrioritizedCommand& other) const { - return priority < other.priority; - } - }; - - std::priority_queue cmdQueue_; - std::unique_ptr network_; - - void retryMechanism(const VehicleCommand& cmd); - void processQueue(); -}; - -#endif // AIRPORT_COMMAND_COMMAND_SENDER_H \ No newline at end of file diff --git a/src/concurrent/LockFreeQueue.h b/src/concurrent/LockFreeQueue.h deleted file mode 100644 index 967a793..0000000 --- a/src/concurrent/LockFreeQueue.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef AIRPORT_CONCURRENT_LOCK_FREE_QUEUE_H -#define AIRPORT_CONCURRENT_LOCK_FREE_QUEUE_H - -#include -#include - -template -class LockFreeQueue { -public: - LockFreeQueue() : head_(new Node(T())), tail_(head_.load()) {} - - ~LockFreeQueue() { - while (Node* const old_head = head_.load()) { - head_.store(old_head->next); - delete old_head; - } - } - - void push(T value) { - Node* new_node = new Node(std::move(value)); - Node* old_tail = tail_.load(); - while (!tail_.compare_exchange_weak(old_tail, new_node)) { - old_tail = tail_.load(); - } - old_tail->next.store(new_node); - } - - std::optional pop() { - Node* old_head = head_.load(); - Node* new_head; - do { - new_head = old_head->next; - if (!new_head) { - return std::nullopt; - } - } while (!head_.compare_exchange_weak(old_head, new_head)); - - T value = std::move(new_head->value); - delete old_head; - return value; - } - - bool empty() const { - return head_.load()->next == nullptr; - } - -private: - struct Node { - T value; - std::atomic next{nullptr}; - explicit Node(T v) : value(std::move(v)) {} - }; - - std::atomic head_; - std::atomic tail_; -}; - -#endif // AIRPORT_CONCURRENT_LOCK_FREE_QUEUE_H \ No newline at end of file diff --git a/src/concurrent/ThreadPool.h b/src/concurrent/ThreadPool.h deleted file mode 100644 index 36fddfc..0000000 --- a/src/concurrent/ThreadPool.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef AIRPORT_CONCURRENT_THREAD_POOL_H -#define AIRPORT_CONCURRENT_THREAD_POOL_H - -#include -#include -#include -#include -#include -#include -#include -#include - -class ThreadPool { -public: - explicit ThreadPool(size_t numThreads) : stop_(false) { - for(size_t i = 0; i < numThreads; ++i) { - workers_.emplace_back([this] { - while(true) { - std::function task; - { - std::unique_lock lock(queue_mutex_); - condition_.wait(lock, [this] { - return stop_ || !tasks_.empty(); - }); - - if(stop_ && tasks_.empty()) { - return; - } - - task = std::move(tasks_.front()); - tasks_.pop(); - } - task(); - } - }); - } - } - - template - auto enqueue(F&& f, Args&&... args) - -> std::future> { - using return_type = std::invoke_result_t; - - auto task = std::make_shared>( - std::bind(std::forward(f), std::forward(args)...) - ); - - std::future res = task->get_future(); - { - std::unique_lock lock(queue_mutex_); - if(stop_) { - throw std::runtime_error("enqueue on stopped ThreadPool"); - } - - tasks_.emplace([task]() { (*task)(); }); - } - condition_.notify_one(); - return res; - } - - ~ThreadPool() { - { - std::unique_lock lock(queue_mutex_); - stop_ = true; - } - condition_.notify_all(); - for(std::thread &worker: workers_) { - worker.join(); - } - } - -private: - std::vector workers_; - std::queue> tasks_; - - std::mutex queue_mutex_; - std::condition_variable condition_; - bool stop_; -}; - -#endif // AIRPORT_CONCURRENT_THREAD_POOL_H \ No newline at end of file diff --git a/src/config/IntersectionConfig.cpp b/src/config/IntersectionConfig.cpp new file mode 100644 index 0000000..b5b033c --- /dev/null +++ b/src/config/IntersectionConfig.cpp @@ -0,0 +1,64 @@ +#include "config/IntersectionConfig.h" +#include "utils/Logger.h" +#include +#include +#include +#include + +IntersectionConfig IntersectionConfig::load(const std::string& configFile) { + IntersectionConfig config; + + std::ifstream file(configFile); + if (!file.is_open()) { + throw std::runtime_error("Failed to open intersection config file: " + configFile); + } + + try { + nlohmann::json jsonConfig; + file >> jsonConfig; + + for (const auto& item : jsonConfig["intersections"]) { + Intersection info; + info.id = item["id"].get(); + info.name = item["name"].get(); + info.trafficLightId = item["trafficLightId"].get(); + + // 加载位置信息 + info.position.longitude = item["position"]["longitude"].get(); + info.position.latitude = item["position"]["latitude"].get(); + info.position.altitude = item["position"]["altitude"].get(); + + // 加载距离阈值 + info.radius = item["radius"].get(); + info.stopDistance = item["stopDistance"].get(); + + config.intersections_.push_back(info); + + Logger::debug("Loaded intersection: id=", info.id, + " name=", info.name, + " trafficLightId=", info.trafficLightId); + } + + Logger::info("Loaded ", config.intersections_.size(), " intersections"); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to parse intersection config: " + std::string(e.what())); + } + + return config; +} + +const Intersection* IntersectionConfig::findByTrafficLightId(const std::string& trafficLightId) const { + auto iter = std::find_if(intersections_.begin(), intersections_.end(), + [&](const Intersection& info) { + return info.trafficLightId == trafficLightId; + }); + return iter != intersections_.end() ? &(*iter) : nullptr; +} + +const Intersection* IntersectionConfig::findById(const std::string& intersectionId) const { + auto iter = std::find_if(intersections_.begin(), intersections_.end(), + [&](const Intersection& info) { + return info.id == intersectionId; + }); + return iter != intersections_.end() ? &(*iter) : nullptr; +} \ No newline at end of file diff --git a/src/config/IntersectionConfig.h b/src/config/IntersectionConfig.h new file mode 100644 index 0000000..35ee9d6 --- /dev/null +++ b/src/config/IntersectionConfig.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include "types/BasicTypes.h" + +struct IntersectionPosition { + double longitude; + double latitude; + double altitude; +}; + +struct Intersection { + std::string id; + std::string name; + std::string trafficLightId; + IntersectionPosition position; + double radius; // 路口影响半径,单位:米 + double stopDistance; // 建议停车距离,单位:米 +}; + +class IntersectionConfig { +public: + static IntersectionConfig load(const std::string& configFile); + + // 根据红绿灯ID查找路口 + const Intersection* findByTrafficLightId(const std::string& trafficLightId) const; + + // 根据路口ID查找路口 + const Intersection* findById(const std::string& intersectionId) const; + + // 获取所有路口配置 + const std::vector& getIntersections() const { return intersections_; } + +private: + std::vector intersections_; +}; \ No newline at end of file diff --git a/src/config/SystemConfig.cpp b/src/config/SystemConfig.cpp index aca7ddf..f2d137e 100644 --- a/src/config/SystemConfig.cpp +++ b/src/config/SystemConfig.cpp @@ -26,8 +26,10 @@ SystemConfig SystemConfig::load(const std::string& filename) { config.data_source.port = j["data_source"]["port"]; config.data_source.aircraft_path = j["data_source"]["aircraft_path"]; config.data_source.vehicle_path = j["data_source"]["vehicle_path"]; + config.data_source.traffic_light_path = j["data_source"]["traffic_light_path"]; config.data_source.refresh_interval_ms = j["data_source"]["refresh_interval_ms"]; config.data_source.timeout_ms = j["data_source"]["timeout_ms"]; + config.data_source.read_timeout_ms = j["data_source"]["read_timeout_ms"]; // 加载 WebSocket 配置 config.websocket.port = j["websocket"]["port"]; diff --git a/src/config/SystemConfig.h b/src/config/SystemConfig.h index fac8a05..09bf903 100644 --- a/src/config/SystemConfig.h +++ b/src/config/SystemConfig.h @@ -19,17 +19,20 @@ struct SystemConfig { uint16_t port; std::string aircraft_path; std::string vehicle_path; + std::string traffic_light_path; int refresh_interval_ms; int timeout_ms; + int read_timeout_ms; } data_source; struct WebSocket { - int port; + uint16_t port; int max_connections; int ping_interval_ms; struct PositionUpdate { int aircraft_interval_ms; int vehicle_interval_ms; + int traffic_light_interval_ms; } position_update; } websocket; diff --git a/src/core/System.cpp b/src/core/System.cpp index ddc9a67..b8a8d2e 100644 --- a/src/core/System.cpp +++ b/src/core/System.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "core/System.h" #include "utils/Logger.h" #include "collector/DataCollector.h" @@ -31,6 +32,10 @@ bool System::initialize() { // 加载系统配置 system_config_ = SystemConfig::load("config/system_config.json"); + // 加载路口配置 + intersection_config_ = IntersectionConfig::load("config/intersections.json"); + Logger::info("Loaded {} intersections", intersection_config_.getIntersections().size()); + // 初始化 WebSocket 服务器 ws_server_ = std::make_unique(system_config_.websocket.port); ws_thread_ = std::thread([this]() { @@ -41,13 +46,14 @@ bool System::initialize() { airportBounds_ = std::make_unique("config/airport_bounds.json"); // 加载可控车辆配置 - controllableVehicles_ = std::make_unique("config/controllable_vehicles.json"); - + controllableVehicles_ = std::make_unique("config/vehicle_speed_limits.json"); - - // 初始化碰撞检测器 + // 初始化冲突检测器 collisionDetector_ = std::make_unique(*airportBounds_, *controllableVehicles_); + // 初始化红绿灯检测器 + trafficLightDetector_ = std::make_unique(intersection_config_, *controllableVehicles_); + // 创建数据采集器 dataCollector_ = std::make_unique(); @@ -57,6 +63,7 @@ bool System::initialize() { system_config_.data_source.port, system_config_.data_source.aircraft_path, system_config_.data_source.vehicle_path, + system_config_.data_source.traffic_light_path, system_config_.data_source.refresh_interval_ms, system_config_.data_source.timeout_ms }; @@ -116,57 +123,151 @@ void System::stop() { void System::processLoop() { auto last_aircraft_update = std::chrono::steady_clock::now(); auto last_vehicle_update = std::chrono::steady_clock::now(); - auto last_data_refresh = std::chrono::steady_clock::now(); + auto last_collision_update = std::chrono::steady_clock::now(); + auto last_traffic_light_update = std::chrono::steady_clock::now(); + int64_t last_aircraft_timestamp = 0; + int64_t last_vehicle_timestamp = 0; + int64_t last_collision_timestamp = 0; + int64_t last_traffic_light_timestamp = 0; + + Logger::debug("数据处理循环启动"); while (running_) { try { auto now = std::chrono::steady_clock::now(); + auto last_data_collection = now; - if (std::chrono::duration_cast( - now - last_data_refresh).count() >= system_config_.data_source.refresh_interval_ms) { - dataCollector_->refresh(); - last_data_refresh = now; - - auto aircraft = dataCollector_->getAircraftData(); - auto vehicles = dataCollector_->getVehicleData(); + Logger::debug("开始获取数据..."); + + // 获取最新数据 + dataCollector_->refresh(); // 先刷新数据 + auto aircraft = dataCollector_->getAircraftData(); + auto vehicles = dataCollector_->getVehicleData(); + auto traffic_lights = dataCollector_->getTrafficLightSignals(); + + // 检查航空器更新 + auto aircraft_elapsed = std::chrono::duration_cast( + now - last_aircraft_update).count(); + if (aircraft_elapsed >= system_config_.websocket.position_update.aircraft_interval_ms) { + bool has_new_aircraft = false; + int64_t max_timestamp = last_aircraft_timestamp; - Logger::debug("Got data: ", aircraft.size(), " aircraft, ", vehicles.size(), " vehicles"); + Logger::debug("开始检查航空器数据更新,当前时间戳: ", last_aircraft_timestamp); + for (const auto& ac : aircraft) { + Logger::debug("航空器 ", ac.flightNo, " 时间戳: current=", ac.timestamp, " last=", last_aircraft_timestamp); + if (ac.timestamp > last_aircraft_timestamp) { // 改为大于比较 + has_new_aircraft = true; + if (ac.timestamp > max_timestamp) { + max_timestamp = ac.timestamp; + } + Logger::debug("发现新数据: ", ac.flightNo, " (新时间戳: ", ac.timestamp, ")"); + } + } - // 检查航空器更新 - auto aircraft_elapsed = std::chrono::duration_cast( - now - last_aircraft_update).count(); - if (aircraft_elapsed >= system_config_.websocket.position_update.aircraft_interval_ms) { - Logger::debug("Broadcasting aircraft positions (", aircraft.size(), " aircraft)"); + if (has_new_aircraft) { + Logger::debug("广播 ", aircraft.size(), " 架航空器位置, 时间戳更新: ", last_aircraft_timestamp, " -> ", max_timestamp); for (const auto& ac : aircraft) { broadcastPositionUpdate(ac); } - last_aircraft_update = now; + last_aircraft_timestamp = max_timestamp; + } else { + Logger::debug("没有新的航空器数据,当前时间戳: ", last_aircraft_timestamp); + } + last_aircraft_update = now; + } + + // 检查车辆更新 + auto vehicle_elapsed = std::chrono::duration_cast( + now - last_vehicle_update).count(); + if (vehicle_elapsed >= system_config_.websocket.position_update.vehicle_interval_ms) { + bool has_new_vehicles = false; + int64_t max_timestamp = last_vehicle_timestamp; + + Logger::debug("开始检查车辆数据更新,当前时间戳: ", last_vehicle_timestamp); + for (const auto& veh : vehicles) { + Logger::debug("车辆 ", veh.vehicleNo, " 时间戳: current=", veh.timestamp, " last=", last_vehicle_timestamp); + if (veh.timestamp > last_vehicle_timestamp) { // 改为大于比较 + has_new_vehicles = true; + if (veh.timestamp > max_timestamp) { + max_timestamp = veh.timestamp; + } + Logger::debug("发现新数据: ", veh.vehicleNo, " (新时间戳: ", veh.timestamp, ")"); + } } - // 检查车辆更新 - auto vehicle_elapsed = std::chrono::duration_cast( - now - last_vehicle_update).count(); - if (vehicle_elapsed >= system_config_.websocket.position_update.vehicle_interval_ms) { - Logger::debug("Broadcasting vehicle positions (", vehicles.size(), " vehicles)"); + if (has_new_vehicles) { + Logger::debug("广播 ", vehicles.size(), " 辆车辆位置, 时间戳更新: ", last_vehicle_timestamp, " -> ", max_timestamp); for (const auto& veh : vehicles) { broadcastPositionUpdate(veh); } - last_vehicle_update = now; - } - - // 更新碰撞检测器 - collisionDetector_->updateTraffic(aircraft, vehicles); - auto collisions = collisionDetector_->detectCollisions(); - - if (!collisions.empty()) { - processCollisions(collisions); + last_vehicle_timestamp = max_timestamp; + } else { + Logger::debug("没有新的车辆数据,当前时间戳: ", last_vehicle_timestamp); } + last_vehicle_update = now; } - // 处理间隔设置为数据刷新间隔的 1/10 - std::this_thread::sleep_for(std::chrono::milliseconds( - system_config_.data_source.refresh_interval_ms / 10 - )); + // 检查冲突更新 + auto collision_elapsed = std::chrono::duration_cast( + now - last_collision_update).count(); + if (collision_elapsed >= system_config_.collision_detection.update_interval_ms) { + // 只有当有新的数据时才更新冲突检测 + if (last_aircraft_timestamp > last_collision_timestamp || + last_vehicle_timestamp > last_collision_timestamp) { + // 更新冲突检测器 + collisionDetector_->updateTraffic(aircraft, vehicles); + auto collisions = collisionDetector_->detectCollisions(); + + if (!collisions.empty()) { + processCollisions(collisions); + } + + last_collision_timestamp = std::max(last_aircraft_timestamp, last_vehicle_timestamp); + } + last_collision_update = now; + } + + // 处理红绿灯信号 + auto traffic_light_elapsed = std::chrono::duration_cast( + now - last_traffic_light_update).count(); + if (traffic_light_elapsed >= system_config_.websocket.position_update.traffic_light_interval_ms) { + bool has_new_signals = false; + int64_t max_timestamp = last_traffic_light_timestamp; + + Logger::debug("开始检查红绿灯数据更新,当前时间戳: ", last_traffic_light_timestamp); + for (const auto& signal : traffic_lights) { + if (signal.timestamp > last_traffic_light_timestamp) { + has_new_signals = true; + if (signal.timestamp > max_timestamp) { + max_timestamp = signal.timestamp; + } + Logger::debug("红绿灯 ", signal.trafficLightId, " 时间戳: current=", signal.timestamp, " last=", last_traffic_light_timestamp); + Logger::debug("发现新数据: ", signal.trafficLightId, " (新时间戳: ", signal.timestamp, ")"); + + // 广播红绿灯状态更新 + broadcastTrafficLightUpdate(signal); + + // 处理红绿灯信号并检查是否需要停车 + trafficLightDetector_->processSignal(signal, vehicles); + } + } + + if (has_new_signals) { + Logger::debug("处理了 ", traffic_lights.size(), " 个红绿灯信号, 时间戳更新: ", + last_traffic_light_timestamp, " -> ", max_timestamp); + last_traffic_light_timestamp = max_timestamp; + } + last_traffic_light_update = now; + } + + // 计算下一次数据采集前的等待时间 + auto next_collection = last_data_collection + std::chrono::milliseconds(system_config_.data_source.refresh_interval_ms); + auto wait_time = std::chrono::duration_cast(next_collection - now).count(); + + if (wait_time > 0) { + Logger::debug("等待 ", wait_time, "ms 进行下一次采集"); + std::this_thread::sleep_for(std::chrono::milliseconds(wait_time)); + } } catch (const std::exception& e) { Logger::error("处理循环发生错误: ", e.what()); @@ -176,28 +277,22 @@ void System::processLoop() { void System::processCollisions(const std::vector& collisions) { if (!collisions.empty()) { - Logger::info("检测到 ", collisions.size(), " 个碰撞风险"); + Logger::info("检测到 ", collisions.size(), " 个冲突风险"); for (const auto& risk : collisions) { - // 发送碰撞警告到 Unity 客户端 + // 发送冲突警告到 Unity 客户端 broadcastCollisionWarning(risk); // 根据风险等级选择不同的日志级别 switch (risk.level) { - case RiskLevel::EMERGENCY: - Logger::error("严重碰撞风险: ", risk.id1, " 与 ", risk.id2, - ", 距离: ", risk.distance, "米", - ", 相对速度: ", risk.relativeSpeed, "m/s"); - break; - case RiskLevel::CRITICAL: - Logger::warning("高度碰撞风险: ", risk.id1, " 与 ", risk.id2, + Logger::warning("高度冲突风险: ", risk.id1, " 与 ", risk.id2, ", 距离: ", risk.distance, "米", ", 相对速度: ", risk.relativeSpeed, "m/s"); break; case RiskLevel::WARNING: - Logger::warning("低度碰撞风险: ", risk.id1, " 与 ", risk.id2, + Logger::warning("低度冲突风险: ", risk.id1, " 与 ", risk.id2, ", 距离: ", risk.distance, "米", ", 相对速度: ", risk.relativeSpeed, "m/s"); break; @@ -209,53 +304,83 @@ void System::processCollisions(const std::vector& collisions) { } void System::broadcastPositionUpdate(const MovingObject& obj) { + if (!ws_server_) { + return; + } + network::PositionUpdateMessage msg; msg.objectId = obj.id; msg.objectType = (typeid(obj) == typeid(Aircraft)) ? "aircraft" : "vehicle"; + msg.timestamp = obj.timestamp; msg.longitude = obj.geo.longitude; msg.latitude = obj.geo.latitude; msg.heading = obj.heading; msg.speed = obj.speed; - msg.timestamp = obj.timestamp; - if (ws_server_) { - ws_server_->broadcast(msg.toJson().dump()); - Logger::debug("Broadcast position update: type=", msg.objectType, - ", id=", msg.objectId, - ", pos=(", msg.longitude, ",", msg.latitude, ")"); + nlohmann::json j = { + {"type", "position_update"}, + {"object_id", msg.objectId}, + {"object_type", msg.objectType}, + {"timestamp", msg.timestamp}, + {"position", { + {"longitude", msg.longitude}, + {"latitude", msg.latitude} + }}, + {"heading", msg.heading}, + {"speed", msg.speed} + }; + + ws_server_->broadcast(j.dump()); + Logger::debug("广播位置更新: id=", msg.objectId, + " type=", msg.objectType, + " pos=(", msg.longitude, ",", msg.latitude, ")", + " heading=", msg.heading, + " speed=", msg.speed, + " timestamp=", msg.timestamp); +} + +void System::broadcastTrafficLightUpdate(const TrafficLightSignal& signal) { + if (!ws_server_) { + return; + } + + nlohmann::json j = { + {"type", "traffic_light_update"}, + {"id", signal.trafficLightId}, + {"status", static_cast(signal.status)}, + {"timestamp", signal.timestamp} + }; + + ws_server_->broadcast(j.dump()); + Logger::debug("广播红绿灯状态: id=", signal.trafficLightId, + " status=", static_cast(signal.status), + " timestamp=", signal.timestamp); +} + +std::string getRiskLevelString(RiskLevel level) { + switch (level) { + case RiskLevel::CRITICAL: return "critical"; + case RiskLevel::WARNING: return "warning"; + default: return "none"; } } void System::broadcastCollisionWarning(const CollisionRisk& risk) { - network::CollisionWarningMessage msg; - msg.id1 = risk.id1; - msg.id2 = risk.id2; - - // 根据风险等级设置预警级别和阈值 - switch (risk.level) { - case RiskLevel::EMERGENCY: - msg.warningLevel = "high"; - msg.threshold = system_config_.collision_detection.thresholds.runway.aircraft_ground; - break; - case RiskLevel::CRITICAL: - msg.warningLevel = "medium"; - msg.threshold = system_config_.collision_detection.thresholds.taxiway.aircraft_ground; - break; - case RiskLevel::WARNING: - msg.warningLevel = "low"; - msg.threshold = system_config_.collision_detection.thresholds.apron.aircraft_ground; - break; - default: - return; // 不发送无风险的警告 + if (!ws_server_) { + return; } - msg.distance = risk.distance; - msg.relativeSpeed = risk.relativeSpeed; - msg.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); + nlohmann::json j = { + {"type", "collision_warning"}, + {"id1", risk.id1}, + {"id2", risk.id2}, + {"distance", risk.distance}, + {"relativeSpeed", risk.relativeSpeed}, + {"warningLevel", getRiskLevelString(risk.level)}, + {"timestamp", std::chrono::system_clock::now().time_since_epoch().count()} + }; - if (ws_server_) { - ws_server_->broadcast(msg.toJson().dump()); - } + ws_server_->broadcast(j.dump()); } void System::broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning) { @@ -264,10 +389,10 @@ void System::broadcastTimeoutWarning(const network::TimeoutWarningMessage& warni // 根据超时时间记录不同级别的日志 if (warning.elapsed_ms > 30000) { // 30秒以上 - Logger::error("Severe timeout: ", warning.host, ":", warning.port, " - ", + Logger::error("Severe timeout: ", warning.source, " - ", warning.elapsed_ms, "ms without response"); } else { - Logger::warning("Connection timeout: ", warning.host, ":", warning.port, " - ", + Logger::warning("Connection timeout: ", warning.source, " - ", warning.elapsed_ms, "ms without response"); } } diff --git a/src/core/System.h b/src/core/System.h index 8465e30..4a52085 100644 --- a/src/core/System.h +++ b/src/core/System.h @@ -6,11 +6,13 @@ #include #include "types/BasicTypes.h" #include "detector/CollisionDetector.h" +#include "detector/TrafficLightDetector.h" #include "spatial/AirportBounds.h" #include "vehicle/ControllableVehicles.h" #include "network/WebSocketServer.h" #include "network/MessageTypes.h" #include "config/SystemConfig.h" +#include "config/IntersectionConfig.h" // 前向声明 class DataCollector; @@ -18,7 +20,7 @@ class DataCollector; class System { public: System(); - ~System(); + virtual ~System(); bool initialize(); void start(); @@ -27,22 +29,28 @@ public: static System* instance() { return instance_; } static void signalHandler(int signal); - void broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning); + // 广播消息给客户端 + virtual void broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning); + void broadcastPositionUpdate(const MovingObject& obj); + void broadcastCollisionWarning(const CollisionRisk& risk); + void broadcastTrafficLightUpdate(const TrafficLightSignal& signal); + + const SystemConfig& getSystemConfig() const { return system_config_; } + const IntersectionConfig& getIntersectionConfig() const { return intersection_config_; } private: void processLoop(); void processCollisions(const std::vector& collisions); - // WebSocket 相关方法 - void broadcastPositionUpdate(const MovingObject& obj); - void broadcastCollisionWarning(const CollisionRisk& risk); - + + std::atomic running_{false}; std::thread processThread_; std::unique_ptr airportBounds_; std::unique_ptr controllableVehicles_; std::unique_ptr collisionDetector_; + std::unique_ptr trafficLightDetector_; std::unique_ptr dataCollector_; // WebSocket 服务器 @@ -52,5 +60,8 @@ private: // 系统配置 SystemConfig system_config_; + // 路口配置 + IntersectionConfig intersection_config_; + static System* instance_; }; \ No newline at end of file diff --git a/src/detector/CollisionDetector.cpp b/src/detector/CollisionDetector.cpp index 19adcef..697a508 100644 --- a/src/detector/CollisionDetector.cpp +++ b/src/detector/CollisionDetector.cpp @@ -61,6 +61,12 @@ std::vector CollisionDetector::detectCollisions() { // 计算相对速度大小 double relativeSpeed = std::sqrt(vx*vx + vy*vy); + // 记录调试信息 + Logger::debug("航空器与车辆相对速度计算: ", aircraft.flightNo, " vs ", vehicle.vehicleNo, + ", 航空器: speed=", aircraft.speed, "m/s, heading=", aircraft.heading, "°", + ", 车辆: speed=", vehicle.speed, "m/s, heading=", vehicle.heading, "°", + ", 相对速度: vx=", vx, "m/s, vy=", vy, "m/s, magnitude=", relativeSpeed, "m/s"); + // 计算风险等级 RiskLevel level = calculateRiskLevel(distance, aircraft.position, true, false); @@ -100,6 +106,12 @@ std::vector CollisionDetector::detectCollisions() { double vy = v1v.vy - v2v.vy; double relativeSpeed = std::sqrt(vx*vx + vy*vy); + // 记录调试信息 + Logger::debug("车辆间相对速度计算: ", controlVehicle.vehicleNo, " vs ", otherVehicle.vehicleNo, + ", 车辆1: speed=", controlVehicle.speed, "m/s, heading=", controlVehicle.heading, "°", + ", 车辆2: speed=", otherVehicle.speed, "m/s, heading=", otherVehicle.heading, "°", + ", 相对速度: vx=", vx, "m/s, vy=", vy, "m/s, magnitude=", relativeSpeed, "m/s"); + // 计算风险等级 RiskLevel level = calculateRiskLevel(distance, controlVehicle.position, false, false); @@ -128,9 +140,7 @@ RiskLevel CollisionDetector::calculateRiskLevel(double distance, const Vector2D& auto thresholds = areaConfig.getThresholds(isAircraft1, isAircraft2); // 直接比较距离和阈值 - if (distance <= thresholds.emergency) { - return RiskLevel::EMERGENCY; - } else if (distance <= thresholds.critical) { + if (distance <= thresholds.critical) { return RiskLevel::CRITICAL; } else if (distance <= thresholds.warning) { return RiskLevel::WARNING; @@ -149,10 +159,10 @@ bool CollisionDetector::checkAircraftVehicleCollision(const Aircraft& aircraft, // 获取该区域的告警阈值 auto thresholds = areaConfig.getThresholds(true, false); // aircraft vs vehicle - double emergencyThresholdSquared = thresholds.emergency * thresholds.emergency; + double criticalThresholdSquared = thresholds.critical * thresholds.critical; // 如果距离小于紧急阈值,直接报警 - if (distanceSquared < emergencyThresholdSquared) { + if (distanceSquared < criticalThresholdSquared) { return true; } @@ -186,7 +196,7 @@ bool CollisionDetector::checkVehicleCollision(const Vehicle& v1, auto thresholds = areaConfig.getThresholds(false, false); // vehicle vs vehicle // 如果距离小于紧急阈值,直接报警 - if (distance < thresholds.emergency) { + if (distance < thresholds.critical) { return true; } diff --git a/src/detector/CollisionDetector.h b/src/detector/CollisionDetector.h index e444689..62686eb 100644 --- a/src/detector/CollisionDetector.h +++ b/src/detector/CollisionDetector.h @@ -7,13 +7,13 @@ #include "vehicle/ControllableVehicles.h" #include #include +#include // 碰撞风险等级 enum class RiskLevel { NONE = 0, // 无风险 WARNING = 1, // 低风险:距离在阈值的 50%-100% 之间 - CRITICAL = 2, // 中等风险:距离在阈值的 25%-50% 之间 - EMERGENCY = 3, // 高风险:距离在阈值的 0%-25% 之间 + CRITICAL = 2, // 中等风险:距离在阈值的 0%-50% 之间 }; // 碰撞风险信息 @@ -62,9 +62,17 @@ private: double vx, vy; MovementVector(double speed, double heading) { - double radians = (90 - heading) * M_PI / 180.0; - vx = speed * std::cos(radians); - vy = speed * std::sin(radians); + // 航向角是以正北为0度,顺时针增加 + // 需要转换为数学坐标系(以正东为0度,逆时针增加) + double radians = (90.0 - heading) * M_PI / 180.0; + + // 计算速度分量 + vx = speed * std::cos(radians); // 东向分量 + vy = speed * std::sin(radians); // 北向分量 + + // 记录调试信息 + Logger::debug("速度分量计算: speed=", speed, "m/s, heading=", heading, + "°, vx=", vx, "m/s, vy=", vy, "m/s"); } }; }; diff --git a/src/detector/TrafficLightDetector.cpp b/src/detector/TrafficLightDetector.cpp new file mode 100644 index 0000000..4d2f56a --- /dev/null +++ b/src/detector/TrafficLightDetector.cpp @@ -0,0 +1,75 @@ +#include "detector/TrafficLightDetector.h" +#include "utils/Logger.h" +#include "types/BasicTypes.h" +#include + +TrafficLightDetector::TrafficLightDetector(const IntersectionConfig& intersectionConfig, + const ControllableVehicles& controllableVehicles) + : intersection_config_(intersectionConfig) + , controllable_vehicles_(controllableVehicles) { +} + +void TrafficLightDetector::processSignal(const TrafficLightSignal& signal, + const std::vector& vehicles) { + // 根据红绿灯ID查找对应的路口 + const Intersection* intersection = intersection_config_.findByTrafficLightId(signal.trafficLightId); + if (!intersection) { + Logger::warning("未找到红绿灯对应的路口: ", signal.trafficLightId); + return; + } + + // 检查每个车辆 + for (const auto& vehicle : vehicles) { + if (controllable_vehicles_.isControllable(vehicle.vehicleNo)) { + checkVehicleIntersectionDistance(vehicle, *intersection, signal.status); + } + } +} + +void TrafficLightDetector::checkVehicleIntersectionDistance(const Vehicle& vehicle, + const Intersection& intersection, + SignalStatus status) { + // 创建 GeoPosition 用于距离计算 + GeoPosition intersectionPos{ + intersection.position.latitude, + intersection.position.longitude + }; + + // 计算车辆到路口的距离 + double distance = MovingObject::calculateDistance(vehicle.geo, intersectionPos); + + // 如果车辆在路口影响范围内 + if (distance <= intersection.radius) { + switch (status) { + case SignalStatus::RED: + sendStopCommand(vehicle.vehicleNo); + break; + case SignalStatus::GREEN: + sendContinueCommand(vehicle.vehicleNo); + break; + default: + Logger::warning("未知的信号灯状态"); + break; + } + } +} + +void TrafficLightDetector::sendStopCommand(const std::string& vehicleNo) { + VehicleCommand cmd; + cmd.type = CommandType::SIGNAL; + cmd.reason = CommandReason::TRAFFIC_LIGHT; + cmd.signalState = SignalState::RED; + cmd.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); + const_cast(controllable_vehicles_).sendCommand(vehicleNo, cmd); + Logger::debug("发送停止指令到车辆: ", vehicleNo); +} + +void TrafficLightDetector::sendContinueCommand(const std::string& vehicleNo) { + VehicleCommand cmd; + cmd.type = CommandType::SIGNAL; + cmd.reason = CommandReason::TRAFFIC_LIGHT; + cmd.signalState = SignalState::GREEN; + cmd.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); + const_cast(controllable_vehicles_).sendCommand(vehicleNo, cmd); + Logger::debug("发送继续指令到车辆: ", vehicleNo); +} \ No newline at end of file diff --git a/src/detector/TrafficLightDetector.h b/src/detector/TrafficLightDetector.h new file mode 100644 index 0000000..64a8f30 --- /dev/null +++ b/src/detector/TrafficLightDetector.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include "types/TrafficLightTypes.h" +#include "types/VehicleCommand.h" +#include "config/IntersectionConfig.h" +#include "vehicle/ControllableVehicles.h" + +class TrafficLightDetector { +public: + TrafficLightDetector(const IntersectionConfig& intersectionConfig, + const ControllableVehicles& controllableVehicles); + + // 处理红绿灯信号 + void processSignal(const TrafficLightSignal& signal, const std::vector& vehicles); + +private: + // 检查车辆与路口的距离,决定是否需要发送指令 + void checkVehicleIntersectionDistance(const Vehicle& vehicle, + const Intersection& intersection, + SignalStatus status); + // 发送车辆控制指令 + void sendStopCommand(const std::string& vehicleId); + void sendContinueCommand(const std::string& vehicleId); + + const IntersectionConfig& intersection_config_; + const ControllableVehicles& controllable_vehicles_; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 2460662..516e40b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,10 @@ #include "core/System.h" #include "utils/Logger.h" +#include "config/SystemConfig.h" #include #include #include +#include volatile sig_atomic_t running = true; @@ -11,14 +13,30 @@ void signalHandler(int signum) { running = false; } +LogLevel getLogLevelFromString(const std::string& level) { + if (level == "debug") return LogLevel::DEBUG; + if (level == "info") return LogLevel::INFO; + if (level == "warning") return LogLevel::WARNING; + if (level == "error") return LogLevel::ERROR; + return LogLevel::INFO; // 默认级别 +} + int main() { try { // 设置信号处理 signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); - // 配置日志级别 - Logger::setLogLevel(LogLevel::INFO); + // 创建日志目录 + std::filesystem::create_directories("logs"); + + // 加载系统配置 + auto config = SystemConfig::load("config/system_config.json"); + + // 初始化日志系统 + Logger::initialize(config.logging.file, getLogLevelFromString(config.logging.level)); + Logger::info("Starting system..."); + Logger::debug("Log level set to: ", config.logging.level); // 初始化系统 System system; @@ -43,7 +61,7 @@ int main() { return 0; } catch (const std::exception& e) { - Logger::error("Fatal error: ", e.what()); + std::cerr << "Fatal error: " << e.what() << std::endl; return 1; } } \ No newline at end of file diff --git a/src/mock/MockDataService.cpp b/src/mock/MockDataService.cpp index 556f72d..fe31d76 100644 --- a/src/mock/MockDataService.cpp +++ b/src/mock/MockDataService.cpp @@ -1,5 +1,7 @@ #include "mock/MockDataService.h" #include +#include +#include MockDataService::MockDataService(const AirportBounds& bounds) : bounds_(bounds) @@ -124,9 +126,43 @@ double MockDataService::generateAltitude(AreaType areaType) { } std::string MockDataService::generateAircraftId() { - return std::format("AC{:04d}", ++aircraftCounter_); + std::stringstream ss; + ss << "AC" << std::setw(4) << std::setfill('0') << ++aircraftCounter_; + return ss.str(); } std::string MockDataService::generateVehicleId() { - return std::format("VH{:04d}", ++vehicleCounter_); + std::stringstream ss; + ss << "VH" << std::setw(4) << std::setfill('0') << ++vehicleCounter_; + return ss.str(); +} + +std::vector MockDataService::generateTrafficLightData() { + std::vector signals; + + // 生成 5-10 个红绿灯信号 + std::uniform_int_distribution<> numSignals(5, 10); + int count = numSignals(rng_); + + for (int i = 0; i < count; ++i) { + TrafficLightSignal signal; + signal.trafficLightId = generateTrafficLightId(); + signal.status = generateTrafficLightState(); + signal.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); + signals.push_back(signal); + } + + return signals; +} + +std::string MockDataService::generateTrafficLightId() { + std::stringstream ss; + ss << "TL" << std::setw(4) << std::setfill('0') << ++trafficLightCounter_; + return ss.str(); +} + +SignalStatus MockDataService::generateTrafficLightState() { + std::uniform_int_distribution<> dist(0, 2); + int state = dist(rng_); + return static_cast(state); } \ No newline at end of file diff --git a/src/mock/MockDataService.h b/src/mock/MockDataService.h index 7cc2ed8..0b8cad2 100644 --- a/src/mock/MockDataService.h +++ b/src/mock/MockDataService.h @@ -3,6 +3,7 @@ #include "types/BasicTypes.h" #include "spatial/AirportBounds.h" +#include "types/TrafficLightTypes.h" #include #include @@ -12,6 +13,7 @@ public: std::vector generateAircraftData(); std::vector generateVehicleData(); + std::vector generateTrafficLightData(); private: const AirportBounds& bounds_; @@ -28,9 +30,14 @@ private: // 生成ID std::string generateAircraftId(); std::string generateVehicleId(); + std::string generateTrafficLightId(); + + // 生成红绿灯状态 + SignalStatus generateTrafficLightState(); int aircraftCounter_ = 0; int vehicleCounter_ = 0; + int trafficLightCounter_ = 0; }; #endif // AIRPORT_MOCK_MOCKDATASERVICE_H \ No newline at end of file diff --git a/src/network/HTTPDataSource.cpp b/src/network/HTTPDataSource.cpp index 9e6107f..362b336 100644 --- a/src/network/HTTPDataSource.cpp +++ b/src/network/HTTPDataSource.cpp @@ -1,50 +1,53 @@ #include "network/HTTPDataSource.h" #include "utils/Logger.h" #include -#include +#include using json = nlohmann::json; +size_t HTTPDataSource::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + HTTPDataSource::HTTPDataSource(const DataSourceConfig& config) : config_(config) , host_(config.host) , port_(std::to_string(config.port)) - , socket_(std::make_unique(io_context_)) { + , last_health_check_(std::chrono::steady_clock::now()) { + curl_ = curl_easy_init(); + if (!curl_) { + Logger::error("Failed to initialize CURL"); + } } HTTPDataSource::~HTTPDataSource() { disconnect(); + if (curl_) { + curl_easy_cleanup(curl_); + } } bool HTTPDataSource::connect() { - try { - Logger::debug("Connecting to ", host_, ":", port_); - - auto resolver = asio::ip::tcp::resolver(io_context_); - auto endpoints = resolver.resolve(host_, port_); - - socket_ = std::make_unique(io_context_); - asio::connect(*socket_, endpoints); - - return true; - } - catch (const std::exception& e) { - Logger::error("Connection error: ", e.what()); - return false; + if (!curl_) { + curl_ = curl_easy_init(); + if (!curl_) { + Logger::error("Failed to initialize CURL"); + return false; + } } + return true; } void HTTPDataSource::disconnect() { - if (socket_ && socket_->is_open()) { - boost::system::error_code ec; - socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); - socket_->close(ec); + if (curl_) { + curl_easy_cleanup(curl_); + curl_ = nullptr; } - socket_.reset(); } bool HTTPDataSource::isAvailable() const { - return socket_ && socket_->is_open(); + return curl_ != nullptr; } bool HTTPDataSource::tryReconnect() { @@ -91,14 +94,68 @@ bool HTTPDataSource::tryReconnect() { } } +bool HTTPDataSource::checkConnectionHealth() { + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast( + now - last_health_check_).count(); + + if (elapsed < HEALTH_CHECK_INTERVAL) { + return true; // 距离上次检查时间不够长 + } + + last_health_check_ = now; + return curl_ != nullptr; +} + bool HTTPDataSource::ensureConnected() { - if (isAvailable()) { + if (isAvailable() && checkConnectionHealth()) { return true; } return tryReconnect(); } +bool HTTPDataSource::sendRequest(const std::string& path, std::string& response) { + if (!curl_) { + Logger::error("CURL not initialized"); + return false; + } + + std::string url = "http://" + host_ + ":" + port_ + path; + Logger::debug("Sending request to: ", url); + + curl_easy_reset(curl_); + curl_easy_setopt(curl_, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl_, CURLOPT_TIMEOUT, config_.read_timeout_ms / 1000); + curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, config_.timeout_ms / 1000); + curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L); + + // 添加 HTTP 头 + struct curl_slist* headers = nullptr; + headers = curl_slist_append(headers, "Accept: application/json"); + headers = curl_slist_append(headers, "Cache-Control: no-cache"); + curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers); + + CURLcode res = curl_easy_perform(curl_); + curl_slist_free_all(headers); + + if (res != CURLE_OK) { + Logger::error("Failed to send request: ", curl_easy_strerror(res)); + return false; + } + + long http_code = 0; + curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code != 200) { + Logger::error("HTTP request failed with code: ", http_code); + return false; + } + + return true; +} + bool HTTPDataSource::fetchAircraftData(std::vector& aircraft) { std::lock_guard lock(mutex_); @@ -107,8 +164,7 @@ bool HTTPDataSource::fetchAircraftData(std::vector& aircraft) { } std::string response; - if (!sendRequest(config_.aircraft_path) || !readResponse(response)) { - disconnect(); + if (!sendRequest(config_.aircraft_path, response)) { return false; } @@ -123,192 +179,198 @@ bool HTTPDataSource::fetchVehicleData(std::vector& vehicles) { } std::string response; - if (!sendRequest(config_.vehicle_path) || !readResponse(response)) { - disconnect(); + if (!sendRequest(config_.vehicle_path, response)) { return false; } return parseVehicleResponse(response, vehicles); } -bool HTTPDataSource::sendRequest(const std::string& path) { - try { - if (!connect()) { - return false; - } - - std::stringstream request; - request << "GET " << path << " HTTP/1.1\r\n" - << "Host: " << host_ << "\r\n" - << "Connection: close\r\n" - << "Accept: application/json\r\n" - << "\r\n"; - - std::string request_str = request.str(); - Logger::debug("Sending request:\n", request_str); - - asio::write(*socket_, asio::buffer(request_str)); - return true; - } - catch (const std::exception& e) { - Logger::error("Failed to send request: ", e.what()); +bool HTTPDataSource::fetchTrafficLightSignals(std::vector& signals) { + std::lock_guard lock(mutex_); + + if (!ensureConnected()) { return false; } + + std::string response; + if (!sendRequest(config_.traffic_light_path, response)) { + return false; + } + + return parseTrafficLightResponse(response, signals); } -bool HTTPDataSource::readResponse(std::string& response) { +bool HTTPDataSource::parseTrafficLightResponse(const std::string& response, std::vector& signals) { try { - Logger::debug("Reading response"); + json j = json::parse(response); - asio::streambuf response_buf; - boost::system::error_code ec; - - // 读取响应头 - size_t header_length = asio::read_until(*socket_, response_buf, "\r\n\r\n", ec); - if (ec) { - Logger::error("Failed to read headers: ", ec.message()); + if (!j.is_array()) { + Logger::error("Invalid traffic light response format: expected array"); return false; } - // 提取响应头 - std::string header{asio::buffers_begin(response_buf.data()), - asio::buffers_begin(response_buf.data()) + header_length}; - Logger::debug("Header received:\n", header); + signals.clear(); + signals.reserve(j.size()); - // 解析 Content-Length - std::regex content_length_regex("Content-Length: (\\d+)"); - std::smatch matches; - if (!std::regex_search(header, matches, content_length_regex)) { - Logger::error("No Content-Length found"); - return false; - } - - size_t content_length = std::stoul(matches[1]); - Logger::debug("Content-Length: ", content_length); - - // 读取响应体 - std::string body; - size_t total_read = 0; - - // 首先处理已经在缓冲区中的数据 - size_t body_part = response_buf.size() - header_length; - if (body_part > 0) { - body.append( - asio::buffers_begin(response_buf.data()) + header_length, - asio::buffers_end(response_buf.data()) - ); - total_read += body_part; - Logger::debug("Read ", body_part, " bytes from buffer"); - } - - // 读取剩余数据 - while (total_read < content_length) { - std::vector buf(1024); - size_t to_read = std::min(buf.size(), content_length - total_read); - size_t bytes_read = socket_->read_some(asio::buffer(buf, to_read), ec); + for (const auto& item : j) { + TrafficLightSignal signal; - if (ec) { - Logger::error("Error reading body: ", ec.message()); - return false; + // 解析必需字段 + if (!item.contains("id") || !item.contains("state")) { + Logger::error("Missing required fields in traffic light signal"); + continue; } - body.append(buf.data(), bytes_read); - total_read += bytes_read; - Logger::debug("Read ", total_read, " of ", content_length, " bytes"); + signal.trafficLightId = item["id"].get(); + + // 支持数字或字符串类型的状态 + if (item["state"].is_number()) { + signal.status = static_cast(item["state"].get()); + } else { + signal.status = TrafficLightSignal::parseStatus(item["state"].get()); + } + + // 解析可选字段 + if (item.contains("timestamp")) { + signal.timestamp = item["timestamp"].get(); + } + + signals.push_back(signal); } - response = body; - //Logger::info("Successfully read complete response"); - return true; + return !signals.empty(); + } + catch (const json::exception& e) { + Logger::error("Failed to parse traffic light response: ", e.what()); + return false; } catch (const std::exception& e) { - Logger::error("Exception in readResponse: ", e.what()); + Logger::error("Error processing traffic light response: ", e.what()); return false; } } bool HTTPDataSource::parseVehicleResponse(const std::string& response, std::vector& vehicles) { try { - auto j = json::parse(response); + json j = json::parse(response); + + if (!j.is_array()) { + Logger::error("Invalid vehicle response format: expected array"); + return false; + } + + vehicles.clear(); + vehicles.reserve(j.size()); for (const auto& item : j) { - // 检查必需字段 - if (!item.contains("vehicleNo") || !item.contains("longitude") || - !item.contains("latitude") || !item.contains("time")) { + Vehicle veh; + + // 解析必需字段 + if (!item.contains("vehicleNo") || !item.contains("latitude") || + !item.contains("longitude") || !item.contains("time")) { Logger::error("Missing required fields in vehicle data"); continue; } - Vehicle v; - v.vehicleNo = item["vehicleNo"].get(); - v.id = v.vehicleNo; + veh.vehicleNo = item["vehicleNo"].get(); + veh.id = veh.vehicleNo; // 使用车辆号作为ID + veh.geo.latitude = item["latitude"].get(); + veh.geo.longitude = item["longitude"].get(); - // 获取经纬度信息 - GeoPosition pos; - pos.longitude = item["longitude"].get(); - pos.latitude = item["latitude"].get(); + // 时间戳转换 + int64_t time = item["time"].get(); + if (time < 0) { + Logger::warning("收到负数时间戳: ", time, ",设置为0"); + time = 0; + } + veh.timestamp = static_cast(time); - // 设置时间戳 - v.timestamp = item["time"].get(); + // 解析可选字段 + if (item.contains("direction")) { + veh.heading = item["direction"].get(); + } - // 更新位置和运动信息 - v.geo = pos; - v.position = coordinateConverter_.toLocalXY(pos.latitude, pos.longitude); - v.updateMotion(pos, v.timestamp); // 更新速度和航向 + // 更新位置信息 + veh.position = coordinateConverter_.toLocalXY(veh.geo.latitude, veh.geo.longitude); + veh.updateMotion(veh.geo, veh.timestamp); // 更新速度和航向 - vehicles.push_back(v); + Logger::debug("Parsed vehicle: id=", veh.id, " vehicleNo=", veh.vehicleNo, + " timestamp=", veh.timestamp, " pos=(", veh.geo.longitude, ",", veh.geo.latitude, ")"); + + vehicles.push_back(veh); } return !vehicles.empty(); } catch (const json::exception& e) { - Logger::error("JSON parsing error: ", e.what()); - Logger::error("Response data: ", response); + Logger::error("Failed to parse vehicle response: ", e.what()); + return false; + } + catch (const std::exception& e) { + Logger::error("Error processing vehicle response: ", e.what()); return false; } } bool HTTPDataSource::parseAircraftResponse(const std::string& response, std::vector& aircraft) { try { - auto j = json::parse(response); - Logger::debug("Parsing aircraft data: ", response); + json j = json::parse(response); + + if (!j.is_array()) { + Logger::error("Invalid aircraft response format: expected array"); + return false; + } + + aircraft.clear(); + aircraft.reserve(j.size()); for (const auto& item : j) { - // 检查必需字段 - if (!item.contains("flightNo") || !item.contains("longitude") || - !item.contains("latitude") || !item.contains("time")) { + Aircraft ac; + + // 解析必需字段 + if (!item.contains("flightNo") || !item.contains("latitude") || + !item.contains("longitude") || !item.contains("time")) { Logger::error("Missing required fields in aircraft data"); continue; } - Aircraft a; - a.flightNo = item["flightNo"].get(); - a.id = a.flightNo; + ac.flightNo = item["flightNo"].get(); + ac.id = ac.flightNo; // 使用航班号作为ID + ac.geo.latitude = item["latitude"].get(); + ac.geo.longitude = item["longitude"].get(); - // 获取经纬度信息 - GeoPosition pos; - pos.longitude = item["longitude"].get(); - pos.latitude = item["latitude"].get(); + // 时间戳转换 + int64_t time = item["time"].get(); + if (time < 0) { + Logger::warning("收到负数时间戳: ", time, ",设置为0"); + time = 0; + } + ac.timestamp = static_cast(time); - // 设置时间戳 - a.timestamp = item["time"].get(); + // 解析可选字段 + if (item.contains("direction")) { + ac.heading = item["direction"].get(); + } - // 更新位置和运动信息 - a.geo = pos; - a.position = coordinateConverter_.toLocalXY(pos.latitude, pos.longitude); - a.updateMotion(pos, a.timestamp); // 更新速度和航向 + // 更新位置信息 + ac.position = coordinateConverter_.toLocalXY(ac.geo.latitude, ac.geo.longitude); + ac.updateMotion(ac.geo, ac.timestamp); // 更新速度和航向 - // 默认高度为0(地面) - a.altitude = 0.0; + Logger::debug("Parsed aircraft: id=", ac.id, " flightNo=", ac.flightNo, + " timestamp=", ac.timestamp, " pos=(", ac.geo.longitude, ",", ac.geo.latitude, ")"); - aircraft.push_back(a); + aircraft.push_back(ac); } return !aircraft.empty(); } catch (const json::exception& e) { - Logger::error("JSON parsing error: ", e.what()); - Logger::error("Response data: ", response); + Logger::error("Failed to parse aircraft response: ", e.what()); + return false; + } + catch (const std::exception& e) { + Logger::error("Error processing aircraft response: ", e.what()); return false; } } \ No newline at end of file diff --git a/src/network/HTTPDataSource.h b/src/network/HTTPDataSource.h index f554e1d..80c3d8e 100644 --- a/src/network/HTTPDataSource.h +++ b/src/network/HTTPDataSource.h @@ -3,21 +3,21 @@ #include "collector/DataSource.h" #include "spatial/CoordinateConverter.h" -#include #include #include "collector/DataSourceConfig.h" #include #include #include #include "core/System.h" - -namespace asio = boost::asio; +#include class HTTPDataSource : public DataSource { public: explicit HTTPDataSource(const DataSourceConfig& config); ~HTTPDataSource() override; + const DataSourceConfig& getConfig() const { return config_; } + DataSourceConfig config_; bool connect() override; void disconnect() override; @@ -25,26 +25,31 @@ public: bool fetchAircraftData(std::vector& aircraft) override; bool fetchVehicleData(std::vector& vehicles) override; + bool fetchTrafficLightSignals(std::vector& signals) override; private: std::string host_; std::string port_; - asio::io_context io_context_; - std::unique_ptr socket_; CoordinateConverter coordinateConverter_; std::mutex mutex_; + CURL* curl_; std::chrono::steady_clock::time_point last_connect_attempt_; + std::chrono::steady_clock::time_point last_health_check_; // 上次健康检查时间 static constexpr int MAX_RETRIES = 3; // 单次连接最大重试次数 + static constexpr int HEALTH_CHECK_INTERVAL = 60; // 健康检查间隔(秒) std::atomic is_reconnecting_{false}; // 重连状态标志 bool tryReconnect(); bool ensureConnected(); + bool checkConnectionHealth(); // 检查连接健康状况 - bool sendRequest(const std::string& path); - bool readResponse(std::string& response); + bool sendRequest(const std::string& path, std::string& response); bool parseAircraftResponse(const std::string& response, std::vector& aircraft); bool parseVehicleResponse(const std::string& response, std::vector& vehicles); + bool parseTrafficLightResponse(const std::string& response, std::vector& signals); + + static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp); }; #endif // AIRPORT_NETWORK_HTTP_DATA_SOURCE_H diff --git a/src/network/MessageTypes.h b/src/network/MessageTypes.h index 857ddd3..37ae820 100644 --- a/src/network/MessageTypes.h +++ b/src/network/MessageTypes.h @@ -55,19 +55,15 @@ struct CollisionWarningMessage { }; struct TimeoutWarningMessage { - std::string type = "data_source_timeout"; - std::string host; - int port; - int64_t elapsed_ms; - uint64_t timestamp; + std::string source; // 超时来源 + int64_t elapsed_ms; // 超时时长 + bool is_read_timeout; // 是否是读取超时 nlohmann::json toJson() const { return { - {"type", type}, - {"host", host}, - {"port", port}, + {"source", source}, {"elapsed_ms", elapsed_ms}, - {"timestamp", timestamp} + {"is_read_timeout", is_read_timeout} }; } }; diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 2d2708d..0cdb367 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -3,7 +3,6 @@ #include #include -#include #include "types/VehicleCommand.h" class NetworkInterface { @@ -13,9 +12,6 @@ public: virtual void disconnect() = 0; virtual bool send(const VehicleCommand& cmd) = 0; virtual bool isConnected() const = 0; - -protected: - boost::asio::io_context io_context_; }; #endif // AIRPORT_NETWORK_INTERFACE_H \ No newline at end of file diff --git a/src/network/WebSocketServer.cpp b/src/network/WebSocketServer.cpp index c064fe2..aafe70e 100644 --- a/src/network/WebSocketServer.cpp +++ b/src/network/WebSocketServer.cpp @@ -1,4 +1,5 @@ #include "network/WebSocketServer.h" +#include "utils/Logger.h" #include namespace network { @@ -24,6 +25,7 @@ WebSocketServer::~WebSocketServer() { } void WebSocketServer::start() { + Logger::info("WebSocket 服务器启动,监听端口: ", acceptor_.local_endpoint().port()); handleAccept(); ioc_.run(); } @@ -31,24 +33,35 @@ void WebSocketServer::start() { void WebSocketServer::broadcast(const std::string& message) { std::lock_guard lock(sessions_mutex_); auto it = sessions_.begin(); + int successCount = 0; + int failCount = 0; + while (it != sessions_.end()) { if (auto session = it->lock()) { // 获取 shared_ptr try { session->write(boost::asio::buffer(message)); + ++successCount; ++it; } catch (...) { + Logger::warning("广播消息到客户端失败"); + ++failCount; it = sessions_.erase(it); // 移除失败的会话 } } else { + Logger::debug("移除失效的会话"); it = sessions_.erase(it); // 移除已失效的会话 } } + + Logger::debug("广播消息完成: ", successCount, " 个成功, ", failCount, " 个失败, 当前共 ", sessions_.size(), " 个连接"); } void WebSocketServer::handleAccept() { acceptor_.async_accept( [this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { if (!ec) { + Logger::info("接受新的 WebSocket 连接: ", socket.remote_endpoint().address().to_string()); + // 创建新的 WebSocket 会话 auto ws = std::make_shared>(std::move(socket)); @@ -60,10 +73,15 @@ void WebSocketServer::handleAccept() { { std::lock_guard lock(sessions_mutex_); sessions_.push_back(ws); // 存储 weak_ptr + Logger::info("WebSocket 握手成功,当前连接数: ", sessions_.size()); } doRead(ws); + } else { + Logger::error("WebSocket 握手失败: ", ec.message()); } }); + } else { + Logger::error("接受 WebSocket 连接失败: ", ec.message()); } // 继续接受新的连接 @@ -99,6 +117,7 @@ void WebSocketServer::doRead(std::shared_ptrclose(boost::beast::websocket::close_code::normal); + Logger::debug("关闭 WebSocket 连接"); } catch (...) { - // 忽略关闭时的错误 + Logger::warning("关闭 WebSocket 连接时发生错误"); } } } sessions_.clear(); + Logger::info("WebSocket 服务器已停止"); } } // namespace network \ No newline at end of file diff --git a/src/spatial/AirportBounds.h b/src/spatial/AirportBounds.h index 8f99b18..1b65b35 100644 --- a/src/spatial/AirportBounds.h +++ b/src/spatial/AirportBounds.h @@ -15,15 +15,14 @@ enum class AreaType { // 区域配置 struct AreaConfig { - double vehicleCollisionRadius; // 车辆间碰撞检测半径 - double aircraftGroundRadius; // 航空器与车辆碰撞检测半径 + double vehicleCollisionRadius; // 车辆间冲突检测半径 + double aircraftGroundRadius; // 航空器与车辆冲突检测半径 double heightThreshold; // 高度阈值 // 获取不同类型交通工具间的告警阈值 struct CollisionThresholds { - double warning; // 警告距离 - double critical; // 危险距离 - double emergency; // 紧急距离 + double warning; // 预警距离 + double critical; // 告警距离 }; // 计算两个交通工具之间的告警阈值 @@ -34,9 +33,8 @@ struct AreaConfig { // 计算不同级别的阈值 return { - baseRadius, // 警告距离:为基础距离 - baseRadius / 2.0, // 危险距离:为基础距离的一半 - baseRadius / 4.0 // 紧急距离:为基础距离的四分之一 + baseRadius, // 预警距离:为基础距离 + baseRadius / 2.0 // 告警距离:为基础距离的一半 }; } }; diff --git a/src/types/BasicTypes.cpp b/src/types/BasicTypes.cpp index dde7c1c..938b397 100644 --- a/src/types/BasicTypes.cpp +++ b/src/types/BasicTypes.cpp @@ -82,6 +82,8 @@ bool Vehicle::isValidSpeed(double speed) const { void MovingObject::updateMotion(const GeoPosition& newPos, uint64_t newTime) { // 检查时间戳 if (!positionHistory.empty() && newTime <= positionHistory.back().timestamp) { + Logger::debug("[Motion] Ignore outdated data: current=", newTime, + " last=", positionHistory.back().timestamp); return; // 忽略重复或过时的数据 } @@ -93,35 +95,66 @@ void MovingObject::updateMotion(const GeoPosition& newPos, uint64_t newTime) { // 计算速度和航向 if (positionHistory.size() >= 2) { - // 使用所有可用的历史记录计算平均速度 - double totalDistance = 0; - double totalTime = 0; + // 找到合适的历史点来计算速度和航向 + const auto& curr = positionHistory.back(); + const auto& prev = positionHistory.front(); // 使用最早的历史点 - for (size_t i = 1; i < positionHistory.size(); ++i) { - const auto& prev = positionHistory[i-1]; - const auto& curr = positionHistory[i]; + // 计算距离和时间差 + double distance = calculateDistance(prev.geo, curr.geo); // 单位:米 + double timeDiff = static_cast(curr.timestamp - prev.timestamp) / 1000.0; // 转换为秒 + + Logger::debug("[Motion] Position update: ", + "\n Current: lat=", curr.geo.latitude, ", lon=", curr.geo.longitude, + "\n Previous: lat=", prev.geo.latitude, ", lon=", prev.geo.longitude, + "\n Distance=", distance, "m, TimeDiff=", timeDiff, "s"); + + // 只有当位置变化足够大且时间差足够长时才更新速度和航向 + static const double MIN_DISTANCE = 0.5; // 最小位置变化阈值(米) + static const double MIN_TIME = 0.2; // 最小时间差阈值(秒) + + if (distance > MIN_DISTANCE && timeDiff > MIN_TIME) { + // 计算新的速度 + double newSpeed = distance / timeDiff; // 米/秒 - totalDistance += calculateDistance(prev.geo, curr.geo); - totalTime += static_cast(curr.timestamp - prev.timestamp); - } - - if (totalTime > 0) { - double avgSpeed = totalDistance / totalTime; - if (isValidSpeed(avgSpeed)) { + if (isValidSpeed(newSpeed)) { // 使用指数移动平均来平滑速度 const double alpha = getSpeedSmoothingFactor(); if (speed == 0) { - speed = avgSpeed; // 第一次计算 + speed = newSpeed; // 第一次计算 } else { - speed = speed * (1 - alpha) + avgSpeed * alpha; // 平滑更新 + speed = speed * (1 - alpha) + newSpeed * alpha; // 平滑更新 } + + Logger::debug("[Motion] Speed updated: distance=", distance, "m, timeDiff=", timeDiff, + "s, newSpeed=", newSpeed, "m/s, smoothedSpeed=", speed, "m/s"); + } else { + Logger::debug("[Motion] Invalid speed: ", newSpeed, "m/s (exceeds limit)"); } + + // 计算新的航向 + double newHeading = calculateHeading(prev.geo, curr.geo); + + // 使用指数移动平均来平滑航向 + const double beta = 0.3; // 航向平滑因子 + if (heading == 0) { + heading = newHeading; // 第一次计算 + } else { + // 处理航向角的循环性(0-360度) + double diff = newHeading - heading; + if (diff > 180) { + diff -= 360; + } else if (diff < -180) { + diff += 360; + } + heading = fmod(heading + diff * beta + 360, 360); + } + + Logger::debug("[Motion] Heading updated: newHeading=", newHeading, + "deg, smoothedHeading=", heading, "deg"); + } else { + Logger::debug("[Motion] No update: distance=", distance, "m < ", MIN_DISTANCE, + "m or timeDiff=", timeDiff, "s < ", MIN_TIME, "s"); } - - // 使用最近两个点计算航向 - const auto& prev = positionHistory[positionHistory.size()-2]; - const auto& curr = positionHistory.back(); - heading = calculateHeading(prev.geo, curr.geo); } // 更新当前位置 diff --git a/src/types/BasicTypes.h b/src/types/BasicTypes.h index 10c3122..4f282d3 100644 --- a/src/types/BasicTypes.h +++ b/src/types/BasicTypes.h @@ -28,6 +28,7 @@ struct PositionRecord { // 移动物体基类 class MovingObject { public: + MovingObject() : heading(0.0), speed(0.0), timestamp(0) {} // 添加构造函数 virtual ~MovingObject() = default; // 添加虚析构函数以支持 dynamic_cast std::string id; diff --git a/src/types/TrafficLightTypes.h b/src/types/TrafficLightTypes.h new file mode 100644 index 0000000..5f44c59 --- /dev/null +++ b/src/types/TrafficLightTypes.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +enum class SignalStatus { + RED, + GREEN, + UNKNOWN +}; + +struct TrafficLightSignal { + std::string trafficLightId; // 红绿灯设备 ID + SignalStatus status; // 信号灯状态 + uint64_t timestamp; // 时间戳 + + static SignalStatus parseStatus(const std::string& status) { + if (status == "red") return SignalStatus::RED; + if (status == "green") return SignalStatus::GREEN; + return SignalStatus::UNKNOWN; + } +}; \ No newline at end of file diff --git a/src/types/VehicleCommand.h b/src/types/VehicleCommand.h index fb61684..17b78e7 100644 --- a/src/types/VehicleCommand.h +++ b/src/types/VehicleCommand.h @@ -4,24 +4,54 @@ #include #include "VehicleData.h" +// 指令类型 enum class CommandType { - CONTINUE, // 继续运行 - STOP, // 停止 - SLOW_DOWN, // 减速 - TURN_LEFT, // 左转 - TURN_RIGHT // 右转 + SIGNAL, // 信号灯指令(最高优先级) + ALERT, // 告警指令(中等优先级) + WARNING, // 预警指令(最低优先级) + RESUME // 恢复指令(最低优先级) +}; + +// 信号灯状态 +enum class SignalState { + RED, // 红灯 + GREEN // 绿灯 +}; + +// 指令原因 +enum class CommandReason { + TRAFFIC_LIGHT, // 红绿灯控制 + AIRCRAFT_CROSSING, // 航空器交叉 + SPECIAL_VEHICLE, // 特勤车辆 + AIRCRAFT_PUSH, // 航空器推出 + RESUME_TRAFFIC // 恢复通行 }; struct VehicleCommand { std::string vehicleId; // 车辆ID CommandType type; // 命令类型 - Vector2D avoidanceVector; // 避障向量 + CommandReason reason; // 命令原因 + SignalState signalState; // 信号灯状态(仅当 type 为 SIGNAL 时有效) + Vector2D targetPosition; // 目标位置(如交叉路口) double speed{0.0}; // 目标速度 - double heading{0.0}; // 目标航向 + double distance{0.0}; // 与关键点的距离 uint64_t timestamp; // 时间戳 - // 可选的优先级 - int priority{0}; // 数字越大优先级越高 + // 获取指令优先级(数字越大优先级越高) + int getPriority() const { + switch(type) { + case CommandType::SIGNAL: return 4; + case CommandType::ALERT: return 3; + case CommandType::WARNING: return 2; + case CommandType::RESUME: return 1; + default: return 0; + } + } + + // 判断是否可以被新指令覆盖 + bool canBeOverriddenBy(const VehicleCommand& newCmd) const { + return newCmd.getPriority() >= this->getPriority(); + } }; #endif // AIRPORT_TYPES_VEHICLE_COMMAND_H \ No newline at end of file diff --git a/src/utils/Logger.h b/src/utils/Logger.h index 0967dce..ecc29d7 100644 --- a/src/utils/Logger.h +++ b/src/utils/Logger.h @@ -2,9 +2,11 @@ #define AIRPORT_UTILS_LOGGER_H #include +#include #include #include #include +#include enum class LogLevel { DEBUG, @@ -15,7 +17,14 @@ enum class LogLevel { class Logger { public: + static void initialize(const std::string& filename, LogLevel level = LogLevel::INFO) { + std::lock_guard lock(getMutex()); + currentLevel() = level; + logFile().open(filename, std::ios::app); + } + static void setLogLevel(LogLevel level) { + std::lock_guard lock(getMutex()); currentLevel() = level; } @@ -53,6 +62,16 @@ private: return level; } + static std::ofstream& logFile() { + static std::ofstream file; + return file; + } + + static std::mutex& getMutex() { + static std::mutex mutex; + return mutex; + } + template static void log(const char* level, Args... args) { auto now = std::chrono::system_clock::now(); @@ -66,7 +85,13 @@ private: << " [" << level << "] "; (ss << ... << args) << std::endl; + + std::lock_guard lock(getMutex()); std::cerr << ss.str(); + if (logFile().is_open()) { + logFile() << ss.str(); + logFile().flush(); + } } }; diff --git a/src/vehicle/ControllableVehicles.cpp b/src/vehicle/ControllableVehicles.cpp index 2f8991e..a424e70 100644 --- a/src/vehicle/ControllableVehicles.cpp +++ b/src/vehicle/ControllableVehicles.cpp @@ -49,4 +49,11 @@ void ControllableVehicles::loadConfig(const std::string& configFile) { } catch (const std::exception& e) { throw std::runtime_error("Failed to parse controllable vehicles config: " + std::string(e.what())); } -} \ No newline at end of file +} + +void ControllableVehicles::sendCommand(const std::string& vehicleNo, const VehicleCommand& command) { + auto vehicle = findVehicle(vehicleNo); + if (vehicle) { + // TODO: 发送控制命令 + } +} \ No newline at end of file diff --git a/src/vehicle/ControllableVehicles.h b/src/vehicle/ControllableVehicles.h index 3151edd..018a8c8 100644 --- a/src/vehicle/ControllableVehicles.h +++ b/src/vehicle/ControllableVehicles.h @@ -3,6 +3,7 @@ #include #include #include +#include "types/VehicleCommand.h" struct ControllableVehicleConfig { std::string vehicleNo; // 车牌号 @@ -13,7 +14,7 @@ struct ControllableVehicleConfig { class ControllableVehicles { public: explicit ControllableVehicles(const std::string& configFile); - ~ControllableVehicles() = default; + virtual ~ControllableVehicles() = default; // 获取所有可控车辆配置 const std::vector& getVehicles() const; @@ -24,6 +25,9 @@ public: // 检查车辆是否可控 virtual bool isControllable(const std::string& vehicleNo) const; + // 发送控制命令 + virtual void sendCommand(const std::string& vehicleNo, const VehicleCommand& command); + private: std::vector vehicles_; void loadConfig(const std::string& configFile); diff --git a/tests/BasicTypesTest.cpp b/tests/BasicTypesTest.cpp index fb706e9..8ab20de 100644 --- a/tests/BasicTypesTest.cpp +++ b/tests/BasicTypesTest.cpp @@ -39,8 +39,8 @@ protected: TEST_F(BasicTypesTest, MovingObjectSpeedCalculation) { TestMovingObject obj; - // 获取当前时间戳 - time_t now = time(nullptr); + // 使用毫秒级时间戳 + uint64_t now = 1000000; // 第一个位置 GeoPosition pos1{36.0, 120.0}; @@ -49,7 +49,12 @@ TEST_F(BasicTypesTest, MovingObjectSpeedCalculation) { // 第二个位置(向东移动约90米/秒) GeoPosition pos2{36.0, 120.001}; - obj.updateMotion(pos2, now + 1); // 1秒后 + double distance = obj.calculateDistance(pos1, pos2); + printf("Distance: %.2f meters\n", distance); + + obj.updateMotion(pos2, now + 1000); // 1000毫秒后 + printf("Speed: %.2f m/s\n", obj.speed); + EXPECT_NEAR(obj.speed, 90.0, 2.0); // 使用 Haversine 公式计算的实际距离 // 检查航向角 @@ -61,15 +66,15 @@ TEST_F(BasicTypesTest, AircraftValidation) { Aircraft aircraft; aircraft.flightNo = "CES2501"; - // 获取当前时间戳 - time_t now = time(nullptr); + // 使用毫秒级时间戳 + uint64_t now = 1000000; // 测试位置更新 GeoPosition pos1{36.0, 120.0}; aircraft.updateMotion(pos1, now); GeoPosition pos2{36.0, 120.001}; // 约90米 - aircraft.updateMotion(pos2, now + 2); // 2秒后 + aircraft.updateMotion(pos2, now + 2000); // 2000毫秒后 // 速度应该约为45米/秒 EXPECT_NEAR(aircraft.speed, 45.0, 2.0); @@ -80,23 +85,44 @@ TEST_F(BasicTypesTest, VehicleValidation) { Vehicle vehicle; vehicle.vehicleNo = "VEH001"; - // 获取当前时间戳 - time_t now = time(nullptr); + // 使用毫秒级时间戳 + uint64_t now = 1000000; // 测试位置更新 GeoPosition pos1{36.0, 120.0}; vehicle.updateMotion(pos1, now); - // 使用约22米的位置变化(小于50米的跳变限制) - GeoPosition pos2{36.0002, 120.0}; // 约22米 - vehicle.updateMotion(pos2, now + 1.0); // 1秒后 + // 使用约9米的位置变化(小于10米的跳变限制) + GeoPosition pos2{36.00008, 120.0}; // 约9米 + double distance = vehicle.calculateDistance(pos1, pos2); + printf("Vehicle distance: %.2f meters\n", distance); - // 速度应该约为22米/秒 - EXPECT_NEAR(vehicle.speed, 22.0, 2.0); + vehicle.updateMotion(pos2, now + 1000); // 1000毫秒后 + printf("Vehicle speed: %.2f m/s\n", vehicle.speed); + + // 速度应该约为9米/秒(小于20米/秒的限制) + EXPECT_NEAR(vehicle.speed, 9.0, 1.0); } // 添加边界条件测试 TEST_F(BasicTypesTest, VectorZeroMagnitude) { Vector2D v(0.0, 0.0); EXPECT_DOUBLE_EQ(v.magnitude(), 0.0); +} + +// 测试距离计算 +TEST_F(BasicTypesTest, DistanceCalculation) { + TestMovingObject obj; + + // 测试经度方向的距离(约90米) + GeoPosition pos1{36.0, 120.0}; + GeoPosition pos2{36.0, 120.001}; + double distance1 = obj.calculateDistance(pos1, pos2); + EXPECT_NEAR(distance1, 90.0, 1.0); + + // 测试纬度方向的距离(约22米) + GeoPosition pos3{36.0, 120.0}; + GeoPosition pos4{36.0002, 120.0}; + double distance2 = obj.calculateDistance(pos3, pos4); + EXPECT_NEAR(distance2, 22.0, 1.0); } \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 160a241..ff8e4e8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,7 @@ set(TEST_SOURCES BasicTypesTest.cpp HTTPDataSourceTest.cpp DataCollectorTest.cpp + TrafficLightDetectorTest.cpp ) # 创建测试可执行文件 diff --git a/tests/CollisionDetectorTest.cpp b/tests/CollisionDetectorTest.cpp index d0ca932..3ec77e3 100644 --- a/tests/CollisionDetectorTest.cpp +++ b/tests/CollisionDetectorTest.cpp @@ -46,7 +46,7 @@ protected: airportBounds_ = std::make_unique(); mockControllableVehicles_ = std::make_unique(); - // 创建碰撞检测器 + // 创建冲突检测器 detector_ = std::make_unique(*airportBounds_, *mockControllableVehicles_); } @@ -55,7 +55,7 @@ protected: std::unique_ptr detector_; }; -// 测试可控车辆与航空器的碰撞检测 +// 测试可控车辆与航空器的冲突检测 TEST_F(CollisionDetectorTest, DetectControllableVehicleAircraftCollision) { // 设置 Mock 期望 - 在创建数据之前设置 EXPECT_CALL(*mockControllableVehicles_, isControllable("VEH001")) @@ -83,22 +83,22 @@ TEST_F(CollisionDetectorTest, DetectControllableVehicleAircraftCollision) { detector_->updateTraffic({aircraft}, {vehicle}); Logger::info("Updated traffic data"); - // 执行碰撞检测 + // 执行冲突检测 auto risks = detector_->detectCollisions(); // 验证结果 - ASSERT_EQ(risks.size(), 1); // 应该检测到一个碰撞风险 + ASSERT_EQ(risks.size(), 1); // 应该检测到一个冲突风险 if (!risks.empty()) { const auto& risk = risks[0]; EXPECT_EQ(risk.id1, "TEST001"); // 航空器ID EXPECT_EQ(risk.id2, "VEH001"); // 车辆ID EXPECT_EQ(risk.distance, 20); // 距离应该是20米 - EXPECT_EQ(risk.level, RiskLevel::EMERGENCY); // 20米距离应该是严重风险 + EXPECT_EQ(risk.level, RiskLevel::CRITICAL); // 20米距离应该是严重风险 EXPECT_GT(risk.relativeSpeed, 0); // 相对速度应该大于0 } } -// 测试可控车辆与其他车辆的碰撞检测 +// 测试可控车辆与其他车辆的冲突检测 TEST_F(CollisionDetectorTest, DetectControllableVehicleOtherVehicleCollision) { // 设置 Mock 期望 EXPECT_CALL(*mockControllableVehicles_, isControllable("VEH001")) @@ -122,20 +122,20 @@ TEST_F(CollisionDetectorTest, DetectControllableVehicleOtherVehicleCollision) { // 更新交通数据 detector_->updateTraffic({}, {controlVehicle, otherVehicle}); - // 执行碰撞检测 + // 执行冲突检测 auto risks = detector_->detectCollisions(); // 验证结果 - ASSERT_EQ(risks.size(), 1); // 应该检测到一个碰撞风险 + ASSERT_EQ(risks.size(), 1); // 应该检测到一个冲突风险 if (!risks.empty()) { EXPECT_EQ(risks[0].id1, "VEH001"); // 可控车辆ID EXPECT_EQ(risks[0].id2, "VEH002"); // 其他车辆ID EXPECT_EQ(risks[0].distance, 20); // 距离应该是20米 - EXPECT_EQ(risks[0].level, RiskLevel::CRITICAL); // 20米距离应该是严重风险 + EXPECT_EQ(risks[0].level, RiskLevel::CRITICAL); // 20米距离应该是告警 } } -// 测试非可控车辆的碰撞检测(不应该产生碰撞风险) +// 测试非可控车辆的冲突检测(不应该产生冲突风险) TEST_F(CollisionDetectorTest, NonControllableVehicleCollision) { // 设置测试数据 Vehicle vehicle1; @@ -157,14 +157,14 @@ TEST_F(CollisionDetectorTest, NonControllableVehicleCollision) { // 更新交通数据 detector_->updateTraffic({}, {vehicle1, vehicle2}); - // 执行碰撞检测 + // 执行冲突检测 auto risks = detector_->detectCollisions(); // 验证结果 - EXPECT_EQ(risks.size(), 0); // 非可控车辆之间的碰撞不应该被检测 + EXPECT_EQ(risks.size(), 0); // 非可控车辆之间的冲突不应该被检测 } -// 测试多个可控车辆之间的碰撞检测 +// 测试多个可控车辆之间的冲突检测 TEST_F(CollisionDetectorTest, MultipleControllableVehiclesCollision) { // 设置 Mock 期望 - 所有车辆都是可控的 ON_CALL(*mockControllableVehicles_, isControllable(testing::_)) @@ -203,18 +203,18 @@ TEST_F(CollisionDetectorTest, MultipleControllableVehiclesCollision) { // 更新交通数据 detector_->updateTraffic({}, vehicles); - // 执行碰撞检测 + // 执行冲突检测 auto risks = detector_->detectCollisions(); // 验证结果 - EXPECT_EQ(risks.size(), 1); // 应该只检测到1个碰撞风险(VEH001和VEH002之间) + EXPECT_EQ(risks.size(), 1); // 应该只检测到1个冲突风险(VEH001和VEH002之间) if (risks.size() == 1) { - // 验证碰撞风险的详细信息 + // 验证冲突风险的详细信息 EXPECT_TRUE((risks[0].id1 == "VEH001" && risks[0].id2 == "VEH002") || (risks[0].id1 == "VEH002" && risks[0].id2 == "VEH001")); EXPECT_EQ(risks[0].distance, 20.0); - EXPECT_EQ(risks[0].level, RiskLevel::CRITICAL); // 20米���该是危险级别 + EXPECT_EQ(risks[0].level, RiskLevel::CRITICAL); // 20米应该是告警级别 } } @@ -341,7 +341,7 @@ TEST_F(CollisionDetectorTest, PerformanceTest) { Logger::info("Updated traffic data with ", aircraft.size(), " aircraft and ", vehicles.size(), " vehicles (including 3 controllable vehicles)"); - // 执行碰撞检测并记录时间 + // 执行冲突检测并记录时间 auto start = std::chrono::high_resolution_clock::now(); auto risks = detector_->detectCollisions(); @@ -352,7 +352,7 @@ TEST_F(CollisionDetectorTest, PerformanceTest) { Logger::info("Found ", risks.size(), " risks"); // 验证结果 - ASSERT_GT(risks.size(), 0); // 应该检测到一些碰撞风险 + ASSERT_GT(risks.size(), 0); // 应该检测到一些冲突风险 // 验证性能要求 EXPECT_LT(duration.count(), 100000); // 期望处理时间小于100ms diff --git a/tests/DataCollectorTest.cpp b/tests/DataCollectorTest.cpp index 79a53c0..e5ce18b 100644 --- a/tests/DataCollectorTest.cpp +++ b/tests/DataCollectorTest.cpp @@ -2,6 +2,8 @@ #include #include "collector/DataCollector.h" #include "utils/Logger.h" +#include "network/MessageTypes.h" +#include "core/System.h" // 创建一个 Mock DataSource 类 class MockDataSource : public DataSource { @@ -11,6 +13,7 @@ public: MOCK_METHOD(bool, isAvailable, (), (const, override)); MOCK_METHOD(bool, fetchAircraftData, (std::vector&), (override)); MOCK_METHOD(bool, fetchVehicleData, (std::vector&), (override)); + MOCK_METHOD(bool, fetchTrafficLightSignals, (std::vector&), (override)); }; class DataCollectorTest : public ::testing::Test { @@ -48,6 +51,15 @@ protected: return v; } + // 创建测试用的红绿灯信号 + TrafficLightSignal createTestTrafficLight(const std::string& id, int state) { + TrafficLightSignal signal; + signal.trafficLightId = id; + signal.status = static_cast(state); + signal.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); + return signal; + } + std::unique_ptr collector; std::shared_ptr mockSource; }; @@ -167,4 +179,323 @@ TEST_F(DataCollectorTest, ErrorHandling) { auto vehicles = collector->getVehicleData(); EXPECT_TRUE(vehicles.empty()); +} + +// 测试红绿灯信号获取 +TEST_F(DataCollectorTest, TrafficLightSignalsTest) { + std::vector testSignals = { + createTestTrafficLight("TL001", 0), // RED = 0 + createTestTrafficLight("TL002", 1), // GREEN = 1 + createTestTrafficLight("TL003", 2) // YELLOW = 2 + }; + + // 设置 Mock 数据返回 + EXPECT_CALL(*mockSource, fetchTrafficLightSignals) + .WillOnce(::testing::DoAll( + ::testing::SetArgReferee<0>(testSignals), + ::testing::Return(true) + )); + + // 执行刷新 + collector->refresh(); + + // 验证数据 + auto signals = collector->getTrafficLightSignals(); + EXPECT_EQ(signals.size(), 3); + if (signals.size() >= 3) { + EXPECT_EQ(signals[0].trafficLightId, "TL001"); + EXPECT_EQ(signals[0].status, SignalStatus::RED); // RED = 0 + EXPECT_EQ(signals[1].trafficLightId, "TL002"); + EXPECT_EQ(signals[1].status, SignalStatus::GREEN); // GREEN = 1 + EXPECT_EQ(signals[2].trafficLightId, "TL003"); + EXPECT_EQ(signals[2].status, SignalStatus::YELLOW); // YELLOW = 2 + } +} + +// 测试红绿灯信号错误处理 +TEST_F(DataCollectorTest, TrafficLightSignalsErrorTest) { + // 设置 Mock 返回错误 + EXPECT_CALL(*mockSource, fetchTrafficLightSignals) + .WillOnce(::testing::Return(false)); + + // 执行刷新 + collector->refresh(); + + // 验证数据为空 + auto signals = collector->getTrafficLightSignals(); + EXPECT_TRUE(signals.empty()); +} + +// 测试连接健康检查和超时告警 +TEST_F(DataCollectorTest, ConnectionHealthAndTimeout) { + DataSourceConfig dataSourceConfig; + dataSourceConfig.host = "localhost"; + dataSourceConfig.port = 8080; + dataSourceConfig.timeout_ms = 1000; // 1秒超时 + dataSourceConfig.refresh_interval_ms = 100; // 100ms刷新间隔 + + WarnConfig warnConfig; + warnConfig.warning_interval_ms = 500; // 500ms告警间隔 + + collector->initialize(dataSourceConfig, warnConfig); + + // 模拟连接健康检查失败 + EXPECT_CALL(*mockSource, isAvailable()) + .WillRepeatedly(::testing::Return(false)); + + // 模据��取失败 + EXPECT_CALL(*mockSource, fetchAircraftData) + .WillRepeatedly(::testing::Return(false)); + EXPECT_CALL(*mockSource, fetchVehicleData) + .WillRepeatedly(::testing::Return(false)); + + // 启动采集 + collector->start(); + + // 等待超过超时时间 + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + + // 停止采集 + collector->stop(); + + // 验证数据为空 + auto aircraft = collector->getAircraftData(); + EXPECT_TRUE(aircraft.empty()); + auto vehicles = collector->getVehicleData(); + EXPECT_TRUE(vehicles.empty()); +} + +// 测试连接恢复 +TEST_F(DataCollectorTest, ConnectionRecovery) { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8080; + config.refresh_interval_ms = 10; + + WarnConfig warnConfig; + warnConfig.warning_interval_ms = 20; + + collector->initialize(config, warnConfig); + + std::vector testAircraft = { + createTestAircraft("TEST1", 36.36, 120.08) + }; + + // 移除 InSequence,直接设置期望 + EXPECT_CALL(*mockSource, fetchAircraftData) + .WillOnce(::testing::Return(false)) + .WillOnce(::testing::DoAll( + ::testing::SetArgReferee<0>(testAircraft), + ::testing::Return(true) + )); + + // ���行两次刷新 + collector->refresh(); // 第一次失败 + collector->refresh(); // 第二次成功 + + // 验证数据 + auto aircraft = collector->getAircraftData(); + EXPECT_EQ(aircraft.size(), 1); + if (!aircraft.empty()) { + EXPECT_EQ(aircraft[0].flightNo, "TEST1"); + } +} + +// 测试长连接下的数据获取 +TEST_F(DataCollectorTest, LongConnectionDataFetch) { + DataSourceConfig dataSourceConfig; + dataSourceConfig.host = "localhost"; + dataSourceConfig.port = 8080; + dataSourceConfig.refresh_interval_ms = 100; + + collector->initialize(dataSourceConfig, WarnConfig{}); + + std::vector testAircraft = { + createTestAircraft("TEST1", 36.36, 120.08) + }; + + // 模拟连接保持可用 + EXPECT_CALL(*mockSource, isAvailable()) + .WillRepeatedly(::testing::Return(true)); + + // 模拟多次成功获取数据 + EXPECT_CALL(*mockSource, fetchAircraftData) + .Times(::testing::AtLeast(3)) + .WillRepeatedly(::testing::DoAll( + ::testing::SetArgReferee<0>(testAircraft), + ::testing::Return(true) + )); + + // 启动采集 + collector->start(); + + // 等待多个刷新周期 + std::this_thread::sleep_for(std::chrono::milliseconds(350)); // 应该执行至少3次获取 + + // 验证数据 + auto aircraft = collector->getAircraftData(); + EXPECT_FALSE(aircraft.empty()); + if (!aircraft.empty()) { + EXPECT_EQ(aircraft[0].flightNo, "TEST1"); + } + + // 停止采集 + collector->stop(); +} + +TEST_F(DataCollectorTest, InitializeWithTimeoutConfig) { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8080; + config.timeout_ms = 5000; // 连接超时 5 秒 + config.read_timeout_ms = 2000; // 读取超时 2 秒 + config.refresh_interval_ms = 100; + + WarnConfig warnConfig; + warnConfig.warning_interval_ms = 500; + + EXPECT_NO_THROW(collector->initialize(config, warnConfig)); +} + +TEST_F(DataCollectorTest, TimeoutWarningTest) { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8080; + config.timeout_ms = 1000; // 连接超时 1 秒 + config.read_timeout_ms = 500; // 读取超时 0.5 秒 + config.refresh_interval_ms = 100; + + WarnConfig warnConfig; + warnConfig.warning_interval_ms = 200; // 警告间隔 0.2 秒 + + collector->initialize(config, warnConfig); + + // 模拟连接成功但读取超时 + EXPECT_CALL(*mockSource, isAvailable()) + .WillRepeatedly(::testing::Return(true)); + + EXPECT_CALL(*mockSource, fetchAircraftData) + .WillRepeatedly(::testing::Return(false)); // 持续读取超时 + + // 启动采集 + collector->start(); + + // 等待足够长的时间以触发多次警告 + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // 停止采集 + collector->stop(); +} + +// 测试超时警告机制 +TEST_F(DataCollectorTest, TimeoutWarningMechanism) { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8080; + config.timeout_ms = 200; // 连接超时 200ms + config.read_timeout_ms = 100; // 读取超时 100ms + config.refresh_interval_ms = 10; + + WarnConfig warnConfig; + warnConfig.warning_interval_ms = 50; // 警告间隔 50ms + + // 创建一个计数器来记录收到的警告数量 + int warningCount = 0; + + // 创建一个 mock System 来捕获警告 + class MockSystem : public System { + public: + MOCK_METHOD(void, broadcastTimeoutWarning, (const network::TimeoutWarningMessage&), (override)); + }; + + auto mockSystem = std::make_shared(); + + // 设置期望接收到超时警告 + EXPECT_CALL(*mockSystem, broadcastTimeoutWarning) + .WillRepeatedly(::testing::Invoke([&warningCount](const network::TimeoutWarningMessage& warning) { + warningCount++; + // 验证警告消息的内容 + EXPECT_EQ(warning.source, "data_source"); + EXPECT_GT(warning.elapsed_ms, 0); + // 不检查具体的超时类型,因为这取决于实际的时间流逝 + })); + + // 设置 mock system 到 collector + collector->setSystem(mockSystem); + collector->initialize(config, warnConfig); + + // 启动采集器 + collector->start(); + + // 模拟数据获取失败 + EXPECT_CALL(*mockSource, fetchAircraftData) + .WillRepeatedly(::testing::Return(false)); + + // 等待足够长的时间以确保警告被发送 + // 等待时间应该超过 refresh_interval_ms + timeout_ms + warning_interval_ms + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + // 停止采集器 + collector->stop(); + + // 验证是否收到了警告 + EXPECT_GT(warningCount, 0); +} + +// 测试读取超时机制 +TEST_F(DataCollectorTest, ReadTimeoutMechanism) { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8080; + config.timeout_ms = 100; // 连接超时 100ms + config.read_timeout_ms = 50; // 读取超时 50ms + config.refresh_interval_ms = 10; + + WarnConfig warnConfig; + warnConfig.warning_interval_ms = 20; // 警告间隔 20ms + + // 创建一个计数器来记录收到的警告数量 + int warningCount = 0; + + // 创建一个 mock System 来捕获警告 + class MockSystem : public System { + public: + MOCK_METHOD(void, broadcastTimeoutWarning, (const network::TimeoutWarningMessage&), (override)); + }; + + auto mockSystem = std::make_shared(); + + // 设置期望接收到超时警告 + EXPECT_CALL(*mockSystem, broadcastTimeoutWarning) + .WillRepeatedly(::testing::Invoke([&warningCount](const network::TimeoutWarningMessage& warning) { + warningCount++; + // 验证警告消息的内容 + EXPECT_EQ(warning.source, "data_source"); + EXPECT_GT(warning.elapsed_ms, 0); + EXPECT_TRUE(warning.is_read_timeout); // 标记为读取超时 + })); + + // 设置 mock system 到 collector + collector->setSystem(mockSystem); + collector->initialize(config, warnConfig); + + // 启动采集器 + collector->start(); + + // 模拟读取超时 + EXPECT_CALL(*mockSource, fetchAircraftData) + .WillRepeatedly(::testing::Invoke([](std::vector& aircraft) { + std::this_thread::sleep_for(std::chrono::milliseconds(60)); // 睡眠超过读取超时时间但小于连接超时 + return false; + })); + + // 等待足够长的时间以确保警告被发送 + // 等待时间应该超过 refresh_interval_ms + read_timeout_ms + warning_interval_ms + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // 停止采集器 + collector->stop(); + + // 验证是否收到了警告 + EXPECT_GT(warningCount, 0); } \ No newline at end of file diff --git a/tests/HTTPDataSourceTest.cpp b/tests/HTTPDataSourceTest.cpp index 1134bcd..a25d29b 100644 --- a/tests/HTTPDataSourceTest.cpp +++ b/tests/HTTPDataSourceTest.cpp @@ -1,6 +1,7 @@ #include #include #include "network/HTTPDataSource.h" +#include "utils/Logger.h" class HTTPDataSourceTest : public ::testing::Test { protected: @@ -9,9 +10,10 @@ protected: void SetUp() override { DataSourceConfig config; config.host = "localhost"; - config.port = 8080; + config.port = 8081; config.aircraft_path = "/api/getCurrentFlightPositions"; config.vehicle_path = "/api/getCurrentVehiclePositions"; + config.traffic_light_path = "/api/getTrafficLightSignals"; source = std::make_unique(config); } @@ -35,23 +37,46 @@ TEST_F(HTTPDataSourceTest, DisconnectTest) { TEST_F(HTTPDataSourceTest, FetchAircraftDataTest) { std::vector aircraft; - source->connect(); - EXPECT_TRUE(source->fetchAircraftData(aircraft)); - // 注意:这里的具体数据验证取决于你的测试环境和模拟数据 + + // 尝试连接 + bool connected = source->connect(); + Logger::info("Connection result: ", connected ? "success" : "failed"); + EXPECT_TRUE(connected); + + // 检查连接状态 + bool available = source->isAvailable(); + Logger::info("Connection available: ", available ? "yes" : "no"); + EXPECT_TRUE(available); + + // 获取数据 + bool fetchResult = source->fetchAircraftData(aircraft); + Logger::info("Fetch result: ", fetchResult ? "success" : "failed"); + EXPECT_TRUE(fetchResult); + + // 检查数据 + Logger::info("Aircraft data size: ", aircraft.size()); + EXPECT_FALSE(aircraft.empty()); } TEST_F(HTTPDataSourceTest, FetchVehicleDataTest) { std::vector vehicles; - source->connect(); + EXPECT_TRUE(source->connect()); // 确保连接成功 EXPECT_TRUE(source->fetchVehicleData(vehicles)); - // 注意:这里的具体数据验证取决于你的测试环境和模拟数据 + EXPECT_FALSE(vehicles.empty()); +} + +TEST_F(HTTPDataSourceTest, FetchTrafficLightSignalsTest) { + std::vector signals; + EXPECT_TRUE(source->connect()); // 确保连接成功 + EXPECT_TRUE(source->fetchTrafficLightSignals(signals)); + EXPECT_FALSE(signals.empty()); } TEST_F(HTTPDataSourceTest, CustomPathTest) { // 测试使用自定义路径创建数据源 DataSourceConfig config; config.host = "localhost"; - config.port = 8080; + config.port = 8081; config.aircraft_path = "/custom/path"; auto customSource = std::make_unique(config); EXPECT_TRUE(customSource->connect()); @@ -61,8 +86,8 @@ TEST_F(HTTPDataSourceTest, CustomPathTest) { TEST_F(HTTPDataSourceTest, ConnectionFailureTest) { // 测试连接到不存在的服务器 DataSourceConfig config; - config.host = "invalid-host"; - config.port = 9999; + config.host = "localhost"; // 使用本地地址 + config.port = 1; // 使用一个通常不会被使用的端口 auto invalidSource = std::make_unique(config); EXPECT_FALSE(invalidSource->connect()); EXPECT_FALSE(invalidSource->isAvailable()); @@ -72,9 +97,43 @@ TEST_F(HTTPDataSourceTest, FetchDataWithoutConnectionTest) { // 测试在未连接状态下获取数据 std::vector aircraft; std::vector vehicles; + std::vector signals; + // 配置一个很短的刷新间隔,这样重连会快速失败 + DataSourceConfig config; + config.host = "localhost"; + config.port = 1; // 使用一个通常不会被使用的端口 + config.refresh_interval_ms = 1; // 设置很短的刷新间隔 + source = std::make_unique(config); + + // 测试所有数据获取方法 EXPECT_FALSE(source->fetchAircraftData(aircraft)); EXPECT_FALSE(source->fetchVehicleData(vehicles)); + EXPECT_FALSE(source->fetchTrafficLightSignals(signals)); + + // 验证数据集为空 + EXPECT_TRUE(aircraft.empty()); + EXPECT_TRUE(vehicles.empty()); + EXPECT_TRUE(signals.empty()); +} + +// 添加一个新的测试用例,专门测试重连机制 +TEST_F(HTTPDataSourceTest, ReconnectionTest) { + // 配置一个较短的刷新间隔 + DataSourceConfig config; + config.host = "localhost"; + config.port = 1; // 使用一个通常不会被使用的端口 + config.refresh_interval_ms = 100; // 100毫秒的刷新间隔 + auto reconnectSource = std::make_unique(config); + + // 第一次连接应该失败 + EXPECT_FALSE(reconnectSource->connect()); + EXPECT_FALSE(reconnectSource->isAvailable()); + + // 尝试获取数据,这会触发重连 + std::vector aircraft; + EXPECT_FALSE(reconnectSource->fetchAircraftData(aircraft)); + EXPECT_TRUE(aircraft.empty()); } // 如果你的环境支持模拟网络响应,可以添加更多测试 @@ -85,4 +144,71 @@ TEST_F(HTTPDataSourceTest, InvalidResponseTest) { std::vector aircraft; // 假设服务器返回无效的 JSON 数据 // EXPECT_FALSE(source->fetchAircraftData(aircraft)); +} + +// 测试不同的超时配置 +TEST_F(HTTPDataSourceTest, DifferentTimeoutConfigTest) { + // 测试读取超时小于连接超时 + { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8081; + config.timeout_ms = 5000; // 连接超时 5 秒 + config.read_timeout_ms = 2000; // 读取超时 2 秒 + + auto source = std::make_unique(config); + EXPECT_FALSE(source->isAvailable()); // 只验证初始状态 + + std::vector aircraft; + EXPECT_FALSE(source->fetchAircraftData(aircraft)); + EXPECT_TRUE(aircraft.empty()); + } + + // 测试读取超时等于连接超时 + { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8081; + config.timeout_ms = 5000; // 连接超时 5 秒 + config.read_timeout_ms = 5000; // 读取超时 5 秒 + + auto source = std::make_unique(config); + EXPECT_FALSE(source->isAvailable()); // 只验证初始状态 + + std::vector aircraft; + EXPECT_FALSE(source->fetchAircraftData(aircraft)); + EXPECT_TRUE(aircraft.empty()); + } +} + +// 修改读取超时测试 +TEST_F(HTTPDataSourceTest, ReadTimeoutTest) { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8081; + config.timeout_ms = 5000; // 连接超时 5 秒 + config.read_timeout_ms = 2000; // 读取超时 2 秒 + + auto source = std::make_unique(config); + EXPECT_FALSE(source->isAvailable()); // 验证初始状态 + + std::vector aircraft; + EXPECT_FALSE(source->fetchAircraftData(aircraft)); + EXPECT_TRUE(aircraft.empty()); +} + +// 修改连接健康和超时测试 +TEST_F(HTTPDataSourceTest, ConnectionHealthAndTimeout) { + DataSourceConfig config; + config.host = "localhost"; + config.port = 8081; + config.timeout_ms = 5000; // 连接超时 5 秒 + config.read_timeout_ms = 2000; // 读取超时 2 秒 + + auto source = std::make_unique(config); + EXPECT_FALSE(source->isAvailable()); // 验证初始状态 + + std::vector aircraft; + EXPECT_FALSE(source->fetchAircraftData(aircraft)); + EXPECT_TRUE(aircraft.empty()); } \ No newline at end of file diff --git a/tests/TrafficLightDetectorTest.cpp b/tests/TrafficLightDetectorTest.cpp new file mode 100644 index 0000000..82a7594 --- /dev/null +++ b/tests/TrafficLightDetectorTest.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include "detector/TrafficLightDetector.h" +#include "config/IntersectionConfig.h" +#include "vehicle/ControllableVehicles.h" +#include "utils/Logger.h" +#include "types/VehicleCommand.h" +#include "types/TrafficLightTypes.h" + +class MockControllableVehicles : public ControllableVehicles { +public: + explicit MockControllableVehicles() : ControllableVehicles("config/vehicle_speed_limits.json") {} + MOCK_METHOD(bool, isControllable, (const std::string& vehicleNo), (const, override)); + MOCK_METHOD(void, sendCommand, (const std::string& vehicleNo, const VehicleCommand& command), (override)); +}; + +class TrafficLightDetectorTest : public ::testing::Test { +protected: + void SetUp() override { + // 创建测试用的交叉口配置 + Intersection intersection; + intersection.id = "INT001"; + intersection.name = "Test Intersection"; + intersection.position.latitude = 36.36; + intersection.position.longitude = 120.08; + intersection.radius = 50.0; + intersection.trafficLightId = "TL001"; + + // 创建临时配置文件 + std::ofstream config_file("test_intersections.json"); + nlohmann::json j; + j["intersections"] = nlohmann::json::array(); + j["intersections"].push_back({ + {"id", intersection.id}, + {"name", intersection.name}, + {"position", { + {"latitude", intersection.position.latitude}, + {"longitude", intersection.position.longitude} + }}, + {"radius", intersection.radius}, + {"trafficLightId", intersection.trafficLightId} + }); + config_file << j.dump(4); + config_file.close(); + + intersectionConfig = IntersectionConfig::load("test_intersections.json"); + + mockControllableVehicles = std::make_shared<::testing::NiceMock>(); + detector = std::make_unique(intersectionConfig, *mockControllableVehicles); + } + + void TearDown() override { + detector.reset(); + std::remove("test_intersections.json"); + } + + // 创建测试用的红绿灯信号 + TrafficLightSignal createTestSignal(const std::string& id, SignalStatus status) { + TrafficLightSignal signal; + signal.trafficLightId = id; + signal.status = status; + signal.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); + return signal; + } + + // 创建测试用的车辆数据 + Vehicle createTestVehicle(const std::string& id, double lat, double lon) { + Vehicle v; + v.id = id; + v.vehicleNo = id; + v.geo.latitude = lat; + v.geo.longitude = lon; + v.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); + return v; + } + + std::unique_ptr detector; + IntersectionConfig intersectionConfig; + std::shared_ptr mockControllableVehicles; +}; + +// 测试红绿灯信号处理 +TEST_F(TrafficLightDetectorTest, SignalProcessing) { + // 创建一个在交叉口范围内的车辆 + Vehicle vehicle = createTestVehicle("V001", 36.36, 120.08); + std::vector vehicles = {vehicle}; + + // 测试红灯 + TrafficLightSignal redSignal = createTestSignal("TL001", SignalStatus::RED); + EXPECT_CALL(*mockControllableVehicles, isControllable("V001")) + .WillOnce(::testing::Return(true)); + EXPECT_CALL(*mockControllableVehicles, sendCommand("V001", ::testing::_)) + .Times(1); + + detector->processSignal(redSignal, vehicles); + + // 测试绿灯 + TrafficLightSignal greenSignal = createTestSignal("TL001", SignalStatus::GREEN); + EXPECT_CALL(*mockControllableVehicles, isControllable("V001")) + .WillOnce(::testing::Return(true)); + EXPECT_CALL(*mockControllableVehicles, sendCommand("V001", ::testing::_)) + .Times(1); + + detector->processSignal(greenSignal, vehicles); +} + +// 测试无效的红绿灯信号 +TEST_F(TrafficLightDetectorTest, InvalidSignal) { + Vehicle vehicle = createTestVehicle("V001", 36.36, 120.08); + std::vector vehicles = {vehicle}; + + // 测试无效的红绿灯ID + TrafficLightSignal invalidSignal = createTestSignal("TL999", SignalStatus::RED); + EXPECT_CALL(*mockControllableVehicles, isControllable(::testing::_)) + .Times(0); + EXPECT_CALL(*mockControllableVehicles, sendCommand(::testing::_, ::testing::_)) + .Times(0); + + detector->processSignal(invalidSignal, vehicles); +} + +// 测试边界情况 +TEST_F(TrafficLightDetectorTest, EdgeCases) { + // 测试空车辆列表 + std::vector emptyVehicles; + TrafficLightSignal signal = createTestSignal("TL001", SignalStatus::RED); + EXPECT_CALL(*mockControllableVehicles, isControllable(::testing::_)) + .Times(0); + EXPECT_CALL(*mockControllableVehicles, sendCommand(::testing::_, ::testing::_)) + .Times(0); + + detector->processSignal(signal, emptyVehicles); + + // 测试交叉口范围外的车辆 + Vehicle farVehicle = createTestVehicle("V002", 36.40, 120.12); + std::vector vehicles = {farVehicle}; + EXPECT_CALL(*mockControllableVehicles, isControllable(::testing::_)) + .Times(0); + EXPECT_CALL(*mockControllableVehicles, sendCommand(::testing::_, ::testing::_)) + .Times(0); + + detector->processSignal(signal, vehicles); +} \ No newline at end of file diff --git a/tools/mock_server.py b/tools/mock_server.py index 04a23f5..51276a8 100644 --- a/tools/mock_server.py +++ b/tools/mock_server.py @@ -4,96 +4,208 @@ import math app = Flask(__name__) -# 基准坐标(青岛胶东机场) -BASE_LAT = 36.361999 -BASE_LON = 120.088003 +# 坐标转换常数(粗略计算:1度约等于111km,所以1米约等于0.000009度) +METERS_TO_DEG = 0.000009 -# 模拟数据 +# 距离配置(米) +DIST_150M = 150 +DIST_100M = 100 +DIST_50M = 50 + +# 两个路口的位置 +WEST_INTERSECTION = {"longitude": 120.088003, "latitude": 36.361999} +EAST_INTERSECTION = {"longitude": 120.088303, "latitude": 36.361999} + +# 位置更新间隔(秒) +UPDATE_INTERVAL = 1.0 + +# 航空器数据 aircraft_data = [ { - "flightNo": "CES2501", # 在跑道上 - "latitude": BASE_LAT, - "longitude": BASE_LON + 0.001, # 在跑道中间位置 - "time": int(time.time()), - "direction": 1 # 1表示向东,-1表示向西 - }, - { - "flightNo": "CES2502", # 在滑行道上 - "latitude": BASE_LAT - 0.001, - "longitude": BASE_LON, - "time": int(time.time()), - "direction": 1 # 1表示向东,-1表示向西 + "flightNo": "AC001", # 航空器 + "latitude": EAST_INTERSECTION["latitude"] - (DIST_150M * METERS_TO_DEG), # 东路口南150米 + "longitude": EAST_INTERSECTION["longitude"], + "time": int(time.time() * 1000), + "direction": 1, # 1表示向北 + "speed": 50.0 # 滑行速度 50km/h } ] +# 车辆数据 vehicle_data = [ { - "vehicleNo": "VEH001", # 南北方向移动 - "latitude": BASE_LAT - 0.0005, - "longitude": BASE_LON + 0.001, - "time": int(time.time()), - "direction": 1, # 1表示向北,-1表示向南 - "phase": 0 # 用于控制循环运动 + "vehicleNo": "QN001", # 无人车1(西路口) + "latitude": WEST_INTERSECTION["latitude"] + (DIST_50M * METERS_TO_DEG), # 西路口北50米 + "longitude": WEST_INTERSECTION["longitude"], + "time": int(time.time() * 1000), + "direction": 1, # 1表示向东 + "speed": 36.0 # 36km/h }, { - "vehicleNo": "VEH002", # 南北方向移动 - "latitude": BASE_LAT + 0.0005, - "longitude": BASE_LON + 0.001, - "time": int(time.time()), - "direction": -1, # 1表示向北,-1表示向南 - "phase": math.pi # 与VEH001相位差180度 + "vehicleNo": "QN002", # 无人车2(东路口) + "latitude": EAST_INTERSECTION["latitude"], + "longitude": EAST_INTERSECTION["longitude"] - (DIST_150M * METERS_TO_DEG), # 东路口西150米 + "time": int(time.time() * 1000), + "direction": 1, # 1表示向东 + "speed": 36.0 # 36km/h + }, + { + "vehicleNo": "TQ001", # 特勤车(西路口) + "latitude": WEST_INTERSECTION["latitude"], + "longitude": WEST_INTERSECTION["longitude"] + (DIST_50M * METERS_TO_DEG), # 西路口东50米 + "time": int(time.time() * 1000), + "direction": -1, # -1表示向西 + "speed": 36.0 # 36km/h } ] -# 定义运动范围 -LAT_RANGE = 0.001 # 约110米 -LON_RANGE = 0.002 # 约220米 -VEHICLE_SPEED = 0.00005 # 每次更新的位置变化 -AIRCRAFT_SPEED = 0.00002 # 每次更新的位置变化 +def update_vehicle_position(vehicle, elapsed_time): + """更新车辆位置""" + # 计算一次更新的移动距离(度) + speed_mps = vehicle["speed"] * 1000 / 3600 # 速度转换为米/秒 + distance = speed_mps * elapsed_time + distance_deg = distance * METERS_TO_DEG + + if vehicle["vehicleNo"] == "QN001": + # 无人车1:东西方向往返(西路口) + new_lon = vehicle["longitude"] + (distance_deg * vehicle["direction"]) + if new_lon >= WEST_INTERSECTION["longitude"] + (DIST_50M * METERS_TO_DEG): + vehicle["direction"] = -1 # 向西 + elif new_lon <= WEST_INTERSECTION["longitude"]: + vehicle["direction"] = 1 # 向东 + vehicle["longitude"] = new_lon + + elif vehicle["vehicleNo"] == "QN002": + # 无人车2:东西方向往返(东路口) + new_lon = vehicle["longitude"] + (distance_deg * vehicle["direction"]) + if new_lon >= EAST_INTERSECTION["longitude"] + (DIST_100M * METERS_TO_DEG): + vehicle["direction"] = -1 # 向西 + elif new_lon <= EAST_INTERSECTION["longitude"] - (DIST_150M * METERS_TO_DEG): + vehicle["direction"] = 1 # 向东 + vehicle["longitude"] = new_lon + + elif vehicle["vehicleNo"] == "TQ001": + # 特勤车:先东西后南北,然后返回(西路口) + if "phase" not in vehicle: + vehicle["phase"] = 0 + + if vehicle["phase"] == 0: # 东西移动 + new_lon = vehicle["longitude"] + (distance_deg * vehicle["direction"]) + if vehicle["direction"] == -1 and new_lon <= WEST_INTERSECTION["longitude"]: + vehicle["phase"] = 1 # 切换到南北移动 + vehicle["direction"] = -1 # 向南 + elif vehicle["direction"] == 1 and new_lon >= WEST_INTERSECTION["longitude"] + (DIST_50M * METERS_TO_DEG): + vehicle["direction"] = -1 # 向西 + vehicle["longitude"] = new_lon + else: # 南北移动 + new_lat = vehicle["latitude"] + (distance_deg * vehicle["direction"]) + if new_lat <= WEST_INTERSECTION["latitude"] - (DIST_100M * METERS_TO_DEG): # 到达南端 + vehicle["direction"] = 1 # 向北 + elif new_lat >= WEST_INTERSECTION["latitude"]: # 返回到路口 + vehicle["phase"] = 0 # 切换回东西移动 + vehicle["direction"] = 1 # 向东 + vehicle["longitude"] = WEST_INTERSECTION["longitude"] # 重置到起始位置 + vehicle["latitude"] = new_lat + +def update_aircraft_position(aircraft, elapsed_time): + """更新航空器位置""" + # 计算一次更新的移动距离(度) + speed_mps = aircraft["speed"] * 1000 / 3600 # 速度转换为米/秒 + distance = speed_mps * elapsed_time + distance_deg = distance * METERS_TO_DEG + + new_lat = aircraft["latitude"] + (distance_deg * aircraft["direction"]) + if new_lat >= EAST_INTERSECTION["latitude"] + (DIST_150M * METERS_TO_DEG): + aircraft["direction"] = -1 # 向南 + elif new_lat <= EAST_INTERSECTION["latitude"] - (DIST_150M * METERS_TO_DEG): + aircraft["direction"] = 1 # 向北 + aircraft["latitude"] = new_lat + +def update_traffic_light_for_aircraft(): + """根据航空器位置更新东路口红绿灯状态""" + if not aircraft_data: + return + + aircraft = aircraft_data[0] + # 计算到东路口的距离(米) + lat_diff = abs(aircraft["latitude"] - EAST_INTERSECTION["latitude"]) / METERS_TO_DEG + + # 更新TL002(东路口)的状态 + for light in traffic_light_data: + if light["id"] == "TL002": + if lat_diff <= 50.0: # 距离小于50米时为红灯 + light["state"] = 0 # 红灯 + else: + light["state"] = 1 # 绿灯 + break + +# 红绿灯数据 +traffic_light_data = [ + { + "id": "TL001", # 西侧路口 + "state": 1, # 0: 红灯, 1: 绿灯 + "timestamp": int(time.time() * 1000) + }, + { + "id": "TL002", # 东侧路口 + "state": 1, # 初始为绿灯 + "timestamp": int(time.time() * 1000) + } +] + +last_update_time = time.time() +last_light_switch_time = time.time() @app.route('/api/getCurrentFlightPositions') def get_flight_positions(): - current_time = int(time.time()) + global last_update_time + current_time = time.time() + elapsed_time = current_time - last_update_time - # 更新航空器位置 - for aircraft in aircraft_data: - aircraft["time"] = current_time - - # 更新经度位置 - new_lon = aircraft["longitude"] + (AIRCRAFT_SPEED * aircraft["direction"]) - - # 检查是否需要改变方向 - if new_lon > BASE_LON + LON_RANGE: - aircraft["direction"] = -1 # 向西移动 - elif new_lon < BASE_LON - LON_RANGE: - aircraft["direction"] = 1 # 向东移动 - - aircraft["longitude"] = new_lon + # 只在达到更新间隔时更新位置 + if elapsed_time >= UPDATE_INTERVAL: + for aircraft in aircraft_data: + update_aircraft_position(aircraft, UPDATE_INTERVAL) + aircraft["time"] = int(current_time * 1000) + last_update_time = current_time return jsonify(aircraft_data) @app.route('/api/getCurrentVehiclePositions') def get_vehicle_positions(): - current_time = int(time.time()) + global last_update_time + current_time = time.time() + elapsed_time = current_time - last_update_time - # 更新车辆位置 - for vehicle in vehicle_data: - vehicle["time"] = current_time - - # 更新相位 - vehicle["phase"] += 0.1 # 调整此值可以改变运动速度 - if vehicle["phase"] > 2 * math.pi: - vehicle["phase"] -= 2 * math.pi - - # 使用正弦函数生成循环运动 - offset = math.sin(vehicle["phase"]) * LAT_RANGE - vehicle["latitude"] = BASE_LAT + offset - - # 可以添加一些随机扰动使运动更自然 - vehicle["longitude"] = BASE_LON + 0.001 + (math.sin(vehicle["phase"] * 0.5) * 0.0001) + # 只在达到更新间隔时更新位置 + if elapsed_time >= UPDATE_INTERVAL: + for vehicle in vehicle_data: + update_vehicle_position(vehicle, UPDATE_INTERVAL) + vehicle["time"] = int(current_time * 1000) + last_update_time = current_time return jsonify(vehicle_data) +@app.route('/api/getTrafficLightSignals') +def get_traffic_light_signals(): + global last_light_switch_time + current_time = time.time() + + # 更新时间戳 + for light in traffic_light_data: + light["timestamp"] = int(current_time * 1000) + + # TL001(西路口)的状态每15秒切换一次 + elapsed_since_switch = current_time - last_light_switch_time + if elapsed_since_switch >= 15: # 每15秒切换一次 + traffic_light_data[0]["state"] = 2 if traffic_light_data[0]["state"] == 0 else 0 + last_light_switch_time = current_time + + # 根据航空器位置更新TL002(东路口) + update_traffic_light_for_aircraft() + + return jsonify(traffic_light_data) + @app.after_request def add_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = '*' @@ -102,4 +214,4 @@ def add_cors_headers(response): return response if __name__ == '__main__': - app.run(host='localhost', port=8080, debug=True) \ No newline at end of file + app.run(host='localhost', port=8081, debug=True) \ No newline at end of file diff --git a/tools/test_websocket.html b/tools/test_websocket.html index e85fbe2..d5522ac 100644 --- a/tools/test_websocket.html +++ b/tools/test_websocket.html @@ -5,7 +5,7 @@