增加了与后台通信的Socket模块,增加了超时告警

This commit is contained in:
Tian jianyong 2024-11-22 10:29:01 +08:00
parent 16048839bd
commit b642bf3a6f
30 changed files with 1388 additions and 441 deletions

View File

@ -30,7 +30,7 @@
- 格式统一一致
请遵循如下 C++ 规范:
- 使用 C++17 标准
- 使用 C++20 标准
- 使用 CMake 3.14 及以上版本
- 使用 nlohmann_json 3.11.3 版本
- 使用 FetchContent 管理第三方库

View File

@ -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>();
double latitude = json["latitude"].Value<double>();
double heading = json["heading"].Value<double>();
// 更新对应物体的位置和朝向
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<double>();
// 显示碰撞警告效果
WarningManager.Instance.ShowWarning(object1Id, object2Id, warningLevel, distance);
}
private async void OnApplicationQuit()
{
await websocket.Close();
}
}

View File

@ -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
)
#

65
config/system_config.json Normal file
View File

@ -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
}
}

View File

@ -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<Vehicle> vehicleTree_(bounds, 8); // 容量为8的四叉树
```
2. 邻近查询:
2.邻近查询:
```cpp
auto nearbyVehicles = vehicleTree_.queryNearby(

View File

@ -1,40 +0,0 @@
#pragma once
#include "CollisionDetector.h"
#include "DataCollector.h"
#include "airport/AirportBounds.h"
#include "config/ConnectionConfig.h"
#include <memory>
#include <thread>
#include <vector>
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> airportBounds_;
std::unique_ptr<DataCollector> dataCollector_;
std::unique_ptr<CollisionDetector> collisionDetector_;
std::vector<ControllableVehicleConfig> controllableVehicles_;
std::thread processThread_;
bool running_ = false;
void processLoop();
void processCollisions(const std::vector<CollisionRisk>& collisions);
bool loadAirportBounds();
bool loadControllableVehicles();
};

View File

@ -1,30 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
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<ControllableVehicleConfig>& getVehicles() const;
// 根据车牌号查找可控车辆配置
const ControllableVehicleConfig* findVehicle(const std::string& vehicleNo) const;
// 检查车辆是否可控 - 添加 virtual
virtual bool isControllable(const std::string& vehicleNo) const;
private:
std::vector<ControllableVehicleConfig> vehicles_;
void loadConfig(const std::string& configFile);
};

View File

@ -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<HTTPDataSource>(config.host, config.port);
dataSourceConfig_ = dataSourceConfig;
dataSource_ = std::make_shared<HTTPDataSource>(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<std::chrono::milliseconds>(
now - lastSuccessfulFetch_).count();
if (elapsed > dataSourceConfig_.timeout_ms) {
// 检查是否达到告警间隔
auto warning_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
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<std::chrono::milliseconds>(
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> 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<Aircraft> DataCollector::getAircraftData() {
std::vector<Vehicle> DataCollector::getVehicleData() {
std::lock_guard<std::mutex> lock(cacheMutex_);
return vehicleCache_;
}
void DataCollector::refresh() {
try {
// 从数据源获取最新数据
std::vector<Aircraft> newAircraft;
std::vector<Vehicle> newVehicles;
if (dataSource_->fetchAircraftData(newAircraft)) {
std::lock_guard<std::mutex> 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<std::mutex> 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;
}
}

View File

@ -8,15 +8,16 @@
#include <mutex>
#include <map>
#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<DataSource> source) {
dataSource_ = source;
}
// 添加刷新方法
void refresh();
private:
std::shared_ptr<DataSource> dataSource_;
DataSourceConfig dataSourceConfig_;
WarnConfig warnConfig_;
std::thread collectorThread_;
std::atomic<bool> running_{false};
std::atomic<uint64_t> loopCount_{0};
@ -44,9 +50,15 @@ private:
std::map<std::string, Aircraft> lastAircraftPositions_;
std::map<std::string, Vehicle> 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();
};

View File

@ -0,0 +1,15 @@
#ifndef AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H
#define AIRPORT_COLLECTOR_DATA_SOURCE_CONFIG_H
#include <string>
struct DataSourceConfig {
std::string host;
uint16_t port;
std::string aircraft_path;
std::string vehicle_path;
int refresh_interval_ms;
int timeout_ms;
};
#endif

View File

@ -0,0 +1,75 @@
#include "config/SystemConfig.h"
#include "utils/Logger.h"
#include <fstream>
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;
}
}

70
src/config/SystemConfig.h Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include <string>
#include <nlohmann/json.hpp>
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);
};

11
src/config/WarnConfig.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef AIRPORT_NETWORK_WARN_CONFIG_H
#define AIRPORT_NETWORK_WARN_CONFIG_H
#include <string>
struct WarnConfig {
int warning_interval_ms;
int log_interval_ms;
};
#endif // AIRPORT_NETWORK_WARN_CONFIG_H

View File

@ -1,29 +1,72 @@
#include <csignal>
#include <typeinfo>
#include <chrono>
#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<network::WebSocketServer>(system_config_.websocket.port);
ws_thread_ = std::thread([this]() {
ws_server_->start();
});
// 加载机场区域配置
airportBounds_ = std::make_unique<AirportBounds>("config/airport_bounds.json");
// 加载可控车辆配置
controllableVehicles_ = std::make_unique<ControllableVehicles>("config/controllable_vehicles.json");
// 初始化数据采集器
dataCollector_ = std::make_unique<DataCollector>();
// 初始化碰撞检测器
collisionDetector_ = std::make_unique<CollisionDetector>(*airportBounds_, *controllableVehicles_);
// 初始化数据采集器
return dataCollector_->initialize(config);
// 创建数据采集器
dataCollector_ = std::make_unique<DataCollector>();
// 数据采集器初始化并启动
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<std::chrono::milliseconds>(
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<std::chrono::milliseconds>(
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<std::chrono::milliseconds>(
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<CollisionRisk>& 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<CollisionRisk>& 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");
}
}
}

View File

@ -1,38 +1,56 @@
#ifndef AIRPORT_CORE_SYSTEM_H
#define AIRPORT_CORE_SYSTEM_H
#pragma once
#include "collector/DataCollector.h"
#include <memory>
#include <thread>
#include <string>
#include <atomic>
#include "types/BasicTypes.h"
#include "detector/CollisionDetector.h"
#include "spatial/AirportBounds.h"
#include "network/ConnectionConfig.h"
#include "vehicle/ControllableVehicles.h"
#include <thread>
#include <atomic>
#include <memory>
#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> controllableVehicles_;
std::unique_ptr<DataCollector> dataCollector_;
std::unique_ptr<CollisionDetector> collisionDetector_;
std::unique_ptr<AirportBounds> airportBounds_;
std::thread processThread_;
std::atomic<bool> running_{false};
void processLoop();
void processCollisions(const std::vector<CollisionRisk>& collisions);
// WebSocket 相关方法
void broadcastPositionUpdate(const MovingObject& obj);
void broadcastCollisionWarning(const CollisionRisk& risk);
bool loadAirportBounds();
bool loadControllableVehicles();
};
#endif // AIRPORT_CORE_SYSTEM_H
std::atomic<bool> running_{false};
std::thread processThread_;
std::unique_ptr<AirportBounds> airportBounds_;
std::unique_ptr<ControllableVehicles> controllableVehicles_;
std::unique_ptr<CollisionDetector> collisionDetector_;
std::unique_ptr<DataCollector> dataCollector_;
// WebSocket 服务器
std::unique_ptr<network::WebSocketServer> ws_server_;
std::thread ws_thread_;
// 系统配置
SystemConfig system_config_;
static System* instance_;
};

View File

@ -116,7 +116,6 @@ std::vector<CollisionRisk> CollisionDetector::detectCollisions() {
}
}
Logger::info("Collision detection completed, found ", risks.size(), " risks");
return risks;
}

View File

@ -1,6 +1,5 @@
#include "core/System.h"
#include "utils/Logger.h"
#include "network/ConnectionConfig.h"
#include <iostream>
#include <thread>
#include <csignal>
@ -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;
}

View File

@ -1,12 +0,0 @@
#ifndef AIRPORT_NETWORK_CONNECTION_CONFIG_H
#define AIRPORT_NETWORK_CONNECTION_CONFIG_H
#include <string>
struct ConnectionConfig {
std::string host; // 服务器主机名
int port; // 服务器端口
int timeout; // 连接超时时间(毫秒)
};
#endif // AIRPORT_NETWORK_CONNECTION_CONFIG_H

View File

@ -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<asio::ip::tcp::socket>(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<std::chrono::milliseconds>(
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>& aircraft) {
// 首先检查连接状态
if (!isAvailable()) {
Logger::error("Cannot fetch aircraft data: not connected");
std::lock_guard<std::mutex> 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<Vehicle>& vehicles) {
// 首先检查连接状态
if (!isAvailable()) {
Logger::error("Cannot fetch vehicle data: not connected");
std::lock_guard<std::mutex> 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) {

View File

@ -5,14 +5,20 @@
#include "spatial/CoordinateConverter.h"
#include <boost/asio.hpp>
#include <string>
#include "collector/DataSourceConfig.h"
#include <mutex>
#include <atomic>
#include <chrono>
#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<asio::ip::tcp::socket> socket_;
CoordinateConverter coordinateConverter_;
std::mutex mutex_;
std::chrono::steady_clock::time_point last_connect_attempt_;
static constexpr int MAX_RETRIES = 3; // 单次连接最大重试次数
std::atomic<bool> 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<Vehicle>& vehicles);
};
#endif // AIRPORT_NETWORK_HTTP_DATA_SOURCE_H
#endif // AIRPORT_NETWORK_HTTP_DATA_SOURCE_H

View File

@ -0,0 +1,75 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
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

View File

@ -0,0 +1,118 @@
#include "network/WebSocketServer.h"
#include <nlohmann/json.hpp>
namespace network {
WebSocketServer::WebSocketServer(uint16_t port)
: acceptor_(ioc_, {boost::asio::ip::tcp::v4(), port}) {
}
WebSocketServer::~WebSocketServer() {
// 关闭所有连接
std::lock_guard<std::mutex> 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<std::mutex> 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<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>(std::move(socket));
// 异步完成 WebSocket 握手
ws->async_accept(
[this, ws](boost::system::error_code ec) {
if (!ec) {
// 握手成功,保存会话并开始读取消息
{
std::lock_guard<std::mutex> lock(sessions_mutex_);
sessions_.push_back(ws); // 存储 weak_ptr
}
doRead(ws);
}
});
}
// 继续接受新的连接
handleAccept();
});
}
void WebSocketServer::doRead(std::shared_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>> ws) {
// 为每个会话创建一个专用的缓冲区
auto buffer = std::make_shared<boost::beast::flat_buffer>();
// 异步读取消息
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<std::mutex> lock(sessions_mutex_);
auto it = std::find_if(sessions_.begin(), sessions_.end(),
[ws](const std::weak_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>& 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<std::mutex> 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

View File

@ -0,0 +1,32 @@
#pragma once
#include <boost/beast/websocket.hpp>
#include <boost/asio.hpp>
#include <memory>
#include <string>
#include <vector>
#include <mutex>
#include <atomic>
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<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>> ws);
boost::asio::io_context ioc_;
boost::asio::ip::tcp::acceptor acceptor_;
std::vector<std::weak_ptr<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>>> sessions_;
std::mutex sessions_mutex_;
std::atomic<bool> running_{true};
};
} // namespace network

View File

@ -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米才认为发生了变化
}

View File

@ -1,28 +1,23 @@
#ifndef AIRPORT_TYPES_BASIC_TYPES_H
#define AIRPORT_TYPES_BASIC_TYPES_H
#pragma once
#include <string>
#include <cstdint>
#include <deque>
#include <utility>
#include <cstdint>
// 基础数据类型
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<PositionRecord> positionHistory; // 位置历史记录
std::deque<PositionRecord> 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
static constexpr double MAX_SPEED = 20.0; // 最大速度(米/秒)
static constexpr double MAX_POSITION_JUMP = 10.0; // 最大位置跳变(米)
};

View File

@ -62,7 +62,7 @@ TEST_F(CollisionDetectorTest, DetectControllableVehicleAircraftCollision) {
.WillRepeatedly(testing::Return(true));
Logger::info("Set mock expectation: VEH001 is controllable");
// 设置测试数
// 设置测试数<EFBFBD><EFBFBD><EFBFBD>
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米<EFBFBD><EFBFBD><EFBFBD>该是危险级别
}
}

View File

@ -1,7 +1,6 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#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<DataCollector>();
mockSource = std::make_shared<::testing::NiceMock<MockDataSource>>();
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<DataCollector> collector;
std::shared_ptr<MockDataSource> 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<MockDataSource>>();
// 设置期望行为
EXPECT_CALL(*mockSource, connect())
.WillOnce(::testing::Return(true));
// 测试刷新方法
TEST_F(DataCollectorTest, RefreshTest) {
std::vector<Aircraft> 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) {
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<Vehicle>& 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<Aircraft> testAircraft = {
createTestAircraft("TEST1", 36.36, 120.08)
};
std::vector<Vehicle> 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<MockDataSource>>();
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<std::vector<Aircraft>> 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<Aircraft> 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>& 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());
}

View File

@ -1,106 +1,88 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "network/HTTPDataSource.h"
#include "utils/Logger.h"
class HTTPDataSourceTest : public ::testing::Test {
protected:
std::unique_ptr<HTTPDataSource> source;
void SetUp() override {
source = std::make_unique<HTTPDataSource>("localhost", 8080);
DataSourceConfig config;
config.host = "localhost";
config.port = 8080;
config.aircraft_path = "/api/getCurrentFlightPositions";
config.vehicle_path = "/api/getCurrentVehiclePositions";
source = std::make_unique<HTTPDataSource>(config);
}
void TearDown() override {
source.reset();
}
std::unique_ptr<HTTPDataSource> 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> 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<Vehicle> 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<HTTPDataSource>("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<HTTPDataSource>(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<HTTPDataSource>(config);
EXPECT_FALSE(invalidSource->connect());
EXPECT_FALSE(invalidSource->isAvailable());
}
TEST_F(HTTPDataSourceTest, FetchDataWithoutConnectionTest) {
// 测试在未连接状态下获取数据
std::vector<Aircraft> aircraft;
source->disconnect(); // 断开连接后尝试获取数据
std::vector<Vehicle> vehicles;
EXPECT_FALSE(source->fetchAircraftData(aircraft));
EXPECT_FALSE(source->fetchVehicleData(vehicles));
}
// 测试数据解析
TEST_F(HTTPDataSourceTest, DataParsing) {
ASSERT_TRUE(source->connect());
// 获取并检查航空器数据
{
std::vector<Aircraft> 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<Vehicle> 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> aircraft;
// 假设服务器返回无效的 JSON 数据
// EXPECT_FALSE(source->fetchAircraftData(aircraft));
}

View File

@ -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)
app.run(host='localhost', port=8080, debug=True)

103
tools/test_websocket.html Normal file
View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html>
<head>
<title>WebSocket 测试</title>
<style>
#messages {
width: 100%;
height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
margin-bottom: 10px;
padding: 10px;
font-family: monospace;
}
.error { color: red; }
.success { color: green; }
.info { color: blue; }
.position { color: #666; }
.warning { color: #f90; }
</style>
</head>
<body>
<h2>WebSocket 测试客户端</h2>
<div id="messages"></div>
<div>
<button onclick="connect()">连接</button>
<button onclick="disconnect()">断开</button>
<button onclick="clearMessages()">清空日志</button>
</div>
<script>
let ws = null;
const messagesDiv = document.getElementById('messages');
function log(message, type = 'info') {
const div = document.createElement('div');
div.className = type;
div.textContent = `${new Date().toLocaleTimeString()} - ${message}`;
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function clearMessages() {
messagesDiv.innerHTML = '';
}
function connect() {
if (ws) {
log('已经连接,请先断开', 'error');
return;
}
try {
ws = new WebSocket('ws://localhost:8010');
ws.onopen = () => {
log('连接成功', 'success');
};
ws.onclose = () => {
log('连接关闭', 'info');
ws = null;
};
ws.onerror = (error) => {
log('发生错误: ' + error, 'error');
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
const formattedData = JSON.stringify(data, null, 2);
// 根据消息类型使用不同的样式
let type = 'info';
if (data.type === 'position_update') {
type = 'position';
} else if (data.type === 'collision_warning') {
type = 'warning';
}
log('收到消息:\n' + formattedData, type);
} catch (e) {
log('收到消息: ' + event.data, 'info');
}
};
} catch (error) {
log('连接失败: ' + error, 'error');
}
}
function disconnect() {
if (!ws) {
log('未连接', 'error');
return;
}
ws.close();
ws = null;
}
</script>
</body>
</html>