修改航空器路由完整机制

This commit is contained in:
Tian jianyong 2025-09-19 19:10:18 +08:00
parent 589a212877
commit 7556a5e3c3
14 changed files with 4323 additions and 604 deletions

View File

@ -0,0 +1,196 @@
# 红绿灯设备固定标识识别方案
**文档日期**: 2025年1月19日
**创建人**: AI Assistant
**项目**: QAUP-Management 机场冲突避让管理系统
## 问题背景
当前系统计划接入多台红绿灯设备(物联网设备),面临以下挑战:
1. **设备识别困难**: 接收到的信息格式固定,无法通过消息来区分是哪个红绿灯
2. **网络地址不稳定**: 物联网设备通过蜂窝网络接入IP地址每次重启都可能变化
3. **现有识别方式局限**: 系统目前仅使用IP地址作为设备识别不适用于动态IP场景
## 技术调研结果
### 国标协议支持
- **GB/T 20999-2017**: 交通信号控制机与上位机间的数据通信协议
- **GB/T 43229-2023**: 交通信号控制机与车辆检测器间通信协议
- **GA/T 1049.2-2024**: 公安交通集成指挥平台通信协议
### 设备标识技术可行性
根据网络调研,物联网红绿灯设备**完全支持**在消息中发送设备ID
**设备ID (Device ID)**: IoT平台普遍要求设备包含厂商ID、终端型号、终端ID信息
**序列号 (Serial Number)**: 设备硬件序列号,全球唯一
**MAC地址**: 48位网络地址前3字节为OUI组织唯一标识符
**JSON格式支持**: 现代IoT设备普遍使用JSON格式天然支持标识字段
## 解决方案
### 方案1要求设备厂商在消息中添加设备标识推荐
**技术依据**:
- GB/T 20999-2017国标协议支持设备标识
- IoT设备普遍使用JSON格式天然支持device_id字段
- 行业最佳实践要求设备身份识别
**消息格式扩展**:
```json
{
"device_id": "TL001_ABCD1234", // 设备唯一标识
"serial_number": "202401001234", // 设备序列号
"mac_address": "00:1A:2B:3C:4D:5E", // 网络MAC地址
"timestamp": 1642492800000,
"DI-01": "1", // 原有DI信号数据
"DI-02": "0"
}
```
**实施步骤**:
1. 与设备厂商沟通要求在现有DI信号消息中添加设备标识字段
2. 修改TrafficLightSignalHandler解析逻辑支持设备ID提取
3. 扩展设备管理数据库,存储设备标识映射关系
4. 实现基于设备ID的智能识别和管理
### 方案2TCP连接层面获取网络标识备用方案
**适用场景**: 设备厂商暂时无法修改消息格式时的临时方案
**技术实现**:
- 扩展ClientConnection类尝试获取网络接口信息
- 实现连接指纹识别机制
- 建立IP地址与设备特征的映射关系
**局限性**:
- 可靠性低于应用层标识
- 无法获取真正的硬件唯一标识
- 依赖网络层特征,可能不稳定
### 方案3混合识别策略最佳实践
**多重标识优先级**:
1. **主标识**: 消息中的device_id/serial_number最可靠
2. **辅助标识**: TCP连接特征指纹
3. **备用标识**: IP地址现有方式
**智能匹配逻辑**:
- **设备迁移检测**: 同一device_id出现新IP时自动更新映射
- **冲突处理**: 同一IP出现不同device_id时记录告警
- **历史记录**: 维护设备标识变更历史,便于故障排查
## 代码实现要点
### 1. 消息解析器扩展
```java
// TrafficLightSignalParser增强
public class TrafficLightSignalParser {
public ParsedSignal parseSignalWithDeviceId(String jsonMessage) {
// 解析设备标识字段
String deviceId = extractDeviceId(jsonMessage);
String serialNumber = extractSerialNumber(jsonMessage);
String macAddress = extractMacAddress(jsonMessage);
// 创建包含设备标识的解析结果
return ParsedSignal.builder()
.deviceId(deviceId)
.serialNumber(serialNumber)
.macAddress(macAddress)
.signalData(extractSignalData(jsonMessage))
.build();
}
}
```
### 2. 设备管理服务增强
```java
// TrafficLightService扩展
public class TrafficLightService {
public TrafficLightDevice findDeviceByIdentifier(String deviceId, String serialNumber, String ipAddress) {
// 优先级查找设备ID > 序列号 > IP地址
TrafficLightDevice device = findByDeviceId(deviceId);
if (device == null) {
device = findBySerialNumber(serialNumber);
}
if (device == null) {
device = findByIpAddress(ipAddress);
}
return device;
}
public void updateDeviceMapping(String deviceId, String ipAddress) {
// 更新设备ID与IP地址的映射关系
// 检测设备迁移,记录变更历史
}
}
```
### 3. 数据库模型扩展
```sql
-- 扩展红绿灯设备表
ALTER TABLE traffic_light_devices ADD COLUMN device_id VARCHAR(100);
ALTER TABLE traffic_light_devices ADD COLUMN serial_number VARCHAR(100);
ALTER TABLE traffic_light_devices ADD COLUMN mac_address VARCHAR(17);
ALTER TABLE traffic_light_devices ADD COLUMN last_ip_address VARCHAR(15);
ALTER TABLE traffic_light_devices ADD COLUMN ip_change_history TEXT;
-- 创建设备标识索引
CREATE INDEX idx_device_id ON traffic_light_devices(device_id);
CREATE INDEX idx_serial_number ON traffic_light_devices(serial_number);
CREATE INDEX idx_mac_address ON traffic_light_devices(mac_address);
```
## 实施计划
### 第一阶段协调与准备1-2周
- [ ] 联系设备厂商,讨论消息格式扩展需求
- [ ] 确认设备标识字段的具体格式和命名规范
- [ ] 制定设备标识管理策略和数据库设计
### 第二阶段代码实现2-3周
- [ ] 扩展消息解析器,支持设备标识提取
- [ ] 修改设备管理服务,实现多重标识查找
- [ ] 扩展数据库模型,存储设备标识信息
- [ ] 实现设备迁移检测和冲突处理逻辑
### 第三阶段测试与部署1-2周
- [ ] 单元测试和集成测试
- [ ] 设备标识功能验证
- [ ] 设备迁移场景测试
- [ ] 生产环境部署和监控
### 第四阶段:运维优化(持续)
- [ ] 监控设备标识识别效果
- [ ] 优化匹配算法和冲突处理
- [ ] 建立设备生命周期管理流程
## 预期效果
### 直接效益
- ✅ **彻底解决IP地址变化问题**: 设备重启后仍能准确识别
- ✅ **提供可靠的设备唯一标识**: 基于硬件序列号的全球唯一性
- ✅ **符合国标协议要求**: 遵循GB/T 20999-2017等标准规范
- ✅ **支持设备精准管理**: 实现设备的全生命周期追踪
### 间接效益
- 提升系统可靠性和稳定性
- 降低设备管理和运维成本
- 为后续设备扩展提供标准化基础
- 增强故障排查和问题定位能力
## 风险评估与应对
### 主要风险
1. **设备厂商配合度**: 可能存在厂商不愿意修改消息格式的情况
2. **消息格式兼容性**: 新旧设备消息格式可能存在差异
3. **性能影响**: 额外的设备标识处理可能影响系统性能
### 应对措施
1. **多方案准备**: 同时准备方案1和方案2确保有备选方案
2. **向后兼容**: 设计时考虑向后兼容,支持新旧消息格式共存
3. **性能优化**: 实现高效的设备标识缓存和查找机制
## 总结
基于技术调研结果,**强烈建议采用方案1**要求设备厂商添加设备标识这是最符合行业标准和最佳实践的解决方案。通过在DI信号消息中添加device_id、serial_number等标识字段可以彻底解决物联网设备动态IP地址带来的识别问题为系统的稳定运行和后续扩展奠定坚实基础。

View File

@ -225,7 +225,7 @@ data:
password: dianxin@123
# 无人车厂商数据源配置
vehicle-api:
base-url: http://localhost:8090
base-url: http://localhost:8091
endpoints:
vehicle-location: /api/VehicleLocationInfo
vehicle-state: /api/VehicleStateInfo

View File

@ -9,6 +9,7 @@ import com.qaup.collision.common.model.dto.Response;
import com.qaup.collision.datacollector.service.AuthService;
import com.qaup.collision.datacollector.dto.AircraftRouteDTO;
import com.qaup.collision.datacollector.dto.AircraftStatusDTO;
import com.qaup.collision.datacollector.dto.AircraftRouteParamsDTO;
import com.qaup.collision.datacollector.dto.FlightNotificationDTO;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@ -44,6 +45,10 @@ import java.util.stream.Collectors;
@Component
public class DataCollectorDao {
// 机场数据源相关配置
@Value("${data.collector.airport-api.base-url}")
private String airportBaseUrl;
// 无人车厂商数据源相关配置
@Value("${data.collector.vehicle-api.base-url}")
private String vehicleBaseUrl;
@ -272,7 +277,7 @@ public class DataCollectorDao {
/**
* 获取航空器进港路由
*
*
* @param inRunway 进港跑道编号
* @param outRunway 出港跑道编号
* @param contactCross 联络道口
@ -287,7 +292,7 @@ public class DataCollectorDao {
return null;
}
String url = UriComponentsBuilder.fromUriString(vehicleBaseUrl)
String url = UriComponentsBuilder.fromUriString(airportBaseUrl)
.path("/runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat")
.queryParam("inRunway", inRunway)
.queryParam("outRunway", outRunway)
@ -326,7 +331,7 @@ public class DataCollectorDao {
/**
* 获取航空器出港路由
*
*
* @param inRunway 进港跑道编号
* @param outRunway 出港跑道编号
* @param startSeat 起始机位
@ -340,7 +345,7 @@ public class DataCollectorDao {
return null;
}
String url = UriComponentsBuilder.fromUriString(vehicleBaseUrl)
String url = UriComponentsBuilder.fromUriString(airportBaseUrl)
.path("/runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat")
.queryParam("inRunway", inRunway)
.queryParam("outRunway", outRunway)
@ -389,7 +394,7 @@ public class DataCollectorDao {
return null;
}
String url = vehicleBaseUrl + "/aircraftStatusController/getAircraftStatus";
String url = airportBaseUrl + "/aircraftStatusController/getAircraftStatus";
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);
@ -418,6 +423,57 @@ public class DataCollectorDao {
}
}
/**
* 获取航班路由参数
*
* 数据来源机场系统API (/aircraftRouteParamsController/getRouteParams)
* 说明根据航班号和路由类型获取完整的路由查询参数
*
* @param flightNo 航班号
* @param routeType 路由类型IN或OUT
* @return 航班路由参数数据
*/
public AircraftRouteParamsDTO getAircraftRouteParams(String flightNo, String routeType) {
try {
String token = authService.getToken();
if (token == null) {
log.error("无法获取有效的认证token");
return null;
}
String url = UriComponentsBuilder.fromUriString(airportBaseUrl)
.path("/aircraftRouteParamsController/getRouteParams")
.queryParam("flightNo", flightNo)
.queryParam("routeType", routeType)
.toUriString();
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);
headers.set("Content-Type", "application/json");
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
ResponseEntity<Response<AircraftRouteParamsDTO>> response = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
new ParameterizedTypeReference<Response<AircraftRouteParamsDTO>>() {}
);
Response<AircraftRouteParamsDTO> responseBody = response.getBody();
if (responseBody != null && responseBody.getStatus() == 200) {
log.info("成功获取航班路由参数: flightNo={}, routeType={}", flightNo, routeType);
return responseBody.getData();
} else {
log.warn("获取航班路由参数失败: {}", responseBody != null ? responseBody.getMsg() : "未知错误");
return null;
}
} catch (Exception e) {
log.error("获取航班路由参数时发生异常: flightNo={}, routeType={}", flightNo, routeType, e);
return null;
}
}
/**
* 获取航班进出港通知
*

View File

@ -0,0 +1,92 @@
package com.qaup.collision.datacollector.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
/**
* 航班路由参数DTO
*
* 数据来源机场系统API (/aircraftRouteParamsController/getRouteParams)
* 说明根据航班号和路由类型获取的完整路由查询参数
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class AircraftRouteParamsDTO {
/**
* 航班号
*/
@JsonProperty("flightNo")
private String flightNo;
/**
* 路由类型IN进港OUT出港
*/
@JsonProperty("routeType")
private String routeType;
/**
* 进港跑道编号
*/
@JsonProperty("inRunway")
private String inRunway;
/**
* 出港跑道编号
*/
@JsonProperty("outRunway")
private String outRunway;
/**
* 联络道口进港时使用
*/
@JsonProperty("contactCross")
private String contactCross;
/**
* 目的机位进港时使用
*/
@JsonProperty("seat")
private String seat;
/**
* 起始机位出港时使用
*/
@JsonProperty("startSeat")
private String startSeat;
/**
* 时间戳
*/
@JsonProperty("timestamp")
private Long timestamp;
/**
* 检查是否为有效的路由参数
*/
public boolean isValid() {
return flightNo != null && !flightNo.trim().isEmpty() &&
routeType != null && !routeType.trim().isEmpty() &&
inRunway != null && !inRunway.trim().isEmpty() &&
outRunway != null && !outRunway.trim().isEmpty();
}
/**
* 检查是否为进港路由参数
*/
public boolean isArrivalRoute() {
return "IN".equalsIgnoreCase(routeType);
}
/**
* 检查是否为出港路由参数
*/
public boolean isDepartureRoute() {
return "OUT".equalsIgnoreCase(routeType);
}
}

View File

@ -357,6 +357,7 @@ public class DataCollectorService {
.aircraftStatus(status.getType())
.routeGeometry(route.getGeometry() != null ? route.getGeometry().toText() : null)
.timestamp(System.currentTimeMillis())
.eventSource("SCHEDULED") // 标识为定时采集触发的事件
.build();
// 发布WebSocket事件
@ -833,6 +834,10 @@ public class DataCollectorService {
// 发布WebSocket事件推送到前端
publishFlightNotificationEvent(notification);
// 🚀 新增事件驱动的路由查询
// 基于航班通知触发路由查询和更新
triggerRouteQueryByFlightNotification(dto);
} else {
log.warn("⚠️ 航班进出港通知数据无效,跳过处理: {}", dto);
}
@ -915,6 +920,155 @@ public class DataCollectorService {
}
}
/**
* 基于航班通知触发路由查询
*
* 实现事件驱动的路由更新流程
* 1. 收到航班进出港通知
* 2. 使用航班号和类型查询路由参数
* 3. 调用路由查询接口获取完整路由数据
* 4. 发布WebSocket路由更新事件
*
* @param flightNotification 航班进出港通知
*/
private void triggerRouteQueryByFlightNotification(FlightNotificationDTO flightNotification) {
try {
String flightNo = flightNotification.getFlightNo();
String routeType = flightNotification.getType();
log.info("🛫 航班通知触发路由查询: 航班号={}, 类型={}", flightNo, routeType);
// 步骤1: 查询航班路由参数
com.qaup.collision.datacollector.dto.AircraftRouteParamsDTO routeParams =
dataCollectorDao.getAircraftRouteParams(flightNo, routeType);
if (routeParams == null || !routeParams.isValid()) {
log.warn("⚠️ 未能获取有效的航班路由参数: flightNo={}, routeType={}", flightNo, routeType);
return;
}
log.info("✅ 成功获取航班路由参数: flightNo={}, inRunway={}, outRunway={}, contactCross={}, seat/startSeat={}",
routeParams.getFlightNo(),
routeParams.getInRunway(),
routeParams.getOutRunway(),
routeParams.getContactCross(),
routeParams.isArrivalRoute() ? routeParams.getSeat() : routeParams.getStartSeat());
// 步骤2: 基于路由参数调用相应的路由查询接口
AircraftRouteDTO routeData = null;
if (routeParams.isArrivalRoute()) {
// 进港路由查询
routeData = dataCollectorDao.getArrivalRoute(
routeParams.getInRunway(),
routeParams.getOutRunway(),
routeParams.getContactCross(),
routeParams.getSeat()
);
log.info("🛬 查询进港路由: inRunway={}, outRunway={}, contactCross={}, seat={}",
routeParams.getInRunway(), routeParams.getOutRunway(),
routeParams.getContactCross(), routeParams.getSeat());
} else if (routeParams.isDepartureRoute()) {
// 出港路由查询
routeData = dataCollectorDao.getDepartureRoute(
routeParams.getInRunway(),
routeParams.getOutRunway(),
routeParams.getStartSeat()
);
log.info("🛫 查询出港路由: inRunway={}, outRunway={}, startSeat={}",
routeParams.getInRunway(), routeParams.getOutRunway(), routeParams.getStartSeat());
}
// 步骤3: 处理路由查询结果
if (routeData != null) {
log.info("🎯 成功获取{}路由数据: 编码={}, 状态={}",
routeData.getType(), routeData.getCodes(), routeData.getStatus());
// 转换DTO为航空器路由对象
AircraftRoute aircraftRoute = convertToAircraftRoute(routeData);
if (aircraftRoute != null) {
// 保存路由到数据库
saveAircraftRouteToDatabase(flightNo, aircraftRoute);
// 更新缓存中的航空器路由信息
updateAircraftRouteInCacheFromNotification(flightNo, aircraftRoute, routeParams);
// 发布WebSocket路由更新事件
publishAircraftRouteUpdateEventFromNotification(flightNo, aircraftRoute, routeParams);
log.info("🚀 事件驱动的路由更新完成: 航班号={}, 路由类型={}", flightNo, routeType);
} else {
log.warn("⚠️ 路由数据转换失败: flightNo={}", flightNo);
}
} else {
log.warn("⚠️ 未获取到路由数据: flightNo={}, routeType={}", flightNo, routeType);
}
} catch (Exception e) {
log.error("❌ 航班通知触发路由查询异常: flightNo={}",
flightNotification.getFlightNo(), e);
}
}
/**
* 基于航班通知更新缓存中的航空器路由信息
*/
private void updateAircraftRouteInCacheFromNotification(String flightNo, AircraftRoute route,
com.qaup.collision.datacollector.dto.AircraftRouteParamsDTO routeParams) {
MovingObject cachedAircraft = activeMovingObjectsCache.get(flightNo);
if (cachedAircraft != null && cachedAircraft instanceof Aircraft) {
Aircraft aircraft = (Aircraft) cachedAircraft;
// 根据路由类型更新路由
if (routeParams.isArrivalRoute()) {
aircraft.setArrivalRoute(route);
aircraft.activateArrivalRoute();
} else if (routeParams.isDepartureRoute()) {
aircraft.setDepartureRoute(route);
aircraft.activateDepartureRoute();
}
// 更新缓存
activeMovingObjectsCache.put(flightNo, aircraft);
log.debug("✅ 成功更新航空器路由缓存 (事件驱动): flightNo={}, type={}, codes={}",
flightNo, route.getType(), route.getCodes());
} else {
log.debug("🔍 缓存中暂无航空器对象,跳过缓存更新 (事件驱动): flightNo={}", flightNo);
}
}
/**
* 基于航班通知发布航空器路由更新事件
*/
private void publishAircraftRouteUpdateEventFromNotification(String flightNo, AircraftRoute route,
com.qaup.collision.datacollector.dto.AircraftRouteParamsDTO routeParams) {
try {
// 创建路由更新事件
AircraftRouteUpdateEvent routeUpdateEvent = AircraftRouteUpdateEvent.builder()
.flightNo(flightNo)
.routeType(route.getType())
.routeStatus(route.getStatus())
.routeCodes(route.getCodes())
.aircraftStatus(routeParams.getRouteType()) // 使用路由参数中的类型
.routeGeometry(route.getGeometry() != null ? route.getGeometry().toText() : null)
.timestamp(System.currentTimeMillis())
.eventSource("FLIGHT_NOTIFICATION") // 标识事件来源
.build();
// 发布WebSocket事件
eventPublisher.publishEvent(routeUpdateEvent);
log.info("📡 发布航空器路由更新事件 (事件驱动): 航班号={}, 路由类型={}, 事件来源=航班通知",
flightNo, route.getType());
} catch (Exception e) {
log.error("❌ 发布航空器路由更新事件失败 (事件驱动): flightNo={}", flightNo, e);
}
}
@PreDestroy
public void shutdown() {
log.info("DataCollectorService 正在关闭...");

View File

@ -54,4 +54,9 @@ public class AircraftRouteUpdateEvent {
* 事件时间戳
*/
private Long timestamp;
/**
* 事件来源 (SCHEDULED: 定时采集, FLIGHT_NOTIFICATION: 航班通知触发)
*/
private String eventSource;
}

View File

@ -5,7 +5,6 @@ import com.qaup.collision.websocket.event.FlightNotificationEvent;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.locationtech.jts.geom.GeometryFactory;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@ -174,9 +173,6 @@ class FlightNotificationIntegrationTest {
void testEmptyDataHandling() {
System.out.println("=== 测试空数据处理 ===");
// 记录事件发布前的调用次数
int initialEventCount = mockingDetails(eventPublisher).getInvocations().size();
// 执行数据采集可能返回空数据
dataCollectorService.collectFlightNotificationData();

File diff suppressed because it is too large Load Diff

View File

@ -96,13 +96,6 @@ SPECIAL_VEHICLE_END = {"longitude": 120.083899, "latitude": 36.367403} # 特
NORMAL_VEHICLE_START = {"longitude": 120.087259, "latitude": 36.368299} # 普通车起点
NORMAL_VEHICLE_END = {"longitude": 120.083899, "latitude": 36.367403} # 普通车终点
# 无人车A 鲁B567 路径
UNMANNED_A_START = {"longitude": 120.083084, "latitude": 36.369696} # 无人车A起点
UNMANNED_A_END = {"longitude": 120.084637, "latitude": 36.365617} # 无人车A终点
# 无人车B 鲁B579 路径
UNMANNED_B_START = {"longitude": 120.086965, "latitude": 36.368599} # 无人车B起点
UNMANNED_B_END = {"longitude": 120.086263, "latitude": 36.370484} # 无人车B终点
# 飞机和车辆尺寸(半径 米)
AIRCRAFT_SIZE_M = 30.0
@ -137,12 +130,12 @@ flight_data = {
}
}
# CA3456 航空器路由参数配置
# 航空器路由参数配置
aircraft_route_params = {
"CA3456": {
"arrival": { # 进港参数
"inRunway": "35",
"outRunway": "34",
"outRunway": "34",
"contactCross": "F1",
"seat": "138"
},
@ -156,7 +149,7 @@ aircraft_route_params = {
"arrival": {
"inRunway": "17",
"outRunway": "35",
"contactCross": "A2",
"contactCross": "A2",
"seat": "201"
},
"departure": {
@ -174,7 +167,34 @@ aircraft_route_params = {
},
"departure": {
"inRunway": "35",
"outRunway": "17",
"outRunway": "17",
"startSeat": "156"
}
},
# 新增:航班进出港通知中出现的航班配置
"CA8901": { # 出港航班
"arrival": {
"inRunway": "35",
"outRunway": "17",
"contactCross": "L4",
"seat": "201"
},
"departure": { # 出港参数
"inRunway": "35",
"outRunway": "17",
"startSeat": "201"
}
},
"MU2678": { # 进港航班
"arrival": { # 进港参数
"inRunway": "17",
"outRunway": "35",
"contactCross": "B3",
"seat": "156"
},
"departure": {
"inRunway": "17",
"outRunway": "35",
"startSeat": "156"
}
}
@ -1282,34 +1302,11 @@ vehicle_data = [
"start_point": NORMAL_VEHICLE_START, # 起点
"end_point": NORMAL_VEHICLE_END, # 终点
"moving_to_end": True # 当前是否向终点移动
},
{
"vehicleNo": "鲁B567", # 无人车A根据route.md更新
"longitude": UNMANNED_A_START["longitude"],
"latitude": UNMANNED_A_START["latitude"],
"time": int(time.time() * 1000),
"direction": 90.0, # 根据route.md设置为90度
"speed": 25.0, # 根据route.md设置为25km/h
"start_point": UNMANNED_A_START, # 起点
"end_point": UNMANNED_A_END, # 终点
"moving_to_end": True # 当前是否向终点移动
},
{
"vehicleNo": "鲁B579", # 无人车B根据route.md更新
"longitude": UNMANNED_B_START["longitude"],
"latitude": UNMANNED_B_START["latitude"],
"time": int(time.time() * 1000),
"direction": 270.0, # 根据route.md设置为270度
"speed": 25.0, # 根据route.md设置为25km/h
"start_point": UNMANNED_B_START, # 起点
"end_point": UNMANNED_B_END, # 终点
"moving_to_end": True # 当前是否向终点移动
}
]
# 车辆分类
airport_vehicle_data = [v for v in vehicle_data if v["vehicleNo"] in ["鲁B123", "鲁B234"]] # 特勤车和普通车
unmanned_vehicle_data = [v for v in vehicle_data if v["vehicleNo"] in ["鲁B567", "鲁B579"]] # 无人车
# 添加车辆状态类
@final
@ -1441,7 +1438,7 @@ class VehicleState:
# 添加车辆状态管理
vehicle_states = {}
for vehicle in vehicle_data:
for vehicle in airport_vehicle_data:
vehicle_states[str(vehicle["vehicleNo"])] = VehicleState(str(vehicle["vehicleNo"]))
from collections.abc import Mapping
@ -1464,187 +1461,7 @@ def calculate_distance_to_target(vehicle: Mapping[str, float], target_lat: float
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
return EARTH_RADIUS * c
class RequestVehicleCommandPayload(TypedDict, total=False):
vehicleID: str
commandType: str
commandReason: str
signalState: str
latitude: float
longitude: float
transId: str
@app.route('/api/VehicleCommandInfo', methods=['POST'])
def handle_vehicle_command():
# 提前定义并注解,避免未绑定与类型未知
data: RequestVehicleCommandPayload = {}
try:
data = request.get_json(silent=True) or {}
vehicle_id = data.get("vehicleID")
command_type = data.get("commandType", "").upper()
reason = data.get("commandReason", "").upper()
signal_state = data.get("signalState", "").upper()
target_lat = data.get("latitude", None)
target_lon = data.get("longitude", None)
print(f"收到车辆控制指令: vehicle_id={vehicle_id}, type={command_type}, reason={reason}, signal_state={signal_state}")
print(f"完整请求数据: {data}")
# 检查 SIGNAL 类型命令必须包含 signalState
if command_type == "SIGNAL" and not signal_state:
print(f"SIGNAL 类型命令缺少 signalState")
return jsonify({
"code": 400,
"msg": "SIGNAL command must include signalState",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
}), 400
# 检查车辆是否存在
vehicle_state = vehicle_states.get(vehicle_id)
if not vehicle_state:
print(f"未找到车辆: {vehicle_id}")
return jsonify({
"code": 404,
"msg": f"Vehicle {vehicle_id} not found",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
}), 404
# 打印当前车辆状态
print(f"当前车辆状态: vehicle={vehicle_id}")
vehicle_state.log_state()
# 特勤车鲁B123只响应红绿灯信号
if vehicle_id == "鲁B123":
if command_type != "SIGNAL":
print(f"特勤车辆忽略非红绿灯指令: {vehicle_id}")
return jsonify({
"code": 200,
"msg": "Special vehicle only responds to traffic light signals",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
})
# 更新红绿灯状态和指令状态
vehicle_state.update_command(signal_state, target_lat, target_lon)
print(f"特勤车 {vehicle_id} 更新状态: command={command_type}, traffic_light={vehicle_state.traffic_light_state}")
return jsonify({
"code": 200,
"msg": "Traffic light state updated",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
})
# 检查指令优先级并添加详细日志
check_command = signal_state if command_type == "SIGNAL" else command_type
can_override = vehicle_state.can_be_overridden_by(check_command)
print(f"指令优先级检查: vehicle={vehicle_id}, current_command={str(vehicle_state.current_command)}, new_command={check_command}, can_override={can_override}")
if not can_override:
print(f"指令优先级过低: vehicle={vehicle_id}, current_priority={vehicle_state.command_priority}, command={check_command}")
return jsonify({
"code": 400,
"msg": "Command priority too low",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
})
# 处理不同类型的指令
if command_type == "SIGNAL":
# 更新红绿灯状态和指令状态
vehicle_state.update_command(signal_state, target_lat, target_lon)
print(f"车辆 {vehicle_id} 更新状态: command={command_type}, traffic_light={vehicle_state.traffic_light_state}")
return jsonify({
"code": 200,
"msg": "Traffic light state updated",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
})
elif command_type in ["ALERT", "WARNING"]:
# 查找当前车辆
current_vehicle = None
for v in vehicle_data:
if v["vehicleNo"] == vehicle_id:
current_vehicle = v
break
# 执行告警指令直接停车
print(f"执行{'紧急' if command_type == 'ALERT' else '正常'}制动: vehicle={vehicle_id}")
vehicle_state.current_command = command_type
vehicle_state.command_priority = COMMAND_PRIORITIES.get(command_type, 0)
vehicle_state.is_running = False
vehicle_state.target_speed = 0
vehicle_state.brake_mode = "emergency" if command_type == "ALERT" else "normal"
vehicle_state.target_lat = target_lat
vehicle_state.target_lon = target_lon
# 注释掉不再直接设置车辆速度为0让位置更新逻辑统一处理
# if current_vehicle:
# current_vehicle["speed"] = 0
return jsonify({
"code": 200,
"msg": "Command executed successfully",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
})
elif command_type == "RESUME":
print(f"执行恢复指令: vehicle_id={vehicle_id}")
# 检查当前车辆
current_vehicle = None
for v in vehicle_data:
if v["vehicleNo"] == vehicle_id:
current_vehicle = v
break
# 清除限制性指令
if vehicle_state.current_command in ["ALERT", "WARNING"]:
print(f"清除限制性指令: vehicle={vehicle_id}")
vehicle_state.current_command = None
vehicle_state.command_priority = 0
vehicle_state.is_running = True
vehicle_state.target_speed = DEFAULT_VEHICLE_SPEED
vehicle_state.brake_mode = None
vehicle_state.target_lat = None
vehicle_state.target_lon = None
# 更新车辆运行状态
vehicle_state.is_running = vehicle_state.can_move()
if vehicle_state.is_running and current_vehicle:
print(f"车辆 {vehicle_id} 恢复运行")
# 注释掉:不再直接设置车辆速度,让位置更新逻辑统一处理
# current_vehicle["speed"] = DEFAULT_VEHICLE_SPEED
# 记录状态变化但不更新指令
vehicle_state.command_reason = reason
vehicle_state.last_command_time = time.time()
print(f"Vehicle {vehicle_id} state updated: running={vehicle_state.is_running}, "
f"command={vehicle_state.current_command}, traffic_light={vehicle_state.traffic_light_state}, "
f"reason={reason}, priority={vehicle_state.command_priority}, "
f"target_speed={vehicle_state.target_speed}, brake_mode={vehicle_state.brake_mode}")
return jsonify({
"code": 200,
"msg": "Command executed successfully",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
})
return jsonify({
"code": 200,
"msg": "Command executed successfully",
"transId": data.get("transId"),
"timestamp": int(time.time() * 1000)
})
except Exception as e:
print(f"Error handling vehicle command: {str(e)}")
return jsonify({
"code": 500,
"msg": str(e),
"transId": data.get("transId", ""),
"timestamp": int(time.time() * 1000)
}), 500
# 删除了复杂的红绿灯和路口计算函数,因为已简化为往复运动逻辑
@ -1840,10 +1657,9 @@ def generate_traffic_light_data() -> list[dict[str, Any]]:
# 红绿灯数据
traffic_light_data = generate_traffic_light_data()
# 分别记录航空器、机场车辆和无人车的上次更新时间
# 分别记录航空器、机场车辆的上次更新时间
last_aircraft_update_time = time.time()
last_vehicle_update_time = time.time() # 机场车辆更新时间
last_unmanned_vehicle_update_time = time.time() # 无人车更新时间
last_light_switch_time = time.time()
def check_auth() -> bool:
@ -2105,270 +1921,11 @@ def login():
"data": None
}), 401
@app.route('/openApi/getVehicleStatus', methods=['GET', 'OPTIONS'])
def get_vehicle_status():
"""获取无人车状态信息"""
if request.method == 'OPTIONS':
return '', 204
vehicle_id = request.args.get('vehicleId')
if not vehicle_id:
return jsonify({
"status": 400,
"msg": "缺少 vehicleId 参数",
"data": None
}), 400
# 查找对应的车辆
vehicle_state = vehicle_states.get(vehicle_id)
if not vehicle_state:
return jsonify({
"status": 404,
"msg": f"未找到车辆 {vehicle_id}",
"data": None
}), 404
# 返回车辆状态
return jsonify({
"status": 200,
"msg": "获取车辆状态成功",
"data": {
"vehicleId": vehicle_id,
"status": "NORMAL" if vehicle_state.is_running else "STOPPED",
"command": vehicle_state.current_command,
"commandPriority": vehicle_state.command_priority,
"trafficLightState": vehicle_state.traffic_light_state,
"timestamp": int(time.time() * 1000)
}
})
# 无人车位置数据生成函数
def generate_unmanned_vehicle_location_data() -> list[dict[str, Any]]:
"""生成无人车位置数据符合官方API格式"""
unmanned_vehicles = []
# 从现有vehicle_data中筛选无人车QN开头的车辆
for vehicle in unmanned_vehicle_data:
lon_val = to_float_safe(vehicle.get("longitude"))
lat_val = to_float_safe(vehicle.get("latitude"))
location_info = {
"transId": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"vehicleID": vehicle["vehicleNo"],
"longitude": round(lon_val, 6) if lon_val is not None else vehicle.get("longitude"),
"latitude": round(lat_val, 6) if lat_val is not None else vehicle.get("latitude")
}
unmanned_vehicles.append(location_info)
return unmanned_vehicles
# 生成符合API规范的完整车辆状态数据
def generate_comprehensive_vehicle_status(vehicle_id: str) -> dict[str, Any]:
"""生成符合universal_autonomous_vehicle_api规范的完整车辆状态数据"""
current_time = time.time()
timestamp_ms = int(current_time * 1000)
# 获取车辆基础数据
vehicle_data_item = None
for v in unmanned_vehicle_data:
if v["vehicleNo"] == vehicle_id:
vehicle_data_item = v
break
if not vehicle_data_item:
return None
# 获取车辆状态
vehicle_state = vehicle_states.get(vehicle_id)
# 基础位置和运动数据
lat_val = to_float_safe(vehicle_data_item.get("latitude"))
lon_val = to_float_safe(vehicle_data_item.get("longitude"))
speed_val = to_float_safe(vehicle_data_item.get("speed", 0))
direction_val = to_float_safe(vehicle_data_item.get("direction", 0))
# 生成完整状态数据
status_data = {
"vehicleInfo": {
"vehicleId": vehicle_id
},
"operationalStatus": {
"powerStatus": "ON",
"systemHealth": "HEALTHY" if vehicle_state and vehicle_state.is_running else "DEGRADED",
"operationalMode": "AUTONOMOUS",
"emergencyStatus": "CRITICAL" if vehicle_state and vehicle_state.current_command == "ALERT"
else "WARNING" if vehicle_state and vehicle_state.current_command == "WARNING"
else "NORMAL",
"lastHeartbeat": timestamp_ms
},
"controlStatus": {
"controlMode": "AUTONOMOUS",
"controlAuthority": "SYSTEM",
"remoteControlActive": vehicle_state.remoteControlActive if vehicle_state else False
},
"motionStatus": {
"position": {
"latitude": round(lat_val, 6) if lat_val is not None else 0.0,
"longitude": round(lon_val, 6) if lon_val is not None else 0.0
},
"velocity": {
"speed": round(speed_val * 1000 / 3600, 2) if speed_val is not None else 0.0, # 转换为 m/s
"direction": round(math.radians(direction_val), 4) if direction_val is not None else 0.0 # 转换为弧度
}
},
"safetyStatus": {
"collisionAvoidanceActive": vehicle_state.is_running if vehicle_state else True,
"emergencyBrakingReady": True,
"pathPlanningStatus": "ACTIVE" if vehicle_state and vehicle_state.is_running else "INACTIVE",
"obstacleDetectionStatus": "ACTIVE" if vehicle_state and vehicle_state.is_running else "INACTIVE",
"minimumRiskManeuverTriggered": vehicle_state.current_command == "ALERT" if vehicle_state else False
},
"sensorStatus": {
"gps": {
"status": "ACTIVE",
"accuracy": 0.5, # 0.5米精度
"lastUpdate": timestamp_ms
}
},
"batteryStatus": {
"mainBattery": {
"chargeLevel": 85.5, # 85.5%
"voltage": 48.2, # 48.2V
"current": -15.3, # -15.3A (放电)
"temperature": 35.2, # 35.2°C
"chargingStatus": "DISCHARGING"
}
},
"communicationStatus": {
"v2xStatus": "CONNECTED",
"cellularSignalStrength": -65, # -65dBm
"wifiStatus": "CONNECTED",
"cloudConnectivity": "ONLINE"
}
}
return status_data
def filter_vehicle_status_fields(status_data: dict[str, Any], fields: str | None) -> dict[str, Any]:
"""根据fields参数过滤车辆状态字段"""
if not fields:
return status_data
# 解析fields参数
requested_fields = [f.strip() for f in fields.split(',')]
filtered_data = {}
# 可用的字段组
available_fields = {
"vehicleInfo", "operationalStatus", "controlStatus", "motionStatus",
"safetyStatus", "sensorStatus", "batteryStatus", "communicationStatus"
}
for field in requested_fields:
if field in available_fields and field in status_data:
filtered_data[field] = status_data[field]
return filtered_data
# 无人车状态数据生成函数
def generate_unmanned_vehicle_state_data(vehicle_id: str | None = None, is_single: bool = True) -> list[dict[str, Any]]:
"""生成无人车状态数据符合官方API格式"""
vehicle_states_data = []
if is_single and vehicle_id:
# 单个车辆状态查询
vehicle_state = vehicle_states.get(vehicle_id)
if vehicle_state:
state_info = {
"transId": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"vehicleID": vehicle_id,
"loginState": True,
"faultInfo": [],
"activeSafety": not vehicle_state.is_running,
"RC": False,
"Command": 1 if vehicle_state.current_command == "ALERT" else 0,
"airportInfo": [],
"vehicleMode": 2, # 自动模式
"gearState": 2, # D档
"chassisReady": vehicle_state.is_running,
"collisionStatus": False,
"clearance": 1 if vehicle_state.is_running else 0,
"turnSignalStstus": 0,
"pointCloud": []
}
vehicle_states_data.append(state_info)
else:
# 所有车辆状态查询
for vehicle in unmanned_vehicle_data:
vehicle_state = vehicle_states.get(vehicle["vehicleNo"])
state_info = {
"transId": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"vehicleID": vehicle["vehicleNo"],
"loginState": True,
"faultInfo": [],
"activeSafety": not vehicle_state.is_running if vehicle_state else False,
"RC": False,
"Command": 1 if vehicle_state and vehicle_state.current_command == "ALERT" else 0,
"airportInfo": [],
"vehicleMode": 2,
"gearState": 2,
"chassisReady": vehicle_state.is_running if vehicle_state else True,
"collisionStatus": False,
"clearance": 1 if vehicle_state and vehicle_state.is_running else 0,
"turnSignalStstus": 0,
"pointCloud": []
}
vehicle_states_data.append(state_info)
return vehicle_states_data
# 无人车API接口
@app.route('/api/VehicleLocationInfo', methods=['GET', 'OPTIONS'])
def get_unmanned_vehicle_location():
"""无人车位置上报接口"""
if request.method == 'OPTIONS':
return '', 204
try:
current_time = time.time()
global last_unmanned_vehicle_update_time
elapsed_time = current_time - last_unmanned_vehicle_update_time
if elapsed_time >= UPDATE_INTERVAL:
for vehicle in unmanned_vehicle_data:
update_vehicle_position(vehicle, UPDATE_INTERVAL)
vehicle["time"] = int(current_time * 1000)
last_unmanned_vehicle_update_time = current_time
# 生成无人车位置数据
location_data = generate_unmanned_vehicle_location_data()
return jsonify(location_data)
except Exception as e:
print(f"Error in get_unmanned_vehicle_location: {str(e)}")
return jsonify([]), 500
@app.route('/api/VehicleStateInfo', methods=['POST', 'OPTIONS'])
def get_unmanned_vehicle_state():
"""无人车状态查询接口"""
if request.method == 'OPTIONS':
return '', 204
try:
data = request.json
vehicle_id = data.get("vehicleID") if data else None
is_single = data.get("isSingle", True) if data else False
print(f"收到无人车状态查询请求: vehicle_id={vehicle_id}, is_single={is_single}")
# 生成无人车状态数据
state_data = generate_unmanned_vehicle_state_data(vehicle_id, is_single)
print(f"返回无人车状态数据,数量: {len(state_data)}")
return jsonify(state_data)
except Exception as e:
print(f"Error in get_unmanned_vehicle_state: {str(e)}")
return jsonify([]), 500
def update_ca3456_status() -> None:
"""更新CA3456航空器状态 - 进港->停留1分钟->出港循环"""
@ -2616,107 +2173,6 @@ def get_aircraft_status():
}
})
@app.route('/api/v1/vehicles/<vehicle_id>/status', methods=['GET', 'OPTIONS'])
def get_vehicle_status_universal(vehicle_id):
"""通用无人车状态上报接口 - 符合universal_autonomous_vehicle_api规范"""
if request.method == 'OPTIONS':
return '', 204
logging.info(f"🔍 收到车辆状态查询请求: vehicleId={vehicle_id}")
# JWT认证检查
if not check_auth():
logging.warning(f"❌ 车辆状态查询认证失败: vehicleId={vehicle_id}")
return jsonify({
"code": 401,
"message": "认证失败",
"timestamp": int(time.time() * 1000),
"data": None
}), 401
# 检查vehicleId格式字母数字 3~20 位)
if not vehicle_id or len(vehicle_id) < 3 or len(vehicle_id) > 20:
return jsonify({
"code": 400,
"message": "Bad Request",
"timestamp": int(time.time() * 1000),
"error": {
"type": "VALIDATION_ERROR",
"details": "vehicleId must be 3-20 alphanumeric characters",
"field": "vehicleId"
}
}), 400
# 检查车辆是否存在
vehicle_exists = False
for v in unmanned_vehicle_data:
if v["vehicleNo"] == vehicle_id:
vehicle_exists = True
break
if not vehicle_exists:
return jsonify({
"code": 404,
"message": "Not Found",
"timestamp": int(time.time() * 1000),
"error": {
"type": "RESOURCE_NOT_FOUND",
"details": f"Vehicle {vehicle_id} not found",
"field": "vehicleId"
}
}), 404
try:
# 更新车辆位置数据
current_time = time.time()
global last_unmanned_vehicle_update_time
elapsed_time = current_time - last_unmanned_vehicle_update_time
if elapsed_time >= UPDATE_INTERVAL:
for vehicle in unmanned_vehicle_data:
update_vehicle_position(vehicle, UPDATE_INTERVAL)
vehicle["time"] = int(current_time * 1000)
last_unmanned_vehicle_update_time = current_time
# 生成完整的车辆状态数据
status_data = generate_comprehensive_vehicle_status(vehicle_id)
if not status_data:
return jsonify({
"code": 500,
"message": "Internal Server Error",
"timestamp": int(time.time() * 1000),
"error": {
"type": "DATA_ERROR",
"details": "Failed to generate vehicle status data",
"field": None
}
}), 500
# 处理fields查询参数
fields = request.args.get('fields')
if fields:
status_data = filter_vehicle_status_fields(status_data, fields)
# 返回成功响应
logging.info(f"🚗 返回车辆状态数据: vehicleId={vehicle_id}, 电量={status_data.get('batteryStatus', {}).get('mainBattery', {}).get('chargeLevel', '未知')}%")
return jsonify({
"code": 200,
"message": "success",
"timestamp": int(time.time() * 1000),
"data": status_data
})
except Exception as e:
logging.error(f"Error in get_vehicle_status_universal: {str(e)}")
return jsonify({
"code": 500,
"message": "Internal Server Error",
"timestamp": int(time.time() * 1000),
"error": {
"type": "SERVER_ERROR",
"details": str(e),
"field": None
}
}), 500
if __name__ == '__main__':
app.run(host='localhost', port=8090, debug=True)

View File

@ -16,6 +16,13 @@ import argparse
import sys
from datetime import datetime
import signal
from typing import TypedDict
class TLState(TypedDict):
name: str
duration: int
ns: tuple[int, int, int]
ew: tuple[int, int, int]
class TrafficLightDevice:
"""红绿灯设备模拟器"""
@ -27,8 +34,8 @@ class TrafficLightDevice:
self.running = False
self.socket = None
def create_di_signal(self, ns_red=0, ns_yellow=0, ns_green=0,
ew_red=0, ew_yellow=0, ew_green=0):
def create_di_signal(self, ns_red: int = 0, ns_yellow: int = 0, ns_green: int = 0,
ew_red: int = 0, ew_yellow: int = 0, ew_green: int = 0):
"""
创建红绿灯DI信号数据纯JSON格式
@ -129,7 +136,7 @@ class TrafficLightDevice:
tuple: (状态名称, (ns_red, ns_yellow, ns_green), (ew_red, ew_yellow, ew_green))
"""
# 红绿灯状态序列,每个状态持续时间(秒)
states = [
states: list[TLState] = [
{"name": "南北红+东西绿", "duration": 30, "ns": (1,0,0), "ew": (0,0,1)},
{"name": "南北红+东西黄", "duration": 3, "ns": (1,0,0), "ew": (0,1,0)},
{"name": "南北绿+东西红", "duration": 25, "ns": (0,0,1), "ew": (1,0,0)},
@ -214,14 +221,14 @@ class TrafficLightDevice:
print("开始测试各种红绿灯信号状态...")
test_cases = [
{"name": "南北红+东西绿", "ns": (1,0,0), "ew": (0,0,1)},
{"name": "南北红+东西黄", "ns": (1,0,0), "ew": (0,1,0)},
{"name": "南北绿+东西红", "ns": (0,0,1), "ew": (1,0,0)},
{"name": "南北黄+东西红", "ns": (0,1,0), "ew": (1,0,0)},
{"name": "全红状态", "ns": (1,0,0), "ew": (1,0,0)},
{"name": "无信号状态", "ns": (0,0,0), "ew": (0,0,0)},
{"name": "冲突状态(两绿)", "ns": (0,0,1), "ew": (0,0,1)},
test_cases: list[TLState] = [
{"name": "南北红+东西绿", "duration": 2, "ns": (1,0,0), "ew": (0,0,1)},
{"name": "南北红+东西黄", "duration": 2, "ns": (1,0,0), "ew": (0,1,0)},
{"name": "南北绿+东西红", "duration": 2, "ns": (0,0,1), "ew": (1,0,0)},
{"name": "南北黄+东西红", "duration": 2, "ns": (0,1,0), "ew": (1,0,0)},
{"name": "全红状态", "duration": 2, "ns": (1,0,0), "ew": (1,0,0)},
{"name": "无信号状态", "duration": 2, "ns": (0,0,0), "ew": (0,0,0)},
{"name": "冲突状态(两绿)", "duration": 2, "ns": (0,0,1), "ew": (0,0,1)},
]
try:

File diff suppressed because it is too large Load Diff

View File

@ -120,6 +120,8 @@
<div><strong>红绿灯状态:</strong> <span id="trafficLightStatusCount">0</span></div>
<div><strong>路口红绿灯:</strong> <span id="intersectionTrafficLightCount">0</span></div>
<div><strong>航空器路由:</strong> <span id="aircraftRouteCount">0</span></div>
<div><strong>事件驱动路由:</strong> <span id="eventDrivenRouteCount">0</span></div>
<div><strong>定时采集路由:</strong> <span id="scheduledRouteCount">0</span></div>
<div><strong>航班进出港:</strong> <span id="flightNotificationCount">0</span></div>
<div><strong>路径冲突:</strong> <span id="pathConflictCount">0</span></div>
<div><strong>车辆指令:</strong> <span id="vehicleCommandCount">0</span></div>
@ -186,6 +188,8 @@
traffic_light_status: 0,
intersection_traffic_light_status: 0,
aircraftRouteUpdate: 0,
eventDrivenRoute: 0, // 事件驱动的路由更新
scheduledRoute: 0, // 定时采集的路由更新
flight_notification: 0,
path_conflict_alert: 0,
vehicle_command: 0,
@ -252,6 +256,8 @@
document.getElementById('trafficLightStatusCount').textContent = messageStats.traffic_light_status;
document.getElementById('intersectionTrafficLightCount').textContent = messageStats.intersection_traffic_light_status;
document.getElementById('aircraftRouteCount').textContent = messageStats.aircraftRouteUpdate;
document.getElementById('eventDrivenRouteCount').textContent = messageStats.eventDrivenRoute;
document.getElementById('scheduledRouteCount').textContent = messageStats.scheduledRoute;
document.getElementById('flightNotificationCount').textContent = messageStats.flight_notification;
document.getElementById('pathConflictCount').textContent = messageStats.path_conflict_alert;
document.getElementById('vehicleCommandCount').textContent = messageStats.vehicle_command;
@ -404,10 +410,32 @@
log('collisionLog', `🚦 路口红绿灯状态: ${intersectionId} - 设备${deviceId} - 南北:${nsStatus} 东西:${ewStatus}`, 'info');
break;
case 'aircraftRouteUpdate':
const flightNo = message.payload?.flightNo || '未知航班';
const routeType = message.payload?.routeType || '未知路由';
const routeStatus = message.payload?.status || '未知状态';
log('collisionLog', `航空器路由更新: ${flightNo} - ${routeType} (${routeStatus})`, 'info');
const flightNo = message.data?.flightNo || '未知航班';
const routeType = message.data?.routeType || '未知路由';
const routeStatus = message.data?.routeStatus || '未知状态';
const aircraftStatus = message.data?.aircraftStatus || '未知状态';
const routeCodes = message.data?.routeCodes || '无编码';
const eventSource = message.data?.eventSource || '未知来源';
const routeGeometry = message.data?.routeGeometry ? '已提供几何数据' : '无几何数据';
// 根据事件来源更新分类统计
if (eventSource === 'FLIGHT_NOTIFICATION') {
messageStats.eventDrivenRoute++;
} else if (eventSource === 'SCHEDULED') {
messageStats.scheduledRoute++;
}
// 根据事件来源设置不同的日志类型和图标
let logType = 'info';
let icon = '🛫';
if (eventSource === 'FLIGHT_NOTIFICATION') {
logType = 'success';
icon = '🚀'; // 事件驱动的路由更新用火箭图标
} else if (eventSource === 'SCHEDULED') {
icon = '🕐'; // 定时采集的路由更新用时钟图标
}
log('collisionLog', `${icon} 航空器路由更新: ${flightNo} | 路由:${routeType} 状态:${routeStatus} | 航空器:${aircraftStatus} | 编码:${routeCodes} | 来源:${eventSource} | ${routeGeometry}`, logType);
break;
case 'flight_notification':
case 'FLIGHT_NOTIFICATION':
@ -420,9 +448,9 @@
const notificationDescription = message.payload?.eventDescription || message.eventDescription || '无描述';
// 根据通知级别设置日志类型
const logType = notificationLevel === 'IMPORTANT' ? 'warning' : 'info';
const notificationLogType = notificationLevel === 'IMPORTANT' ? 'warning' : 'info';
log('collisionLog', `✈️ 航班进出港通知: ${notificationFlightNo} - ${notificationEventType} (${notificationFlightType}) | 跑道:${notificationRunway} 机位:${notificationSeat} | ${notificationDescription}`, logType);
log('collisionLog', `✈️ 航班进出港通知: ${notificationFlightNo} - ${notificationEventType} (${notificationFlightType}) | 跑道:${notificationRunway} 机位:${notificationSeat} | ${notificationDescription}`, notificationLogType);
break;
case 'path_conflict_alert':
const object1 = message.payload?.object1Name || '未知对象1';
@ -569,6 +597,8 @@
document.getElementById('trafficLightStatusCount').textContent = '0';
document.getElementById('intersectionTrafficLightCount').textContent = '0';
document.getElementById('aircraftRouteCount').textContent = '0';
document.getElementById('eventDrivenRouteCount').textContent = '0';
document.getElementById('scheduledRouteCount').textContent = '0';
document.getElementById('flightNotificationCount').textContent = '0';
document.getElementById('pathConflictCount').textContent = '0';
document.getElementById('vehicleCommandCount').textContent = '0';