diff --git a/.gitignore b/.gitignore index 9a673a8..078ff44 100644 --- a/.gitignore +++ b/.gitignore @@ -250,6 +250,7 @@ bin/ccuState.txt bin/pPowerManger bin/SQLiteTest bin/pPMtest +bin/pPowerMangerHost # CI test reports reports/ diff --git a/build.sh b/build.sh index e96a58a..25e9198 100755 --- a/build.sh +++ b/build.sh @@ -38,7 +38,24 @@ done mkdir -p build cd build -cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ../ +# 修复: PATH 中包含了 Windows Anaconda(/mnt/d/Software/Anaconda), +# 其下的 GTest 是 Windows .lib, 无法被 Linux 链接器使用。 +# 因此显式指定 Linux 平台的 GTest 配置, 覆盖 CMake 从 PATH 探测到的不正确路径。 +CMAKE_ARGS="-DCMAKE_BUILD_TYPE=${BUILD_TYPE}" +if [ -z "${GTest_DIR}" ]; then + for GTEST_CONFIG in \ + /usr/lib/x86_64-linux-gnu/cmake/GTest/GTestConfig.cmake \ + /usr/lib/cmake/GTest/GTestConfig.cmake \ + /usr/local/lib/cmake/GTest/GTestConfig.cmake \ + "${HOME}/miniconda/lib/cmake/GTest/GTestConfig.cmake"; do + if [ -f "${GTEST_CONFIG}" ]; then + CMAKE_ARGS="${CMAKE_ARGS} -DGTest_DIR=$(dirname "${GTEST_CONFIG}")" + break + fi + done +fi + +cmake ${CMAKE_ARGS} ../ make ${CMD_LINE_ARGS} cd ${INVOCATION_ABS_DIR} diff --git a/docs/上位机下位机通信协议.md b/docs/上位机下位机通信协议.md new file mode 100644 index 0000000..44b1e65 --- /dev/null +++ b/docs/上位机下位机通信协议.md @@ -0,0 +1,1240 @@ +# H100PowerManger 上位机 / 下位机通信协议 + +> 本文档整理自 `src/pPowerManger` 源码,完整描述能源管理程序 `pPowerManger` 与 **上位机**(任务规划 / 决策 / 外部通信 / XC 阵 / ZDP 等 MOOS 应用)以及与 **下位机**(复合管控器 CCU、高压母线、高压汇流排 A、仪表母线、仪表汇流排等配电设备)之间的通信协议。 +> +> 主要源码依据: +> - 上位机:`UpperCommManager.h/.cpp`、`PowerManger.h/.cpp`、`driver.h/.cpp`、`upmsg/*.h` +> - 下位机:`LowerCommManager.h/.cpp`、`udpcomm/udpComm.h/.cpp`、`udpcomm/ccuUdpMsg.h`、`pmSysvariable.h` + +## 目录 + +1. [总体架构](#1-总体架构) +2. [上位机通信协议(MOOS + JSON)](#2-上位机通信协议moos--json) + - 2.1 通信方式与参数 + - 2.2 消息总览 + - 2.3 上位机 → pPowerManger(订阅消息) + - 2.4 pPowerManger → 上位机(发布消息) + - 2.5 设备表(DriverTable)字段 + - 2.6 次级配电(secdist)字段 + - 2.7 设备 ID 枚举 +3. [下位机通信协议(UDP 二进制)](#3-下位机通信协议udp-二进制) + - 3.1 通信方式与参数 + - 3.2 通用帧格式与校验 + - 3.3 消息 ID 汇总 + - 3.4 复合管控器(CCU)报文 + - 3.5 配电系统报文 +4. [附录:常量与枚举](#4-附录常量与枚举) + +--- + +## 1. 总体架构 + +``` + ┌─────────────────────────────────────────────────────┐ + │ 上 位 机 │ + │ 任务规划 / 决策 / 外部通信 / XC阵 / ZDP / 推进器 │ + └─────────────────────┬───────────────────────────────┘ + │ MOOS 消息(JSON 字符串) + │ MOOSDB ServerPort=9000 AppTick=4 + ┌─────────────────────▼───────────────────────────────┐ + │ pPowerManger(能源管理程序) │ + │ UpperCommManager ⇄ 状态机 FSM ⇄ LowerCommManager │ + └─────────────────────┬───────────────────────────────┘ + │ UDP 二进制帧(小端) + │ 本机 5001 ⇄ CCU 192.168.0.140:7000 + ┌─────────────────────▼───────────────────────────────┐ + │ 下 位 机 │ + │ 复合管控器 CCU / 高压母线 / 高压汇流排A / 仪表母线 / 仪表汇流排 │ + └─────────────────────────────────────────────────────┘ +``` + +- **上位机通信**:MOOS 变量承载 **JSON 字符串**,由 `UpperCommManager` 处理。订阅注册于 `PowerManger::registerVariables()`,发布通过 `PowerManger::sendNotification()`。 +- **下位机通信**:**UDP** 承载 **二进制结构体帧**(`#pragma pack(1)` 紧凑、小端),由 `LowerCommManager` + `udpComm` 处理。 +- 关键配置(`PowerManger.h`): + + ```c + #define IPORT 5001 // 本机 UDP 监听端口 + #define CCUPORT 7000 // CCU 目标端口 + #define CCUHOST "192.168.0.140" // CCU 目标地址 + #define MAX_UDP_PKT_SIZE 65535 // UDP 最大报文 + ``` + +--- + +## 2. 上位机通信协议(MOOS + JSON) + +### 2.1 通信方式与参数 + +| 项目 | 说明 | +|------|------| +| 传输载体 | MOOS 消息(`CMOOSMsg`),值为 `std::string` 型 JSON | +| 报文格式 | JSON(jsoncpp),键名带类型后缀,如 `xxx_uint8`、`xxx_fp32`、`xxx_array` | +| 运行频率 | `AppTick = 4`(状态机主循环 4Hz)、`CommsTick = 4` | +| 消息倾斜校验 | `OnNewMail` 中,消息时标与本地时差 > `m_skew`(默认 10s)则丢弃 | +| 周期发送 | `commLoop()` 每 1s 调用 `processPeriodicTasks()` 发布周期反馈 | + +### 2.2 消息总览 + +#### 2.2.1 订阅消息(上位机 → pPowerManger) + +| 序号 | MOOS 变量名 | 含义 | 触发动作 | +|------|-------------|------|----------| +| 1 | `uPower_sysState_cmd` | 系统主控指令 | `MasterCommandEvent` | +| 2 | `uPower_equState_cmd` | 设备控制指令(设备表) | `DevCmdExt`(逐设备) | +| 3 | `uDevice_secdistState_st` | 次级配电状态反馈 | 更新 `m_subDisSysState` | +| 4 | `uDevice_depth_st` | 深度(三路取均值) | 更新 `m_depth` | +| 5 | `uPlan_taskStart_st` | 任务开始 | `TaskStartEvent` | +| 6 | `uPlan_planStop_st` | 任务停止 | — | +| 7 | `uExternComm_setOpCondition_cmd` | 设置工况 | `MasterCommandEvent` | +| 8 | `uExternComm_setOpMode_cmd` | 设置模式 | `MasterCommandEvent` | +| 9 | `uExternComm_setPowerSystem_cmd` | 设置电源系统 | `MasterCommandEvent` | +| 10 | `uExternComm_setDeviceSwitch_cmd` | 单个设备开关 | `DevCmdExt` | +| 11 | `uDetect_xcVoltage_cmd` | XC 阵电压申请 | 舷侧声呐电压指令 | +| 12 | `uDetect_zdpVoltage_cmd` | ZDP 电压申请 | 低频声呐开关 | +| 13 | `uDevice_thruster_st` | 推进器状态 | 更新转速/使能 | + +> `uExternComm_setThrustSpeed_cmd`(字段 `rpm_int16`、`enable_int16`)已在 `upmsg` 中定义,但 `registerVariables()` 中已注释,当前**未订阅**。 + +#### 2.2.2 发布消息(pPowerManger → 上位机) + +| 序号 | MOOS 变量名 | 含义 | +|------|-------------|------| +| 1 | `uPower_pmState_fb` | 能源管理整体状态 | +| 2 | `uPower_fcsState_st` | 燃料电池系统状态 | +| 3 | `uPower_liBatState_st` | 锂电池状态(仪表 / 动力 / 应急) | +| 4 | `uPower_disSysState_st` | 配电系统状态(断路器位图 + 故障码) | +| 5 | `uPower_currentState_st` | 各母线电压 / 电流 | +| 6 | `uPower_equState_fb` | 设备状态反馈(设备表) | +| 7 | `uPower_secdistState_cmd` | 次级配电指令(UNIT1~5 设备开关) | +| 8 | `uPower_equInsState_fb` | 设备在线状态(位图) | +| 9 | `uPower_subDisFault_st` | (仅 `_DEBUG`)次级配电故障表 | + +--- + +### 2.3 上位机 → pPowerManger(订阅消息) + +#### 2.3.1 `uPower_sysState_cmd` — 系统主控指令 + +解析结果写入 `m_msCmd`(`MSControlCmd`),并触发 `MasterCommandEvent`。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `powerCMD` | uint | 电源系统指令 | +| `modCMD` | uint | 模式指令 | +| `workCMD` | uint | 工况指令 | + +示例: + +```json +{"powerCMD":1,"modCMD":2,"workCMD":3} +``` + +#### 2.3.2 `uPower_equState_cmd` — 设备控制指令(设备表) + +解析 `DriverTable`,逐条派发 `DevCmdExt{id, cmd}`(`id` 为设备 ID,见 2.7)。字段名 → 设备 ID 映射见 `Driver::loadDriverIdFromJson()`。 + +JSON 字段为 `<设备名>_uint8`,取值含义见 `EquipmentCommand`(0=断电,1=上电,2=仪表电上电,3=动力电上电,4=仪表电断电,5=动力电断电)。 + +示例: + +```json +{"thruster_uint8":1,"depth1_uint8":1,"buoyage_uint8":0} +``` + +> 完整字段清单见 [2.5 设备表字段](#25-设备表drivertable字段)。 + +#### 2.3.3 `uDevice_secdistState_st` — 次级配电状态反馈 + +解析到 `m_subDisSysState`(`DriverTable`)。字段为 `<设备名>_uint8`,按 UNIT1~5 分组。 + +> 完整字段清单见 [2.6 次级配电字段](#26-次级配电secdist字段)。 + +#### 2.3.4 `uDevice_depth_st` — 深度 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `value_array_int32` | int[] | 深度数组,取前 3 个元素平均值 | + +示例: + +```json +{"value_array_int32":[1200,1210,1190]} +``` + +#### 2.3.5 `uPlan_taskStart_st` — 任务开始 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `taskType_str` | string | 任务类型(见 `TaskType::fromString`) | +| `balanceEnabled_bool` | bool | 使能浮力调节 | +| `bowRudderUnfold_bool` | bool | 艏舵展开 | + +示例: + +```json +{"taskType_str":"sail","balanceEnabled_bool":true,"bowRudderUnfold_bool":false} +``` + +#### 2.3.6 `uPlan_planStop_st` — 任务停止 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `op_int32` | int | 1 = 停止 | + +示例: + +```json +{"op_int32":1} +``` + +#### 2.3.7 `uExternComm_setOpCondition_cmd` — 设置工况 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `state_uint8` | uint | 工况值(见 `WorkCondition`) | + +#### 2.3.8 `uExternComm_setOpMode_cmd` — 设置模式 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `state_uint8` | uint | 模式值(见 `RunMode`) | + +#### 2.3.9 `uExternComm_setPowerSystem_cmd` — 设置电源系统 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `powerCmd_uint8` | uint | 电源系统指令 | + +#### 2.3.10 `uExternComm_setDeviceSwitch_cmd` — 单个设备开关 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id_uint8` | uint | 设备 ID(见 2.7) | +| `cmd_uint8` | uint | 开关指令(`EquipmentCommand`) | + +示例: + +```json +{"id_uint8":53,"cmd_uint8":1} +``` + +#### 2.3.11 `uDetect_xcVoltage_cmd` — XC 阵电压申请 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `xcLowVoltageAdd_int32` | int | 低压申请(0=无命令,1=申请仪表上电,2=申请仪表断电) | +| `xcHighVoltageAdd_int32` | int | 高压申请(0=无命令,1=申请高压上电,2=申请高压断电) | + +#### 2.3.12 `uDetect_zdpVoltage_cmd` — ZDP 电压申请 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `zdpVoltageAdd_int32` | int | 电压申请(同 XC 定义) | + +#### 2.3.13 `uDevice_thruster_st` — 推进器状态 + +| 字段 | 类型 | 单位 | 说明 | +|------|------|------|------| +| `rpm_int16` | int16 | rpm | 实际转速 | +| `voltage_uint16` | uint16 | V | 电压 | +| `current_uint16` | uint16 | A | 电流 | +| `ctrlTemp_uint16` | uint16 | ℃ | 控制器温度 | +| `motorTemp_uint16` | uint16 | ℃ | 电机温度 | +| `motorTorque_uint16` | uint16 | N·m | 电机扭矩 | +| `maxTorque_uint16` | uint16 | N·m | 电机最大扭矩(0=未使能) | +| `error_int64` | int64 | — | 错误码 | + +--- + +### 2.4 pPowerManger → 上位机(发布消息) + +#### 2.4.1 `uPower_pmState_fb` — 能源管理整体状态 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `workCondition_uint8` | uint8 | 当前工况(`WorkCondition`) | +| `currentMod_uint8` | uint8 | 当前模式 | +| `powerState_uint8` | uint8 | 电源状态 | +| `faultLevel_uint8` | uint8 | 故障等级(取 `getFaultCodeLevel()`) | +| `faultCode_array` | uint[] | 故障码数组 | +| `soc_uint16` | uint16 | 荷电状态 SOC | +| `sustainableTime_int64` | int64 | 可持续时间 | +| `leakage_uint8` | uint8 | 漏水标志(`generateLeakageCode()`) | +| `info_str` | string | 系统信息描述 | + +#### 2.4.2 `uPower_fcsState_st` — 燃料电池系统状态 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `backpressure1_fp32` | float | 背压 1 | +| `backpressure2_fp32` | float | 背压 2 | +| `ch4oConcent1_fp32` | float | 甲醇浓度 1 | +| `ch4oConcent2_fp32` | float | 甲醇浓度 2 | +| `ch4o_fp32` | float | 备用 | +| `excTemp_fp32` | float | 钯膜温度 | +| `fcuCurIns_fp32` | float | 备用 | +| `fcuCurPower_fp32` | float | 备用 | +| `fcuCur_fp32` | float | 备用 | +| `fcuFaultLeve_uint8` | uint8 | 故障等级 | +| `fcuOutIns_fp32` | float | 备用 | +| `fcuOutPower_fp32` | float | 备用 | +| `fcuState_uint8` | uint8 | 燃料电池状态(`FcuState`) | +| `fcuVolIns_fp32` | float | 备用 | +| `fcuVolPower_fp32` | float | 备用 | +| `fcuVol_fp32` | float | 备用 | +| `h2Concent1_fp32` | float | H2 浓度 1 | +| `h2Concent2_fp32` | float | H2 浓度 2 | +| `o2Concent1_fp32` | float | O2 浓度 1 | +| `o2Concent2_fp32` | float | O2 浓度 2 | +| `o2Level_fp32` | float | 备用 | +| `o2_fp32` | float | 备用 | +| `powLimit_fp32` | float | 输出功率限制 | +| `powerGen_fp32` | float | 累计发电功率 | +| `flameDet_uint8` | uint8 | 火焰探测器状态 | +| `faultCode1_uint16` | uint16 | 一级故障字 | +| `faultCode2_uint16` | uint16 | 二级故障字 | +| `faultCode3_uint16` | uint16 | 三级故障字 | +| `faultCode4_uint16` | uint16 | 四级故障字 | +| `fcCommState_uint8` | uint8 | 通信状态(当前固定为 0) | + +#### 2.4.3 `uPower_liBatState_st` — 锂电池状态 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `insBatCurElect_uint16` | uint16 | 仪表电池电流电量 | +| `insBatLimit_uint16` | uint16 | 仪表电池限功率 | +| `insBatSoc_uint8` | uint8 | 仪表电池 SOC | +| `insBatTotalElect_uint16` | uint16 | 仪表电池总能量 | +| `insChargeState_uint8` | uint8 | 仪表电池充放电状态 | +| `insCurSocHold1_uint8` | uint8 | 备用 | +| `insCurSocHold2_uint8` | uint8 | 备用 | +| `insCurSocHold3_uint8` | uint8 | 备用 | +| `insCurrent_uint16` | uint16 | 仪表电池电流 | +| `insLinkVol_uint16` | uint16 | 仪表电池 LINK 电压 | +| `insNegRelayState_uint8` | uint8 | 仪表电池负极继电器状态(固定 0) | +| `insNegRes_uint16` | uint16 | 仪表电池负极绝缘电阻 | +| `insPackVol_uint16` | uint16 | 仪表电池 PACK 电压 | +| `insPosRelayState_uint8` | uint8 | 仪表电池正极继电器状态(固定 0) | +| `insPosRes_uint16` | uint16 | 仪表电池正极绝缘电阻 | +| `powBatCurElect_uint16` | uint16 | 动力电池电流电量(固定 0) | +| `powBatSoc_uint8` | uint8 | 动力电池 SOC | +| `powBatTotalElect_uint16` | uint16 | 动力电池总能量 | +| `powChargeState_uint8` | uint8 | 动力电池充放电状态 | +| `powCurSocHold1_uint8` | uint8 | 备用 | +| `powCurSocHold2_uint8` | uint8 | 备用 | +| `powCurSocHold3_uint8` | uint8 | 备用 | +| `powCurToInsLimit1_uint8` | uint8 | 动力→仪表限功率 1 | +| `powCurToInsLimit2_uint8` | uint8 | 动力→仪表限功率 2 | +| `powCur_uint16` | uint16 | 动力电池电流 | +| `powLinkVol_uint16` | uint16 | 动力电池 LINK 电压 | +| `powNegRelayState_uint8` | uint8 | 动力电池负极继电器状态(固定 0) | +| `powNegRes_uint16` | uint16 | 动力电池负极绝缘电阻 | +| `powPosRelayState_uint8` | uint8 | 动力电池正极继电器状态(固定 0) | +| `powPosRes_uint16` | uint16 | 动力电池正极绝缘电阻 | +| `powerBatLimit_uint16` | uint16 | 备用 | +| `powerPackVol_uint16` | uint16 | 动力电池 PACK 电压 | +| `powFaultCode_uint8` | uint8 | 动力电池故障码 | +| `insFaultCode_uint8` | uint8 | 仪表电池故障码 | +| `insCommState_uint8` | uint8 | 仪表电池通信状态(固定 0) | +| `powCommState_uint8` | uint8 | 动力电池通信状态(固定 0) | +| `emergencyBattery1Voltage_int16` | int16 | 应急电池 1 电压 | +| `emergencyBattery1Current_int16` | int16 | 应急电池 1 电流 | +| `emergencyBattery1MaxTemp_int16` | int16 | 应急电池 1 最高温度 | +| `emergencyBattery1FaultWord_uint16` | uint16 | 应急电池 1 故障字 | +| `emergencyBattery2Voltage_int16` | int16 | 应急电池 2 电压 | +| `emergencyBattery2Current_int16` | int16 | 应急电池 2 电流 | +| `emergencyBattery2MaxTemp_int16` | int16 | 应急电池 2 最高温度 | +| `emergencyBattery2FaultWord_uint16` | uint16 | 应急电池 2 故障字 | + +#### 2.4.4 `uPower_disSysState_st` — 配电系统状态 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `HVolABusBreaker_uint16` | uint16 | 高压母线(BUS1)断路器位图 | +| `HVolBBusBreaker_uint16` | uint16 | 高压汇流排 A(BUS2)断路器位图 | +| `HVolBusBreaker_uint16` | uint16 | 仪表母线(BUS3)断路器位图 | +| `LVolBusBreaker_uint16` | uint16 | 仪表汇流排(BUS4)断路器位图 | +| `faultCode_array` | uint[] | 配电故障码数组 | + +位图定义(bit0 起始): + +| 位 | HVolABusBreaker | HVolBBusBreaker | HVolBusBreaker | LVolBusBreaker | +|----|-----------------|-----------------|----------------|----------------| +| bit0 | 燃料电池断路器 | 艏部FT装置1-3 | 锂电池组(仪表)断路器 | UNIT1断路器 | +| bit1 | 动力锂电池断路器 | 启闭机构断路器 | 艏部低压配电箱断路器 | UNIT2断路器 | +| bit2 | 推进电机断路器 | 桅杆舵机控制箱断路器 | UNIT4仪表DC48V断路器 | UNIT3断路器 | +| bit3 | 锂电池组仪表断路器 | XCZ断路器 | DC-C1直流配电板断路器 | UNIT5断路器 | +| bit4 | DC/DC5模块断路器 | 艏舵控制箱断路器 | 预留断路器1 | 艏部PZ装置断路器 | +| bit5 | 艏部高压配电箱断路器 | 敞水盖启动电缸断路器 | 预留断路器2 | 预留断路器1 | +| bit6 | 艉部FT装置4/5断路器 | — | 应急锂电池组2断路器 | 预留断路器2 | +| bit7 | 艉舵开关1断路器 | — | — | 应急锂电池组1断路器 | +| bit8 | 艉舵开关2断路器 | — | — | — | +| bit9 | FB预留断路器 | — | — | — | +| bit10 | TYZ预留断路器 | — | — | — | +| bit11 | 预留断路器 | — | — | — | + +#### 2.4.5 `uPower_currentState_st` — 各母线电压/电流 + +**高压母线(HVB,BUS1)** + +| 字段 | 类型 | 来源 | +|------|------|------| +| `HVBpowerBusVol_uint16` | uint16 | 动力汇流排电压 | +| `HVBdcDc5Cur_uint16` | uint16 | DC/DC5 模块电流 | +| `HVBpowerBusCur_uint16` | uint16 | 动力汇流排电流 | +| `HVBbusAVol_uint16` | uint16 | A 汇流排电压 | +| `HVBmotorCur_uint16` | uint16 | 推进电机控制箱电流 | +| `HVBbusACur_uint16` | uint16 | A 汇流排电流 | +| `HVBbatGroupCur_uint16` | uint16 | 锂电池组(仪表)电流 | +| `HVBbowHVolDisBoxCur_uint16` | uint16 | 艏部高压配电箱电流 | +| `HVBFTDev45Cur_uint16` | uint16 | 艉部FT装置4/5电流 | +| `HVBtyzCur_uint16` | uint16 | TYZ预留开关电流 | +| `HVBsternRud1Cur_uint16` | uint16 | 艉舵开关1电流 | +| `HVBreservedCur1_uint16` | uint16 | 预留电流1 | +| `HVBsternRud2Cur_uint16` | uint16 | 艉舵开关2电流 | +| `HVBreservedCur2_uint16` | uint16 | 预留电流2 | +| `HVBfbCur_uint16` | uint16 | FB预留电流 | +| `HVBreservedCur3_uint16` | uint16 | 预留电流3 | + +**高压汇流排 A(HVAB,BUS2)** + +| 字段 | 类型 | 来源 | +|------|------|------| +| `HVABbusBVol_uint16` | uint16 | 汇流排 B 电压 | +| `HVABbusBCur_uint16` | uint16 | 汇流排 B 电流 | +| `HVABFTDev123Cur_uint16` | uint16 | 艏部FT装置1/2/3电流 | +| `HVABactCur_uint16` | uint16 | 启闭机构电流 | +| `HVABmastSteGeaCur_uint16` | uint16 | 桅杆舵机控制箱电流 | +| `HVABxczCur_uint16` | uint16 | XCZ电流 | +| `HVABbowRudCur_uint16` | uint16 | 艏舵控制箱电流 | +| `HVABopenWaterCur_uint16` | uint16 | 敞水盖启动电缸电流 | + +**仪表母线(HVBB,BUS3)** + +| 字段 | 类型 | 来源 | +|------|------|------| +| `HVBBinsBusVol_uint16` | uint16 | 仪表母排电压 | +| `HVBBbowLowVolDisBoxCur_uint16` | uint16 | 艏部低压配电箱电流 | +| `HVBBdcC1InsCur_uint16` | uint16 | DC-C1 仪表DC48V电流 | +| `HVBBreservedCur1_uint16` | uint16 | 预留电流1 | +| `HVBBemerBatCur_uint16` | uint16 | 应急锂电池组2电流 | +| `HVBBbatInsCur_uint16` | uint16 | 锂电池组(仪表)电流 | +| `HVBBunit4Cur_uint16` | uint16 | UNIT4 仪表DC48V电流 | +| `HVBBreservedCur2_uint16` | uint16 | 预留电流2 | +| `HVBBemer2BusVol_uint16` | uint16 | 应急2汇流排电压 | +| `HVBBCCUCur_uint16` | uint16 | 复合能源管控系统应急DC48V电流 | +| `HVBBFCSSecurSysCur_uint16` | uint16 | 燃料电池安全系统应急DC48V电流 | +| `HVBBplcCur_uint16` | uint16 | PLC控制电源电流 | + +**仪表汇流排(LVB,BUS4)** + +| 字段 | 类型 | 来源 | +|------|------|------| +| `LVBinsBusVol_uint16` | uint16 | 仪表母排电压 | +| `LVBBinsBusCur_uint16` | uint16 | 仪表母排电流 | +| `LVBBunit1Cur_uint16` | uint16 | UNIT1 电流 | +| `LVBBunit2Cur_uint16` | uint16 | UNIT2 电流 | +| `LVBBunit3Cur_uint16` | uint16 | UNIT3 电流 | +| `LVBBunit5Cur_uint16` | uint16 | UNIT5 电流 | +| `LVBBbowPZDevCur_uint16` | uint16 | 艏部PZ装置电流 | +| `LVBBemerBatCur_uint16` | uint16 | 应急锂电池组1电流 | +| `LVBBemerBusVol_uint16` | uint16 | 应急1汇流排电压 | + +#### 2.4.6 `uPower_equState_fb` — 设备状态反馈 + +设备表 JSON(字段同 2.5),反映当前设备状态。 + +#### 2.4.7 `uPower_secdistState_cmd` — 次级配电指令 + +次级配电 JSON(字段同 2.6),向次级配电系统下发 UNIT1~5 设备开关指令。 + +#### 2.4.8 `uPower_equInsState_fb` — 设备在线状态(位图) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `sensor_int16` | uint16 | 传感器状态位图 | +| `actuator_int16` | uint16 | 执行机构状态位图 | +| `motion_int16` | uint16 | 运动状态位图 | +| `loadcomm_int16` | uint16 | 负载通信状态位图 | +| `throwpower_int16` | uint16 | 抛载/电源状态位图 | + +位图定义: + +| 位 | sensor_int16 | actuator_int16 | motion_int16 | loadcomm_int16 | throwpower_int16 | +|----|--------------|----------------|--------------|----------------|------------------| +| bit0 | 单波束声呐1 | 启闭机构1 | 艉部舵机1 | 载荷发控 | 艏部抛载 | +| bit1 | 单波束声呐2 | 启闭机构2 | 艉部舵机2 | 光电桅杆 | 保留 | +| bit2 | 单波束声呐3 | 启闭机构3 | 艉部舵机3 | 电磁桅杆 | 保留 | +| bit3 | 单波束声呐4 | 启闭机构4 | 艉部舵机4 | 保留 | 保留 | +| bit4 | 单波束声呐5 | 启闭机构5 | 艏舵电缸 | 舷侧声纳 | 保留 | +| bit5 | 单波束声呐6 | 启闭机构6 | 注水机构 | 拖曳声纳 | 保留 | +| bit6 | 单波束声呐7 | 保留 | 推进电机 | 保留 | 保留 | +| bit7 | 单波束声呐8 | 保留 | 浮调机构1 | 保留 | 保留 | +| bit8 | 前视声呐 | 敞水机构1 | 浮调机构2 | 通信桅杆 | 燃料电池控制器 | +| bit9 | CTD1 | 敞水机构2 | 浮调机构3 | 中低频声呐 | 仪表配电板 | +| bit10 | CTD2 | 敞水机构3 | 浮调机构4 | 高频声通机 | 动力配电板 | +| bit11 | 声速仪 | 敞水机构4 | 浮调机构5 | 浮标系统 | 仪表锂电池 | +| bit12 | NTP | 敞水机构5 | DVL | 通信桅杆舵机 | 动力锂电 | +| bit13 | 深度计1 | 敞水机构6 | 惯导系统 | 光电桅杆舵机 | 艉部抛载 | +| bit14 | 深度计2 | 敞水机构7 | 艏舵左舵机 | 电磁桅杆舵机 | 保留 | +| bit15 | 深度计3 | 敞水机构8 | 艏右舵舵机 | 保留 | 保留 | + +--- + +### 2.5 设备表(DriverTable)字段 + +以下字段用于 `uPower_equState_cmd` 与 `uPower_equState_fb`,全部为 `<设备名>_uint8`: + +| 序号 | 字段 | 对应设备 ID | +|------|------|-------------| +| 1 | `depth1_uint8` | 18 | +| 2 | `depth2_uint8` | 19 | +| 3 | `depth3_uint8` | 20 | +| 4 | `overDeep1_uint8` | 21 | +| 5 | `overDeep2_uint8` | 22 | +| 6 | `ctd1_uint8` | 23 | +| 7 | `ctd2_uint8` | 24 | +| 8 | `soundVeloc_uint8` | 25 | +| 9 | `commDev_uint8` | 40 | +| 10 | `ins_uint8` | 57 | +| 11 | `ntp_uint8` | 58 | +| 12 | `inputWaterSensor1_uint8` | 59 | +| 13 | `inputWaterSensor2_uint8` | 60 | +| 14 | `inputWaterSensor3_uint8` | 61 | +| 15 | `singleBeamSonar1_uint8` | 0 | +| 16 | `singleBeamSonar2_uint8` | 1 | +| 17 | `singleBeamSonar3_uint8` | 2 | +| 18 | `singleBeamSonar4_uint8` | 3 | +| 19 | `singleBeamSonar5_uint8` | 4 | +| 20 | `singleBeamSonar6_uint8` | 5 | +| 21 | `singleBeamSonar7_uint8` | 6 | +| 22 | `singleBeamSonar8_uint8` | 7 | +| 23 | `forwardSonar_uint8` | 8 | +| 24 | `DVL_uint8` | 43 | +| 25 | `lowFreqSonar_uint8` | 36 | +| 26 | `USBL_uint8` | 42 | +| 27 | `bowCover1_uint8` | 9 | +| 28 | `bowCover2_uint8` | 10 | +| 29 | `bowCover3_uint8` | 11 | +| 30 | `bowCover4_uint8` | 12 | +| 31 | `bowCover5_uint8` | 13 | +| 32 | `bowCover6_uint8` | 14 | +| 33 | `bowRudderDeploy_uint8` | 15 | +| 34 | `bowRudderServo1_uint8` | 16 | +| 35 | `bowRudderServo2_uint8` | 17 | +| 36 | `bowWaterInet1_uint8` | 27 | +| 37 | `bowWaterInet2_uint8` | 28 | +| 38 | `bowWaterInet3_uint8` | 29 | +| 39 | `bowWaterInet4_uint8` | 30 | +| 40 | `bowWaterInet5_uint8` | 31 | +| 41 | `bowWaterInet6_uint8` | 32 | +| 42 | `bowWaterInet7_uint8` | 33 | +| 43 | `bowWaterInet8_uint8` | 34 | +| 44 | `waterInject_uint8` | 26 | +| 45 | `mastLiftingServo_uint8` | 41 | +| 46 | `buoyancyAdjust1_uint8` | 44 | +| 47 | `buoyancyAdjust2_uint8` | 45 | +| 48 | `buoyancyAdjust3_uint8` | 46 | +| 49 | `buoyancyAdjust4_uint8` | 47 | +| 50 | `buoyancyAdjust5_uint8` | 48 | +| 51 | `sternRudderServo1_uint8` | 49 | +| 52 | `sternRudderServo2_uint8` | 50 | +| 53 | `sternRudderServo3_uint8` | 51 | +| 54 | `sternRudderServo4_uint8` | 52 | +| 55 | `thruster_uint8` | 53 | +| 56 | `bowThrow_uint8` | 54 | +| 57 | `sternThrow_uint8` | 55 | +| 58 | `photoSensor_uint8` | 38 | +| 59 | `electSensor_uint8` | 39 | +| 60 | `launchControler_uint8` | 37 | +| 61 | `sideSonar_uint8` | 35 | +| 62 | `buoyage_uint8` | 56 | + +--- + +### 2.6 次级配电(secdist)字段 + +以下字段用于 `uPower_secdistState_cmd` 与 `uDevice_secdistState_st`,全部为 `<设备名>_uint8`: + +**UNIT1(19 项)** + +| 字段 | 字段 | 字段 | +|------|------|------| +| `bowCover1_uint8` | `bowCover2_uint8` | `bowCover3_uint8` | +| `bowCover4_uint8` | `bowCover5_uint8` | `bowCover6_uint8` | +| `singleBeamSonar1_uint8` | `singleBeamSonar2_uint8` | `singleBeamSonar3_uint8` | +| `singleBeamSonar4_uint8` | `singleBeamSonar5_uint8` | `singleBeamSonar6_uint8` | +| `forwardSonar_uint8` | `depth3_uint8` | `soundVeloc_uint8` | +| `bowRudderServo_uint8` | `bowRudderDeploy_uint8` | `ctd1_uint8` | +| `waterInject_uint8` | | | + +**UNIT2(11 项)** + +| 字段 | 字段 | 字段 | +|------|------|------| +| `sideSonar_uint8` | `bowWaterInet1_uint8` | `bowWaterInet2_uint8` | +| `bowWaterInet3_uint8` | `bowWaterInet4_uint8` | `bowWaterInet5_uint8` | +| `bowWaterInet6_uint8` | `bowWaterInet7_uint8` | `bowWaterInet8_uint8` | +| `launchControler_uint8` | `electSensor_uint8` | `lowFreqSonar_uint8` | + +**UNIT3(11 项)** + +| 字段 | 字段 | 字段 | +|------|------|------| +| `buoyancyAdjust1_uint8` | `buoyancyAdjust2_uint8` | `buoyancyAdjust3_uint8` | +| `USBL_uint8` | `depth1_uint8` | `commDev_uint8` | +| `mastLiftingServo_uint8` | `DVL_uint8` | `ADCP_uint8` | +| `inOutLetWater1_uint8` | `inOutLetWater2_uint8` | | + +**UNIT4(16 项)** + +| 字段 | 字段 | 字段 | +|------|------|------| +| `singleBeamSonar7_uint8` | `singleBeamSonar8_uint8` | `buoyage_uint8` | +| `bowThrow_uint8` | `depth2_uint8` | `thruster_uint8` | +| `dragSonar_uint8` | `haulingMachine_uint8` | `buoyancyAdjust4_uint8` | +| `buoyancyAdjust5_uint8` | `sternRudderServo1_uint8` | `sternRudderServo2_uint8` | +| `sternRudderServo3_uint8` | `sternRudderServo4_uint8` | `ctd2_uint8` | +| `commDevStern_uint8` | `inOutLetWater3_uint8` | | + +**UNIT5(4 项)** + +| 字段 | 字段 | +|------|------| +| `secINS_uint8` | `photoSensor_uint8` | +| `sternThrow_uint8` | `mainINS_uint8` | + +--- + +### 2.7 设备 ID 枚举(DeviceId) + +| ID | 名称 | ID | 名称 | ID | 名称 | +|----|------|----|------|----|------| +| 0 | singleBeamSonar1 | 21 | overDeep1 | 42 | USBL | +| 1 | singleBeamSonar2 | 22 | overDeep2 | 43 | DVL | +| 2 | singleBeamSonar3 | 23 | ctd1 | 44 | buoyancyAdjust1 | +| 3 | singleBeamSonar4 | 24 | ctd2 | 45 | buoyancyAdjust2 | +| 4 | singleBeamSonar5 | 25 | soundVeloc | 46 | buoyancyAdjust3 | +| 5 | singleBeamSonar6 | 26 | waterInject | 47 | buoyancyAdjust4 | +| 6 | singleBeamSonar7 | 27 | bowWaterInet1 | 48 | buoyancyAdjust5 | +| 7 | singleBeamSonar8 | 28 | bowWaterInet2 | 49 | sternRudderServo1 | +| 8 | forwardSonar | 29 | bowWaterInet3 | 50 | sternRudderServo2 | +| 9 | bowCover1 | 30 | bowWaterInet4 | 51 | sternRudderServo3 | +| 10 | bowCover2 | 31 | bowWaterInet5 | 52 | sternRudderServo4 | +| 11 | bowCover3 | 32 | bowWaterInet6 | 53 | thruster | +| 12 | bowCover4 | 33 | bowWaterInet7 | 54 | bowThrow | +| 13 | bowCover5 | 34 | bowWaterInet8 | 55 | sternThrow | +| 14 | bowCover6 | 35 | sideSonar | 56 | buoyage | +| 15 | bowRudderDeploy | 36 | lowFreqSonar | 57 | ins | +| 16 | bowRudderServo1 | 37 | launchControler | 58 | ntp | +| 17 | bowRudderServo2 | 38 | photoSensor | 59 | inputWaterSensor1 | +| 18 | depth1 | 39 | electSensor | 60 | inputWaterSensor2 | +| 19 | depth2 | 40 | commMast | 61 | inputWaterSensor3 | +| 20 | depth3 | 41 | mastLiftingServo | — | — | + +--- + +## 3. 下位机通信协议(UDP 二进制) + +### 3.1 通信方式与参数 + +| 项目 | 说明 | +|------|------| +| 传输 | UDP 单播 | +| 本机地址 | 绑定端口 `5001` | +| 下位机地址 | `192.168.0.140:7000` | +| 字节序 | 小端(little-endian) | +| 对齐 | `#pragma pack(1)` 紧凑,无填充 | +| 监听 | `LowerCommManager::listenThreadFunc()` 独立线程接收 | +| 分拣 | `udpComm::pushMsgToQueue()` 按消息 ID 入队,队列上限 `QUENUE_SIZE = 100` | +| 周期发送 | `commLoop()` 每 1s:`sendCcuCommand`(CCU 操作指令)+ `processPeriodicTasks` | + +### 3.2 通用帧格式与校验 + +所有报文统一为:**消息头(6B)+ 数据区 + 校验字段**。以下帧结构表中,`偏移(字节)` 均为相对整帧起始的绝对偏移。 + +#### 3.2.1 消息头 `m_header`(6 字节) + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 1 | +| 1 | start2 | u8 | 起始符 2 | +| 2~3 | id | u16 | 消息标识(小端) | +| 4~5 | length | u16 | 数据区长度 | + +- 起始符分类: + - CCU 报文:`start1 = start2 = 0x20` + - 配电报文:`start1 = start2 = 0x40` + +#### 3.2.2 日期 `m_date`(8 字节,仅 CCU 操作指令携带) + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | year | u16 | 年 | +| 2 | month | u8 | 月 | +| 3 | day | u8 | 日 | +| 4 | hour | u8 | 时 | +| 5 | minute | u8 | 分 | +| 6 | second | u8 | 秒 | +| 7 | milliseconds | u8 | 毫秒(每 10ms 一档) | + +#### 3.2.3 校验字段宽度 + +| 报文类别 | 校验字段类型 | 宽度 | +|----------|--------------|------| +| CCU 报文 | `Checksum`(unsigned int) | 4 字节 | +| 配电报文 | `cChecksum`(unsigned char) | 1 字节 | + +#### 3.2.4 校验算法 + +``` +校验和 = 从 start1 起至校验字段之前的所有字节累加求和(不含校验字段本身) +``` + +- CCU 报文:校验字段 4 字节,实际发送 / 比较取累加和**低 8 位**(`cc & 0xFF`)。 +- 配电报文:校验字段 1 字节,`sum = 累加和 & 0xFF`。 +- 接收端校验不符则丢弃该帧并记录 `Check error`。 + +```c +Checksum calculateChecksum(const unsigned char* data, int size) { + Checksum checksum = 0; + int size_data = size - sizeof(Checksum); // 去掉末尾校验字段 + for (int i = 0; i < size_data; i++) checksum += data[i]; + return checksum; +} +``` + +### 3.3 消息 ID 汇总 + +| 分类 | ID | 方向 | 报文结构体 | 说明 | +|------|-----|------|------------|------| +| CCU | `0x0040` | 发送 | `msg_CcuColCmdMsg` | 复合管控器操作指令 | +| CCU | `0x0041` | 发送 | `msg_CcuSetParmMsg` | 复合管控器参数设定指令 | +| CCU | `0x0051` | 接收 | `msg_CcuStateFbMsg` | 复合管控器状态反馈 | +| CCU | `0x0040` | 接收 | `msg_CcuSetParmFbMsg` | 复合管控器参数设定反馈(与操作指令 ID 复用) | +| 配电 | `0x0001` | 发送 | `msg_disHVBCmd` | 高压母线配电指令 | +| 配电 | `0x0002` | 发送 | `msg_disHVBACmd` | 高压汇流排A配电指令 | +| 配电 | `0x0003` | 发送 | `msg_disHVBBCmd` | 仪表母线配电指令 | +| 配电 | `0x0004` | 发送 | `msg_disLVBCmd` | 仪表汇流排配电指令 | +| 配电 | `0x0005` | 接收 | `msg_disHighVolBusFbMsg` | 高压母线配电反馈 | +| 配电 | `0x0006` | 接收 | `msg_disHighAVolBusFbMsg` | 高压汇流排A配电反馈 | +| 配电 | `0x0007` | 接收 | `msg_disLowMainBusFbMsg` | 仪表母线配电反馈 | +| 配电 | `0x0008` | 接收 | `msg_disLowBusFbMsg` | 仪表汇流排配电反馈 | + +> 说明:`CCU_SET_PARM_FB_ID` 与 `CCU_CMD_ID` 同为 `0x0040`。接收端以起始符 `0x20 0x20` 判定为 CCU 报文后,将 `0x0040` 一律按“参数设定反馈”处理(操作指令只发送不接收,实际不冲突)。 + +### 3.4 复合管控器(CCU)报文 + +#### 3.4.1 操作指令 `msg_CcuColCmdMsg`(0x0040,发送) + +总长度 36 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x20 | +| 1 | start2 | u8 | 起始符 0x20 | +| 2~3 | id | u16 | 消息标识 0x0040 | +| 4~5 | length | u16 | 数据区长度(=18) | +| 6~7 | year | u16 | 年 | +| 8 | month | u8 | 月 | +| 9 | day | u8 | 日 | +| 10 | hour | u8 | 时 | +| 11 | minute | u8 | 分 | +| 12 | second | u8 | 秒 | +| 13 | milliseconds | u8 | 毫秒(每 10ms 一档) | +| 14 | fcmode | u8 | 燃料电池运行模式 | +| 15 | fcuCmd | u8 | 燃料电池控制指令(`FcuCommand`) | +| 16 | fcuPowerEfficiency | u8 | 燃料电池输出功率(默认 8kW) | +| 17 | insChargeEnable | u8 | 仪表电池充电使能(0x30=不允许充电) | +| 18 | insBatCmd | u8 | 仪表电池指令(`BatteryCommand`) | +| 19 | powerBatCmd | u8 | 动力电池指令 | +| 20~21 | powerBatEfficiency | u16 | 动力电池效率(0=最大) | +| 22 | heartbeat | u8 | 心跳计数(周期自增) | +| 23 | masterState | u8 | 主站状态(0xAA=运行中) | +| 24~27 | paceholder1 | u32 | 预留 | +| 28~31 | paceholder2 | u32 | 预留 | +| 32~35 | checkCode | u32 | 校验和(累加和低 8 位) | + +#### 3.4.2 参数设定指令 `msg_CcuSetParmMsg`(0x0041,发送) + +总长度 20 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x20 | +| 1 | start2 | u8 | 起始符 0x20 | +| 2~3 | id | u16 | 消息标识 0x0041 | +| 4~5 | length | u16 | 数据区长度(=10) | +| 6 | insSocHold1 | u8 | 仪表 SOC 保持门限 1 | +| 7 | insSocHold2 | u8 | 仪表 SOC 保持门限 2 | +| 8 | insSocHold3 | u8 | 仪表 SOC 保持门限 3 | +| 9 | fcuToInsLimit1 | u8 | FCU→仪表限功率 1 | +| 10 | fcuToinsLimit2 | u8 | FCU→仪表限功率 2 | +| 11 | powerSocHold1 | u8 | 动力 SOC 保持门限 1 | +| 12 | powerSocHold2 | u8 | 动力 SOC 保持门限 2 | +| 13 | powerSocHold3 | u8 | 动力 SOC 保持门限 3 | +| 14 | fcuToPowerLimit1 | u8 | FCU→动力限功率 1 | +| 15 | fcuToPowerLimit2 | u8 | FCU→动力限功率 2 | +| 16~19 | checkCode | u32 | 校验和(累加和低 8 位) | + +#### 3.4.3 状态反馈 `msg_CcuStateFbMsg`(0x0051,接收) + +总长度 182 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x20 | +| 1 | start2 | u8 | 起始符 0x20 | +| 2~3 | id | u16 | 消息标识 0x0051 | +| 4~5 | length | u16 | 数据区长度 | +| 6 | fc_mode | u8 | 燃料电池运行模式(00=无效,01=调试,02=自动,03=补给) | +| 7 | fc_status | u8 | 燃料电池状态(`FcuState`) | +| 8 | fc_fault_level | u8 | 故障等级(00=无,10=一级,20=二级,30=三级,40=四级) | +| 9~10 | output_power_limit | u16 | 输出功率限制 [W],0~30000 | +| 11~12 | total_generation_time | u16 | 累计发电时间 [0.1h],最大 6553.5h | +| 13~14 | total_generation_power | u16 | 累计发电总功率 [W] | +| 15~16 | fault_level_1 | u16 | 一级故障位 | +| 17~18 | fault_level_2 | u16 | 二级故障位 | +| 19~20 | fault_level_3 | u16 | 三级故障位 | +| 21~22 | fault_level_4 | u16 | 四级故障位 | +| 23 | hydrogen_capacity | u8 | 储氢罐剩余容量 [%] | +| 24 | liquid_oxygen_capacity | u8 | 液氧罐剩余容量 [%] | +| 25~26 | palladium_temp | u16 | 钯膜最高温度 [℃] | +| 27~28 | main_pipe_pressure | u16 | 主水路压力 [0.01kPa] | +| 29~30 | aux_pipe_pressure | u16 | 辅水路压力 [0.01kPa] | +| 31~32 | cabin_pressure1 | u16 | 舱室1压力 [0.01kPa] | +| 33~34 | cabin_pressure2 | u16 | 舱室2压力 [0.01kPa] | +| 35~36 | cabin_temp1 | u16 | 舱室1温度 [0.01℃] | +| 37~38 | cabin_temp2 | u16 | 舱室2温度 [0.01℃] | +| 39~40 | cabin_humidity1 | u16 | 舱室1湿度 [0.01%RH] | +| 41~42 | cabin_humidity2 | u16 | 舱室2湿度 [0.01%RH] | +| 43 | flame_detector1 | u8 | 火焰探测器1(00=无效,01=报警,02=故障) | +| 44 | flame_detector2 | u8 | 火焰探测器2 | +| 45~46 | h2_concentration1 | u16 | 舱室H2浓度1 [0.01% LEL] | +| 47~48 | h2_concentration2 | u16 | 舱室H2浓度2 [0.01% LEL] | +| 49~50 | h2_concentration3 | u16 | 舱室H2浓度3 [0.01% LEL] | +| 51~52 | o2_concentration1 | u16 | 舱室O2浓度1 [0.01% LEL] | +| 53~54 | o2_concentration2 | u16 | 舱室O2浓度2 [0.01% LEL] | +| 55~56 | ch3oh_concentration1 | u16 | 舱室甲醇浓度1 [0.01% LEL] | +| 57~58 | ch3oh_concentration2 | u16 | 舱室甲醇浓度2 [0.01% LEL] | +| 59~62 | reserved1[2] | u16×2 | 保留 | +| 63~64 | emergency_battery1_voltage | u16 | 应急电池1总电压 [V] | +| 65~66 | emergency_battery1_current | u16 | 应急电池1总电流 [A] | +| 67~68 | emergency_battery1_max_temp | u16 | 应急电池1最高温度 [℃] | +| 69~70 | emergency_battery1_fault_word | u16 | 应急电池1故障字 | +| 71~72 | emergency_battery2_voltage | u16 | 应急电池2总电压 [V] | +| 73~74 | emergency_battery2_current | u16 | 应急电池2总电流 [A] | +| 75~76 | emergency_battery2_max_temp | u16 | 应急电池2最高温度 [℃] | +| 77~78 | emergency_battery2_fault_word | u16 | 应急电池2故障字 | +| 79~80 | cabin_ox_concentration | u16 | 仪表舱氧气浓度 [%] | +| 81~82 | cabin_temperature | u16 | 仪表舱温度 [℃] | +| 83~84 | cabin_humidity | u16 | 仪表舱湿度 [%RH] | +| 85~86 | cabin_pressure | u16 | 仪表舱大气压 [kPa] | +| 87~88 | power_cabin_ox_concentration | u16 | 动力舱氧气浓度 [%] | +| 89~90 | power_cabin_temperature | u16 | 动力舱温度 [℃] | +| 91~92 | power_cabin_humidity | u16 | 动力舱湿度 [%RH] | +| 93~94 | power_cabin_pressure | u16 | 动力舱大气压 [kPa] | +| 95 | reserved2[1] | u8 | 保留 | +| 96~101 | dyn_alarm_flag[6] | u8×6 | 动力锂电池报警标志 | +| 102~107 | ins_alarm_flag[6] | u8×6 | 仪表锂电池报警标志 | +| 108 | ins_relay_status1 | u8 | 仪表电池正极(高4)/充电(低4)继电器 | +| 109 | ins_relay_status2 | u8 | 仪表电池预充/负极继电器 | +| 110 | dyn_relay_status1 | u8 | 动力电池正极/充电继电器 | +| 111 | dyn_relay_status2 | u8 | 动力电池预充/负极继电器 | +| 112~113 | ins_max_discharge_power | u16 | 仪表电池最大放电功率 [kW, 0.05] | +| 114~115 | dyn_max_discharge_power | u16 | 动力电池最大放电功率 [kW, 0.05] | +| 116 | ins_soc | u8 | 仪表电池 SOC [%] | +| 117 | dyn_soc | u8 | 动力电池 SOC [%] | +| 118~119 | ins_total_energy | u16 | 仪表电池总能量 [kWh] | +| 120~121 | dyn_total_energy | u16 | 动力电池总能量 [kWh] | +| 122~123 | ins_power_input | u16 | 仪表电池输入功率 [kW] | +| 124~125 | dyn_power_input | u16 | 动力电池输入功率 [kW] | +| 126 | ins_charge_status | u8 | 仪表电池充放电(00=无效,10=充电,20=放电) | +| 127 | dyn_charge_status | u8 | 动力电池充放电状态 | +| 128~129 | ins_voltage_link | u16 | 仪表电池 LINK 电压 [0.01V] | +| 130~131 | ins_voltage_pack | u16 | 仪表电池 PACK 电压 [0.01V] | +| 132~133 | ins_current | u16 | 仪表电池电流 [0.01A],正值放电 | +| 134~135 | ins_resistance_pos | u16 | 仪表电池正极绝缘电阻 [kΩ] | +| 136~137 | ins_resistance_neg | u16 | 仪表电池负极绝缘电阻 [kΩ] | +| 138~139 | dyn_voltage_link | u16 | 动力电池 LINK 电压 [0.01V] | +| 140~141 | dyn_voltage_pack | u16 | 动力电池 PACK 电压 [0.01V] | +| 142~143 | dyn_current | u16 | 动力电池电流 [0.01A],正值放电 | +| 144~145 | dyn_resistance_pos | u16 | 动力电池正极绝缘电阻 [kΩ] | +| 146~147 | dyn_resistance_neg | u16 | 动力电池负极绝缘电阻 [kΩ] | +| 148 | ins_emergency_status | u8 | 仪表电池紧急状态(0x00=无,0x3A=4级…0x3D=1级) | +| 149 | dyn_emergency_status | u8 | 动力电池紧急状态 | +| 150~151 | reserved3[2] | u8×2 | 保留 | +| 152 | ins_soc_threshold1 | u8 | 仪表电池1 SOC 门限 | +| 153 | ins_soc_threshold2 | u8 | 仪表电池2 SOC 门限 | +| 154 | ins_soc_threshold3 | u8 | 仪表电池3 SOC 门限 | +| 155 | dyn_soc_threshold1 | u8 | 动力电池1 SOC 门限 | +| 156 | dyn_soc_threshold2 | u8 | 动力电池2 SOC 门限 | +| 157 | dyn_soc_threshold3 | u8 | 动力电池3 SOC 门限 | +| 158 | ins_power_limit1 | u8 | 仪表电池1功率限制 [%] | +| 159 | ins_power_limit2 | u8 | 仪表电池2功率限制 [%] | +| 160 | dyn_power_limit1 | u8 | 动力电池1功率限制 [%] | +| 161 | dyn_power_limit2 | u8 | 动力电池2功率限制 [%] | +| 162~165 | device_online_flag1 | u32 | 设备在线标志1(byte0=低压配电板,byte1=CCU主机,byte2=CCU备机) | +| 166~169 | device_online_flag2 | u32 | 设备在线标志2(byte0=仪表锂电,byte1=动力锂电,byte2=燃电锂电,byte3=高压配电板) | +| 170 | dyn_charge_level | u8 | 动力电池充电(01=1级,02=2级限功率,03=3级关闭) | +| 171 | dyn_water_leak_status | u8 | 动力漏水(0X=正常,1X=异常) | +| 172 | ins_water_leak_dcdc_status | u8 | 仪表漏水(低4)/DCDC(高4)状态 | +| 173 | reserved4 | u8 | 保留 | +| 174~177 | heartbeat | u32 | 主机心跳 | +| 178~181 | checkCode | u32 | 校验和 | + +#### 3.4.4 参数设定反馈 `msg_CcuSetParmFbMsg`(0x0040,接收) + +总长度 12 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x20 | +| 1 | start2 | u8 | 起始符 0x20 | +| 2~3 | id | u16 | 消息标识 0x0040 | +| 4~5 | length | u16 | 数据区长度 | +| 6 | flag | u8 | 应答标志 | +| 7 | failureCode | u8 | 失败码 | +| 8~11 | checkCode | u32 | 校验和 | + +### 3.5 配电系统报文 + +断路器字段取值: + +| 值 | 含义 | +|----|------| +| `0x55` | 合闸(ON) | +| `0xAA` | 分闸(OFF) | +| `0x00` | 无操作 / 保持 | +| `0x5A` | 故障脱扣(仅反馈) | + +#### 3.5.1 高压母线(BUS1) + +**指令 `msg_disHVBCmd`(0x0001,发送)** + +总长度 21 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x40 | +| 1 | start2 | u8 | 起始符 0x40 | +| 2~3 | id | u16 | 消息标识 0x0001 | +| 4~5 | length | u16 | 数据区长度(固定 18) | +| 6 | fuelCellCircuitBreaker | u8 | 燃料电池断路器 | +| 7 | powerLithiumBatteryCircuitBreaker | u8 | 动力锂电池断路器 | +| 8 | propulsionMotorCircuitBreaker | u8 | 推进电机断路器 | +| 9 | lithiumBatteryGroupInstrumentCircuitBreaker | u8 | 锂电池组仪表断路器 | +| 10 | dcDc5ModuleCircuitBreaker | u8 | DC/DC5模块断路器 | +| 11 | bowHighVoltageDistributionBoxCircuitBreaker | u8 | 艏部高压配电箱断路器 | +| 12 | sternFTDevice45CircuitBreaker | u8 | 艉部FT装置4/5断路器 | +| 13 | fbReservedCircuitBreaker | u8 | FB预留断路器 | +| 14 | sternRudderSwitch1CircuitBreaker | u8 | 艉舵开关1断路器 | +| 15 | sternRudderSwitch2CircuitBreaker | u8 | 艉舵开关2断路器 | +| 16 | tyzReservedCircuitBreaker | u8 | TYZ预留断路器 | +| 17 | reservedCircuitBreaker | u8 | 预留断路器 | +| 18 | dcdc5Module | u8 | DC/DC5模块启动 | +| 19 | coolingSystem | u8 | 冷却系统启动 | +| 20 | checkCode | u8 | 校验和(累加和低 8 位) | + +**反馈 `msg_disHighVolBusFbMsg`(0x0005,接收)** + +总长度 60 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x40 | +| 1 | start2 | u8 | 起始符 0x40 | +| 2~3 | id | u16 | 消息标识 0x0005 | +| 4~5 | length | u16 | 数据区长度 | +| 6 | fuelCellCircuitBreaker | u8 | 燃料电池断路器状态 | +| 7 | powerLithiumBatteryCircuitBreaker | u8 | 动力锂电池断路器状态 | +| 8 | propulsionMotorCircuitBreaker | u8 | 推进电机断路器状态 | +| 9 | lithiumBatteryGroupInstrumentCircuitBreaker | u8 | 锂电池组仪表断路器状态 | +| 10 | dcDc5ModuleCircuitBreaker | u8 | DC/DC5模块断路器状态 | +| 11 | bowHighVoltageDistributionBoxCircuitBreaker | u8 | 艏部高压配电箱断路器状态 | +| 12 | sternFTDevice45CircuitBreaker | u8 | 艉部FT装置4/5断路器状态 | +| 13 | fbReservedCircuitBreaker | u8 | FB预留断路器状态 | +| 14 | sternRudderSwitch1CircuitBreaker | u8 | 艉舵开关1断路器状态 | +| 15 | sternRudderSwitch2CircuitBreaker | u8 | 艉舵开关2断路器状态 | +| 16 | tyzReservedCircuitBreaker | u8 | TYZ预留断路器状态 | +| 17 | reservedCircuitBreaker | u8 | 预留断路器状态 | +| 18 | dcDcFaultWord1 | u8 | DC/DC5故障字低(Bit0过温关机…Bit7内部故障) | +| 19 | dcDcFaultWord2 | u8 | DC/DC5故障字高(Bit0通讯故障,Bit1工作状态,Bit2/3冷却泵1/2故障,Bit5/6冷却泵1/2运行) | +| 20 | powerBusInsulationStatus | u8 | 动力母排绝缘状态(0x55正常,0xAA绝缘低) | +| 21 | aBusInsulationStatus | u8 | A汇流排绝缘状态(0x55正常,0xAA绝缘低) | +| 22 | meterPowerLossSignal | u8 | 仪表电源状态(0x55正常,0xAA故障) | +| 23 | emergencyPowerLossSignal | u8 | 应急电源状态(0x55正常,0xAA故障) | +| 24 | dcDcTemperature | u8 | DC/DC温度 | +| 25~26 | powerBusVoltage | u16 | 动力汇流排电压 | +| 27~28 | dcDc5ModuleCurrent | u16 | DC/DC5模块电流 | +| 29~30 | powerBusCurrent | u16 | 动力汇流排电流 | +| 31~32 | busbarAVoltage | u16 | A汇流排电压 | +| 33~34 | propulsionMotorControlBoxCurrent | u16 | 推进电机控制箱电流 | +| 35~36 | busbarACurrent | u16 | A汇流排电流 | +| 37~38 | lithiumBatteryGroupMeterCurrent | u16 | 锂电池组(仪表)电流 | +| 39~40 | bowHighVoltageDistributionBoxCurrent | u16 | 艏部高压配电箱电流 | +| 41~42 | sternFTDevice45Current | u16 | 艉部FT装置4/5电流 | +| 43~44 | tyzReservedSwitchCurrent | u16 | TYZ预留开关电流 | +| 45~46 | sternRudderSwitch1Current | u16 | 艉舵开关1电流 | +| 47~48 | reservedCurrent1 | u16 | 预留电流1 | +| 49~50 | sternRudderSwitch2Current | u16 | 艉舵开关2电流 | +| 51~52 | reservedCurrent2 | u16 | 预留电流2 | +| 53~54 | fbReservedSwitchCurrent | u16 | FB预留电流 | +| 55~56 | reservedCurrent3 | u16 | 预留电流3 | +| 57~58 | coolWaterPressure | u16 | 冷却水压力 | +| 59 | checkCode | u8 | 校验和(累加和低 8 位) | + +#### 3.5.2 高压汇流排 A(BUS2) + +**指令 `msg_disHVBACmd`(0x0002,发送)** + +总长度 13 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x40 | +| 1 | start2 | u8 | 起始符 0x40 | +| 2~3 | id | u16 | 消息标识 0x0002 | +| 4~5 | length | u16 | 数据区长度(固定 6) | +| 6 | bowFTDevice123CircuitBreaker | u8 | 艏部FT装置1/2/3断路器 | +| 7 | actuatorCircuitBreaker | u8 | 启闭机构断路器 | +| 8 | mastSteeringGearControlBoxCircuitBreaker | u8 | 桅杆舵机控制箱断路器 | +| 9 | xczCircuitBreaker | u8 | XCZ断路器 | +| 10 | bowRudderControlBoxCircuitBreaker | u8 | 艏舵控制箱断路器 | +| 11 | openWaterCoverStartCylinderCircuitBreaker | u8 | 敞水盖启动电缸断路器 | +| 12 | checkCode | u8 | 校验和(累加和低 8 位) | + +**反馈 `msg_disHighAVolBusFbMsg`(0x0006,接收)** + +总长度 32 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x40 | +| 1 | start2 | u8 | 起始符 0x40 | +| 2~3 | id | u16 | 消息标识 0x0006 | +| 4~5 | length | u16 | 数据区长度 | +| 6 | bowFTDevice123CircuitBreaker | u8 | 艏部FT装置1/2/3断路器状态 | +| 7 | actuatorCircuitBreaker | u8 | 启闭机构断路器状态 | +| 8 | mastSteeringGearControlBoxCircuitBreaker | u8 | 桅杆舵机控制箱断路器状态 | +| 9 | xczCircuitBreaker | u8 | XCZ断路器状态 | +| 10 | bowRudderControlBoxCircuitBreaker | u8 | 艏舵控制箱断路器状态 | +| 11 | openWaterCoverStartCylinderCircuitBreaker | u8 | 敞水盖启动电缸断路器状态 | +| 12 | instrumentPowerFailureSignal | u8 | 仪表电源失电信号(0x55正常,0xAA故障) | +| 13 | emergencyPowerFailureSignal | u8 | 应急电源失电信号(0x55正常,0xAA故障) | +| 14 | waterIngressionAlarm | u8 | 浸水报警 | +| 15~16 | busbarBVoltage | u16 | 汇流排B电压 | +| 17~18 | busbarBCurrent | u16 | 汇流排B电流 | +| 19~20 | bowFTDevice123Current | u16 | 艏部FT装置1/2/3电流 | +| 21~22 | actuatorCurrent | u16 | 启闭机构电流 | +| 23~24 | mastSteeringGearControlBoxCurrent | u16 | 桅杆舵机控制箱电流 | +| 25~26 | xczCurrent | u16 | XCZ电流 | +| 27~28 | bowRudderControlBoxCurrent | u16 | 艏舵控制箱电流 | +| 29~30 | openWaterCoverStartCylinderCurrent | u16 | 敞水盖启动电缸电流 | +| 31 | checkCode | u8 | 校验和(累加和低 8 位) | + +#### 3.5.3 仪表母线(BUS3,低压主母线) + +**指令 `msg_disHVBBCmd`(0x0003,发送)** + +总长度 14 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x40 | +| 1 | start2 | u8 | 起始符 0x40 | +| 2~3 | id | u16 | 消息标识 0x0003 | +| 4~5 | length | u16 | 数据区长度(固定 8) | +| 6 | lithiumBatteryGroupInstrumentCircuitBreaker | u8 | 锂电池组(仪表)断路器 | +| 7 | bowLowVoltageDistributionBoxCircuitBreaker | u8 | 艏部低压配电箱断路器 | +| 8 | unit4InstrumentDC48VCircuitBreaker | u8 | UNIT4仪表DC48V断路器 | +| 9 | dcC1DCDistributionPanelCircuitBreaker | u8 | DC-C1直流配电板断路器 | +| 10 | reservedCircuitBreaker1 | u8 | 预留断路器1 | +| 11 | reservedCircuitBreaker2 | u8 | 预留断路器2 | +| 12 | emergencyLithiumBatteryGroup2CircuitBreaker | u8 | 应急锂电池组2断路器 | +| 13 | checkCode | u8 | 校验和(累加和低 8 位) | + +**反馈 `msg_disLowMainBusFbMsg`(0x0007,接收)** + +总长度 41 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x40 | +| 1 | start2 | u8 | 起始符 0x40 | +| 2~3 | id | u16 | 消息标识 0x0007 | +| 4~5 | length | u16 | 数据区长度 | +| 6 | lithiumBatteryGroupInstrumentCircuitBreaker | u8 | 锂电池组(仪表)断路器状态 | +| 7 | bowLowVoltageDistributionBoxCircuitBreaker | u8 | 艏部低压配电箱断路器状态 | +| 8 | unit4InstrumentDC48VCircuitBreaker | u8 | UNIT4仪表DC48V断路器状态 | +| 9 | dcC1DCDistributionPanelCircuitBreaker | u8 | DC-C1直流配电板断路器状态 | +| 10 | reservedCircuitBreaker1 | u8 | 预留断路器1状态 | +| 11 | reservedCircuitBreaker2 | u8 | 预留断路器2状态 | +| 12 | emergencyLithiumBatteryGroup2CircuitBreaker | u8 | 应急锂电池组2断路器状态 | +| 13 | instrumentPowerFailureSignal | u8 | 仪表电源失电信号(0x55正常,0xAA故障) | +| 14 | emergencyPowerFailureSignal | u8 | 应急电源失电信号(0x55正常,0xAA故障) | +| 15 | instrumentBusbarInsulationLow | u8 | 仪表汇流排绝缘低(0x55正常,0x55绝缘低) | +| 16~17 | instrumentBusbarVoltage | u16 | 仪表母排电压 | +| 18~19 | bowLowVoltageDistributionBoxCurrent | u16 | 艏部低压配电箱电流 | +| 20~21 | dcC1InstrumentDC48VCurrent | u16 | DC-C1仪表DC48V电流 | +| 22~23 | reservedCurrent1 | u16 | 预留电流1 | +| 24~25 | emergencyLithiumBatteryGroup2Current | u16 | 应急锂电池组2电流 | +| 26~27 | lithiumBatteryGroupInstrumentCurrent | u16 | 锂电池组(仪表)电流 | +| 28~29 | unit4InstrumentDC48VCurrent | u16 | UNIT4仪表DC48V电流 | +| 30~31 | reservedCurrent2 | u16 | 预留电流2 | +| 32~33 | emergency2BusbarVoltage | u16 | 应急2汇流排电压 | +| 34~35 | compositeEnergyManagementSystemEmergencyDC48VCurrent | u16 | 复合能源管控系统应急DC48V电流 | +| 36~37 | fuelCellSecuritySystemEmergencyDC48VCurrent | u16 | 燃料电池安全系统应急DC48V电流 | +| 38~39 | plcControlPowerCurrent | u16 | PLC控制电源电流 | +| 40 | checkCode | u8 | 校验和(累加和低 8 位) | + +#### 3.5.4 仪表汇流排(BUS4) + +**指令 `msg_disLVBCmd`(0x0004,发送)** + +总长度 15 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x40 | +| 1 | start2 | u8 | 起始符 0x40 | +| 2~3 | id | u16 | 消息标识 0x0004 | +| 4~5 | length | u16 | 数据区长度(固定 8) | +| 6 | unit1CircuitBreaker | u8 | UNIT1断路器 | +| 7 | unit2CircuitBreaker | u8 | UNIT2断路器 | +| 8 | unit3CircuitBreaker | u8 | UNIT3断路器 | +| 9 | unit5CircuitBreaker | u8 | UNIT5断路器 | +| 10 | bowPZDeviceCircuitBreaker | u8 | 艏部PZ装置断路器 | +| 11 | reservedCircuitBreaker1 | u8 | 预留断路器1 | +| 12 | reservedCircuitBreaker2 | u8 | 预留断路器2 | +| 13 | emergencyLithiumBatteryGroup1CircuitBreaker | u8 | 应急锂电池组1断路器 | +| 14 | checkCode | u8 | 校验和(累加和低 8 位) | + +**反馈 `msg_disLowBusFbMsg`(0x0008,接收)** + +总长度 36 字节。 + +| 偏移(字节) | 字段 | 类型 | 说明 | +|------------|------|------|------| +| 0 | start1 | u8 | 起始符 0x40 | +| 1 | start2 | u8 | 起始符 0x40 | +| 2~3 | id | u16 | 消息标识 0x0008 | +| 4~5 | length | u16 | 数据区长度 | +| 6 | unit1CircuitBreaker | u8 | UNIT1断路器状态 | +| 7 | unit2CircuitBreaker | u8 | UNIT2断路器状态 | +| 8 | unit3CircuitBreaker | u8 | UNIT3断路器状态 | +| 9 | unit5CircuitBreaker | u8 | UNIT5断路器状态 | +| 10 | bowPZDeviceCircuitBreaker | u8 | 艏部PZ装置断路器状态 | +| 11 | reservedCircuitBreaker1 | u8 | 预留断路器1状态 | +| 12 | reservedCircuitBreaker2 | u8 | 预留断路器2状态 | +| 13 | emergencyLithiumBatteryGroup1CircuitBreaker | u8 | 应急锂电池组1断路器状态 | +| 14 | powerFailureSignal | u8 | 仪表电源失电信号(0x55正常,0xAA故障) | +| 15 | emergencyPowerFailureSignal | u8 | 应急电源失电信号(0x55正常,0xAA故障) | +| 16 | waterIngressionAlarm | u8 | 浸水报警(0x55正常,0xAA故障) | +| 17~18 | instrumentBusbarVoltage | u16 | 仪表母排电压 | +| 19~20 | instrumentBusbarCurrent | u16 | 仪表母排电流 | +| 21~22 | unit1Current | u16 | UNIT1电流 | +| 23~24 | unit2Current | u16 | UNIT2电流 | +| 25~26 | unit3Current | u16 | UNIT3电流 | +| 27~28 | unit5Current | u16 | UNIT5电流 | +| 29~30 | bowPZDeviceCurrent | u16 | 艏部PZ装置电流 | +| 31~32 | emergencyLithiumBatteryGroup1Current | u16 | 应急锂电池组1电流 | +| 33~34 | emergency1BusbarVoltage | u16 | 应急1汇流排电压 | +| 35 | checkCode | u8 | 校验和(累加和低 8 位) | + +--- + +## 4. 附录:常量与枚举 + +### 4.1 断路器 / 信号常量 + +```c +#define BREAK_OFF 0xAA // 分闸 +#define BREAK_ON 0x55 // 合闸 +#define BREAK_FALSE 0x5A // 故障脱扣(仅反馈) +``` + +### 4.2 燃料电池控制指令 `FcuCommand` + +| 值 | 含义 | +|----|------| +| 0x01 | 自检 SelfCheck | +| 0x02 | 启动 Start | +| 0x03 | 停机 Stop | +| 0x04 | 复位 Reset | +| 0x05 | 急停 EmergencyStop | +| 0x06 | 补给 Supply | +| 0x07 | 预热 WarmUp | +| 0x08 | 维护 Maintenance | + +### 4.3 燃料电池状态 `FcuState` + +| 值 | 含义 | 值 | 含义 | +|----|------|----|------| +| 0x01 | 初始化 Init | 0x08 | 故障 Fault | +| 0x02 | 待机 Standby | 0x09 | 补给 Supply | +| 0x03 | 自检异常 SelfCheckError | 0x0A | 预热 WarmUp | +| 0x04 | 就绪 Ready | 0x0B | 预热完成 WarmUpComplete | +| 0x05 | 启动 Start | 0x0C | 维护 Maintenance | +| 0x06 | 运行 Running | 0x0D | 维护完成 MaintenanceComplete | +| 0x07 | 停机 Stop | — | — | + +### 4.4 电池指令 `BatteryCommand` + +| 值 | 含义 | +|----|------| +| 0x00 | 保持 Hold | +| 0x10 | 启动 Start | +| 0x20 | 停机 Stop | + +### 4.5 设备控制 `EquipmentCommand` + +| 值 | 含义 | +|----|------| +| 0 | 断电 OFF | +| 1 | 上电 ON | +| 2 | 仪表电上电 METER_ON | +| 3 | 动力电上电 POWER_ON | +| 4 | 仪表电断电 METER_OFF | +| 5 | 动力电断电 POWER_OFF | + +### 4.6 任务类型 `TaskType` + +| 值 | 含义 | 字符串 | +|----|------|--------| +| 1 | 抛载 | throwLoad | +| 2 | 运动上浮 | floatUp | +| 3 | 回收 | recycle | +| 4 | 悬停 | hover | +| 5 | 上浮通信 | satComm | +| 6 | 上浮校准 | satCalibrate | +| 7 | 游曳 | tour | +| 8 | 航行 | sail | +| 9 | 紧急上浮通信 | emerSatComm | +| 10 | 紧急航行 | emerSail | + +### 4.7 工况 `WorkCondition` + +| 值 | 含义 | 值 | 含义 | +|----|------|----|------| +| 0 | 工况切换中 | 7 | 上浮下潜 | +| 1 | 待机 | 8 | 浮调模式 | +| 2 | 岸基备航 | 9 | 水下侦查 | +| 3 | 水中备航 | 10 | 水面侦查 | +| 4 | 遥控工况 | 11 | DJ模式 | +| 5 | 巡航模式 | 12 | 调试模式 | +| 6 | 高速模式 | — | — | + +### 4.8 运行模式 `RunMode` + +| 值 | 含义 | +|----|------| +| 0 | 正常模式 Normal | +| 1 | 测试模式 Test | +| 2 | 调试模式 Debug | + +### 4.9 故障码格式 + +- 系统故障码:`faultCode` 中每个值为 `0xabcd`,`b` 位(右移 8 位取低 4 位)表示故障等级(`PowerManger::getFaultLevel()`)。 +- 锂电池故障码映射表:`src/pPowerManger/faultCode.h` 中 `batfaultMap`(1~4 级,0x01~0xC9)。 +- 配电故障码映射表:`src/pPowerManger/faultCode.h` 中 `disfaultMap`(1~45 项断路器脱扣/绝缘/失电/浸水故障)。 diff --git a/scripts/run_host_sim.sh b/scripts/run_host_sim.sh new file mode 100755 index 0000000..0b4a815 --- /dev/null +++ b/scripts/run_host_sim.sh @@ -0,0 +1,306 @@ +#!/bin/bash +#======================================================================= +# FILE: scripts/run_host_sim.sh +# DESC: 运行与测试 pPowerMangerHost(模拟 pPowerManger 上位机的网页程序) +# +# 用法: +# ./scripts/run_host_sim.sh run 启动 MOOSDB + pPowerManger +# + pPowerMangerHost(前台运行, +# Ctrl-C 退出并清理) +# ./scripts/run_host_sim.sh test 自动化测试 pPowerMangerHost: +# 网页 HTTP / WebSocket 反馈 / +# 指令下发到 MOOSDB +# ./scripts/run_host_sim.sh build 仅编译需要的目标 +# ./scripts/run_host_sim.sh stop 清理残留进程 +# ./scripts/run_host_sim.sh help 显示帮助 +# +# 环境变量(可覆盖): +# SERVER_HOST MOOSDB 地址(默认 localhost) +# SERVER_PORT MOOSDB 端口(默认 9000) +# WEB_PORT 网页监听端口(默认 18080) +# WITH_POWERMANGER run 模式是否同时启动 pPowerManger(默认 1) +#======================================================================= +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BIN_DIR="${ROOT}/bin" +MOOS_TEMPLATE="${ROOT}/src/pPowerMangerHost/pPowerMangerHost.moos" + +SERVER_HOST="${SERVER_HOST:-localhost}" +SERVER_PORT="${SERVER_PORT:-9000}" +WEB_PORT="${WEB_PORT:-18080}" +WITH_POWERMANGER="${WITH_POWERMANGER:-1}" + +MOOSDB_BIN="${MOOSDB_BIN:-$(command -v MOOSDB || true)}" +POKE_BIN="$(command -v uPokeDB || true)" +PM_BIN="${BIN_DIR}/pPowerManger" +HOST_BIN="${BIN_DIR}/pPowerMangerHost" + +MOOSDB_PID="" +PM_PID="" +HOST_PID="" +WORKDIR="" +MISSION="" + +info() { printf '\033[1;36m[host-sim]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[host-sim]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[host-sim]\033[0m %s\n' "$*"; } +err() { printf '\033[1;31m[host-sim]\033[0m %s\n' "$*"; } + +usage() { + sed -n '2,23p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' + exit 0 +} + +# 生成临时 mission 文件(同时含 pPowerManger 与 pPowerMangerHost 两个配置块) +make_mission() { + WORKDIR="$(mktemp -d)" + MISSION="${WORKDIR}/host_sim.moos" + cat > "${MISSION}" </dev/null && wait "$1" 2>/dev/null +} + +cleanup() { + kill_proc "${HOST_PID}" + kill_proc "${PM_PID}" + kill_proc "${MOOSDB_PID}" + [ -n "${WORKDIR}" ] && rm -rf "${WORKDIR}" +} + +build_targets() { + info "编译 pPowerManger / pPowerMangerHost ..." + mkdir -p "${ROOT}/build" + cd "${ROOT}/build" + cmake -DCMAKE_BUILD_TYPE=Release "${ROOT}" >/dev/null 2>&1 || { err "cmake 失败"; return 1; } + make -j2 pPowerManger pPowerMangerHost >/dev/null 2>&1 || { err "make 失败"; return 1; } + cd "${ROOT}" + ok "编译完成" +} + +check_binaries() { + local missing=0 + [ -n "${MOOSDB_BIN}" ] || { err "未找到 MOOSDB(请加入 PATH)"; missing=1; } + [ -x "${HOST_BIN}" ] || { err "未找到 ${HOST_BIN}(请先运行: $0 build)"; missing=1; } + if [ "${WITH_POWERMANGER}" = "1" ]; then + [ -x "${PM_BIN}" ] || { err "未找到 ${PM_BIN}(请先运行: $0 build)"; missing=1; } + fi + return "${missing}" +} + +start_moosdb() { + make_mission + info "启动 MOOSDB (${SERVER_HOST}:${SERVER_PORT}) ..." + "${MOOSDB_BIN}" --moos_no_colour "${MISSION}" > "${WORKDIR}/moosdb.log" 2>&1 & + MOOSDB_PID=$! + sleep 1 + kill -0 "${MOOSDB_PID}" 2>/dev/null || { err "MOOSDB 未存活"; return 1; } +} + +start_pm() { + info "启动 pPowerManger ..." + ( cd "${WORKDIR}" && exec "${PM_BIN}" "${MISSION}" ) > "${WORKDIR}/pm.log" 2>&1 & + PM_PID=$! + sleep 1 + kill -0 "${PM_PID}" 2>/dev/null || warn "pPowerManger 未存活(无 CCU 硬件属正常)" +} + +start_host() { + info "启动 pPowerMangerHost (网页端口 ${WEB_PORT}) ..." + ( cd "${WORKDIR}" && exec "${HOST_BIN}" "${MISSION}" ) > "${WORKDIR}/host.log" 2>&1 & + HOST_PID=$! + sleep 2 + kill -0 "${HOST_PID}" 2>/dev/null || { err "pPowerMangerHost 未存活"; return 1; } +} + +#----------------------------------------------------------------------- +# 测试项 +#----------------------------------------------------------------------- +test_http() { + info "测试: 网页 HTTP (http://${SERVER_HOST}:${WEB_PORT}/) ..." + local code + code="$(curl -s -o /dev/null -w '%{http_code}' "http://${SERVER_HOST}:${WEB_PORT}/" 2>/dev/null || echo 000)" + if [ "${code}" = "200" ]; then + ok "网页 HTTP 200 通过" + return 0 + else + err "网页 HTTP 失败 (code=${code})" + return 1 + fi +} + +# 反馈链路:向 MOOSDB 注入一条假的反馈变量 → 宿主订阅后应经 WS 推给浏览器 +test_monitor_loop() { + info "测试: 反馈链路 (MOOSDB -> pPowerMangerHost -> WebSocket) ..." + if [ -z "${POKE_BIN}" ]; then + warn "未找到 uPokeDB,跳过反馈链路测试" + return 0 + fi + if ! python3 -c "import websockets" >/dev/null 2>&1; then + warn "未安装 python3 websockets,跳过反馈链路测试" + return 0 + fi + + local fb_key="uPower_pmState_fb" + local fb_val='{"workCondition_uint8":9,"currentMod_uint8":1,"powerState_uint8":1,"faultLevel_uint8":0,"soc_uint16":88,"sustainableTime_int64":3600,"leakage_uint8":0,"info_str":"host-sim-test"}' + + # 先让 WS 客户端连接等待快照,再注入反馈 + FB_KEY="${fb_key}" python3 - "${WEB_PORT}" > "${WORKDIR}/ws_fb.log" 2>&1 <<'PY' & +import asyncio, json, os, sys +import websockets + +PORT = int(sys.argv[1]) +KEY = os.environ["FB_KEY"] + +async def main(): + uri = f"ws://localhost:{PORT}/ws" + async with websockets.connect(uri) as ws: + try: + while True: + msg = await asyncio.wait_for(ws.recv(), timeout=8) + snap = json.loads(msg) + if isinstance(snap, dict) and KEY in snap: + print(f"FEEDBACK_OK {KEY}") + return + except asyncio.TimeoutError: + pass + print("FEEDBACK_TIMEOUT") + +asyncio.run(main()) +PY + WS_PID=$! + sleep 1 + + "${POKE_BIN}" --host="${SERVER_HOST}" --port="${SERVER_PORT}" "${fb_key}=${fb_val}" >/dev/null 2>&1 + wait "${WS_PID}" 2>/dev/null + + if grep -q "FEEDBACK_OK" "${WORKDIR}/ws_fb.log" 2>/dev/null; then + ok "反馈链路通过(${fb_key} 已推送到浏览器)" + return 0 + else + err "反馈链路失败(WS 未收到 ${fb_key})" + cat "${WORKDIR}/ws_fb.log" >&2 + return 1 + fi +} + +# 指令下发:网页 WS 下发 -> 宿主 Notify -> MOOSDB -> (宿主订阅回环) -> 推回浏览器 +# 利用宿主订阅了 uPower_pmState_fb,下发该变量后经 MOOSDB 回环,能收到即证明 +# 指令确实进入了 MOOSDB。 +test_command_notify() { + info "测试: 指令下发 (WebSocket -> pPowerMangerHost -> MOOSDB) ..." + if ! python3 -c "import websockets" >/dev/null 2>&1; then + warn "未安装 python3 websockets,跳过指令下发校验" + return 0 + fi + + python3 - "${WEB_PORT}" > "${WORKDIR}/ws_cmd.log" 2>&1 <<'PY' +import asyncio, json, sys +import websockets + +PORT = int(sys.argv[1]) +KEY = "uPower_pmState_fb" +TAG = "cmd-loopback-test" + +async def main(): + uri = f"ws://localhost:{PORT}/ws" + async with websockets.connect(uri) as ws: + await ws.send(json.dumps({"key": KEY, "value": {"info_str": TAG, "workCondition_uint8": 7}})) + try: + while True: + msg = await asyncio.wait_for(ws.recv(), timeout=8) + snap = json.loads(msg) + if isinstance(snap, dict) and KEY in snap: + obj = json.loads(snap[KEY]) + if obj.get("info_str") == TAG: + print("CMD_OK") + return + except asyncio.TimeoutError: + pass + print("CMD_TIMEOUT") + +asyncio.run(main()) +PY + local r=$? + + if [ ${r} -eq 0 ] && grep -q "CMD_OK" "${WORKDIR}/ws_cmd.log" 2>/dev/null; then + ok "指令下发到 MOOSDB 通过(uPower_pmState_fb 已回环)" + return 0 + else + err "指令下发校验失败:未收到回环快照" + cat "${WORKDIR}/ws_cmd.log" >&2 + return 1 + fi +} + +#----------------------------------------------------------------------- +# 命令入口 +#----------------------------------------------------------------------- +cmd_run() { + check_binaries || return 1 + start_moosdb || return 1 + [ "${WITH_POWERMANGER}" = "1" ] && start_pm + start_host || return 1 + + info "全部启动完成。" + info "浏览器访问: http://${SERVER_HOST}:${WEB_PORT}/" + info "按 Ctrl-C 退出并清理。" + echo "" + + trap 'info "退出清理..."; cleanup; exit 0' INT TERM + while kill -0 "${HOST_PID}" 2>/dev/null; do sleep 1; done + info "pPowerMangerHost 已退出。" + cleanup +} + +cmd_test() { + check_binaries || return 1 + start_moosdb || return 1 + start_host || return 1 + + local pass=0 fail=0 r + + test_http; r=$?; [ $r -eq 0 ] && pass=$((pass+1)) || fail=$((fail+1)) + test_monitor_loop; r=$?; [ $r -eq 0 ] && pass=$((pass+1)) || fail=$((fail+1)) + test_command_notify;r=$?; [ $r -eq 0 ] && pass=$((pass+1)) || fail=$((fail+1)) + + echo "" + info "==== 测试结果: 通过 ${pass} / 失败 ${fail} ====" + cleanup + [ "${fail}" -eq 0 ] +} + +cmd_stop() { + pkill -f "${HOST_BIN}" 2>/dev/null + pkill -f "${PM_BIN}" 2>/dev/null + pkill -x MOOSDB 2>/dev/null + ok "已清理残留进程" +} + +case "${1:-run}" in + run) cmd_run ;; + test) cmd_test ;; + build) build_targets ;; + stop) cmd_stop ;; + help|-h|--help) usage ;; + *) usage ;; +esac diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 900b228..40c1d62 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ INCLUDE_DIRECTORIES(${LOCAL_LIBRARY_DIRS}) ADD_SUBDIRECTORY(pPowerManger) # ADD_SUBDIRECTORY(appPowerMangerWebUI) add_subdirectory(pPMtest) +add_subdirectory(pPowerMangerHost) ############################################################################## # END of CMakeLists.txt ############################################################################## diff --git a/src/pPowerManger/httpserver/httpserver.cpp b/src/pPowerManger/httpserver/httpserver.cpp index 68c82dc..0a466dc 100644 --- a/src/pPowerManger/httpserver/httpserver.cpp +++ b/src/pPowerManger/httpserver/httpserver.cpp @@ -865,7 +865,7 @@ void HttpServer::handleLogRequest(struct mg_connection* c, const std::string& ms response["type"] = "log"; response["page"] = page; response["pageSize"] = pageSize; - response["total"] = static_cast(logs.size()); // 返回总条数 + response["total"] = static_cast(logs.size()); // 返回总条数 response["content"] = Json::arrayValue; if (reverse) { diff --git a/src/pPowerManger/upmsg/uPower_disSysState.h b/src/pPowerManger/upmsg/uPower_disSysState.h index e7f7fed..6af268a 100644 --- a/src/pPowerManger/upmsg/uPower_disSysState.h +++ b/src/pPowerManger/upmsg/uPower_disSysState.h @@ -152,7 +152,7 @@ public: m_state_of_bus_hv.set_fb_reserved_breaker((data.power_bus_status.fbReservedCircuitBreaker == 1)); m_state_of_bus_hv.set_tyz_reserved_breaker((data.power_bus_status.tyzReservedCircuitBreaker == 1)); m_state_of_bus_hv.set_reserved_breaker((data.power_bus_status.reservedCircuitBreaker == 1)); - dis["HVolABusBreaker_uint16"] = static_cast(m_state_of_bus_hv.relay_states.to_ulong()); + dis["HVolABusBreaker_uint16"] = static_cast(m_state_of_bus_hv.relay_states.to_ulong()); m_state_of_bus_hv_a.set_bow_ft_device_1_3_breaker((data.power_a_bus_status.bowFTDevice123CircuitBreaker == 1)); @@ -161,7 +161,7 @@ public: m_state_of_bus_hv_a.set_xcz_breaker((data.power_a_bus_status.xczCircuitBreaker == 1)); m_state_of_bus_hv_a.set_bow_rudder_control_box_breaker((data.power_a_bus_status.bowRudderControlBoxCircuitBreaker == 1)); m_state_of_bus_hv_a.set_open_water_cover_actuator_breaker((data.power_a_bus_status.openWaterCoverStartCylinderCircuitBreaker == 1)); - dis["HVolBBusBreaker_uint16"] = static_cast(m_state_of_bus_hv_a.relay_states.to_ulong()); + dis["HVolBBusBreaker_uint16"] = static_cast(m_state_of_bus_hv_a.relay_states.to_ulong()); m_state_of_bus_lv.set_bow_low_voltage_box_breaker((data.instrument_main_bus_status.bowLowVoltageDistributionBoxCircuitBreaker == 1)); m_state_of_bus_lv.set_dc_c1_dc_distribution_board_breaker((data.instrument_main_bus_status.dcC1DCDistributionPanelCircuitBreaker == 1)); @@ -170,7 +170,7 @@ public: m_state_of_bus_lv.set_emergency_lithium_battery_group_2_breaker((data.instrument_main_bus_status.emergencyLithiumBatteryGroup2CircuitBreaker == 1)); m_state_of_bus_lv.set_reserved_breaker_1((data.instrument_main_bus_status.reservedCircuitBreaker1 == 1)); m_state_of_bus_lv.set_reserved_breaker_2((data.instrument_main_bus_status.reservedCircuitBreaker2 == 1)); - dis["HVolBusBreaker_uint16"] = static_cast(m_state_of_bus_lv.relay_states.to_ulong()); + dis["HVolBusBreaker_uint16"] = static_cast(m_state_of_bus_lv.relay_states.to_ulong()); m_state_of_bus_lv_a.set_unit1_breaker((data.instrument_bus_status.unit1CircuitBreaker == 1)); @@ -181,7 +181,7 @@ public: m_state_of_bus_lv_a.set_reserved_breaker_1((data.instrument_bus_status.reservedCircuitBreaker1 == 1)); m_state_of_bus_lv_a.set_reserved_breaker_2((data.instrument_bus_status.reservedCircuitBreaker2 == 1)); m_state_of_bus_lv_a.set_emergency_lithium_battery_group_1_breaker((data.instrument_bus_status.emergencyLithiumBatteryGroup1CircuitBreaker == 1)); - dis["LVolBusBreaker_uint16"] = static_cast(m_state_of_bus_lv_a.relay_states.to_ulong()); + dis["LVolBusBreaker_uint16"] = static_cast(m_state_of_bus_lv_a.relay_states.to_ulong()); //构建配电系统故障码 Json::Value faultCodeDis_array = Json::Value(Json::arrayValue); diff --git a/src/pPowerMangerHost/CMakeLists.txt b/src/pPowerMangerHost/CMakeLists.txt new file mode 100644 index 0000000..6f0d0fe --- /dev/null +++ b/src/pPowerMangerHost/CMakeLists.txt @@ -0,0 +1,115 @@ +#-------------------------------------------------------- +# The CMakeLists.txt for: pPowerMangerHost +# Description: 模拟 pPowerManger 上位机(发指令 / 监控), +# 通过 mongoose HTTP+WebSocket 提供网页交互。 +# 复用 src/pPowerManger/httpserver/mongoose.c, +# 不新增任何第三方依赖。 +#-------------------------------------------------------- + +SET(MONGOOSE_SRC + ${CMAKE_SOURCE_DIR}/src/pPowerManger/httpserver/mongoose.c) + +SET(SRC + HostSim.cpp + HostSim_Info.cpp + WebServer.cpp + main.cpp + ${MONGOOSE_SRC} +) + +#-------------------------------------------------------- +# web/ 目录 -> webassets_gen.h : 通用嵌入式资源表 +# 扫描 web/ 下所有文件,逐个读成字节数组,并生成 {路径, MIME, 数据} 注册表。 +# 新增页面/资源只需放入 web/ 目录,CMake 自动重新生成(含重新 configure)。 +#-------------------------------------------------------- +SET(WEB_ASSET_DIR ${CMAKE_CURRENT_SOURCE_DIR}/web) +SET(WEB_ASSET_HDR ${CMAKE_CURRENT_BINARY_DIR}/webassets_gen.h) + +FILE(GLOB WEB_ASSET_FILES ${WEB_ASSET_DIR}/*) +LIST(SORT WEB_ASSET_FILES) +LIST(LENGTH WEB_ASSET_FILES WEB_ASSET_COUNT) +IF(NOT WEB_ASSET_FILES) + MESSAGE(FATAL_ERROR "web/ 目录为空,请至少放入一个页面资源") +ENDIF() + +# 目录内文件增删/修改时自动重新 configure +SET_PROPERTY(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + ${WEB_ASSET_DIR} ${WEB_ASSET_FILES}) + +SET(WEB_ASSET_HEADER +"// Auto-generated from web/, DO NOT EDIT +#ifndef WEBASSETS_GEN_H +#define WEBASSETS_GEN_H + +#include + +namespace webassets { + +struct Asset { + const char* path; + const char* mime; + const unsigned char* data; + unsigned long len; +}; + +") + +SET(WEB_ASSET_ARRAYS "") +SET(WEB_ASSET_REGISTRY "") + +FOREACH(f ${WEB_ASSET_FILES}) + GET_FILENAME_COMPONENT(name ${f} NAME) + STRING(MAKE_C_IDENTIFIER ${name} arrname) + + # 读成十六进制字节序列,再转成 C 数组初始化 + FILE(READ ${f} hexdata HEX) + STRING(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," hexdata "${hexdata}") + + # 按扩展名推断 MIME(if/elseif 体内命令须另起一行) + SET(mime "application/octet-stream") + IF(name MATCHES "\\.(html|htm)$") + SET(mime "text/html; charset=utf-8") + ELSEIF(name MATCHES "\\.css$") + SET(mime "text/css") + ELSEIF(name MATCHES "\\.js$") + SET(mime "application/javascript") + ELSEIF(name MATCHES "\\.json$") + SET(mime "application/json") + ELSEIF(name MATCHES "\\.png$") + SET(mime "image/png") + ELSEIF(name MATCHES "\\.(jpe?g)$") + SET(mime "image/jpeg") + ELSEIF(name MATCHES "\\.gif$") + SET(mime "image/gif") + ELSEIF(name MATCHES "\\.svg$") + SET(mime "image/svg+xml") + ELSEIF(name MATCHES "\\.(woff2?)$") + SET(mime "font/woff2") + ELSEIF(name MATCHES "\\.txt$") + SET(mime "text/plain") + ENDIF() + + STRING(APPEND WEB_ASSET_ARRAYS + "static const unsigned char ${arrname}[] = { ${hexdata} };\n\n") + STRING(APPEND WEB_ASSET_REGISTRY + " { \"${name}\", \"${mime}\", ${arrname}, sizeof(${arrname}) },\n") +ENDFOREACH() + +FILE(WRITE ${WEB_ASSET_HDR} +"${WEB_ASSET_HEADER}${WEB_ASSET_ARRAYS}static const Asset REGISTRY[] = {\n${WEB_ASSET_REGISTRY}};\n\ninline const Asset* find(const char* path) {\n const unsigned long n = sizeof(REGISTRY) / sizeof(REGISTRY[0]);\n for (unsigned long i = 0; i < n; ++i) {\n if (strcmp(REGISTRY[i].path, path) == 0) return ®ISTRY[i];\n }\n return NULL;\n}\n\n}\n#endif\n") + +MESSAGE(STATUS "Generated ${WEB_ASSET_HDR} (${WEB_ASSET_COUNT} files) from ${WEB_ASSET_DIR}") + + +ADD_EXECUTABLE(pPowerMangerHost ${SRC}) + +TARGET_INCLUDE_DIRECTORIES(pPowerMangerHost PRIVATE + ${CMAKE_SOURCE_DIR}/src/pPowerManger/httpserver + ${CMAKE_CURRENT_BINARY_DIR}) + +TARGET_LINK_LIBRARIES(pPowerMangerHost + ${MOOS_LIBRARIES} + mbutil + m + pthread + jsoncpp) diff --git a/src/pPowerMangerHost/HostSim.cpp b/src/pPowerMangerHost/HostSim.cpp new file mode 100644 index 0000000..4d3d268 --- /dev/null +++ b/src/pPowerMangerHost/HostSim.cpp @@ -0,0 +1,229 @@ +/************************************************************/ +/* NAME: */ +/* ORGN: MIT */ +/* FILE: HostSim.cpp */ +/* DATE: */ +/************************************************************/ + +#include +#include +#include "MBUtils.h" +#include "HostSim.h" +#include "WebServer.h" +#include "json/json.h" + +using namespace std; + +//--------------------------------------------------------- +// Constructor + +HostSim::HostSim() + : m_feedbackDirty(false), m_webServer(nullptr), m_webPort(18080) +{ +} + +//--------------------------------------------------------- +// Destructor + +HostSim::~HostSim() +{ + if (m_webServer) { + m_webServer->stop(); + delete m_webServer; + m_webServer = nullptr; + } +} + +//--------------------------------------------------------- +// Procedure: OnNewMail + +bool HostSim::OnNewMail(MOOSMSG_LIST &NewMail) +{ + MOOSMSG_LIST::iterator p; + for (p = NewMail.begin(); p != NewMail.end(); p++) { + CMOOSMsg &msg = *p; + string key = msg.GetKey(); + + if (msg.IsString()) { + lock_guard lock(m_feedbackMutex); + m_feedback[key] = msg.GetString(); + m_feedbackDirty = true; + } + } + return true; +} + +//--------------------------------------------------------- +// Procedure: OnConnectToServer + +bool HostSim::OnConnectToServer() +{ + RegisterVariables(); + return true; +} + +//--------------------------------------------------------- +// Procedure: Iterate() +// happens AppTick times per second + +bool HostSim::Iterate() +{ + // 1. 处理网页下发的指令 -> Notify 到 MOOSDB + drainCommands(); + + // 2. 若有反馈更新,推送快照到所有网页客户端 + bool dirty = false; + { + lock_guard lock(m_feedbackMutex); + dirty = m_feedbackDirty; + } + if (dirty && m_webServer) { + m_webServer->broadcast(buildSnapshot()); + lock_guard lock(m_feedbackMutex); + m_feedbackDirty = false; + } + + return true; +} + +//--------------------------------------------------------- +// Procedure: OnStartUp() + +bool HostSim::OnStartUp() +{ + list sParams; + m_MissionReader.EnableVerbatimQuoting(false); + if (m_MissionReader.GetConfiguration(GetAppName(), sParams)) { + list::iterator p; + for (p = sParams.begin(); p != sParams.end(); p++) { + string line = *p; + string param = tolower(biteStringX(line, '=')); + string value = line; + + if (param == "web_port") { + m_webPort = atoi(value.c_str()); + } + } + } + + // 启动网页服务器 + m_webServer = new WebServer(); + m_webServer->setOnMessage([this](const string& msg) { + this->handleWebMessage(msg); + }); + m_webServer->start(m_webPort); + + RegisterVariables(); + return true; +} + +//--------------------------------------------------------- +// Procedure: handleWebMessage +// 网页下发 JSON: {"key":"...", "value":{...}} 或 +// {"key":"...", "value":"..."},把 value 序列化为字符串后入队 + +void HostSim::handleWebMessage(const std::string& msg) +{ + Json::CharReaderBuilder builder; + Json::Value root; + JSONCPP_STRING errs; + istringstream stream(msg); + if (!Json::parseFromStream(builder, stream, &root, &errs)) { + return; + } + + if (!root.isMember("key")) { + return; + } + + string key = root["key"].asString(); + string valueStr; + + if (root.isMember("value")) { + const Json::Value& v = root["value"]; + if (v.isString()) { + // 允许直接发送原始 JSON 字符串 + valueStr = v.asString(); + } else { + Json::StreamWriterBuilder wb; + wb.settings_["indentation"] = ""; + valueStr = Json::writeString(wb, v); + } + } + + pushCommand(key, valueStr); +} + +//--------------------------------------------------------- +// Procedure: pushCommand + +void HostSim::pushCommand(const string& key, const string& jsonValue) +{ + lock_guard lock(m_cmdMutex); + m_cmdQueue.push(make_pair(key, jsonValue)); +} + +//--------------------------------------------------------- +// Procedure: drainCommands + +bool HostSim::drainCommands() +{ + vector > pending; + { + lock_guard lock(m_cmdMutex); + while (!m_cmdQueue.empty()) { + pending.push_back(m_cmdQueue.front()); + m_cmdQueue.pop(); + } + } + + for (size_t i = 0; i < pending.size(); i++) { + Notify(pending[i].first, pending[i].second); + } + return true; +} + +//--------------------------------------------------------- +// Procedure: buildSnapshot + +string HostSim::buildSnapshot() +{ + Json::Value root(Json::objectValue); + { + lock_guard lock(m_feedbackMutex); + for (map::iterator it = m_feedback.begin(); + it != m_feedback.end(); ++it) { + root[it->first] = it->second; + } + } + + Json::StreamWriterBuilder wb; + wb.settings_["indentation"] = ""; + return Json::writeString(wb, root); +} + +//--------------------------------------------------------- +// Procedure: RegisterVariables + +void HostSim::RegisterVariables() +{ + registerFeedbackVars(); +} + +//--------------------------------------------------------- +// Procedure: registerFeedbackVars +// 订阅 pPowerManger 上位机反馈变量 + +void HostSim::registerFeedbackVars() +{ + // 状态报告 + Register("uPower_pmState_fb", 0); // 能源管理总状态 + Register("uPower_fcsState_st", 0); // 燃料电池状态 + Register("uPower_liBatState_st", 0); // 锂电池状态 + Register("uPower_disSysState_st", 0); // 配电系统状态 + Register("uPower_currentState_st", 0); // 母线电压电流 + Register("uPower_equState_fb", 0); // 设备状态 + Register("uPower_equInsState_fb", 0); // 设备仪表状态 + Register("uPower_secdistState_cmd", 0);// 次级配电状态 + Register("uPower_subDisFault_st", 0); // 次级配电故障状态(_DEBUG 下发布) +} diff --git a/src/pPowerMangerHost/HostSim.h b/src/pPowerMangerHost/HostSim.h new file mode 100644 index 0000000..0838970 --- /dev/null +++ b/src/pPowerMangerHost/HostSim.h @@ -0,0 +1,61 @@ +/************************************************************/ +/* NAME: */ +/* ORGN: MIT */ +/* FILE: HostSim.h */ +/* DATE: */ +/************************************************************/ + +#ifndef HOSTSIM_HEADER +#define HOSTSIM_HEADER + +#include "MOOS/libMOOS/MOOSLib.h" +#include +#include +#include +#include +#include +#include + +class WebServer; + +// 模拟 pPowerManger 上位机:网页 <-> MOOSDB 桥 +class HostSim : public CMOOSApp +{ + public: + HostSim(); + ~HostSim(); + + protected: // Standard MOOSApp functions to overload + bool OnNewMail(MOOSMSG_LIST &NewMail); + bool Iterate(); + bool OnConnectToServer(); + bool OnStartUp(); + void RegisterVariables(); + + public: // 由 WebServer 线程调用 + // 网页下发的指令:key 为 MOOS 变量名,jsonValue 为已序列化的 JSON 字符串 + void pushCommand(const std::string& key, const std::string& jsonValue); + // 取当前所有反馈变量的 JSON 快照 + std::string buildSnapshot(); + + private: + void registerFeedbackVars(); + bool drainCommands(); + void handleWebMessage(const std::string& msg); + + private: // State variables + // 反馈缓存:key -> 最新 JSON 字符串 + std::mutex m_feedbackMutex; + std::map m_feedback; + bool m_feedbackDirty; + + // 待发送指令队列 + std::mutex m_cmdMutex; + std::queue > m_cmdQueue; + + // 网页服务器 + WebServer* m_webServer; + int m_webPort; +}; + +#endif diff --git a/src/pPowerMangerHost/HostSim_Info.cpp b/src/pPowerMangerHost/HostSim_Info.cpp new file mode 100644 index 0000000..6008e43 --- /dev/null +++ b/src/pPowerMangerHost/HostSim_Info.cpp @@ -0,0 +1,114 @@ +/****************************************************************/ +/* NAME: */ +/* ORGN: MIT Cambridge MA */ +/* FILE: HostSim_Info.cpp */ +/* DATE: */ +/****************************************************************/ + +#include +#include +#include "HostSim_Info.h" +#include "ColorParse.h" +#include "ReleaseInfo.h" + +using namespace std; + +//---------------------------------------------------------------- +// Procedure: showSynopsis + +void showSynopsis() +{ + blk("SYNOPSIS: "); + blk("------------------------------------ "); + blk(" pPowerMangerHost 模拟 pPowerManger 的上位机。 "); + blk(" 通过 MOOSDB 与 pPowerManger 交互,并使用网页进行界面交互: "); + blk(" 下发指令 / 监控反馈,浏览器访问 http://:18080 。 "); + blk(" "); +} + +//---------------------------------------------------------------- +// Procedure: showHelpAndExit + +void showHelpAndExit() +{ + blk(" "); + blu("=============================================================== "); + blu("Usage: pPowerMangerHost file.moos [OPTIONS] "); + blu("=============================================================== "); + blk(" "); + showSynopsis(); + blk(" "); + blk("Options: "); + mag(" --alias","= "); + blk(" Launch pPowerMangerHost with the given process name. "); + mag(" --example, -e "); + blk(" Display example MOOS configuration block. "); + mag(" --help, -h "); + blk(" Display this help message. "); + mag(" --interface, -i "); + blk(" Display MOOS publications and subscriptions. "); + mag(" --version,-v "); + blk(" Display the release version of pPowerMangerHost. "); + blk(" "); + exit(0); +} + +//---------------------------------------------------------------- +// Procedure: showExampleConfigAndExit + +void showExampleConfigAndExit() +{ + blk(" "); + blu("=============================================================== "); + blu("pPowerMangerHost Example MOOS Configuration "); + blu("=============================================================== "); + blk(" "); + blk("ProcessConfig = pPowerMangerHost "); + blk("{ "); + blk(" AppTick = 4 "); + blk(" CommsTick = 4 "); + blk(" WEB_PORT = 18080 // 网页监听端口 "); + blk("} "); + blk(" "); + exit(0); +} + +//---------------------------------------------------------------- +// Procedure: showInterfaceAndExit + +void showInterfaceAndExit() +{ + blk(" "); + blu("=============================================================== "); + blu("pPowerMangerHost INTERFACE "); + blu("=============================================================== "); + blk(" "); + showSynopsis(); + blk(" "); + blk("SUBSCRIPTIONS: "); + blk("------------------------------------ "); + blk(" uPower_pmState_fb 能源管理总状态 "); + blk(" uPower_fcsState_st 燃料电池状态 "); + blk(" uPower_liBatState_st 锂电池状态 "); + blk(" uPower_disSysState_st 配电系统状态 "); + blk(" uPower_currentState_st 母线电压电流 "); + blk(" uPower_equState_fb 设备状态 "); + blk(" uPower_equInsState_fb 设备仪表状态 "); + blk(" uPower_secdistState_cmd 次级配电状态 "); + blk(" "); + blk("PUBLICATIONS: "); + blk("------------------------------------ "); + blk(" 由网页下发,任意 MOOS 变量(命令类)Notify 到 MOOSDB。 "); + blk(" 例如 uExternComm_setOpCondition_cmd / uPlan_taskStart_st 等。 "); + blk(" "); + exit(0); +} + +//---------------------------------------------------------------- +// Procedure: showReleaseInfoAndExit + +void showReleaseInfoAndExit() +{ + showReleaseInfo("pPowerMangerHost", "gpl"); + exit(0); +} diff --git a/src/pPowerMangerHost/HostSim_Info.h b/src/pPowerMangerHost/HostSim_Info.h new file mode 100644 index 0000000..1361dc5 --- /dev/null +++ b/src/pPowerMangerHost/HostSim_Info.h @@ -0,0 +1,17 @@ +/****************************************************************/ +/* NAME: */ +/* ORGN: MIT Cambridge MA */ +/* FILE: HostSim_Info.h */ +/* DATE: */ +/****************************************************************/ + +#ifndef HOSTSIM_INFO_HEADER +#define HOSTSIM_INFO_HEADER + +void showSynopsis(); +void showHelpAndExit(); +void showExampleConfigAndExit(); +void showInterfaceAndExit(); +void showReleaseInfoAndExit(); + +#endif diff --git a/src/pPowerMangerHost/WebServer.cpp b/src/pPowerMangerHost/WebServer.cpp new file mode 100644 index 0000000..6f65b89 --- /dev/null +++ b/src/pPowerMangerHost/WebServer.cpp @@ -0,0 +1,115 @@ +#include "WebServer.h" +#include "webassets_gen.h" +#include + +//--------------------------------------------------------- +// 事件处理:HTTP 路由 + WebSocket 收发 + +void WebServer::handleEvent(struct mg_connection* c, int ev, void* ev_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message* hm = (struct mg_http_message*)ev_data; + + // WebSocket 升级 + if (mg_match(hm->uri, mg_str("/ws"), NULL)) { + mg_ws_upgrade(c, hm, NULL); + return; + } + + // 静态资源路由:在嵌入式资源表 webassets 中按路径查找并返回 + std::string path(hm->uri.buf, hm->uri.len); + if (path == "/" || path == "/index.html") { + path = "index.html"; + } else if (!path.empty() && path[0] == '/') { + path = path.substr(1); + } + + const webassets::Asset* a = webassets::find(path.c_str()); + if (a) { + std::string hdr = "Content-Type: "; + hdr += a->mime; + hdr += "\r\n"; + mg_http_reply(c, 200, hdr.c_str(), "%.*s", (int) a->len, + (const char*) a->data); + } else { + mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n"); + } + } else if (ev == MG_EV_WS_OPEN) { + std::lock_guard lock(m_wsMutex); + m_wsConnections.push_back(c); + } else if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) { + std::lock_guard lock(m_wsMutex); + for (auto it = m_wsConnections.begin(); it != m_wsConnections.end(); ++it) { + if (*it == c) { + m_wsConnections.erase(it); + break; + } + } + } else if (ev == MG_EV_WS_MSG) { + struct mg_ws_message* wm = (struct mg_ws_message*)ev_data; + std::string msg(wm->data.buf, wm->data.len); + if (m_onMessage) { + m_onMessage(msg); + } + } +} + +WebServer::WebServer() : m_port(0), m_running(false) { + mg_mgr_init(&m_mgr); +} + +WebServer::~WebServer() { + stop(); + mg_mgr_free(&m_mgr); +} + +void WebServer::setOnMessage(std::function handler) { + m_onMessage = handler; +} + +bool WebServer::start(int port) { + if (m_running) return true; + m_port = port; + + std::string addr = "http://0.0.0.0:" + std::to_string(port); + mg_http_listen(&m_mgr, addr.c_str(), [](mg_connection* c, int ev, void* ev_data) { + WebServer* server = static_cast(c->fn_data); + if (server) server->handleEvent(c, ev, ev_data); + }, this); + + m_running = true; + m_thread = std::thread([this]() { serverThreadFunc(); }); + + std::cout << "pPowerMangerHost web UI listening on " << addr << std::endl; + return true; +} + +void WebServer::stop() { + if (!m_running) return; + m_running = false; + if (m_thread.joinable()) m_thread.join(); + + std::lock_guard lock(m_wsMutex); + for (auto c : m_wsConnections) { + mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE); + } + m_wsConnections.clear(); +} + +bool WebServer::serverThreadCB(void* pParam) { + WebServer* pThis = static_cast(pParam); + return pThis->serverThreadFunc(); +} + +bool WebServer::serverThreadFunc() { + while (m_running) { + mg_mgr_poll(&m_mgr, 200); + } + return true; +} + +void WebServer::broadcast(const std::string& text) { + std::lock_guard lock(m_wsMutex); + for (auto c : m_wsConnections) { + mg_ws_send(c, text.c_str(), text.size(), WEBSOCKET_OP_TEXT); + } +} diff --git a/src/pPowerMangerHost/WebServer.h b/src/pPowerMangerHost/WebServer.h new file mode 100644 index 0000000..b2f94e5 --- /dev/null +++ b/src/pPowerMangerHost/WebServer.h @@ -0,0 +1,42 @@ +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include "mongoose.h" +#include +#include +#include +#include +#include +#include + +class WebServer { +public: + WebServer(); + ~WebServer(); + + // 启动 HTTP+WebSocket 服务器(阻塞在独立线程) + bool start(int port); + void stop(); + + // 向所有 WebSocket 客户端广播文本 + void broadcast(const std::string& text); + + // 收到网页 WS 消息时的回调 + void setOnMessage(std::function handler); + +private: + static bool serverThreadCB(void* pParam); + bool serverThreadFunc(); + void handleEvent(struct mg_connection* c, int ev, void* ev_data); + + struct mg_mgr m_mgr; + int m_port; + std::atomic m_running; + std::thread m_thread; + + std::mutex m_wsMutex; + std::vector m_wsConnections; + std::function m_onMessage; +}; + +#endif // WEBSERVER_H diff --git a/src/pPowerMangerHost/main.cpp b/src/pPowerMangerHost/main.cpp new file mode 100644 index 0000000..eee528a --- /dev/null +++ b/src/pPowerMangerHost/main.cpp @@ -0,0 +1,51 @@ +/************************************************************/ +/* NAME: */ +/* ORGN: MIT */ +/* FILE: main.cpp */ +/* DATE: */ +/************************************************************/ + +#include +#include "MBUtils.h" +#include "ColorParse.h" +#include "HostSim.h" +#include "HostSim_Info.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + string mission_file; + string run_command = argv[0]; + + for(int i=1; i + + + + +pPowerManger 上位机模拟 + + + +
+ +

pPowerManger 上位机模拟 (Web UI)

+ 连接中… +
+
+
+

指令下发

+
+ + +
+
+ + +
+
+ +
+ +
+ + +
+ +

快捷指令

+
+ +

通用 MOOS 通知

+
+ + + +
+
+ +
+

反馈监控(pPowerManger 发往上位机的全部消息)

+
+
+
+
+ +
+

连续数据曲线

+
+ + + + +
+
+ +
+ +
+

操作日志

+
+
+ + + + diff --git a/test/disSysTest.zip b/test/disSysTest.zip deleted file mode 100644 index 7b4b4d3..0000000 Binary files a/test/disSysTest.zip and /dev/null differ diff --git a/test/disSysTest/PowerManagerApp111.py b/test/disSysTest/PowerManagerApp111.py new file mode 100644 index 0000000..576b900 --- /dev/null +++ b/test/disSysTest/PowerManagerApp111.py @@ -0,0 +1,1578 @@ +import tkinter as tk +from tkinter import ttk +import socket +import threading +import time +from db import DB +import os +from udpMsg import (msg_disHighVolBusFbMsg, msg_disHighAVolBusFbMsg, + msg_disLowMainBusFbMsg, msg_disLowBusFbMsg, + msg_disHVBCmdMsg, msg_disHVBACmdMsg, msg_disLVMBCmdMsg, + msg_disLVBCmdMsg) + + +class PowerManagerApp: + + def __init__(self, root): + self.root = root + + # 解决字体模糊问题 + style = ttk.Style() + style.configure('.', font=('Microsoft YaHei', 10)) + + self.root.title("电源管理系统") + self.root.tk.call('tk', 'scaling', 1.5) # 适配高DPI显示 + # 移除固定窗口大小设置,实现自适应 + self.root.minsize(800, 600) # 设置最小窗口大小 + + # UDP通信配置 + # self.udp_ip = "127.0.0.1" + self.udp_ip = "192.168.0.140" #本机IP,需配置一致 + self.udp_port = 7000 + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.bind((self.udp_ip, self.udp_port)) + + self.disSysPortC1 = 6000 + self.disSysHostC1 = '192.168.1.154' #动力 + self.disSysPortC2 = 6000 + self.disSysHostC2 = '192.168.1.158' #仪表 + + # self.disSysPortC1 = 7000 + # self.disSysHostC1 = '192.168.0.140' + # self.disSysPortC2 = 7000 + # self.disSysHostC2 = '192.168.0.140' + + # 数据库配置 + # self.db = DB('/home/zjk/project/H100/H100PowerManger/test/disSysTest/test.db') + self.db = DB(os.path.join(os.path.dirname(__file__), 'test.db')) + self.db.connect() + + # 状态数据 - 完全匹配udpMsg.py中的定义 + self.status_data = { + "high_vol_bus": { + "breakers": { + "燃料电池断路器": 0, + "动力锂电池断路器": 0, + "推进电机断路器": 0, + "锂电池组仪表断路器": 0, + "DC/DC汇流排断路器": 0, + "艏部高压断路器": 0, + "艉FT设备45断路器": 0, + "FB备用断路器": 0, + "艉舵机开关1断路器": 0, + "艉舵机开关2断路器": 0, + "TYZ备用断路器": 0, + "备用断路器": 0 + }, + "faults": { + "DC/DC故障1": 0, + "DC/DC故障2": 0 + }, + "insulation": { + "动力母线绝缘状态": 0, + "A母线绝缘状态": 0 + }, + "alarms": { + "仪表电源丢失信号": 0, + "应急电源丢失信号": 0 + }, + "voltage": { + "动力母线电压": 0.0, + "A母线电压": 0.0 + }, + "current": { + "动力母线电流": 0.0, + "DC/DC5模块电流": 0.0, + "推进电机控制箱电流": 0.0, + "A母线电流": 0.0, + "锂电池组仪表电流": 0.0, + "艏高压配电盒电流": 0.0, + "艉FT设备45电流": 0.0, + "TYZ备用开关电流": 0.0, + "FB备用开关电流": 0.0, + "备用电流1": 0.0, + "艉舵机开关1电流": 0.0, + "备用电流2": 0.0, + "艉舵机开关2电流": 0.0, + "备用电流3": 0.0 + }, + "cool_water_pressure": 0.0, + "dcdc_temp": 0 + }, + "high_a_vol_bus": { + "breakers": { + "艏FT设备123断路器": 0, + "启闭机构断路器": 0, + "桅杆舵机断路器": 0, + "XCZ断路器": 0, + "艏舵控制箱断路器": 0, + "敞水盖断路器": 0 + }, + "alarms": { + "仪表电源故障": 0, + "应急电源故障": 0, + "进水报警": 0 + }, + "voltage": { + "B母线电压": 0.0 + }, + "current": { + "B母线电流": 0.0, + "艏FT设备123电流": 0.0, + "执行机构电流": 0.0, + "桅舵机控制箱电流": 0.0, + "XCZ电流": 0.0, + "艏舵控制箱电流": 0.0, + "开盖启动气缸电流": 0.0 + } + }, + "low_main_bus": { + "breakers": { + "锂电池组仪表断路器": 0, + "艏部低压断路器": 0, + "单元4仪表48V断路器": 0, + "DC/C1直流配电板断路器": 0, + "备用断路器1": 0, + "备用断路器2": 0, + "应急锂电池组2断路器": 0 + }, + "alarms": { + "仪表电源故障": 0, + "应急电源故障": 0 + }, + "insulation": 0, + "voltage": { + "仪表母线电压": 0.0, + "应急2母线电压": 0.0 + }, + "current": { + "艏低压配电盒电流": 0.0, + "DC/C1仪表48V电流": 0.0, + "备用电流1": 0.0, + "应急锂电池组2电流": 0.0, + "锂电池组仪表电流": 0.0, + "单元4仪表48V电流": 0.0, + "备用电流2": 0.0, + "综合能量管理系统应急DC48V电流": 0.0, + "燃料电池安全系统应急DC48V电流": 0.0, + "PLC控制电源电流": 0.0 + } + }, + "low_bus": { + "breakers": { + "单元1断路器": 0, + "单元2断路器": 0, + "单元3断路器": 0, + "单元5断路器": 0, + "艏PZ设备断路器": 0, + "备用断路器1": 0, + "备用断路器2": 0, + "应急锂电池组1断路器": 0 + }, + "alarms": { + "仪表电源故障": 0, + "应急电源故障": 0, + "进水报警": 0 + }, + "voltage": { + "仪表母线电压": 0.0, + "应急1母线电压": 0.0 + }, + "current": { + "仪表母线电流": 0.0, + "单元1电流": 0.0, + "单元2电流": 0.0, + "单元3电流": 0.0, + "单元5电流": 0.0, + "艏PZ设备电流": 0.0, + "应急锂电池组1电流": 0.0 + } + } + } + + # 初始化界面 + self.setup_ui() + + # 启动UDP接收线程 + self.running = True + self.recv_thread = threading.Thread(target=self.udp_receive_loop) + self.recv_thread.daemon = True + self.recv_thread.start() + + # 启动状态更新定时器 + self.update_ui() + + def setup_ui(self): + """初始化用户界面""" + # 主框架 + main_frame = ttk.Frame(self.root, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + self.root.grid_rowconfigure(0, weight=1) + self.root.grid_columnconfigure(0, weight=1) + + # 状态显示区 + status_frame = ttk.LabelFrame(main_frame, text="系统状态", padding="10") + status_frame.pack(fill=tk.BOTH, expand=True, pady=5) + + # 创建Canvas和Scrollbar + self.notebook = ttk.Notebook(status_frame) + self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + # 高压母线状态页 + high_vol_frame = ttk.Frame(self.notebook) + self.notebook.add(high_vol_frame, text="动力配电板") + self.setup_high_vol_ui(high_vol_frame) + + # 高压A母线状态页 + high_a_vol_frame = ttk.Frame(self.notebook) + self.notebook.add(high_a_vol_frame, text="艏部高压") + self.setup_high_a_vol_ui(high_a_vol_frame) + + # 仪表主母线状态页 + low_main_frame = ttk.Frame(self.notebook) + self.notebook.add(low_main_frame, text="仪表配电板") + self.setup_low_main_ui(low_main_frame) + + # 仪表母线状态页 + low_bus_frame = ttk.Frame(self.notebook) + self.notebook.add(low_bus_frame, text="艏部低压") + self.setup_low_bus_ui(low_bus_frame) + + # 控制区 + control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding="10") + control_frame.pack(fill=tk.BOTH, expand=True, pady=5) + control_frame.grid_rowconfigure(0, weight=1) + control_frame.grid_columnconfigure(0, weight=1) + + # 创建Canvas和Scrollbar + canvas = tk.Canvas(control_frame) + scrollbar = ttk.Scrollbar(control_frame, + orient="vertical", + command=canvas.yview) + canvas.configure(yscrollcommand=scrollbar.set) + + # 将Canvas和Scrollbar放入控制区 + canvas.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + # 创建内部Frame用于放置Notebook + inner_frame = ttk.Frame(canvas) + self.inner_frame_id = canvas.create_window((0, 0), + window=inner_frame, + anchor="nw") + + # 配置Canvas滚动区域 + def configure_canvas(event): + canvas.configure(scrollregion=canvas.bbox("all")) + canvas.itemconfig(self.inner_frame_id, width=event.width) + + inner_frame.bind("", configure_canvas) + canvas.bind("", configure_canvas) + + # 绑定鼠标滚轮事件 + def on_mousewheel(event): + canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") + + canvas.bind_all("", on_mousewheel) + + # 状态显示区 - 使用Notebook组织不同母线状态 + self.notebook_c = ttk.Notebook(inner_frame) + self.notebook_c.pack(fill="both", expand=True, padx=5, pady=5) + + high_vol_control_frame = ttk.Frame(self.notebook_c) + self.notebook_c.add(high_vol_control_frame, text="动力配电板") + self.setup_hvb_control(high_vol_control_frame) + + high_a_vol_control_frame = ttk.Frame(self.notebook_c) + self.notebook_c.add(high_a_vol_control_frame, text="艏部高压") + self.setup_hvba_control(high_a_vol_control_frame) + + low_main_control_frame = ttk.Frame(self.notebook_c) + self.notebook_c.add(low_main_control_frame, text="仪表配电板") + self.setup_lvmb_control(low_main_control_frame) + + low_bus_control_frame = ttk.Frame(self.notebook_c) + self.notebook_c.add(low_bus_control_frame, text="艏部低压") + self.setup_lvb_control(low_bus_control_frame) + + # 日志区 + log_frame = ttk.LabelFrame(main_frame, text="系统日志", padding="10") + log_frame.pack(fill=tk.BOTH, expand=True, pady=5) + log_frame.grid_rowconfigure(0, weight=1) + log_frame.grid_columnconfigure(0, weight=1) + + self.log_text = tk.Text(log_frame, height=10, state=tk.DISABLED) + self.log_text.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar(log_frame, + orient=tk.VERTICAL, + command=self.log_text.yview) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + self.log_text.config(yscrollcommand=scrollbar.set) + + def setup_hvb_control(self, parent): + """设置动力母线配电指令控制面板""" + frame = ttk.LabelFrame(parent, text="动力母线配电指令", padding="10") + frame.pack(fill=tk.X, pady=5) + + # 断路器控制按钮 + breaker_names = [ + "燃料电池断路器", "动力锂电池断路器", "推进电机断路器", "锂电池组仪表断路器", "DC/DC汇流排断路器", + "艏部高压断路器", "艉FT设备45断路器", "FB备用断路器", "艉舵机开关1断路器", "艉舵机开关2断路器", + "TYZ备用断路器", "备用断路器", "DC/DC模块启动", "冷却系统启动" + ] + + for i, name in enumerate(breaker_names): + row = i // 3 + col = i % 3 + btn_frame = ttk.Frame(frame) + btn_frame.grid(row=row, column=col, padx=10, pady=2, sticky=tk.W) + + ttk.Label(btn_frame, text=name, width=20).pack(side=tk.LEFT) + on_btn = ttk.Button( + btn_frame, + text="合", + width=3, + command=lambda n=name: self.send_hvb_cmd(n, 0x55)) + on_btn.pack(side=tk.LEFT, padx=2) + off_btn = ttk.Button( + btn_frame, + text="分", + width=3, + command=lambda n=name: self.send_hvb_cmd(n, 0xAA)) + off_btn.pack(side=tk.LEFT, padx=2) + + def setup_hvba_control(self, parent): + """设置动力汇流排A配电指令控制面板""" + frame = ttk.LabelFrame(parent, text="动力汇流排A配电指令", padding="10") + frame.pack(fill=tk.X, pady=5) + + # 断路器控制按钮 + breaker_names = [ + "艏FT设备123断路器", "启闭机构断路器", "桅杆舵机断路器", "XCZ断路器", "艏舵控制箱断路器", + "敞水盖断路器" + ] + + for i, name in enumerate(breaker_names): + row = i // 3 + col = i % 3 + btn_frame = ttk.Frame(frame) + btn_frame.grid(row=row, column=col, padx=10, pady=2, sticky=tk.W) + + ttk.Label(btn_frame, text=name, width=20).pack(side=tk.LEFT) + on_btn = ttk.Button( + btn_frame, + text="合", + width=3, + command=lambda n=name: self.send_hvba_cmd(n, 0x55)) + on_btn.pack(side=tk.LEFT, padx=2) + off_btn = ttk.Button( + btn_frame, + text="分", + width=3, + command=lambda n=name: self.send_hvba_cmd(n, 0xAA)) + off_btn.pack(side=tk.LEFT, padx=2) + + def setup_lvmb_control(self, parent): + """设置仪表母线配电指令控制面板""" + frame = ttk.LabelFrame(parent, text="仪表母线配电指令", padding="10") + frame.pack(fill=tk.X, pady=5) + + # 断路器控制按钮 + breaker_names = [ + "锂电池组仪表断路器", "艏部低压断路器", "单元4仪表48V断路器", "DC/C1直流配电板断路器", "备用断路器1", + "备用断路器2", "应急锂电池组2断路器" + ] + + for i, name in enumerate(breaker_names): + row = i // 3 + col = i % 3 + btn_frame = ttk.Frame(frame) + btn_frame.grid(row=row, column=col, padx=10, pady=2, sticky=tk.W) + + ttk.Label(btn_frame, text=name, width=20).pack(side=tk.LEFT) + on_btn = ttk.Button( + btn_frame, + text="合", + width=3, + command=lambda n=name: self.send_lvmb_cmd(n, 0x55)) + on_btn.pack(side=tk.LEFT, padx=2) + off_btn = ttk.Button( + btn_frame, + text="分", + width=3, + command=lambda n=name: self.send_lvmb_cmd(n, 0xaa)) + off_btn.pack(side=tk.LEFT, padx=2) + + def setup_lvb_control(self, parent): + """设置仪表汇流排配电指令控制面板""" + frame = ttk.LabelFrame(parent, text="仪表汇流排配电指令", padding="10") + frame.pack(fill=tk.X, pady=5) + + # 断路器控制按钮 + breaker_names = [ + "单元1断路器", "单元2断路器", "单元3断路器", "单元5断路器", "艏PZ设备断路器", "备用断路器1", + "备用断路器2", "应急锂电池组1断路器" + ] + + for i, name in enumerate(breaker_names): + row = i // 3 + col = i % 3 + btn_frame = ttk.Frame(frame) + btn_frame.grid(row=row, column=col, padx=10, pady=2, sticky=tk.W) + + ttk.Label(btn_frame, text=name, width=20).pack(side=tk.LEFT) + on_btn = ttk.Button( + btn_frame, + text="合", + width=3, + command=lambda n=name: self.send_lvb_cmd(n, 0x55)) + on_btn.pack(side=tk.LEFT, padx=2) + off_btn = ttk.Button( + btn_frame, + text="分", + width=3, + command=lambda n=name: self.send_lvb_cmd(n, 0xaa)) + off_btn.pack(side=tk.LEFT, padx=2) + + def send_hvb_cmd(self, breaker_name, state): + """发送动力母线配电指令""" + # print(breaker_name,state) + cmd = msg_disHVBCmdMsg() + if breaker_name == "燃料电池断路器": + cmd.fuelCellCircuitBreaker = state + elif breaker_name == "动力锂电池断路器": + cmd.powerLithiumBatteryCircuitBreaker = state + elif breaker_name == "推进电机断路器": + cmd.propulsionMotorCircuitBreaker = state + elif breaker_name == "锂电池组仪表断路器": + cmd.lithiumBatteryGroupInstrumentCircuitBreaker = state + elif breaker_name == "DC/DC汇流排断路器": + cmd.dcDc5ModuleCircuitBreaker = state + elif breaker_name == "艏部高压断路器": + cmd.bowHighVoltageDistributionBoxCircuitBreaker = state + elif breaker_name == "艉FT设备45断路器": + cmd.sternFTDevice45CircuitBreaker = state + elif breaker_name == "FB备用断路器": + cmd.sternRudderSwitch1CircuitBreaker = state + elif breaker_name == "艉舵机开关1断路器": + cmd.sternRudderSwitch2CircuitBreaker = state + elif breaker_name == "艉舵机开关2断路器": + cmd.fbReservedCircuitBreaker = state + elif breaker_name == "TYZ备用断路器": + cmd.tyzReservedCircuitBreaker = state + elif breaker_name == "备用断路器": + cmd.reservedCircuitBreaker = state + elif breaker_name == "DC/DC模块启动": + cmd.dcdc5Module = state + elif breaker_name == "冷却系统启动": + cmd.coolingSystem = state + + # self.send_udp_command(cmd.pack()) + self.send_udp_command(cmd.pack(), self.disSysHostC1, self.disSysPortC1) + self.db.insertHighVolBusCmd(cmd) + if state == 0x55: + buntu = True + else: + buntu = False + self.log_message(f"发送动力母线配电指令: {breaker_name} {'合' if buntu else '分'}") + + def send_hvba_cmd(self, breaker_name, state): + """发送动力汇流排A配电指令""" + cmd = msg_disHVBACmdMsg() + + if breaker_name == "艏FT设备123断路器": + cmd.bowFTDevice123CircuitBreaker = state + elif breaker_name == "启闭机构断路器": + cmd.actuatorCircuitBreaker = state + elif breaker_name == "桅杆舵机断路器": + cmd.mastSteeringGearControlBoxCircuitBreaker = state + elif breaker_name == "XCZ断路器": + cmd.xczCircuitBreaker = state + elif breaker_name == "艏舵控制箱断路器": + cmd.bowRudderControlBoxCircuitBreaker = state + elif breaker_name == "敞水盖断路器": + cmd.openWaterCoverStartCylinderCircuitBreaker = state + if state == 0x55: + buntu = True + else: + buntu = False + self.log_message( + f"发送动力汇流排A配电指令: {breaker_name} {'合' if buntu else '分'}") + # self.send_udp_command(cmd.pack()) + self.send_udp_command(cmd.pack(), self.disSysHostC1, self.disSysPortC1) + self.db.insertHighAVolBusCmd(cmd) + + def send_lvmb_cmd(self, breaker_name, state): + """发送仪表母线配电指令""" + cmd = msg_disLVMBCmdMsg() + if breaker_name == "锂电池组仪表断路器": + cmd.lithiumBatteryGroupInstrumentCircuitBreaker = state + elif breaker_name == "艏部低压断路器": + cmd.bowLowVoltageDistributionBoxCircuitBreaker = state + elif breaker_name == "单元4仪表48V断路器": + cmd.unit4InstrumentDC48VCircuitBreaker = state + elif breaker_name == "DC/C1直流配电板断路器": + cmd.dcC1DCDistributionPanelCircuitBreaker = state + elif breaker_name == "备用断路器1": + cmd.reservedCircuitBreaker1 = state + elif breaker_name == "备用断路器2": + cmd.reservedCircuitBreaker2 = state + elif breaker_name == "应急锂电池组2断路器": + cmd.emergencyLithiumBatteryGroup2CircuitBreaker = state + + # self.send_udp_command(cmd.pack()) + self.send_udp_command(cmd.pack(), self.disSysHostC2, self.disSysPortC2) + self.db.insertLowMainBusCmd(cmd) + if state == 0x55: + buntu = True + else: + buntu = False + self.log_message(f"发送仪表母线配电指令: {breaker_name} {'合' if buntu else '分'}") + + def send_lvb_cmd(self, breaker_name, state): + """发送仪表汇流排配电指令""" + cmd = msg_disLVBCmdMsg() + if breaker_name == "单元1断路器": + cmd.unit1CircuitBreaker = state + elif breaker_name == "单元2断路器": + cmd.unit2CircuitBreaker = state + elif breaker_name == "单元3断路器": + cmd.unit3CircuitBreaker = state + elif breaker_name == "单元5断路器": + cmd.unit5CircuitBreaker = state + elif breaker_name == "艏PZ设备断路器": + cmd.bowPZDeviceCircuitBreaker = state + elif breaker_name == "备用断路器1": + cmd.reservedCircuitBreaker1 = state + elif breaker_name == "备用断路器2": + cmd.reservedCircuitBreaker2 = state + elif breaker_name == "应急锂电池组1断路器": + cmd.emergencyLithiumBatteryGroup1CircuitBreaker = state + + # self.send_udp_command(cmd.pack()) + self.send_udp_command(cmd.pack(), self.disSysHostC2, self.disSysPortC2) + self.db.insertLowBusCmd(cmd) + if state == 0x55: + buntu = True + else: + buntu = False + self.log_message( + f"发送仪表汇流排配电指令: {breaker_name} {'合' if buntu else '分'}") + + def udp_receive_loop(self): + """UDP接收消息循环""" + while self.running: + # print('接收中') + try: + data, addr = self.sock.recvfrom(1024) + self.process_udp_message(data) + # print('接收成功') + except Exception as e: + self.log_message(f"接收错误: {str(e)}") + + def setup_high_vol_ui(self, parent): + """设置高压母线状态界面""" + # 初始化故障位列表 + self.high_vol_dcdc_fault1_bits = [] + self.high_vol_dcdc_fault2_bits = [] + + frame = ttk.LabelFrame(parent, text="高压母线状态") + frame.pack(fill=tk.BOTH, expand=True, padx=2, pady=2) + + # 使用网格布局管理器 + frame.grid_columnconfigure(0, weight=1) + frame.grid_columnconfigure(1, weight=1) + frame.grid_columnconfigure(2, weight=1) + frame.grid_columnconfigure(3, weight=1) + + # 电压显示 + ttk.Label(frame, text="动力母线电压(V):").grid(row=0, column=0, sticky=tk.W) + self.high_vol_power_voltage = ttk.Label(frame, text="0.0V") + self.high_vol_power_voltage.grid(row=0, column=1, sticky=tk.W) + + ttk.Label(frame, text="A母线电压(V):").grid(row=0, column=2, sticky=tk.W) + self.high_vol_a_voltage = ttk.Label(frame, text="0.0V") + self.high_vol_a_voltage.grid(row=0, column=3, sticky=tk.W) + + # 断路器状态 + ttk.Label(frame, text="断路器状态:").grid(row=2, + column=0, + columnspan=4, + sticky=tk.W) + self.high_vol_breakers = {} + breaker_names = list( + self.status_data["high_vol_bus"]["breakers"].keys()) + + for i, name in enumerate(breaker_names): + ttk.Label(frame, text=name).grid(row=3 + i // 3, + column=(i % 3) * 2, + sticky=tk.W) + canvas = tk.Canvas(frame, width=20, height=20, bg="white") + canvas.grid(row=3 + i // 3, column=(i % 3) * 2 + 1, sticky=tk.W) + self.high_vol_breakers[name] = canvas + + # 绝缘状态 + ttk.Label(frame, text="动力母线绝缘:").grid(row=9, column=0, sticky=tk.W) + self.high_vol_power_insulation = ttk.Label(frame, text="正常") + self.high_vol_power_insulation.grid(row=9, column=1, sticky=tk.W) + + ttk.Label(frame, text="A母线绝缘:").grid(row=9, column=2, sticky=tk.W) + self.high_vol_a_insulation = ttk.Label(frame, text="正常") + self.high_vol_a_insulation.grid(row=9, column=3, sticky=tk.W) + + # 故障状态 - 扩展显示所有故障位 + fault_labels1 = [ + "过温关机", "输出过流", "输出过压", "输出欠压", "输入过压", "输入欠压", "输出短路保护", "内部故障" + ] + fault_labels2 = [ + "通信故障", "工作状态", "冷却泵1故障", "冷却泵2故障", "冷却水泄漏", "冷却泵1运行状态", + "冷却泵2运行状态", "保留" + ] + + # DC/DC故障显示 + fault_frame = ttk.Frame(frame) + fault_frame.grid(row=10, column=0, columnspan=4, sticky=tk.W, pady=2) + + ttk.Label(fault_frame, text="DC/DC故障字1:").grid(row=0, + column=0, + sticky=tk.W) + for i, label in enumerate(fault_labels1): + ttk.Label(fault_frame, text=label).grid(row=1 + i // 4, + column=(i % 4) * 2, + sticky=tk.W, + padx=2) + canvas = tk.Canvas(fault_frame, + width=16, + height=16, + bg="white", + highlightthickness=1) + canvas.grid(row=1 + i // 4, + column=(i % 4) * 2 + 1, + sticky=tk.W, + padx=2) + self.high_vol_dcdc_fault1_bits.append(canvas) + + ttk.Label(fault_frame, text="DC/DC故障字2:").grid(row=3, + column=0, + sticky=tk.W) + for i, label in enumerate(fault_labels2): + ttk.Label(fault_frame, text=label).grid(row=4 + i // 4, + column=(i % 4) * 2, + sticky=tk.W, + padx=2) + canvas = tk.Canvas(fault_frame, + width=16, + height=16, + bg="white", + highlightthickness=1) + canvas.grid(row=4 + i // 4, + column=(i % 4) * 2 + 1, + sticky=tk.W, + padx=2) + self.high_vol_dcdc_fault2_bits.append(canvas) + + # 绝缘状态 - 详细显示 + ttk.Label(frame, text="动力母线绝缘:").grid(row=16, column=0, sticky=tk.W) + self.high_vol_power_insulation = ttk.Label(frame, text="正常") + self.high_vol_power_insulation.grid(row=16, column=1, sticky=tk.W) + + ttk.Label(frame, text="A母线绝缘:").grid(row=16, column=2, sticky=tk.W) + self.high_vol_a_insulation = ttk.Label(frame, text="正常") + self.high_vol_a_insulation.grid(row=16, column=3, sticky=tk.W) + + # 电流值显示 + current_labels = [ + ("动力母线电流(A):", "powerBusCurrent"), + ("A汇流排电流(A):", "busbarACurrent"), + ("DC/DC5模块电流(A):", "dcDc5ModuleCurrent"), + ("推进电机控制箱电流(A):", "propulsionMotorControlBoxCurrent"), + ("锂电池组(仪表)电流(A):", "lithiumBatteryGroupMeterCurrent"), + ("艏部高压配电箱电流(A):", "bowHighVoltageDistributionBoxCurrent"), + ("艉部FT装置4/5电流(A):", "sternFTDevice45Current"), + ("TYZ预留开关电流(A):", "tyzReservedSwitchCurrent"), + ("艉舵开关1电流(A):", "sternRudderSwitch1Current"), + ("艉舵开关2电流(A):", "sternRudderSwitch2Current"), + ("FB预留开关电流(A):", "fbReservedSwitchCurrent"), + ("备用电流1(A):", "reservedCurrent1"), ("备用电流2(A):", + "reservedCurrent2"), + ("备用电流3(A):", "reservedCurrent3") + ] + + for i, (label, _) in enumerate(current_labels): + ttk.Label(frame, text=label).grid(row=17 + i // 2, + column=(i % 2) * 2, + sticky=tk.W) + label_var = ttk.Label(frame, text="0.0A") + label_var.grid(row=17 + i // 2, + column=(i % 2) * 2 + 1, + sticky=tk.W) + setattr(self, f"high_vol_{current_labels[i][1]}", label_var) + + # 冷却水压力 + ttk.Label(frame, text="冷却水压力(MPa):").grid(row=24, + column=0, + sticky=tk.W) + self.high_vol_cool_water_pressure = ttk.Label(frame, text="0.00KPa") + self.high_vol_cool_water_pressure.grid(row=24, column=1, sticky=tk.W) + + # 冷却水压力 + ttk.Label(frame, text="DC/DC温度:").grid(row=24, column=3, sticky=tk.W) + self.high_vol_dcdc_temp = ttk.Label(frame, text="0.00摄氏度") + self.high_vol_dcdc_temp.grid(row=24, column=4, sticky=tk.W) + + ttk.Label(frame, text="仪表失电信号:").grid(row=25, column=0, sticky=tk.W) + self.high_vol_losspower_insulation = ttk.Label(frame, text="正常") + self.high_vol_losspower_insulation.grid(row=25, column=1, sticky=tk.W) + + ttk.Label(frame, text="应急失电信号").grid(row=25, column=2, sticky=tk.W) + self.high_vol_loss_emerpower_insulation = ttk.Label(frame, text="正常") + self.high_vol_loss_emerpower_insulation.grid(row=25, + column=3, + sticky=tk.W) + + def setup_high_a_vol_ui(self, parent): + """设置高压A母线状态界面""" + frame = ttk.LabelFrame(parent, text="高压A母线状态") + frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + # 使用网格布局管理器 + frame.grid_columnconfigure(0, weight=1) + frame.grid_columnconfigure(1, weight=1) + frame.grid_columnconfigure(2, weight=1) + + # 电压显示 - 添加详细说明 + ttk.Label(frame, text="汇流排B电压(V):").grid(row=0, column=0, sticky=tk.W) + self.high_a_vol_voltage = ttk.Label(frame, text="0.0 V") + self.high_a_vol_voltage.grid(row=0, column=1, sticky=tk.W) + + # 电流显示 - 统一格式 + ttk.Label(frame, text="汇流排B电流(A):").grid(row=1, column=0, sticky=tk.W) + self.high_a_vol_current = ttk.Label(frame, text="0.0 A") + self.high_a_vol_current.grid(row=1, column=1, sticky=tk.W) + + ttk.Label(frame, text="艏部FT设备123电流(A):").grid(row=2, + column=0, + sticky=tk.W) + self.high_a_vol_bow_ft_current = ttk.Label(frame, text="0.0 A") + self.high_a_vol_bow_ft_current.grid(row=2, column=1, sticky=tk.W) + + ttk.Label(frame, text="启闭机构电流(A):").grid(row=3, column=0, sticky=tk.W) + self.high_a_vol_actuator_current = ttk.Label(frame, text="0.0 A") + self.high_a_vol_actuator_current.grid(row=3, column=1, sticky=tk.W) + + # 补充缺失的电流显示项 + ttk.Label(frame, text="桅杆舵机控制箱电流(A):").grid(row=4, + column=0, + sticky=tk.W) + self.high_a_vol_mast_steering_current = ttk.Label(frame, text="0.0 A") + self.high_a_vol_mast_steering_current.grid(row=4, + column=1, + sticky=tk.W) + + ttk.Label(frame, text="XCZ电流(A):").grid(row=4, column=2, sticky=tk.W) + self.high_a_vol_xcz_current = ttk.Label(frame, text="0.0 A") + self.high_a_vol_xcz_current.grid(row=4, column=3, sticky=tk.W) + + ttk.Label(frame, text="艏舵控制箱电流(A):").grid(row=5, column=0, sticky=tk.W) + self.high_a_vol_bow_rudder_current = ttk.Label(frame, text="0.0 A") + self.high_a_vol_bow_rudder_current.grid(row=5, column=1, sticky=tk.W) + + ttk.Label(frame, text="敞水盖启动电缸电流(A):").grid(row=5, + column=2, + sticky=tk.W) + self.high_a_vol_open_cover_current = ttk.Label(frame, text="0.0 A") + self.high_a_vol_open_cover_current.grid(row=5, column=3, sticky=tk.W) + + # 断路器状态 - 添加详细说明 + ttk.Label(frame, text="高压A母线断路器状态:").grid(row=10, + column=0, + columnspan=4, + sticky=tk.W) + self.high_a_vol_breakers = {} + breaker_names = list( + self.status_data["high_a_vol_bus"]["breakers"].keys()) + + for i, name in enumerate(breaker_names): + ttk.Label(frame, text=f"{name}状态:").grid(row=8 + i // 2, + column=(i % 2) * 2, + sticky=tk.W) + canvas = tk.Canvas(frame, width=20, height=20, bg="white") + canvas.grid(row=8 + i // 2, column=(i % 2) * 2 + 1, sticky=tk.W) + self.high_a_vol_breakers[name] = canvas + + # 报警状态 - 更详细描述 + ttk.Label(frame, text="仪表电源失电信号:").grid(row=11, column=0, sticky=tk.W) + self.high_a_vol_instrument_power_fault = ttk.Label(frame, text="正常") + self.high_a_vol_instrument_power_fault.grid(row=11, + column=1, + sticky=tk.W) + + ttk.Label(frame, text="应急电源失电信号:").grid(row=11, column=2, sticky=tk.W) + self.high_a_vol_emergency_power_fault = ttk.Label(frame, text="正常") + self.high_a_vol_emergency_power_fault.grid(row=11, + column=3, + sticky=tk.W) + + ttk.Label(frame, text="进水报警:").grid(row=12, column=0, sticky=tk.W) + self.high_a_vol_water_alarm = ttk.Label(frame, text="无") + self.high_a_vol_water_alarm.grid(row=12, column=1, sticky=tk.W) + + def process_udp_message(self, data): + """处理接收到的UDP消息""" + try: + msg_id = int.from_bytes(data[2:4], byteorder='little') + print('----------------------------', hex(msg_id)) + if msg_id == 0x0005: # 高压母线状态 + msg = msg_disHighVolBusFbMsg() + if msg.unpack(data): + self.db.insertHighVolBusData(msg) + # self.update_high_vol_ui(bus) + bus = self.status_data["high_vol_bus"] + bus["voltage"]["动力母线电压"] = msg.powerBusVoltage / 10.0 + bus["voltage"]["A母线电压"] = msg.busbarAVoltage / 10.0 + bus["current"]["动力母线电流"] = msg.powerBusCurrent / 10.0 + bus["current"][ + "DC/DC5模块电流"] = msg.dcDc5ModuleCurrent / 10.0 + bus["current"][ + "推进电机控制箱电流"] = msg.propulsionMotorControlBoxCurrent / 10.0 + bus["current"]["A母线电流"] = msg.busbarACurrent / 10.0 + bus["current"][ + "锂电池组仪表电流"] = msg.lithiumBatteryGroupMeterCurrent / 10.0 + bus["current"][ + "艏高压配电盒电流"] = msg.bowHighVoltageDistributionBoxCurrent / 10.0 + bus["current"][ + "艉FT设备45电流"] = msg.sternFTDevice45Current / 10.0 + bus["current"][ + "TYZ备用开关电流"] = msg.tyzReservedSwitchCurrent / 10.0 + bus["current"][ + "FB备用开关电流"] = msg.sternRudderSwitch1Current / 10.0 + bus["current"]["备用电流1"] = msg.reservedCurrent1 / 10.0 + bus["current"][ + "艉舵机开关1电流"] = msg.sternRudderSwitch2Current / 10.0 + bus["current"]["备用电流2"] = msg.reservedCurrent2 / 10.0 + bus["current"][ + "艉舵机开关2电流"] = msg.fbReservedSwitchCurrent / 10.0 + bus["current"]["备用电流3"] = msg.reservedCurrent3 / 10.0 + + # bus["breakers"]["燃料电池断路器"] = 1 if msg.fuelCellCircuitBreaker == 0x55 else 0 + # bus["breakers"]["动力锂电池断路器"] = 1 if msg.powerLithiumBatteryCircuitBreaker == 0x55 else 0 + # bus["breakers"]["推进电机断路器"] = 1 if msg.propulsionMotorCircuitBreaker == 0x55 else 0 + # bus["breakers"]["锂电池组仪表断路器"] = 1 if msg.lithiumBatteryGroupInstrumentCircuitBreaker == 0x55 else 0 + # bus["breakers"]["DC/DC汇流排断路器"] = 1 if msg.dcDc5ModuleCircuitBreaker == 0x55 else 0 + # bus["breakers"]["艏部高压断路器"] = 1 if msg.bowHighVoltageDistributionBoxCircuitBreaker == 0x55 else 0 + # bus["breakers"]["艉FT设备45断路器"] = 1 if msg.sternFTDevice45CircuitBreaker == 0x55 else 0 + # bus["breakers"]["艉舵机开关1断路器"] = 1 if msg.sternRudderSwitch1CircuitBreaker == 0x55 else 0 + # bus["breakers"]["艉舵机开关2断路器"] = 1 if msg.sternRudderSwitch2CircuitBreaker == 0x55 else 0 + # bus["breakers"]["FB备用断路器"] = 1 if msg.fbReservedCircuitBreaker == 0x55 else 0 + # bus["breakers"]["TYZ备用断路器"] = 1 if msg.tyzReservedCircuitBreaker == 0x55 else 0 + # bus["breakers"]["备用断路器"] = 1 if msg.reservedCircuitBreaker == 0x55 else 0 + + bus["breakers"]["燃料电池断路器"] = msg.fuelCellCircuitBreaker + bus["breakers"][ + "动力锂电池断路器"] = msg.powerLithiumBatteryCircuitBreaker + bus["breakers"][ + "推进电机断路器"] = msg.propulsionMotorCircuitBreaker + bus["breakers"][ + "锂电池组仪表断路器"] = msg.lithiumBatteryGroupInstrumentCircuitBreaker + bus["breakers"][ + "DC/DC汇流排断路器"] = msg.dcDc5ModuleCircuitBreaker + bus["breakers"][ + "艏部高压断路器"] = msg.bowHighVoltageDistributionBoxCircuitBreaker + bus["breakers"][ + "艉FT设备45断路器"] = msg.sternFTDevice45CircuitBreaker + bus["breakers"][ + "FB备用断路器"] = msg.sternRudderSwitch1CircuitBreaker + bus["breakers"][ + "艉舵机开关1断路器"] = msg.sternRudderSwitch2CircuitBreaker + bus["breakers"]["艉舵机开关2断路器"] = msg.fbReservedCircuitBreaker + bus["breakers"]["TYZ备用断路器"] = msg.tyzReservedCircuitBreaker + bus["breakers"]["备用断路器"] = msg.reservedCircuitBreaker + + # print('----------------------------',bus["breakers"]) + + bus["faults"]["DC/DC故障1"] = msg.dcDcFaultWord1 + bus["faults"]["DC/DC故障2"] = msg.dcDcFaultWord2 + + if msg.powerBusInsulationStatus == 0x55: + bus["insulation"]["动力母线绝缘状态"] = 0 + else: + bus["insulation"]["动力母线绝缘状态"] = 1 + + if msg.aBusInsulationStatus == 0x55: + bus["insulation"]["A母线绝缘状态"] = 0 + else: + bus["insulation"]["A母线绝缘状态"] = 1 + + # if msg.meterPowerLossSignal == 0x55: + # bus["alarms"]["仪表电源丢失信号"] = 0 + # else: + # bus["alarms"]["仪表电源丢失信号"] = 1 + bus["alarms"]["应急电源丢失信号"] = msg.emergencyPowerLossSignal + bus["alarms"]["仪表电源丢失信号"] = msg.meterPowerLossSignal + # if msg.emergencyPowerLossSignal == 0x55: + # bus["alarms"]["应急电源丢失信号"] = 0 + # else: + # bus["alarms"]["应急电源丢失信号"] = 1 + bus["cool_water_pressure"] = msg.coolWaterPressure / 10.0 + bus["dcdc_temp"] = msg.dcdcTemperature - 40 + + self.status_data["high_vol_bus"] = bus + # 更新UI显示 + # print(self.status_data) + + self.log_message("收到高压母线状态更新") + elif msg_id == 0x0006: # 高压A母线状态 + msg = msg_disHighAVolBusFbMsg() + if msg.unpack(data): + self.db.insertHighAVolBusData(msg) + bus = self.status_data["high_a_vol_bus"] + bus["voltage"]["B母线电压"] = msg.busbarBVoltage / 10.0 + bus["current"]["B母线电流"] = msg.busbarBCurrent / 10.0 + bus["current"][ + "艏FT设备123电流"] = msg.bowFTDevice123Current / 10.0 + bus["current"]["执行机构电流"] = msg.actuatorCurrent / 10.0 + bus["current"][ + "桅舵机控制箱电流"] = msg.mastSteeringGearControlBoxCurrent / 10.0 + bus["current"]["XCZ电流"] = msg.xczCurrent / 10.0 + bus["current"][ + "艏舵控制箱电流"] = msg.bowRudderControlBoxCurrent / 10.0 + bus["current"][ + "开盖启动气缸电流"] = msg.openWaterCoverStartCylinderCurrent / 10.0 + + # bus["breakers"]["艏FT设备123断路器"] = 1 if msg.bowFTDevice123CircuitBreaker == 0x55 else 0 + # bus["breakers"]["启闭机构断路器"] = 1 if msg.actuatorCircuitBreaker == 0x55 else 0 + # bus["breakers"]["桅杆舵机断路器"] = 1 if msg.mastSteeringGearControlBoxCircuitBreaker == 0x55 else 0 + # bus["breakers"]["XCZ断路器"] = 1 if msg.xczCircuitBreaker == 0x55 else 0 + # bus["breakers"]["艏舵控制箱断路器"] = 1 if msg.bowRudderControlBoxCircuitBreaker == 0x55 else 0 + # bus["breakers"]["敞水盖断路器"] = 1 if msg.openWaterCoverStartCylinderCircuitBreaker == 0x55 else 0 + bus["breakers"][ + "艏FT设备123断路器"] = msg.bowFTDevice123CircuitBreaker + bus["breakers"]["启闭机构断路器"] = msg.actuatorCircuitBreaker + bus["breakers"][ + "桅杆舵机断路器"] = msg.mastSteeringGearControlBoxCircuitBreaker + bus["breakers"]["XCZ断路器"] = msg.xczCircuitBreaker + bus["breakers"][ + "艏舵控制箱断路器"] = msg.bowRudderControlBoxCircuitBreaker + bus["breakers"][ + "敞水盖断路器"] = msg.openWaterCoverStartCylinderCircuitBreaker + + bus["alarms"]["仪表电源故障"] = msg.instrumentPowerFailureSignal + bus["alarms"]["应急电源故障"] = msg.emergencyPowerFailureSignal + bus["alarms"]["进水报警"] = msg.waterIngressionAlarm + + self.log_message("收到高压A母线状态更新") + elif msg_id == 0x0007: # 仪表主母线状态 + msg = msg_disLowMainBusFbMsg() + if msg.unpack(data): + self.db.insertLowMainBusData(msg) + bus = self.status_data["low_main_bus"] + bus["voltage"][ + "仪表母线电压"] = msg.instrumentBusbarVoltage / 10.0 + bus["voltage"][ + "应急2母线电压"] = msg.emergency2BusbarVoltage / 10.0 + bus["current"][ + "艏低压配电盒电流"] = msg.bowLowVoltageDistributionBoxCurrent / 10.0 + bus["current"][ + "DC/C1仪表48V电流"] = msg.dcC1InstrumentDC48VCurrent / 10.0 + bus["current"]["备用电流1"] = msg.reservedCurrent1 / 10.0 + bus["current"][ + "应急锂电池组2电流"] = msg.emergencyLithiumBatteryGroup2Current / 10.0 + bus["current"][ + "锂电池组仪表电流"] = msg.lithiumBatteryGroupInstrumentCurrent / 10.0 + bus["current"][ + "单元4仪表48V电流"] = msg.unit4InstrumentDC48VCurrent / 10.0 + bus["current"]["备用电流2"] = msg.reservedCurrent2 / 10.0 + bus["current"][ + "综合能量管理系统应急DC48V电流"] = msg.compositeEnergyManagementSystemEmergencyDC48VCurrent / 10.0 + bus["current"][ + "燃料电池安全系统应急DC48V电流"] = msg.fuelCellSecuritySystemEmergencyDC48VCurrent / 10.0 + bus["current"][ + "PLC控制电源电流"] = msg.plcControlPowerCurrent / 10.0 + + # bus["breakers"]["锂电池组仪表断路器"] = 1 if msg.lithiumBatteryGroupInstrumentCircuitBreaker == 0x55 else 0 + # bus["breakers"]["艏部低压断路器"] = 1 if msg.bowLowVoltageDistributionBoxCircuitBreaker == 0x55 else 0 + # bus["breakers"]["单元4仪表48V断路器"] = 1 if msg.unit4InstrumentDC48VCircuitBreaker == 0x55 else 0 + # bus["breakers"]["DC/C1直流配电板断路器"] = 1 if msg.dcC1DCDistributionPanelCircuitBreaker == 0x55 else 0 + # bus["breakers"]["备用断路器1"] = 1 if msg.reservedCircuitBreaker1 == 0x55 else 0 + # bus["breakers"]["备用断路器2"] = 1 if msg.reservedCircuitBreaker2 == 0x55 else 0 + # bus["breakers"]["应急锂电池组2断路器"] = 1 if msg.emergencyLithiumBatteryGroup2CircuitBreaker == 0x55 else 0 + bus["breakers"][ + "锂电池组仪表断路器"] = msg.lithiumBatteryGroupInstrumentCircuitBreaker + bus["breakers"][ + "艏部低压断路器"] = msg.bowLowVoltageDistributionBoxCircuitBreaker + bus["breakers"][ + "单元4仪表48V断路器"] = msg.unit4InstrumentDC48VCircuitBreaker + bus["breakers"][ + "DC/C1直流配电板断路器"] = msg.dcC1DCDistributionPanelCircuitBreaker + bus["breakers"]["备用断路器1"] = msg.reservedCircuitBreaker1 + bus["breakers"]["备用断路器2"] = msg.reservedCircuitBreaker2 + bus["breakers"][ + "应急锂电池组2断路器"] = msg.emergencyLithiumBatteryGroup2CircuitBreaker + + # 0x55 正常 + # 0xaa 故障 + # if msg.instrumentPowerFailureSignal == 0x55: + # bus["alarms"]["仪表电源故障"] = 0 + # else: + # bus["alarms"]["仪表电源故障"] = 1 + bus["alarms"]["仪表电源故障"] = msg.instrumentPowerFailureSignal + # if msg.emergencyPowerFailureSignal == 0x55: + # bus["alarms"]["应急电源故障"] = 0 + # else: + # bus["alarms"]["应急电源故障"] = 1 + bus["alarms"]["应急电源故障"] = msg.emergencyPowerFailureSignal + # bus["insulation"] = msg.instrumentBusbarInsulationLow + if msg.instrumentBusbarInsulationLow == 0x55: + bus["insulation"] = 0 + else: + bus["insulation"] = 1 + bus["insulation"] = msg.instrumentBusbarInsulationLow + + self.log_message("收到仪表主母线状态更新") + else: + print("仪表主母线状态", "解析失败") + + elif msg_id == 0x0008: # 仪表母线状态 + msg = msg_disLowBusFbMsg() + if msg.unpack(data): + self.db.insertLowBusData(msg) + bus = self.status_data["low_bus"] + bus["voltage"][ + "仪表母线电压"] = msg.instrumentBusbarVoltage / 10.0 + bus["voltage"][ + "应急1母线电压"] = msg.emergency1BusbarVoltage / 10.0 + bus["current"][ + "仪表母线电流"] = msg.instrumentBusbarCurrent / 10.0 + bus["current"]["单元1电流"] = msg.unit1Current / 10.0 + bus["current"]["单元2电流"] = msg.unit2Current / 10.0 + bus["current"]["单元3电流"] = msg.unit3Current / 10.0 + bus["current"]["单元5电流"] = msg.unit5Current / 10.0 + bus["current"]["艏PZ设备电流"] = msg.bowPZDeviceCurrent / 10.0 + bus["current"][ + "应急锂电池组1电流"] = msg.emergencyLithiumBatteryGroup1Current / 10.0 + + # bus["breakers"]["单元1断路器"] = 1 if msg.unit1CircuitBreaker == 0x55 else 0 + # bus["breakers"]["单元2断路器"] = 1 if msg.unit2CircuitBreaker == 0x55 else 0 + # bus["breakers"]["单元3断路器"] = 1 if msg.unit3CircuitBreaker == 0x55 else 0 + # bus["breakers"]["单元5断路器"] = 1 if msg.unit5CircuitBreaker == 0x55 else 0 + # bus["breakers"]["艏PZ设备断路器"] = 1 if msg.bowPZDeviceCircuitBreaker == 0x55 else 0 + # bus["breakers"]["备用断路器1"] = 1 if msg.reservedCircuitBreaker1 == 0x55 else 0 + # bus["breakers"]["备用断路器2"] = 1 if msg.reservedCircuitBreaker2 == 0x55 else 0 + # bus["breakers"]["应急锂电池组1断路器"] = 1 if msg.emergencyLithiumBatteryGroup1CircuitBreaker == 0x55 else 0 + + bus["breakers"]["单元1断路器"] = msg.unit1CircuitBreaker + bus["breakers"]["单元2断路器"] = msg.unit2CircuitBreaker + bus["breakers"]["单元3断路器"] = msg.unit3CircuitBreaker + bus["breakers"]["单元5断路器"] = msg.unit5CircuitBreaker + bus["breakers"]["艏PZ设备断路器"] = msg.bowPZDeviceCircuitBreaker + bus["breakers"]["备用断路器1"] = msg.reservedCircuitBreaker1 + bus["breakers"]["备用断路器2"] = msg.reservedCircuitBreaker2 + bus["breakers"][ + "应急锂电池组1断路器"] = msg.emergencyLithiumBatteryGroup1CircuitBreaker + + bus["alarms"]["仪表电源故障"] = msg.instrumentPowerFailureSignal + bus["alarms"]["应急电源故障"] = msg.emergencyPowerFailureSignal + bus["alarms"]["进水报警"] = msg.waterIngressionAlarm + self.log_message("收到仪表母线状态更新") + else: + print("仪表母线状态", "解析失败") + + else: + self.log_message("消息解析失败") + print('消息处理完成') + + except Exception as e: + self.log_message(f"消息处理错误: {str(e)}") + + def setup_low_main_ui(self, parent): + """设置仪表主母线状态界面""" + frame = ttk.LabelFrame(parent, text="仪表主母线状态") + frame.pack(fill=tk.BOTH, expand=True, padx=2, pady=2) + + # 显式配置4列网格布局 + frame.grid_columnconfigure(0, weight=1) + frame.grid_columnconfigure(1, weight=1) + frame.grid_columnconfigure(2, weight=1) + frame.grid_columnconfigure(3, weight=1) + + # 电压显示 + ttk.Label(frame, text="仪表母线电压(V):").grid(row=0, column=0, sticky=tk.W) + self.low_main_instrument_voltage = ttk.Label(frame, text="0.0") + self.low_main_instrument_voltage.grid(row=0, column=1, sticky=tk.W) + + ttk.Label(frame, text="应急2汇流排电压(V):").grid(row=0, + column=2, + sticky=tk.W) + self.low_main_emergency2_voltage = ttk.Label(frame, text="0.0") + self.low_main_emergency2_voltage.grid(row=0, column=3, sticky=tk.W) + + # 电流显示 + ttk.Label(frame, text="艏部低压配电箱电流(A):").grid(row=1, + column=0, + sticky=tk.W) + self.low_main_bow_current = ttk.Label(frame, text="0.0") + self.low_main_bow_current.grid(row=1, column=1, sticky=tk.W) + + ttk.Label(frame, text="锂电池组(仪表)电流(A):").grid(row=1, + column=2, + sticky=tk.W) + self.low_main_battery_current = ttk.Label(frame, text="0.0") + self.low_main_battery_current.grid(row=1, column=3, sticky=tk.W) + + # 补充其他电流显示项 + ttk.Label(frame, text="DC-C1(直流配电板1)仪表DC48V电流(A):").grid(row=2, + column=0, + sticky=tk.W) + self.low_main_dcc1_current = ttk.Label(frame, text="0.0") + self.low_main_dcc1_current.grid(row=2, column=1, sticky=tk.W) + + ttk.Label(frame, text="应急锂电池组2电流(A):").grid(row=2, + column=2, + sticky=tk.W) + self.low_main_emergency2_current = ttk.Label(frame, text="0.0") + self.low_main_emergency2_current.grid(row=2, column=3, sticky=tk.W) + + ttk.Label(frame, text="单元4仪表48V电流(A):").grid(row=3, + column=0, + sticky=tk.W) + self.low_main_unit4_current = ttk.Label(frame, text="0.0") + self.low_main_unit4_current.grid(row=3, column=1, sticky=tk.W) + + ttk.Label(frame, text="复合能源管控系统应急DC48V电流(A):").grid(row=3, + column=2, + sticky=tk.W) + self.low_main_energy_current = ttk.Label(frame, text="0.0") + self.low_main_energy_current.grid(row=3, column=3, sticky=tk.W) + + ttk.Label(frame, text="燃料电池安保系统应急DC48V电流(A):").grid(row=4, + column=0, + sticky=tk.W) + self.low_main_fuelcell_current = ttk.Label(frame, text="0.0") + self.low_main_fuelcell_current.grid(row=4, column=1, sticky=tk.W) + + ttk.Label(frame, text="PLC控制电源电流(A):").grid(row=4, + column=2, + sticky=tk.W) + self.low_main_plc_current = ttk.Label(frame, text="0.0") + self.low_main_plc_current.grid(row=4, column=3, sticky=tk.W) + + # 断路器状态 + ttk.Label(frame, text="断路器状态:").grid(row=5, + column=0, + columnspan=4, + sticky=tk.W) + self.low_main_breakers = {} + breaker_names = list( + self.status_data["low_main_bus"]["breakers"].keys()) + + # 断路器状态 - 调整为每行3个断路器,从行6开始 + for i, name in enumerate(breaker_names): + row = 6 + i // 3 + col = (i % 3) * 2 + ttk.Label(frame, text=name).grid(row=row, column=col, sticky=tk.W) + canvas = tk.Canvas(frame, width=20, height=20, bg="white") + canvas.grid(row=row, column=col + 1, sticky=tk.W) + self.low_main_breakers[name] = canvas + + # 报警状态 + ttk.Label(frame, text="仪表电源失电信号 :").grid(row=9, column=0, sticky=tk.W) + self.low_main_instrument_fault = ttk.Label(frame, text="无") + self.low_main_instrument_fault.grid(row=9, column=1, sticky=tk.W) + + ttk.Label(frame, text="应急电源失电信号 :").grid(row=9, column=2, sticky=tk.W) + self.low_main_emergency_fault = ttk.Label(frame, text="无") + self.low_main_emergency_fault.grid(row=9, column=3, sticky=tk.W) + + # 绝缘状态 + ttk.Label(frame, text="仪表汇流排绝缘低 :").grid(row=10, column=0, sticky=tk.W) + self.low_main_insulation = ttk.Label(frame, text="正常") + self.low_main_insulation.grid(row=10, column=1, sticky=tk.W) + + # 备用电流 + ttk.Label(frame, text="备用电流1 :").grid(row=11, column=0, sticky=tk.W) + self.low_main_reserved1 = ttk.Label(frame, text="0.0") + self.low_main_reserved1.grid(row=11, column=1, sticky=tk.W) + + ttk.Label(frame, text="备用电流2 :").grid(row=11, column=2, sticky=tk.W) + self.low_main_reserved2 = ttk.Label(frame, text="0.0") + self.low_main_reserved2.grid(row=11, column=3, sticky=tk.W) + + return frame + + def setup_low_bus_ui(self, parent): + """设置仪表母线状态界面""" + frame = ttk.LabelFrame(parent, text="仪表母线状态") + frame.pack(fill=tk.BOTH, expand=True, padx=2, pady=2) + + # 显式配置4列网格布局 + frame.grid_columnconfigure(0, weight=1) + frame.grid_columnconfigure(1, weight=1) + frame.grid_columnconfigure(2, weight=1) + frame.grid_columnconfigure(3, weight=1) + + # 电压显示 + ttk.Label(frame, text="仪表母排电压(V):").grid(row=0, column=0, sticky=tk.W) + self.low_bus_instrument_voltage = ttk.Label(frame, text="0.0") + self.low_bus_instrument_voltage.grid(row=0, column=1, sticky=tk.W) + + ttk.Label(frame, text="应急1母线电压(V):").grid(row=0, column=2, sticky=tk.W) + self.low_bus_emergency1_voltage = ttk.Label(frame, text="0.0") + self.low_bus_emergency1_voltage.grid(row=0, column=3, sticky=tk.W) + + # 电流显示 + ttk.Label(frame, text="单元1电流(A):").grid(row=1, column=0, sticky=tk.W) + self.low_bus_unit1_current = ttk.Label(frame, text="0.0") + self.low_bus_unit1_current.grid(row=1, column=1, sticky=tk.W) + + ttk.Label(frame, text="单元2电流(A):").grid(row=1, column=2, sticky=tk.W) + self.low_bus_unit2_current = ttk.Label(frame, text="0.0") + self.low_bus_unit2_current.grid(row=1, column=3, sticky=tk.W) + + ttk.Label(frame, text="仪表母排电流(A):").grid(row=2, column=0, sticky=tk.W) + self.low_bus_main_current = ttk.Label(frame, text="0.0") + self.low_bus_main_current.grid(row=2, column=1, sticky=tk.W) + + ttk.Label(frame, text="单元3电流(A):").grid(row=2, column=2, sticky=tk.W) + self.low_bus_unit3_current = ttk.Label(frame, text="0.0") + self.low_bus_unit3_current.grid(row=2, column=3, sticky=tk.W) + + ttk.Label(frame, text="单元5电流(A):").grid(row=3, column=0, sticky=tk.W) + self.low_bus_unit5_current = ttk.Label(frame, text="0.0") + self.low_bus_unit5_current.grid(row=3, column=1, sticky=tk.W) + + ttk.Label(frame, text="艏PZ设备电流(A):").grid(row=3, column=2, sticky=tk.W) + self.low_bus_bow_pz_current = ttk.Label(frame, text="0.0") + self.low_bus_bow_pz_current.grid(row=3, column=3, sticky=tk.W) + + ttk.Label(frame, text="应急锂电池组1电流(A):").grid(row=4, + column=0, + sticky=tk.W) + self.low_bus_emergency1_current = ttk.Label(frame, text="0.0") + self.low_bus_emergency1_current.grid(row=4, column=1, sticky=tk.W) + + # 断路器状态 + ttk.Label(frame, text="断路器状态:").grid(row=5, + column=0, + columnspan=4, + sticky=tk.W) + self.low_bus_breakers = {} + breaker_names = list(self.status_data["low_bus"]["breakers"].keys()) + + for i, name in enumerate(breaker_names): + row = 6 + i // 2 + col = (i % 2) * 2 + ttk.Label(frame, text=name).grid(row=row, column=col, sticky=tk.W) + canvas = tk.Canvas(frame, width=20, height=20, bg="white") + canvas.grid(row=row, column=col + 1, sticky=tk.W) + self.low_bus_breakers[name] = canvas + + # 报警状态 + ttk.Label(frame, text="仪表电失电信号 :").grid(row=10, column=0, sticky=tk.W) + self.low_bus_power_fault = ttk.Label(frame, text="无") + self.low_bus_power_fault.grid(row=10, column=1, sticky=tk.W) + + ttk.Label(frame, text="应急电失电信号 :").grid(row=11, column=0, sticky=tk.W) + self.low_bus_emgpower_fault = ttk.Label(frame, text="无") + self.low_bus_emgpower_fault.grid(row=11, column=1, sticky=tk.W) + + ttk.Label(frame, text="浸水报警 :").grid(row=10, column=2, sticky=tk.W) + self.low_bus_water_alarm = ttk.Label(frame, text="无") + self.low_bus_water_alarm.grid(row=10, column=3, sticky=tk.W) + + return frame + + def send_udp_command(self, data, ip, port): + """发送UDP命令""" + try: + # self.sock.sendto(data, (self.udp_ip, self.udp_port + 1)) + self.sock.sendto(data, (ip, port)) + print(f"发送命令到 {ip}:{port} ", data) + except Exception as e: + self.log_message(f"发送命令失败: {str(e)}") + + def update_high_vol_ui(self, status): + """更新高压母线UI显示""" + # 更新电压显示 + self.high_vol_power_voltage.config( + text=f"{status['voltage']['动力母线电压']:.1f} V") + self.high_vol_a_voltage.config( + text=f"{status['voltage']['A母线电压']:.1f} V") + + # # 更新电流显示 + # self.high_vol_power_current.config(text=f"{status['current']['动力母线电流']:.1f}") + # self.high_vol_a_current.config(text=f"{status['current']['A母线电流']:.1f}") + + # 更新所有电流值 + current_vars = [('powerBusCurrent', '动力母线电流'), + ('busbarACurrent', 'A母线电流'), + ('dcDc5ModuleCurrent', 'DC/DC5模块电流'), + ('propulsionMotorControlBoxCurrent', '推进电机控制箱电流'), + ('lithiumBatteryGroupMeterCurrent', '锂电池组仪表电流'), + ('bowHighVoltageDistributionBoxCurrent', '艏高压配电盒电流'), + ('sternFTDevice45Current', '艉FT设备45电流'), + ('tyzReservedSwitchCurrent', 'TYZ备用开关电流'), + ('fbReservedSwitchCurrent', '艉舵机开关2电流'), + ('sternRudderSwitch1Current', 'FB备用开关电流'), + ('sternRudderSwitch2Current', '艉舵机开关1电流'), + ('reservedCurrent1', '备用电流1'), + ('reservedCurrent2', '备用电流2'), + ('reservedCurrent3', '备用电流3')] + + for var_name, data_key in current_vars: + label = getattr(self, f"high_vol_{var_name}") + label.config(text=f"{status['current'][data_key]:.1f} A") + + # 更新断路器状态 + for name, state in status['breakers'].items(): + if name in self.high_vol_breakers: + canvas = self.high_vol_breakers[name] + # color = 'green' if state else 'red' + # print(name, hex(state)) + if state == 0x55: + color = "yellow" + elif state == 0xAA: + color = "black" + elif state == 0x5A: + color = "red" + else: + color = "white" + canvas.delete("all") + canvas.create_oval(2, 2, 18, 18, fill=color, outline="black") + + # 更新DC/DC故障状态 + dcdc_fault1 = status['faults']['DC/DC故障1'] + dcdc_fault2 = status['faults']['DC/DC故障2'] + + for i in range(8): + color1 = 'red' if (dcdc_fault1 & (1 << i)) else 'green' + color2 = 'red' if (dcdc_fault2 & (1 << i)) else 'green' + self.high_vol_dcdc_fault1_bits[i].config(bg=color1) + self.high_vol_dcdc_fault2_bits[i].config(bg=color2) + + # 更新绝缘状态 + insulation_power = status['insulation']['动力母线绝缘状态'] + insulation_a = status['insulation']['A母线绝缘状态'] + + power_text = "故障" if insulation_power else f"正常" + a_text = "故障" if insulation_a else f"正常" + + self.high_vol_power_insulation.config(text=power_text) + self.high_vol_a_insulation.config(text=a_text) + + # 更新冷却水压力 + self.high_vol_cool_water_pressure.config( + text=f"{status['cool_water_pressure']:.1f} Kpa") + + # # 更新温度状态 + self.high_vol_dcdc_temp.config(text=f"{status['dcdc_temp']} 摄氏度") + # "仪表电源丢失信号": 0, + # "应急电源丢失信号": 0 + # 更新失电故障显示 + self.high_vol_losspower_insulation.config( + text="正常" if status['alarms']['仪表电源丢失信号'] == 0x55 else "故障") + self.high_vol_loss_emerpower_insulation.config( + text="正常" if status['alarms']['应急电源丢失信号'] == 0x55 else "故障") + + def update_ui(self): + """更新界面状态""" + # 更新高压母线状态 + high_vol = self.status_data["high_vol_bus"] + self.update_high_vol_ui(high_vol) + # 更新高压A母线状态 + high_a_vol = self.status_data["high_a_vol_bus"] + self.high_a_vol_voltage.config( + text=f"{high_a_vol['voltage']['B母线电压']:.1f} V") + self.high_a_vol_current.config( + text=f"{high_a_vol['current']['B母线电流']:.1f} A") + self.high_a_vol_bow_ft_current.config( + text=f"{high_a_vol['current']['艏FT设备123电流']:.1f} A") + self.high_a_vol_actuator_current.config( + text=f"{high_a_vol['current']['执行机构电流']:.1f} A") + + # 更新新增的电流显示项 + self.high_a_vol_mast_steering_current.config( + text=f"{high_a_vol['current']['桅舵机控制箱电流']:.1f} A") + self.high_a_vol_xcz_current.config( + text=f"{high_a_vol['current']['XCZ电流']:.1f} A") + self.high_a_vol_bow_rudder_current.config( + text=f"{high_a_vol['current']['艏舵控制箱电流']:.1f} A") + self.high_a_vol_open_cover_current.config( + text=f"{high_a_vol['current']['开盖启动气缸电流']:.1f} A") + + for name, canvas in self.high_a_vol_breakers.items(): + state = high_a_vol["breakers"].get(name, 0) + if state == 0x55: + color = "yellow" + elif state == 0xAA: + color = "black" + elif state == 0x5A: + color = "red" + else: + color = "white" + # color = "green" if state else "red" + canvas.create_oval(2, 2, 18, 18, fill=color, outline="black") + + # 更新仪表主母线状态 + low_main = self.status_data["low_main_bus"] + for name, canvas in self.low_main_breakers.items(): + state = low_main["breakers"].get(name, 0) + # color = "green" if state else "red" + if state == 0x55: + color = "yellow" + elif state == 0xAA: + color = "black" + elif state == 0x5A: + color = "red" + else: + color = "white" + canvas.create_oval(2, 2, 18, 18, fill=color, outline="black") + + # 更新仪表母线状态 + low_bus = self.status_data["low_bus"] + for name, canvas in self.low_bus_breakers.items(): + state = low_bus["breakers"].get(name, 0) + # color = "green" if state else "red" + if state == 0x55: + color = "yellow" + elif state == 0xAA: + color = "black" + elif state == 0x5A: + color = "red" + else: + color = "white" + canvas.create_oval(2, 2, 18, 18, fill=color, outline="black") + + self.high_a_vol_instrument_power_fault.config( + text="正常" if high_a_vol["alarms"]["仪表电源故障"] == 0x55 else "故障") + self.high_a_vol_emergency_power_fault.config( + text="正常" if high_a_vol["alarms"]["应急电源故障"] == 0x55 else "故障") + self.high_a_vol_water_alarm.config( + text="正常" if high_a_vol["alarms"]["进水报警"] == 0x55 else "故障") + + # 更新仪表主母线状态 + low_main = self.status_data["low_main_bus"] + self.low_main_instrument_voltage.config( + text=f"{low_main['voltage']['仪表母线电压']:.1f} V") + self.low_main_emergency2_voltage.config( + text=f"{low_main['voltage']['应急2母线电压']:.1f} V") + self.low_main_bow_current.config( + text=f"{low_main['current']['艏低压配电盒电流']:.1f} A") + self.low_main_battery_current.config( + text=f"{low_main['current']['锂电池组仪表电流']:.1f} A") + + # 更新新增的电流显示项 + self.low_main_dcc1_current.config( + text=f"{low_main['current']['DC/C1仪表48V电流']:.1f} A") + self.low_main_emergency2_current.config( + text=f"{low_main['current']['应急锂电池组2电流']:.1f} A") + self.low_main_unit4_current.config( + text=f"{low_main['current']['单元4仪表48V电流']:.1f} A") + self.low_main_energy_current.config( + text=f"{low_main['current']['综合能量管理系统应急DC48V电流']:.1f} A") + self.low_main_fuelcell_current.config( + text=f"{low_main['current']['燃料电池安全系统应急DC48V电流']:.1f} A") + self.low_main_plc_current.config( + text=f"{low_main['current']['PLC控制电源电流']:.1f} A") + # 新增加备用电流 + self.low_main_reserved1.config( + text=f"{low_main['current']['备用电流1']:.1f} A") + self.low_main_reserved2.config( + text=f"{low_main['current']['备用电流2']:.1f} A") + + for name, canvas in self.low_main_breakers.items(): + state = low_main["breakers"].get(name, 0) + # color = "green" if state else "red" + if state == 0x55: + color = "yellow" + elif state == 0xAA: + color = "black" + elif state == 0x5A: + color = "red" + else: + color = "white" + canvas.create_oval(2, 2, 18, 18, fill=color, outline="black") + + self.low_main_instrument_fault.config( + text="正常" if low_main["alarms"]["仪表电源故障"] == 0x55 else "故障") + self.low_main_emergency_fault.config( + text="正常" if low_main["alarms"]["应急电源故障"] == 0x55 else "故障") + self.low_main_insulation.config( + text="正常" if low_main["insulation"] == 0x55 else "故障") + + # 更新仪表母线状态 + low_bus = self.status_data["low_bus"] + self.low_bus_instrument_voltage.config( + text=f"{low_bus['voltage']['仪表母线电压']:.1f} V") + self.low_bus_emergency1_voltage.config( + text=f"{low_bus['voltage']['应急1母线电压']:.1f} V") + self.low_bus_unit1_current.config( + text=f"{low_bus['current']['单元1电流']:.1f} A") + self.low_bus_unit2_current.config( + text=f"{low_bus['current']['单元2电流']:.1f} A") + + # 更新新增的电流显示项 + self.low_bus_main_current.config( + text=f"{low_bus['current']['仪表母线电流']:.1f} A") + self.low_bus_unit3_current.config( + text=f"{low_bus['current']['单元3电流']:.1f} A") + self.low_bus_unit5_current.config( + text=f"{low_bus['current']['单元5电流']:.1f} A") + self.low_bus_bow_pz_current.config( + text=f"{low_bus['current']['艏PZ设备电流']:.1f} A") + self.low_bus_emergency1_current.config( + text=f"{low_bus['current']['应急锂电池组1电流']:.1f} A") + + for name, canvas in self.low_bus_breakers.items(): + state = low_bus["breakers"].get(name, 0) + # color = "green" if state else "red" + if state == 0x55: + color = "yellow" + elif state == 0xAA: + color = "black" + elif state == 0x5A: + color = "red" + else: + color = "white" + canvas.create_oval(2, 2, 18, 18, fill=color, outline="black") + + self.low_bus_power_fault.config( + text="正常" if low_bus["alarms"]["仪表电源故障"] == 0x55 else "故障") + self.low_bus_emgpower_fault.config( + text="正常" if low_bus["alarms"]["应急电源故障"] == 0x55 else "故障") + self.low_bus_water_alarm.config( + text="正常" if low_bus["alarms"]["进水报警"] == 0x55 else "故障") + # print('update_ui') + # 每500ms更新一次 + self.root.after(1000, self.update_ui) + + def log_message(self, message): + """记录日志消息""" + timestamp = time.strftime("%Y-%m-%d %H:%M:%S") + self.log_text.config(state=tk.NORMAL) + self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") + + # 获取当前所有行数 + line_count = int(self.log_text.index('end-1c').split('.')[0]) + # 如果超过50行,删除最旧的行 + if line_count > 50: + self.log_text.delete(1.0, 2.0) + + self.log_text.config(state=tk.DISABLED) + self.log_text.see(tk.END) + + def on_closing(self): + """窗口关闭事件处理""" + self.running = False + self.sock.close() + self.root.destroy() + + +if __name__ == "__main__": + root = tk.Tk() + app = PowerManagerApp(root) + + root.protocol("WM_DELETE_WINDOW", app.on_closing) + print('start') + root.mainloop() + print('end') diff --git a/test/until/DriverTest.cpp b/test/until/DriverTest.cpp index cf330d8..d461672 100644 --- a/test/until/DriverTest.cpp +++ b/test/until/DriverTest.cpp @@ -2,6 +2,13 @@ // DriverTest: Driver 类的设备表生成(覆盖全部 10 个工况)、设备状态读写、 // JSON 序列化/反序列化。 // 不改动生产代码;仅链接 src/pPowerManger/driver.cpp。 +// +// 测试目的: +// 1. 验证每种工况模式下,驱动表生成是否符合预期的设备开关状态 +// 2. 验证设备状态的读写操作(setDeviceState / getDeviceState) +// 3. 验证驱动表的 JSON 序列化/反序列化是否能正确往返 +// 4. 验证 JSON 解析为设备命令的逻辑 +// 5. 验证安全读取无符号整数的边界处理 //======================================================================= #include #include @@ -10,6 +17,7 @@ // ---------------------------------------------------------------------- // 全字段比较宏(62 个设备字段) +// 用于比较两个 DriverTable 结构体的所有字段是否完全相等 // ---------------------------------------------------------------------- #define EXPECT_TABLE_EQ(a, b) do { \ EXPECT_EQ((a).depth1,(b).depth1); EXPECT_EQ((a).depth2,(b).depth2); \