diff --git a/.cursorrules b/.cursorrules index 84788bd..64d8808 100644 --- a/.cursorrules +++ b/.cursorrules @@ -30,7 +30,7 @@ - 格式统一一致 请遵循如下 C++ 规范: -- 使用 C++17 标准 +- 使用 C++20 标准 - 使用 CMake 3.14 及以上版本 - 使用 nlohmann_json 3.11.3 版本 - 使用 FetchContent 管理第三方库 diff --git a/Assets/Scripts/WebSocketClient.cs b/Assets/Scripts/WebSocketClient.cs new file mode 100644 index 0000000..66c82da --- /dev/null +++ b/Assets/Scripts/WebSocketClient.cs @@ -0,0 +1,102 @@ +using UnityEngine; +using NativeWebSocket; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +public class WebSocketClient : MonoBehaviour +{ + private WebSocket websocket; + private readonly string serverUrl = "ws://localhost:8080"; + + async void Start() + { + websocket = new WebSocket(serverUrl); + + websocket.OnOpen += () => + { + Debug.Log("Connection open!"); + }; + + websocket.OnError += (e) => + { + Debug.LogError($"Error! {e}"); + }; + + websocket.OnClose += (e) => + { + Debug.Log("Connection closed!"); + }; + + websocket.OnMessage += (bytes) => + { + var message = System.Text.Encoding.UTF8.GetString(bytes); + HandleMessage(message); + }; + + await websocket.Connect(); + } + + void Update() + { + #if !UNITY_WEBGL || UNITY_EDITOR + websocket.DispatchMessageQueue(); + #endif + } + + private void HandleMessage(string jsonMessage) + { + try + { + var json = JObject.Parse(jsonMessage); + string messageType = json["type"].ToString(); + + switch (messageType) + { + case "position_update": + HandlePositionUpdate(json); + break; + case "collision_warning": + HandleCollisionWarning(json); + break; + } + } + catch (System.Exception e) + { + Debug.LogError($"Error parsing message: {e.Message}"); + } + } + + private void HandlePositionUpdate(JObject json) + { + string objectId = json["objectId"].ToString(); + string objectType = json["objectType"].ToString(); + double longitude = json["longitude"].Value(); + double latitude = json["latitude"].Value(); + double heading = json["heading"].Value(); + + // 更新对应物体的位置和朝向 + GameObject obj = GameObject.Find(objectId); + if (obj != null) + { + Vector3 position = CoordinateConverter.ToUnityPosition(longitude, latitude); + obj.transform.position = position; + obj.transform.rotation = Quaternion.Euler(0, (float)heading, 0); + } + } + + private void HandleCollisionWarning(JObject json) + { + string object1Id = json["object1Id"].ToString(); + string object2Id = json["object2Id"].ToString(); + string warningLevel = json["warningLevel"].ToString(); + double distance = json["distance"].Value(); + + // 显示碰撞警告效果 + WarningManager.Instance.ShowWarning(object1Id, object2Id, warningLevel, distance); + } + + private async void OnApplicationQuit() + { + await websocket.Close(); + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 31795d7..d039238 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,11 +47,13 @@ set(LIB_SOURCES src/core/System.cpp src/detector/CollisionDetector.cpp src/network/HTTPDataSource.cpp + src/network/WebSocketServer.cpp src/spatial/AirportBounds.cpp src/spatial/CoordinateConverter.cpp src/types/BasicTypes.cpp src/types/VehicleData.cpp src/vehicle/ControllableVehicles.cpp + src/config/SystemConfig.cpp ) # 创建主库 diff --git a/config/system_config.json b/config/system_config.json new file mode 100644 index 0000000..62c97ce --- /dev/null +++ b/config/system_config.json @@ -0,0 +1,65 @@ +{ + "airport": { + "name": "青岛胶东国际机场", + "iata": "TAO", + "icao": "ZSQD", + "reference_point": { + "latitude": 36.361999, + "longitude": 120.088003 + } + }, + "data_source": { + "host": "localhost", + "port": 8080, + "aircraft_path": "/api/getCurrentFlightPositions", + "vehicle_path": "/api/getCurrentVehiclePositions", + "refresh_interval_ms": 1000, + "timeout_ms": 5000 + }, + "warning": { + "warning_interval_ms": 1000, + "log_interval_ms": 3000 + }, + "websocket": { + "port": 8010, + "max_connections": 100, + "ping_interval_ms": 30000, + "position_update": { + "aircraft_interval_ms": 300, + "vehicle_interval_ms": 500 + } + }, + "collision_detection": { + "update_interval_ms": 200, + "thresholds": { + "runway": { + "aircraft_ground": 100.0, + "vehicle": 50.0 + }, + "taxiway": { + "aircraft_ground": 50.0, + "vehicle": 30.0 + }, + "apron": { + "aircraft_ground": 40.0, + "vehicle": 20.0 + }, + "service": { + "aircraft_ground": 30.0, + "vehicle": 15.0 + } + } + }, + "logging": { + "level": "info", + "file": "logs/system.log", + "max_size_mb": 10, + "max_files": 5, + "console_output": true + }, + "debug": { + "enable_mock_data": false, + "save_raw_data": false, + "profile_performance": false + } +} \ No newline at end of file diff --git a/docs/design.md b/docs/design.md index 75421a8..6205989 100644 --- a/docs/design.md +++ b/docs/design.md @@ -120,7 +120,7 @@ class CollisionDetector { #### 5.2.1 距离检测 -1. 直接报警条件: +1.直接报警条件: ```cpp if (distance < threshold * 0.5) { // 距离小于阈值的一半 @@ -128,7 +128,7 @@ if (distance < threshold * 0.5) { // 距离小于阈值的一半 } ``` -2. 进一步检测条件: +2.进一步检测条件: ```cpp if (distance < threshold) { // 距离在阈值范围内 @@ -138,7 +138,7 @@ if (distance < threshold) { // 距离在阈值范围内 #### 5.2.2 相对运动分析 -1. 速度分量计算: +1.速度分量计算: ```cpp // 考虑航向角,转换为数学坐标系 @@ -146,7 +146,7 @@ double vx = speed * std::cos((90 - heading) * M_PI / 180.0); double vy = speed * std::sin((90 - heading) * M_PI / 180.0); ``` -2. 相对运动计算: +2.相对运动计算: ```cpp // 计算相对速度 @@ -157,7 +157,7 @@ double relativeVy = v1y - v2y; double relativeMotion = dx*relativeVx + dy*relativeVy; ``` -3. 碰撞判定: +3.碰撞判定: ```cpp if (relativeMotion <= 0) { // 物体正在接近或相对静止 @@ -180,13 +180,13 @@ if (relativeMotion <= 0) { // 物体正在接近或相对静止 使用四叉树进行空间索引,优化查询性能: -1. 四叉树构建: +1.四叉树构建: ```cpp QuadTree vehicleTree_(bounds, 8); // 容量为8的四叉树 ``` -2. 邻近查询: +2.邻近查询: ```cpp auto nearbyVehicles = vehicleTree_.queryNearby( diff --git a/include/core/System.h b/include/core/System.h deleted file mode 100644 index ee66ec4..0000000 --- a/include/core/System.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "CollisionDetector.h" -#include "DataCollector.h" -#include "airport/AirportBounds.h" -#include "config/ConnectionConfig.h" -#include -#include -#include - -struct ControllableVehicleConfig { - std::string vehicleNo; - std::string ip; - int port; -}; - -class System { -public: - System(); - ~System(); - - bool initialize(const ConnectionConfig& config); - void start(); - void stop(); - -private: - std::unique_ptr airportBounds_; - std::unique_ptr dataCollector_; - std::unique_ptr collisionDetector_; - std::vector controllableVehicles_; - - std::thread processThread_; - bool running_ = false; - - void processLoop(); - void processCollisions(const std::vector& collisions); - - bool loadAirportBounds(); - bool loadControllableVehicles(); -}; \ No newline at end of file diff --git a/include/vehicle/ControllableVehicles.h b/include/vehicle/ControllableVehicles.h deleted file mode 100644 index 5d54ca6..0000000 --- a/include/vehicle/ControllableVehicles.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include -#include - -struct ControllableVehicleConfig { - std::string vehicleNo; // 车牌号 - std::string ip; // IP地址 - int port; // 端口号 -}; - -class ControllableVehicles { -public: - explicit ControllableVehicles(const std::string& configFile); - virtual ~ControllableVehicles() = default; - - // 获取所有可控车辆配置 - const std::vector& getVehicles() const; - - // 根据车牌号查找可控车辆配置 - const ControllableVehicleConfig* findVehicle(const std::string& vehicleNo) const; - - // 检查车辆是否可控 - 添加 virtual - virtual bool isControllable(const std::string& vehicleNo) const; - -private: - std::vector vehicles_; - void loadConfig(const std::string& configFile); -}; \ No newline at end of file diff --git a/src/collector/DataCollector.cpp b/src/collector/DataCollector.cpp index c70f09c..374269f 100644 --- a/src/collector/DataCollector.cpp +++ b/src/collector/DataCollector.cpp @@ -2,15 +2,22 @@ #include "network/HTTPDataSource.h" #include "utils/Logger.h" -DataCollector::DataCollector() = default; +DataCollector::DataCollector() + : lastSuccessfulFetch_(std::chrono::steady_clock::now()) // 初始化时间戳 + , last_warning_time_(std::chrono::steady_clock::now()) { +} DataCollector::~DataCollector() { stop(); } -bool DataCollector::initialize(const ConnectionConfig& config) { +bool DataCollector::initialize(const DataSourceConfig& dataSourceConfig, const WarnConfig& warnConfig) { if (!dataSource_) { - dataSource_ = std::make_shared(config.host, config.port); + dataSourceConfig_ = dataSourceConfig; + dataSource_ = std::make_shared(dataSourceConfig_); + warnConfig_ = warnConfig; + lastSuccessfulFetch_ = std::chrono::steady_clock::now(); // 重置时间戳 + last_warning_time_ = std::chrono::steady_clock::now(); } return true; } @@ -25,13 +32,17 @@ void DataCollector::start() { return; } - // 确保连接成功 + running_ = true; + lastSuccessfulFetch_ = std::chrono::steady_clock::now(); // 记录启动时间 + last_warning_time_ = lastSuccessfulFetch_; // 初始化告警时间 + + // 尝试连接,但即使失败也继续运行 if (!dataSource_->connect()) { Logger::error("Failed to connect to data source"); - return; + // 立即检查超时,这样如果一开始就连不上,会马上发出告警 + checkTimeout(); } - running_ = true; collectorThread_ = std::thread(&DataCollector::collectLoop, this); } @@ -53,15 +64,66 @@ void DataCollector::stop() { void DataCollector::collectLoop() { while (running_) { loopCount_++; - Logger::debug("Collection loop #", loopCount_.load()); - if (fetchData()) { - Logger::debug("Data fetched successfully in loop ", loopCount_.load()); - } else { + bool success = fetchData(); + if (!success) { Logger::warning("Failed to fetch data in loop ", loopCount_.load()); + checkTimeout(); // 每次获取失败都检查超时 } - std::this_thread::sleep_for(fetchInterval_); + // 即使成功获取数据,也定期检查超时状态 + checkTimeout(); + + std::this_thread::sleep_for(std::chrono::milliseconds(dataSourceConfig_.refresh_interval_ms)); + } +} + +void DataCollector::checkTimeout() { + auto now = std::chrono::steady_clock::now(); + 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(); + + if (warning_elapsed >= warnConfig_.warning_interval_ms) { + sendTimeoutWarning(elapsed); + last_warning_time_ = now; + Logger::warning("Data source timeout: No response for ", elapsed, "ms"); + } + } +} + +void DataCollector::resetTimeout() { + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast( + now - lastSuccessfulFetch_).count(); + + if (elapsed > dataSourceConfig_.timeout_ms) { + // 如果之前处于超时状态,记录恢复日志 + Logger::info("Data source connection restored after ", elapsed, "ms timeout"); + } + + lastSuccessfulFetch_ = now; +} + +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"); } } @@ -71,6 +133,8 @@ bool DataCollector::fetchData() { } try { + bool success = false; + // 获取航空器数据 std::vector aircraft; if (dataSource_->fetchAircraftData(aircraft)) { @@ -99,6 +163,7 @@ bool DataCollector::fetchData() { } aircraftCache_ = std::move(aircraft); Logger::debug("Cached ", aircraftCache_.size(), " aircraft"); + success = true; } // 获取车辆数据 @@ -123,9 +188,14 @@ bool DataCollector::fetchData() { } vehicleCache_ = std::move(vehicles); Logger::debug("Cached ", vehicleCache_.size(), " vehicles"); + success = true; } - return true; + if (success) { + resetTimeout(); // 成功获取数据后重置超时 + } + + return success; } catch (const std::exception& e) { Logger::error("Exception in fetchData: ", e.what()); @@ -141,4 +211,52 @@ std::vector DataCollector::getAircraftData() { std::vector DataCollector::getVehicleData() { std::lock_guard lock(cacheMutex_); return vehicleCache_; +} + +void DataCollector::refresh() { + try { + // 从数据源获取最新数据 + std::vector newAircraft; + std::vector newVehicles; + + if (dataSource_->fetchAircraftData(newAircraft)) { + std::lock_guard lock(cacheMutex_); + // 更新运动信息 + for (auto& a : newAircraft) { + auto it = lastAircraftPositions_.find(a.id); + if (it != lastAircraftPositions_.end()) { + // 复制历史记录 + a.copyHistoryFrom(it->second); + // 更新运动信息 + a.updateMotion(a.geo, a.timestamp); + } else { + a.updateMotion(a.geo, a.timestamp); + } + lastAircraftPositions_[a.id] = a; + } + aircraftCache_ = std::move(newAircraft); + } + + if (dataSource_->fetchVehicleData(newVehicles)) { + std::lock_guard lock(cacheMutex_); + // 更新运动信息 + for (auto& v : newVehicles) { + auto it = lastVehiclePositions_.find(v.id); + if (it != lastVehiclePositions_.end()) { + // 复制历史记录 + v.copyHistoryFrom(it->second); + // 更新运动信息 + v.updateMotion(v.geo, v.timestamp); + } else { + v.updateMotion(v.geo, v.timestamp); + } + lastVehiclePositions_[v.id] = v; + } + vehicleCache_ = std::move(newVehicles); + } + } + catch (const std::exception& e) { + Logger::error("刷新数据失败: ", e.what()); + throw; + } } \ No newline at end of file diff --git a/src/collector/DataCollector.h b/src/collector/DataCollector.h index 65275f2..9b8b404 100644 --- a/src/collector/DataCollector.h +++ b/src/collector/DataCollector.h @@ -8,15 +8,16 @@ #include #include #include "DataSource.h" +#include "DataSourceConfig.h" #include "types/BasicTypes.h" -#include "network/ConnectionConfig.h" +#include "config/WarnConfig.h" class DataCollector { public: DataCollector(); ~DataCollector(); - bool initialize(const ConnectionConfig& config); + bool initialize(const DataSourceConfig& config, const WarnConfig& warnConfig); void start(); void stop(); @@ -28,9 +29,14 @@ public: void setDataSource(std::shared_ptr source) { dataSource_ = source; } + + // 添加刷新方法 + void refresh(); private: std::shared_ptr dataSource_; + DataSourceConfig dataSourceConfig_; + WarnConfig warnConfig_; std::thread collectorThread_; std::atomic running_{false}; std::atomic loopCount_{0}; @@ -44,9 +50,15 @@ private: std::map lastAircraftPositions_; std::map lastVehiclePositions_; - std::chrono::steady_clock::time_point lastFetchTime_; - std::chrono::milliseconds fetchInterval_{1000}; - + // 超时检测相关 + std::chrono::steady_clock::time_point lastSuccessfulFetch_; + std::chrono::steady_clock::time_point last_warning_time_; + std::chrono::steady_clock::time_point last_log_time_; + + void checkTimeout(); + void resetTimeout(); + void sendTimeoutWarning(int64_t elapsed_ms); + void collectLoop(); bool fetchData(); }; diff --git a/src/collector/DataSourceConfig.h b/src/collector/DataSourceConfig.h new file mode 100644 index 0000000..d3e6d5b --- /dev/null +++ b/src/collector/DataSourceConfig.h @@ -0,0 +1,15 @@ +#ifndef AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H +#define AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H + +#include + +struct DataSourceConfig { + std::string host; + uint16_t port; + std::string aircraft_path; + std::string vehicle_path; + int refresh_interval_ms; + int timeout_ms; +}; + +#endif diff --git a/src/config/SystemConfig.cpp b/src/config/SystemConfig.cpp new file mode 100644 index 0000000..aca7ddf --- /dev/null +++ b/src/config/SystemConfig.cpp @@ -0,0 +1,75 @@ +#include "config/SystemConfig.h" +#include "utils/Logger.h" +#include + +SystemConfig SystemConfig::load(const std::string& filename) { + try { + std::ifstream file(filename); + if (!file.is_open()) { + throw std::runtime_error("Cannot open config file: " + filename); + } + + nlohmann::json j; + file >> j; + + SystemConfig config; + + // 加载机场信息 + config.airport.name = j["airport"]["name"]; + config.airport.iata = j["airport"]["iata"]; + config.airport.icao = j["airport"]["icao"]; + config.airport.reference_point.latitude = j["airport"]["reference_point"]["latitude"]; + config.airport.reference_point.longitude = j["airport"]["reference_point"]["longitude"]; + + // 加载数据源配置 + config.data_source.host = j["data_source"]["host"]; + 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.refresh_interval_ms = j["data_source"]["refresh_interval_ms"]; + config.data_source.timeout_ms = j["data_source"]["timeout_ms"]; + + // 加载 WebSocket 配置 + config.websocket.port = j["websocket"]["port"]; + config.websocket.max_connections = j["websocket"]["max_connections"]; + config.websocket.ping_interval_ms = j["websocket"]["ping_interval_ms"]; + config.websocket.position_update.aircraft_interval_ms = j["websocket"]["position_update"]["aircraft_interval_ms"]; + config.websocket.position_update.vehicle_interval_ms = j["websocket"]["position_update"]["vehicle_interval_ms"]; + + // 加载碰撞检测配置 + config.collision_detection.update_interval_ms = j["collision_detection"]["update_interval_ms"]; + + // 加载阈值配置 + auto& thresholds = j["collision_detection"]["thresholds"]; + config.collision_detection.thresholds.runway.aircraft_ground = thresholds["runway"]["aircraft_ground"]; + config.collision_detection.thresholds.runway.vehicle = thresholds["runway"]["vehicle"]; + config.collision_detection.thresholds.taxiway.aircraft_ground = thresholds["taxiway"]["aircraft_ground"]; + config.collision_detection.thresholds.taxiway.vehicle = thresholds["taxiway"]["vehicle"]; + config.collision_detection.thresholds.apron.aircraft_ground = thresholds["apron"]["aircraft_ground"]; + config.collision_detection.thresholds.apron.vehicle = thresholds["apron"]["vehicle"]; + config.collision_detection.thresholds.service.aircraft_ground = thresholds["service"]["aircraft_ground"]; + config.collision_detection.thresholds.service.vehicle = thresholds["service"]["vehicle"]; + + // 加载日志配置 + config.logging.level = j["logging"]["level"]; + config.logging.file = j["logging"]["file"]; + config.logging.max_size_mb = j["logging"]["max_size_mb"]; + config.logging.max_files = j["logging"]["max_files"]; + config.logging.console_output = j["logging"]["console_output"]; + + // 加载调试配置 + config.debug.enable_mock_data = j["debug"]["enable_mock_data"]; + config.debug.save_raw_data = j["debug"]["save_raw_data"]; + config.debug.profile_performance = j["debug"]["profile_performance"]; + + // 加载告警配置 + config.warning.warning_interval_ms = j["warning"]["warning_interval_ms"]; + config.warning.log_interval_ms = j["warning"]["log_interval_ms"]; + + return config; + } + catch (const std::exception& e) { + Logger::error("Failed to load system config: ", e.what()); + throw; + } +} \ No newline at end of file diff --git a/src/config/SystemConfig.h b/src/config/SystemConfig.h new file mode 100644 index 0000000..fac8a05 --- /dev/null +++ b/src/config/SystemConfig.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +struct SystemConfig { + struct Airport { + std::string name; + std::string iata; + std::string icao; + struct { + double latitude; + double longitude; + } reference_point; + } airport; + + struct DataSource { + std::string host; + uint16_t port; + std::string aircraft_path; + std::string vehicle_path; + int refresh_interval_ms; + int timeout_ms; + } data_source; + + struct WebSocket { + int port; + int max_connections; + int ping_interval_ms; + struct PositionUpdate { + int aircraft_interval_ms; + int vehicle_interval_ms; + } position_update; + } websocket; + + struct CollisionDetection { + int update_interval_ms; + struct Thresholds { + struct Area { + double aircraft_ground; + double vehicle; + }; + Area runway; + Area taxiway; + Area apron; + Area service; + } thresholds; + } collision_detection; + + struct Logging { + std::string level; + std::string file; + int max_size_mb; + int max_files; + bool console_output; + } logging; + + struct Debug { + bool enable_mock_data; + bool save_raw_data; + bool profile_performance; + } debug; + + struct Warning { + int warning_interval_ms; // 超时告警间隔 + int log_interval_ms; // 日志记录间隔 + } warning; + + static SystemConfig load(const std::string& filename); +}; \ No newline at end of file diff --git a/src/config/WarnConfig.h b/src/config/WarnConfig.h new file mode 100644 index 0000000..9c87c37 --- /dev/null +++ b/src/config/WarnConfig.h @@ -0,0 +1,11 @@ +#ifndef AIRPORT_NETWORK_WARN_CONFIG_H +#define AIRPORT_NETWORK_WARN_CONFIG_H + +#include + +struct WarnConfig { + int warning_interval_ms; + int log_interval_ms; +}; + +#endif // AIRPORT_NETWORK_WARN_CONFIG_H \ No newline at end of file diff --git a/src/core/System.cpp b/src/core/System.cpp index b12095c..ddc9a67 100644 --- a/src/core/System.cpp +++ b/src/core/System.cpp @@ -1,29 +1,72 @@ +#include +#include +#include #include "core/System.h" #include "utils/Logger.h" -#include "nlohmann/json.hpp" +#include "collector/DataCollector.h" -System::System() = default; +System* System::instance_ = nullptr; + +System::System() { + instance_ = this; + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); +} System::~System() { stop(); + instance_ = nullptr; } -bool System::initialize(const ConnectionConfig& config) { +void System::signalHandler(int signal) { + Logger::info("Received signal: ", signal); + if (instance_) { + instance_->stop(); + } + std::exit(0); +} + +bool System::initialize() { try { + // 加载系统配置 + system_config_ = SystemConfig::load("config/system_config.json"); + + // 初始化 WebSocket 服务器 + ws_server_ = std::make_unique(system_config_.websocket.port); + ws_thread_ = std::thread([this]() { + ws_server_->start(); + }); + // 加载机场区域配置 airportBounds_ = std::make_unique("config/airport_bounds.json"); // 加载可控车辆配置 controllableVehicles_ = std::make_unique("config/controllable_vehicles.json"); - // 初始化数据采集器 - dataCollector_ = std::make_unique(); + // 初始化碰撞检测器 collisionDetector_ = std::make_unique(*airportBounds_, *controllableVehicles_); - - // 初始化数据采集器 - return dataCollector_->initialize(config); + + // 创建数据采集器 + dataCollector_ = std::make_unique(); + + // 数据采集器初始化并启动 + DataSourceConfig dataSourceConfig{ + system_config_.data_source.host, + system_config_.data_source.port, + system_config_.data_source.aircraft_path, + system_config_.data_source.vehicle_path, + system_config_.data_source.refresh_interval_ms, + system_config_.data_source.timeout_ms + }; + + WarnConfig warnConfig{ + system_config_.warning.warning_interval_ms, + system_config_.warning.log_interval_ms + }; + + return dataCollector_->initialize(dataSourceConfig, warnConfig); } catch (const std::exception& e) { Logger::error("Failed to initialize system: ", e.what()); @@ -48,6 +91,17 @@ void System::stop() { } running_ = false; + + // 停止 WebSocket 服务器 + if (ws_server_) { + ws_server_->stop(); + } + + // 等待线程结束 + if (ws_thread_.joinable()) { + ws_thread_.join(); + } + if (processThread_.joinable()) { processThread_.join(); } @@ -55,27 +109,64 @@ void System::stop() { if (dataCollector_) { dataCollector_->stop(); } + Logger::info("System stopped"); } 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(); + while (running_) { try { - // 获取最新数据 - auto aircraft = dataCollector_->getAircraftData(); - auto vehicles = dataCollector_->getVehicleData(); + auto now = std::chrono::steady_clock::now(); - // 更新碰撞检测器 - collisionDetector_->updateTraffic(aircraft, vehicles); + 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("Got data: ", aircraft.size(), " aircraft, ", vehicles.size(), " vehicles"); + + // 检查航空器更新 + 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)"); + for (const auto& ac : aircraft) { + broadcastPositionUpdate(ac); + } + 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) { + Logger::debug("Broadcasting vehicle positions (", vehicles.size(), " vehicles)"); + for (const auto& veh : vehicles) { + broadcastPositionUpdate(veh); + } + last_vehicle_update = now; + } + + // 更新碰撞检测器 + collisionDetector_->updateTraffic(aircraft, vehicles); + auto collisions = collisionDetector_->detectCollisions(); + + if (!collisions.empty()) { + processCollisions(collisions); + } + } - // 检测碰撞 - auto collisions = collisionDetector_->detectCollisions(); - - // 处理碰撞警告 - processCollisions(collisions); - - // 处理间隔 - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // 处理间隔设置为数据刷新间隔的 1/10 + std::this_thread::sleep_for(std::chrono::milliseconds( + system_config_.data_source.refresh_interval_ms / 10 + )); } catch (const std::exception& e) { Logger::error("处理循环发生错误: ", e.what()); @@ -88,6 +179,9 @@ void System::processCollisions(const std::vector& collisions) { Logger::info("检测到 ", collisions.size(), " 个碰撞风险"); for (const auto& risk : collisions) { + // 发送碰撞警告到 Unity 客户端 + broadcastCollisionWarning(risk); + // 根据风险等级选择不同的日志级别 switch (risk.level) { case RiskLevel::EMERGENCY: @@ -112,4 +206,69 @@ void System::processCollisions(const std::vector& collisions) { } } } +} + +void System::broadcastPositionUpdate(const MovingObject& obj) { + network::PositionUpdateMessage msg; + msg.objectId = obj.id; + msg.objectType = (typeid(obj) == typeid(Aircraft)) ? "aircraft" : "vehicle"; + 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, ")"); + } +} + +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; // 不发送无风险的警告 + } + + msg.distance = risk.distance; + msg.relativeSpeed = risk.relativeSpeed; + msg.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); + + if (ws_server_) { + ws_server_->broadcast(msg.toJson().dump()); + } +} + +void System::broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning) { + if (ws_server_) { + ws_server_->broadcast(warning.toJson().dump()); + + // 根据超时时间记录不同级别的日志 + if (warning.elapsed_ms > 30000) { // 30秒以上 + Logger::error("Severe timeout: ", warning.host, ":", warning.port, " - ", + warning.elapsed_ms, "ms without response"); + } else { + Logger::warning("Connection timeout: ", warning.host, ":", warning.port, " - ", + warning.elapsed_ms, "ms without response"); + } + } } \ No newline at end of file diff --git a/src/core/System.h b/src/core/System.h index bb8dcaa..8465e30 100644 --- a/src/core/System.h +++ b/src/core/System.h @@ -1,38 +1,56 @@ -#ifndef AIRPORT_CORE_SYSTEM_H -#define AIRPORT_CORE_SYSTEM_H +#pragma once -#include "collector/DataCollector.h" +#include +#include +#include +#include +#include "types/BasicTypes.h" #include "detector/CollisionDetector.h" #include "spatial/AirportBounds.h" -#include "network/ConnectionConfig.h" #include "vehicle/ControllableVehicles.h" -#include -#include -#include +#include "network/WebSocketServer.h" +#include "network/MessageTypes.h" +#include "config/SystemConfig.h" + +// 前向声明 +class DataCollector; class System { public: System(); ~System(); - bool initialize(const ConnectionConfig& config); + bool initialize(); void start(); void stop(); + static System* instance() { return instance_; } + static void signalHandler(int signal); + + void broadcastTimeoutWarning(const network::TimeoutWarningMessage& warning); + private: - std::unique_ptr controllableVehicles_; - std::unique_ptr dataCollector_; - std::unique_ptr collisionDetector_; - std::unique_ptr airportBounds_; - - std::thread processThread_; - std::atomic running_{false}; - void processLoop(); void processCollisions(const std::vector& collisions); + + // WebSocket 相关方法 + void broadcastPositionUpdate(const MovingObject& obj); + void broadcastCollisionWarning(const CollisionRisk& risk); - bool loadAirportBounds(); - bool loadControllableVehicles(); -}; - -#endif // AIRPORT_CORE_SYSTEM_H \ No newline at end of file + std::atomic running_{false}; + std::thread processThread_; + + std::unique_ptr airportBounds_; + std::unique_ptr controllableVehicles_; + std::unique_ptr collisionDetector_; + std::unique_ptr dataCollector_; + + // WebSocket 服务器 + std::unique_ptr ws_server_; + std::thread ws_thread_; + + // 系统配置 + SystemConfig system_config_; + + static System* instance_; +}; \ No newline at end of file diff --git a/src/detector/CollisionDetector.cpp b/src/detector/CollisionDetector.cpp index 6650ca7..19adcef 100644 --- a/src/detector/CollisionDetector.cpp +++ b/src/detector/CollisionDetector.cpp @@ -116,7 +116,6 @@ std::vector CollisionDetector::detectCollisions() { } } - Logger::info("Collision detection completed, found ", risks.size(), " risks"); return risks; } diff --git a/src/main.cpp b/src/main.cpp index 20cbc55..2460662 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,5 @@ #include "core/System.h" #include "utils/Logger.h" -#include "network/ConnectionConfig.h" #include #include #include @@ -23,13 +22,7 @@ int main() { // 初始化系统 System system; - ConnectionConfig config{ - .host = "localhost", - .port = 8080, - .timeout = 1000 // 1秒超时 - }; - - if (!system.initialize(config)) { + if (!system.initialize()) { Logger::error("Failed to initialize system"); return 1; } diff --git a/src/network/ConnectionConfig.h b/src/network/ConnectionConfig.h deleted file mode 100644 index 6bf2bdc..0000000 --- a/src/network/ConnectionConfig.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef AIRPORT_NETWORK_CONNECTION_CONFIG_H -#define AIRPORT_NETWORK_CONNECTION_CONFIG_H - -#include - -struct ConnectionConfig { - std::string host; // 服务器主机名 - int port; // 服务器端口 - int timeout; // 连接超时时间(毫秒) -}; - -#endif // AIRPORT_NETWORK_CONNECTION_CONFIG_H \ No newline at end of file diff --git a/src/network/HTTPDataSource.cpp b/src/network/HTTPDataSource.cpp index 73bf12f..9e6107f 100644 --- a/src/network/HTTPDataSource.cpp +++ b/src/network/HTTPDataSource.cpp @@ -5,9 +5,10 @@ using json = nlohmann::json; -HTTPDataSource::HTTPDataSource(const std::string& host, uint16_t port) - : host_(host) - , port_(std::to_string(port)) +HTTPDataSource::HTTPDataSource(const DataSourceConfig& config) + : config_(config) + , host_(config.host) + , port_(std::to_string(config.port)) , socket_(std::make_unique(io_context_)) { } @@ -46,44 +47,88 @@ bool HTTPDataSource::isAvailable() const { return socket_ && socket_->is_open(); } +bool HTTPDataSource::tryReconnect() { + if (is_reconnecting_) { + return false; // 已经在重连中 + } + + is_reconnecting_ = true; + + try { + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast( + now - last_connect_attempt_).count(); + + if (elapsed < config_.refresh_interval_ms) { + is_reconnecting_ = false; + return false; // 距离上次尝试时间太短 + } + + Logger::info("Attempting to reconnect to ", config_.host, ":", config_.port); + last_connect_attempt_ = now; + + for (int retry = 0; retry < MAX_RETRIES; ++retry) { + if (connect()) { + Logger::info("Successfully reconnected"); + is_reconnecting_ = false; + return true; + } + + if (retry < MAX_RETRIES - 1) { + Logger::info("Reconnection attempt ", retry + 1, " failed, retrying..."); + std::this_thread::sleep_for(std::chrono::milliseconds(config_.refresh_interval_ms/3)); + } + } + + Logger::error("Failed to reconnect after ", MAX_RETRIES, " attempts"); + is_reconnecting_ = false; + return false; + } + catch (const std::exception& e) { + Logger::error("Error during reconnection: ", e.what()); + is_reconnecting_ = false; + return false; + } +} + +bool HTTPDataSource::ensureConnected() { + if (isAvailable()) { + return true; + } + + return tryReconnect(); +} + bool HTTPDataSource::fetchAircraftData(std::vector& aircraft) { - // 首先检查连接状态 - if (!isAvailable()) { - Logger::error("Cannot fetch aircraft data: not connected"); + std::lock_guard lock(mutex_); + + if (!ensureConnected()) { return false; } - + std::string response; - if (!sendRequest("/api/getCurrentFlightPositions") || !readResponse(response)) { - disconnect(); // 如果请求失败,断开连接 + if (!sendRequest(config_.aircraft_path) || !readResponse(response)) { + disconnect(); return false; } - if (!parseAircraftResponse(response, aircraft)) { - return false; - } - - return true; + return parseAircraftResponse(response, aircraft); } bool HTTPDataSource::fetchVehicleData(std::vector& vehicles) { - // 首先检查连接状态 - if (!isAvailable()) { - Logger::error("Cannot fetch vehicle data: not connected"); + std::lock_guard lock(mutex_); + + if (!ensureConnected()) { return false; } - + std::string response; - if (!sendRequest("/api/getCurrentVehiclePositions") || !readResponse(response)) { - disconnect(); // 如果请求失败,断开连接 + if (!sendRequest(config_.vehicle_path) || !readResponse(response)) { + disconnect(); return false; } - if (!parseVehicleResponse(response, vehicles)) { - return false; - } - - return true; + return parseVehicleResponse(response, vehicles); } bool HTTPDataSource::sendRequest(const std::string& path) { diff --git a/src/network/HTTPDataSource.h b/src/network/HTTPDataSource.h index 3af074f..f554e1d 100644 --- a/src/network/HTTPDataSource.h +++ b/src/network/HTTPDataSource.h @@ -5,14 +5,20 @@ #include "spatial/CoordinateConverter.h" #include #include +#include "collector/DataSourceConfig.h" +#include +#include +#include +#include "core/System.h" namespace asio = boost::asio; class HTTPDataSource : public DataSource { public: - explicit HTTPDataSource(const std::string& host, uint16_t port); + explicit HTTPDataSource(const DataSourceConfig& config); ~HTTPDataSource() override; + DataSourceConfig config_; bool connect() override; void disconnect() override; bool isAvailable() const override; @@ -26,6 +32,14 @@ private: asio::io_context io_context_; std::unique_ptr socket_; CoordinateConverter coordinateConverter_; + std::mutex mutex_; + + std::chrono::steady_clock::time_point last_connect_attempt_; + static constexpr int MAX_RETRIES = 3; // 单次连接最大重试次数 + std::atomic is_reconnecting_{false}; // 重连状态标志 + + bool tryReconnect(); + bool ensureConnected(); bool sendRequest(const std::string& path); bool readResponse(std::string& response); @@ -33,4 +47,4 @@ private: bool parseVehicleResponse(const std::string& response, std::vector& vehicles); }; -#endif // AIRPORT_NETWORK_HTTP_DATA_SOURCE_H \ No newline at end of file +#endif // AIRPORT_NETWORK_HTTP_DATA_SOURCE_H diff --git a/src/network/MessageTypes.h b/src/network/MessageTypes.h new file mode 100644 index 0000000..857ddd3 --- /dev/null +++ b/src/network/MessageTypes.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include + +namespace network { + +// 位置更新消息 +struct PositionUpdateMessage { + std::string type = "position_update"; + std::string objectId; + std::string objectType; // "aircraft" 或 "vehicle" + double longitude; + double latitude; + double heading; + double speed; + uint64_t timestamp; + + nlohmann::json toJson() const { + return { + {"type", type}, + {"objectId", objectId}, + {"objectType", objectType}, + {"longitude", longitude}, + {"latitude", latitude}, + {"heading", heading}, + {"speed", speed}, + {"timestamp", timestamp} + }; + } +}; + +// 碰撞告警消息 +struct CollisionWarningMessage { + std::string type = "collision_warning"; + std::string id1; + std::string id2; + std::string warningLevel; // "low", "medium", "high" + double distance; // 当前距离 + double relativeSpeed; // 相对速度 + double threshold; // 预警距离阈值 + uint64_t timestamp; + + nlohmann::json toJson() const { + return { + {"type", type}, + {"id1", id1}, + {"id2", id2}, + {"warningLevel", warningLevel}, + {"distance", distance}, + {"relativeSpeed", relativeSpeed}, + {"threshold", threshold}, + {"timestamp", timestamp} + }; + } +}; + +struct TimeoutWarningMessage { + std::string type = "data_source_timeout"; + std::string host; + int port; + int64_t elapsed_ms; + uint64_t timestamp; + + nlohmann::json toJson() const { + return { + {"type", type}, + {"host", host}, + {"port", port}, + {"elapsed_ms", elapsed_ms}, + {"timestamp", timestamp} + }; + } +}; + +} // namespace network \ No newline at end of file diff --git a/src/network/WebSocketServer.cpp b/src/network/WebSocketServer.cpp new file mode 100644 index 0000000..c064fe2 --- /dev/null +++ b/src/network/WebSocketServer.cpp @@ -0,0 +1,118 @@ +#include "network/WebSocketServer.h" +#include + +namespace network { + +WebSocketServer::WebSocketServer(uint16_t port) + : acceptor_(ioc_, {boost::asio::ip::tcp::v4(), port}) { +} + +WebSocketServer::~WebSocketServer() { + // 关闭所有连接 + std::lock_guard lock(sessions_mutex_); + for (auto& session : sessions_) { + try { + session.lock()->close(boost::beast::websocket::close_code::normal); + } catch (...) { + // 忽略关闭时的错误 + } + } + sessions_.clear(); + + // 停止 io_context + ioc_.stop(); +} + +void WebSocketServer::start() { + handleAccept(); + ioc_.run(); +} + +void WebSocketServer::broadcast(const std::string& message) { + std::lock_guard lock(sessions_mutex_); + auto it = sessions_.begin(); + while (it != sessions_.end()) { + if (auto session = it->lock()) { // 获取 shared_ptr + try { + session->write(boost::asio::buffer(message)); + ++it; + } catch (...) { + it = sessions_.erase(it); // 移除失败的会话 + } + } else { + it = sessions_.erase(it); // 移除已失效的会话 + } + } +} + +void WebSocketServer::handleAccept() { + acceptor_.async_accept( + [this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) { + if (!ec) { + // 创建新的 WebSocket 会话 + auto ws = std::make_shared>(std::move(socket)); + + // 异步完成 WebSocket 握手 + ws->async_accept( + [this, ws](boost::system::error_code ec) { + if (!ec) { + // 握手成功,保存会话并开始读取消息 + { + std::lock_guard lock(sessions_mutex_); + sessions_.push_back(ws); // 存储 weak_ptr + } + doRead(ws); + } + }); + } + + // 继续接受新的连接 + handleAccept(); + }); +} + +void WebSocketServer::doRead(std::shared_ptr> ws) { + // 为每个会话创建一个专用的缓冲区 + auto buffer = std::make_shared(); + + // 异步读取消息 + ws->async_read( + *buffer, + [this, ws, buffer](boost::system::error_code ec, std::size_t bytes_transferred) { + if (!ec) { + // 成功读取消息,继续读取下一条 + buffer->consume(buffer->size()); // 清空缓冲区 + doRead(ws); + } else { + // 发生错误或连接关闭,移除会话 + std::lock_guard lock(sessions_mutex_); + auto it = std::find_if(sessions_.begin(), sessions_.end(), + [ws](const std::weak_ptr>& weak) { + return !weak.expired() && weak.lock() == ws; + }); + if (it != sessions_.end()) { + sessions_.erase(it); + } + } + }); +} + +void WebSocketServer::stop() { + running_ = false; + ioc_.stop(); // 停止 io_context + + // 关闭所有连接 + std::lock_guard lock(sessions_mutex_); + for (auto& session : sessions_) { + if (auto ws = session.lock()) { + try { + ws->close(boost::beast::websocket::close_code::normal); + } catch (...) { + // 忽略关闭时的错误 + } + } + } + sessions_.clear(); +} + +} // namespace network \ No newline at end of file diff --git a/src/network/WebSocketServer.h b/src/network/WebSocketServer.h new file mode 100644 index 0000000..426be69 --- /dev/null +++ b/src/network/WebSocketServer.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace network { + +class WebSocketServer { +public: + WebSocketServer(uint16_t port); + ~WebSocketServer(); + + void start(); + void broadcast(const std::string& message); + void stop(); + +private: + void handleAccept(); + void doRead(std::shared_ptr> ws); + + boost::asio::io_context ioc_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector>> sessions_; + std::mutex sessions_mutex_; + std::atomic running_{true}; +}; + +} // namespace network \ No newline at end of file diff --git a/src/types/BasicTypes.cpp b/src/types/BasicTypes.cpp index 679c294..dde7c1c 100644 --- a/src/types/BasicTypes.cpp +++ b/src/types/BasicTypes.cpp @@ -131,4 +131,17 @@ void MovingObject::updateMotion(const GeoPosition& newPos, uint64_t newTime) { void MovingObject::copyHistoryFrom(const MovingObject& other) { positionHistory = other.positionHistory; +} + +bool MovingObject::hasPositionChanged() const { + if (positionHistory.size() < 2) { + return true; + } + + const auto& latest = positionHistory.back(); + const auto& previous = positionHistory[positionHistory.size() - 2]; + + // 计算位置变化 + double distance = calculateDistance(latest.geo, previous.geo); + return distance > 0.1; // 位置变化超过0.1米才认为发生了变化 } \ No newline at end of file diff --git a/src/types/BasicTypes.h b/src/types/BasicTypes.h index 9ddd784..10c3122 100644 --- a/src/types/BasicTypes.h +++ b/src/types/BasicTypes.h @@ -1,28 +1,23 @@ -#ifndef AIRPORT_TYPES_BASIC_TYPES_H -#define AIRPORT_TYPES_BASIC_TYPES_H +#pragma once #include -#include #include -#include +#include +// 基础数据类型 struct Vector2D { - double x; // 东西方向(米) - double y; // 南北方向(米) - - Vector2D() : x(0), y(0) {} - Vector2D(double x_, double y_) : x(x_), y(y_) {} + double x; + double y; double magnitude() const; double direction() const; }; struct GeoPosition { - double latitude; // 纬度 - double longitude; // 经度 + double latitude; + double longitude; }; -// 历史位置记录 struct PositionRecord { GeoPosition geo; uint64_t timestamp; @@ -30,71 +25,57 @@ struct PositionRecord { PositionRecord(const GeoPosition& g, uint64_t t) : geo(g), timestamp(t) {} }; -// 基础移动物体数据 +// 移动物体基类 class MovingObject { public: - std::string id; // 唯一标识 - GeoPosition geo; // 地理坐标 - Vector2D position; // 平面坐标 - double heading; // 航向角(度) - double speed; // 速度(米/秒) - uint64_t timestamp; // 时间戳 - - // 更新运动信息 - virtual void updateMotion(const GeoPosition& newPos, uint64_t newTime); + virtual ~MovingObject() = default; // 添加虚析构函数以支持 dynamic_cast - // 复制历史记录 - void copyHistoryFrom(const MovingObject& other); + std::string id; + GeoPosition geo; + Vector2D position; + double heading; + double speed; + uint64_t timestamp; - MovingObject() : heading(0), speed(0), timestamp(0) {} - virtual ~MovingObject() = default; - -protected: - static constexpr size_t MAX_HISTORY = 5; // 保留最近5个位置记录 - std::deque positionHistory; // 位置历史记录 + std::deque positionHistory; - // 计算两个地理位置之间的距离(米) static double calculateDistance(const GeoPosition& pos1, const GeoPosition& pos2); - // 计算航向角 static double calculateHeading(const GeoPosition& from, const GeoPosition& to); - // 检查位置是否合理 + virtual bool isValidPosition(const GeoPosition& newPos) const = 0; - // 检查速度是否合理 virtual bool isValidSpeed(double speed) const = 0; - // 获取速度平滑因子 - virtual double getSpeedSmoothingFactor() const = 0; + virtual double getSpeedSmoothingFactor() const { return 0.3; } + + void updateMotion(const GeoPosition& newPos, uint64_t newTime); + void copyHistoryFrom(const MovingObject& other); + bool hasPositionChanged() const; + + static constexpr size_t MAX_HISTORY = 10; }; -// 航空器数据 -struct Aircraft : MovingObject { - std::string flightNo; // 航班号 - std::string trackNumber; // 航迹号 - double altitude; // 高度(米) - -protected: - // 航空器特定参数 - static constexpr double MAX_SPEED = 100.0; // 最大速度(米/秒) - static constexpr double MAX_POSITION_JUMP = 150.0; // 最大位置跳变(米) - static constexpr double SPEED_SMOOTHING_FACTOR = 0.3; // 速度平滑因子 +// 航空器类 +class Aircraft : public MovingObject { +public: + std::string flightNo; + std::string trackNumber; + double altitude; bool isValidPosition(const GeoPosition& newPos) const override; bool isValidSpeed(double speed) const override; - double getSpeedSmoothingFactor() const override { return SPEED_SMOOTHING_FACTOR; } -}; - -// 车辆数据 -struct Vehicle : MovingObject { - std::string vehicleNo; // 车牌号 - -protected: - // 车辆特定参数 - static constexpr double MAX_SPEED = 30.0; // 最大速度(米/秒) + + static constexpr double MAX_SPEED = 100.0; // 最大速度(米/秒) static constexpr double MAX_POSITION_JUMP = 50.0; // 最大位置跳变(米) - static constexpr double SPEED_SMOOTHING_FACTOR = 0.5; // 速度平滑因子 +}; + +// 车辆类 +class Vehicle : public MovingObject { +public: + std::string vehicleNo; + bool controllable; bool isValidPosition(const GeoPosition& newPos) const override; bool isValidSpeed(double speed) const override; - double getSpeedSmoothingFactor() const override { return SPEED_SMOOTHING_FACTOR; } -}; - -#endif // AIRPORT_TYPES_BASIC_TYPES_H \ No newline at end of file + + static constexpr double MAX_SPEED = 20.0; // 最大速度(米/秒) + static constexpr double MAX_POSITION_JUMP = 10.0; // 最大位置跳变(米) +}; \ No newline at end of file diff --git a/tests/CollisionDetectorTest.cpp b/tests/CollisionDetectorTest.cpp index ef4b691..d0ca932 100644 --- a/tests/CollisionDetectorTest.cpp +++ b/tests/CollisionDetectorTest.cpp @@ -62,7 +62,7 @@ TEST_F(CollisionDetectorTest, DetectControllableVehicleAircraftCollision) { .WillRepeatedly(testing::Return(true)); Logger::info("Set mock expectation: VEH001 is controllable"); - // 设置测试数据 + // 设置测试数��� Aircraft aircraft; aircraft.flightNo = "TEST001"; aircraft.position = {100, 100}; @@ -85,15 +85,16 @@ TEST_F(CollisionDetectorTest, DetectControllableVehicleAircraftCollision) { // 执行碰撞检测 auto risks = detector_->detectCollisions(); - Logger::info("Collision detection completed, found ", risks.size(), " risks"); // 验证结果 ASSERT_EQ(risks.size(), 1); // 应该检测到一个碰撞风险 if (!risks.empty()) { - EXPECT_EQ(risks[0].id1, "TEST001"); // 航空器ID - EXPECT_EQ(risks[0].id2, "VEH001"); // 车辆ID - EXPECT_EQ(risks[0].distance, 20); // 距离应该是20米 - EXPECT_EQ(risks[0].level, RiskLevel::EMERGENCY); // 20米距离应该是严重风险 + 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_GT(risk.relativeSpeed, 0); // 相对速度应该大于0 } } @@ -213,7 +214,7 @@ TEST_F(CollisionDetectorTest, MultipleControllableVehiclesCollision) { 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米���该是危险级别 } } diff --git a/tests/DataCollectorTest.cpp b/tests/DataCollectorTest.cpp index 6e65fd8..79a53c0 100644 --- a/tests/DataCollectorTest.cpp +++ b/tests/DataCollectorTest.cpp @@ -1,7 +1,6 @@ #include #include #include "collector/DataCollector.h" -#include "network/ConnectionConfig.h" #include "utils/Logger.h" // 创建一个 Mock DataSource 类 @@ -18,6 +17,8 @@ class DataCollectorTest : public ::testing::Test { protected: void SetUp() override { collector = std::make_unique(); + mockSource = std::make_shared<::testing::NiceMock>(); + collector->setDataSource(mockSource); } void TearDown() override { @@ -33,7 +34,7 @@ protected: a.geo.latitude = lat; a.geo.longitude = lon; a.altitude = 5.0; - a.timestamp = time(nullptr); + a.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); return a; } @@ -43,28 +44,30 @@ protected: v.vehicleNo = id; v.geo.latitude = lat; v.geo.longitude = lon; - v.timestamp = time(nullptr); + v.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); return v; } std::unique_ptr collector; + std::shared_ptr mockSource; }; // 测试初始化 TEST_F(DataCollectorTest, Initialization) { - ConnectionConfig config{"localhost", 8080}; - EXPECT_TRUE(collector->initialize(config)); + DataSourceConfig dataSourceConfig; + dataSourceConfig.host = "localhost"; + dataSourceConfig.port = 8080; + dataSourceConfig.aircraft_path = "/api/getCurrentFlightPositions"; + + WarnConfig warnConfig; + warnConfig.warning_interval_ms = 1000; + warnConfig.log_interval_ms = 2000; + + EXPECT_TRUE(collector->initialize(dataSourceConfig, warnConfig)); } -// 测试数据采集 -TEST_F(DataCollectorTest, DataCollection) { - // 创建 Mock DataSource - auto mockSource = std::make_shared<::testing::NiceMock>(); - - // 设置期望行为 - EXPECT_CALL(*mockSource, connect()) - .WillOnce(::testing::Return(true)); - +// 测试刷新方法 +TEST_F(DataCollectorTest, RefreshTest) { std::vector testAircraft = { createTestAircraft("TEST1", 36.36, 120.08), createTestAircraft("TEST2", 36.37, 120.09) @@ -76,26 +79,20 @@ TEST_F(DataCollectorTest, DataCollection) { }; // 设置 Mock 数据返回 - ON_CALL(*mockSource, fetchAircraftData) - .WillByDefault([testAircraft](std::vector& aircraft) { - aircraft = testAircraft; - return true; - }); + EXPECT_CALL(*mockSource, fetchAircraftData) + .WillOnce(::testing::DoAll( + ::testing::SetArgReferee<0>(testAircraft), + ::testing::Return(true) + )); - ON_CALL(*mockSource, fetchVehicleData) - .WillByDefault([testVehicles](std::vector& vehicles) { - vehicles = testVehicles; - return true; - }); + EXPECT_CALL(*mockSource, fetchVehicleData) + .WillOnce(::testing::DoAll( + ::testing::SetArgReferee<0>(testVehicles), + ::testing::Return(true) + )); - // 设置 Mock DataSource - collector->setDataSource(mockSource); - - // 启动采集 - collector->start(); - - // 等待数据采集 - std::this_thread::sleep_for(std::chrono::seconds(2)); + // 执行刷新 + collector->refresh(); // 验证数据 auto aircraft = collector->getAircraftData(); @@ -111,70 +108,63 @@ TEST_F(DataCollectorTest, DataCollection) { EXPECT_EQ(vehicles[0].vehicleNo, "VEH1"); EXPECT_EQ(vehicles[1].vehicleNo, "VEH2"); } +} + +// 测试数据采集循环 +TEST_F(DataCollectorTest, DataCollectionLoop) { + std::vector testAircraft = { + createTestAircraft("TEST1", 36.36, 120.08) + }; + + std::vector testVehicles = { + createTestVehicle("VEH1", 36.36, 120.08) + }; + + // 设置 Mock 数据返回 + EXPECT_CALL(*mockSource, fetchAircraftData) + .WillRepeatedly(::testing::DoAll( + ::testing::SetArgReferee<0>(testAircraft), + ::testing::Return(true) + )); + + EXPECT_CALL(*mockSource, fetchVehicleData) + .WillRepeatedly(::testing::DoAll( + ::testing::SetArgReferee<0>(testVehicles), + ::testing::Return(true) + )); + + // 启动采集 + collector->start(); + + // 等待数据采集 + std::this_thread::sleep_for(std::chrono::seconds(2)); // 停止采集 collector->stop(); + + // 验证数据 + auto aircraft = collector->getAircraftData(); + EXPECT_EQ(aircraft.size(), 1); + + auto vehicles = collector->getVehicleData(); + EXPECT_EQ(vehicles.size(), 1); } -// 测试速度计算 -TEST_F(DataCollectorTest, SpeedCalculation) { - auto mockSource = std::make_shared<::testing::NiceMock>(); - EXPECT_CALL(*mockSource, connect()) - .WillOnce(::testing::Return(true)); +// 测试错误处理 +TEST_F(DataCollectorTest, ErrorHandling) { + // 设置 Mock 返回错误 + EXPECT_CALL(*mockSource, fetchAircraftData) + .WillOnce(::testing::Return(false)); + EXPECT_CALL(*mockSource, fetchVehicleData) + .WillOnce(::testing::Return(false)); - uint64_t baseTime = time(nullptr); + // 执行刷新 + collector->refresh(); - // 创建一系列连续的位置数据 - std::vector> positions; - - // 计算经度变化量 - // 在36.36°纬度,1度经度约等于90km - // 要达到55m/s的速度,每秒需要变化:55/(90000) = 0.00061度 - const double LON_CHANGE_PER_SEC = 0.00061; // 每秒经度变化量 - - // 每秒一个位置点,总共10秒 - for (int i = 0; i < 10; i++) { - double lon = 120.08 + (LON_CHANGE_PER_SEC * i); - uint64_t timestamp = baseTime + (i * 1); // 每秒一个点 - - std::vector data = { - createTestAircraft("TEST1", 36.36, lon) - }; - data[0].timestamp = timestamp; - positions.push_back(data); - } - - // 设置 Mock 数据返回 - int callCount = 0; - ON_CALL(*mockSource, fetchAircraftData) - .WillByDefault([positions, &callCount](std::vector& aircraft) { - if (callCount < positions.size()) { - const auto& pos = positions[callCount]; - aircraft = pos; - callCount++; - } else { - const auto& pos = positions.back(); - aircraft = pos; - } - return true; - }); - - collector->setDataSource(mockSource); - collector->start(); - - // 等待数据更新 - std::this_thread::sleep_for(std::chrono::seconds(3)); - - // 验证速度计算 + // 验证数据为空 auto aircraft = collector->getAircraftData(); + EXPECT_TRUE(aircraft.empty()); - EXPECT_FALSE(aircraft.empty()); - if (!aircraft.empty()) { - const auto& a = aircraft[0]; - - // 总共移动约110米,用时2秒,期望速度约55米/秒 - EXPECT_NEAR(a.speed, 55.0, 5.0); - } - - collector->stop(); + auto vehicles = collector->getVehicleData(); + EXPECT_TRUE(vehicles.empty()); } \ No newline at end of file diff --git a/tests/HTTPDataSourceTest.cpp b/tests/HTTPDataSourceTest.cpp index 66414d9..1134bcd 100644 --- a/tests/HTTPDataSourceTest.cpp +++ b/tests/HTTPDataSourceTest.cpp @@ -1,106 +1,88 @@ #include #include #include "network/HTTPDataSource.h" -#include "utils/Logger.h" class HTTPDataSourceTest : public ::testing::Test { protected: + std::unique_ptr source; + void SetUp() override { - source = std::make_unique("localhost", 8080); + DataSourceConfig config; + config.host = "localhost"; + config.port = 8080; + config.aircraft_path = "/api/getCurrentFlightPositions"; + config.vehicle_path = "/api/getCurrentVehiclePositions"; + source = std::make_unique(config); } - + void TearDown() override { source.reset(); } - - std::unique_ptr source; }; -// 测试连接功能 -TEST_F(HTTPDataSourceTest, ConnectionTest) { - EXPECT_TRUE(source->connect()); +TEST_F(HTTPDataSourceTest, ConnectTest) { + EXPECT_FALSE(source->isAvailable()); // 初始状态应该是未连接 + EXPECT_TRUE(source->connect()); // 连接应该成功 + EXPECT_TRUE(source->isAvailable()); // 连接后应该可用 +} + +TEST_F(HTTPDataSourceTest, DisconnectTest) { + source->connect(); EXPECT_TRUE(source->isAvailable()); - source->disconnect(); EXPECT_FALSE(source->isAvailable()); } -// 测试获取航空器数据 -TEST_F(HTTPDataSourceTest, GetAircraftData) { - ASSERT_TRUE(source->connect()); - +TEST_F(HTTPDataSourceTest, FetchAircraftDataTest) { std::vector aircraft; + source->connect(); EXPECT_TRUE(source->fetchAircraftData(aircraft)); - EXPECT_FALSE(aircraft.empty()); - - // 检查第一个航空器的数据 - if (!aircraft.empty()) { - const auto& first = aircraft[0]; - EXPECT_EQ(first.flightNo, "CES2501"); - // 不检查高度,因为接口不返回高度信息 - } + // 注意:这里的具体数据验证取决于你的测试环境和模拟数据 } -// 测试获取车辆数据 -TEST_F(HTTPDataSourceTest, GetVehicleData) { - ASSERT_TRUE(source->connect()); - +TEST_F(HTTPDataSourceTest, FetchVehicleDataTest) { std::vector vehicles; + source->connect(); EXPECT_TRUE(source->fetchVehicleData(vehicles)); - EXPECT_FALSE(vehicles.empty()); - - // 检查第一个车辆的数据 - if (!vehicles.empty()) { - const auto& first = vehicles[0]; - EXPECT_EQ(first.vehicleNo, "VEH001"); - } + // 注意:这里的具体数据验证取决于你的测试环境和模拟数据 } -// 测试错误处理 -TEST_F(HTTPDataSourceTest, ErrorHandling) { - // 测试错误的端口 - auto badPortSource = std::make_unique("localhost", 9999); - EXPECT_FALSE(badPortSource->connect()); - - // 测试无效的响应 - ASSERT_TRUE(source->connect()); +TEST_F(HTTPDataSourceTest, CustomPathTest) { + // 测试使用自定义路径创建数据源 + DataSourceConfig config; + config.host = "localhost"; + config.port = 8080; + config.aircraft_path = "/custom/path"; + auto customSource = std::make_unique(config); + EXPECT_TRUE(customSource->connect()); + EXPECT_TRUE(customSource->isAvailable()); +} + +TEST_F(HTTPDataSourceTest, ConnectionFailureTest) { + // 测试连接到不存在的服务器 + DataSourceConfig config; + config.host = "invalid-host"; + config.port = 9999; + auto invalidSource = std::make_unique(config); + EXPECT_FALSE(invalidSource->connect()); + EXPECT_FALSE(invalidSource->isAvailable()); +} + +TEST_F(HTTPDataSourceTest, FetchDataWithoutConnectionTest) { + // 测试在未连接状态下获取数据 std::vector aircraft; - source->disconnect(); // 断开连接后尝试获取数据 + std::vector vehicles; + EXPECT_FALSE(source->fetchAircraftData(aircraft)); + EXPECT_FALSE(source->fetchVehicleData(vehicles)); } -// 测试数据解析 -TEST_F(HTTPDataSourceTest, DataParsing) { - ASSERT_TRUE(source->connect()); - - // 获取并检查航空器数据 - { - std::vector aircraft; - ASSERT_TRUE(source->fetchAircraftData(aircraft)); - - for (const auto& a : aircraft) { - // 检查基本字段 - EXPECT_FALSE(a.flightNo.empty()); - // 不检查 trackNumber,因为接口不返回该字段 - - // 检查位置在合理范围内 - EXPECT_GE(a.position.x, 0.0); - EXPECT_GE(a.position.y, 0.0); - } - } - - // 获取并检查车辆数据 - { - std::vector vehicles; - ASSERT_TRUE(source->fetchVehicleData(vehicles)); - - for (const auto& v : vehicles) { - // 检查基本字段 - EXPECT_FALSE(v.vehicleNo.empty()); - - // 检查位置在合理范围内 - EXPECT_GE(v.position.x, 0.0); - EXPECT_GE(v.position.y, 0.0); - } - } +// 如果你的环境支持模拟网络响应,可以添加更多测试 +TEST_F(HTTPDataSourceTest, InvalidResponseTest) { + // 这个测试需要模拟无效的服务器响应 + // 你可能需要使用 mock 对象或者设置一个返回无效数据的测试服务器 + source->connect(); + std::vector aircraft; + // 假设服务器返回无效的 JSON 数据 + // EXPECT_FALSE(source->fetchAircraftData(aircraft)); } \ No newline at end of file diff --git a/tools/mock_server.py b/tools/mock_server.py index 5c5167d..04a23f5 100644 --- a/tools/mock_server.py +++ b/tools/mock_server.py @@ -14,56 +14,92 @@ aircraft_data = [ "flightNo": "CES2501", # 在跑道上 "latitude": BASE_LAT, "longitude": BASE_LON + 0.001, # 在跑道中间位置 - "time": int(time.time()) + "time": int(time.time()), + "direction": 1 # 1表示向东,-1表示向西 }, { "flightNo": "CES2502", # 在滑行道上 "latitude": BASE_LAT - 0.001, "longitude": BASE_LON, - "time": int(time.time()) + "time": int(time.time()), + "direction": 1 # 1表示向东,-1表示向西 } ] -# 两辆车从跑道两侧向中间移动(距离更近,速度更快) vehicle_data = [ { - "vehicleNo": "VEH001", # 从南向北接近跑道 - "latitude": BASE_LAT - 0.0005, # 起始位置更近(约50米) - "longitude": BASE_LON + 0.001, # 与飞机同一经度 - "time": int(time.time()) + "vehicleNo": "VEH001", # 南北方向移动 + "latitude": BASE_LAT - 0.0005, + "longitude": BASE_LON + 0.001, + "time": int(time.time()), + "direction": 1, # 1表示向北,-1表示向南 + "phase": 0 # 用于控制循环运动 }, { - "vehicleNo": "VEH002", # 从北向南接近跑道 - "latitude": BASE_LAT + 0.0005, # 起始位置更近(约50米) - "longitude": BASE_LON + 0.001, # 与飞机同一经度 - "time": int(time.time()) + "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度 } ] +# 定义运动范围 +LAT_RANGE = 0.001 # 约110米 +LON_RANGE = 0.002 # 约220米 +VEHICLE_SPEED = 0.00005 # 每次更新的位置变化 +AIRCRAFT_SPEED = 0.00002 # 每次更新的位置变化 + @app.route('/api/getCurrentFlightPositions') def get_flight_positions(): current_time = int(time.time()) - # 更新时间戳 + + # 更新航空器位置 for aircraft in aircraft_data: aircraft["time"] = current_time - # CES2501 在跑道上缓慢滑行 - if aircraft["flightNo"] == "CES2501": - aircraft["longitude"] += 0.00001 * math.sin(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 + return jsonify(aircraft_data) @app.route('/api/getCurrentVehiclePositions') def get_vehicle_positions(): current_time = int(time.time()) - # 更新时间戳和位置 + + # 更新车辆位置 for vehicle in vehicle_data: vehicle["time"] = current_time - if vehicle["vehicleNo"] == "VEH001": - # 从南向北移动(速度更快) - vehicle["latitude"] += 0.00005 # 增加移动速度 - elif vehicle["vehicleNo"] == "VEH002": - # 从北向南移动(速度更快) - vehicle["latitude"] -= 0.00005 # 增加移动速度 + + # 更新相位 + 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) + return jsonify(vehicle_data) +@app.after_request +def add_cors_headers(response): + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Headers'] = 'Content-Type' + response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' + return response + if __name__ == '__main__': - app.run(host='localhost', port=8080) \ No newline at end of file + app.run(host='localhost', port=8080, debug=True) \ No newline at end of file diff --git a/tools/test_websocket.html b/tools/test_websocket.html new file mode 100644 index 0000000..e85fbe2 --- /dev/null +++ b/tools/test_websocket.html @@ -0,0 +1,103 @@ + + + + WebSocket 测试 + + + +

WebSocket 测试客户端

+
+
+ + + +
+ + + + \ No newline at end of file