@@ -0,0 +1,651 @@
<!DOCTYPE html>
< html lang = "zh-CN" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > pPowerManger 代码审查与架构优化报告< / title >
< style >
: root {
--bg : #0f1419 ; --panel : #171e26 ; --panel2 : #1d2630 ; --border : #2a3642 ;
--text : #d8e0e8 ; --text-dim : #8fa0b0 ; --accent : #4da3ff ; --accent2 : #37c8a0 ;
--p0 : #ff5c5c ; --p1 : #ffb347 ; --p2 : #6fbf73 ; --code-bg : #101820 ;
}
* { margin : 0 ; padding : 0 ; box-sizing : border-box ; }
body { background : var ( - - bg ) ; color : var ( - - text ) ; font-family : "Microsoft YaHei" , "PingFang SC" , "Segoe UI" , sans-serif ; line-height : 1.75 ; font-size : 15 px ; }
. layout { display : flex ; max-width : 1440 px ; margin : 0 auto ; }
nav { position : sticky ; top : 0 ; height : 100 vh ; width : 250 px ; flex-shrink : 0 ; overflow-y : auto ; padding : 28 px 18 px ; border-right : 1 px solid var ( - - border ) ; background : var ( - - panel ) ; }
nav h3 { font-size : 13 px ; color : var ( - - text - dim ) ; letter-spacing : 2 px ; margin-bottom : 14 px ; }
nav a { display : block ; color : var ( - - text - dim ) ; text-decoration : none ; padding : 5 px 10 px ; border-radius : 6 px ; font-size : 13.5 px ; border-left : 2 px solid transparent ; }
nav a : hover { color : var ( - - accent ) ; background : var ( - - panel2 ) ; }
main { flex : 1 ; padding : 44 px 56 px 120 px ; max-width : 1140 px ; }
header . cover { border-bottom : 1 px solid var ( - - border ) ; padding-bottom : 30 px ; margin-bottom : 36 px ; }
header . cover . tag { color : var ( - - accent ) ; font-size : 13 px ; letter-spacing : 3 px ; }
header . cover h1 { font-size : 30 px ; margin : 10 px 0 8 px ; font-weight : 600 ; }
header . cover . meta { color : var ( - - text - dim ) ; font-size : 13.5 px ; }
h2 { font-size : 22 px ; margin : 52 px 0 18 px ; padding-left : 14 px ; border-left : 4 px solid var ( - - accent ) ; font-weight : 600 ; }
h3 { font-size : 17 px ; margin : 30 px 0 12 px ; color : var ( - - accent ) ; font-weight : 600 ; }
h4 { font-size : 15.5 px ; margin : 20 px 0 8 px ; color : var ( - - text ) ; }
p { margin : 10 px 0 ; color : var ( - - text ) ; }
. card { background : var ( - - panel ) ; border : 1 px solid var ( - - border ) ; border-radius : 10 px ; padding : 20 px 24 px ; margin : 16 px 0 ; }
table { width : 100 % ; border-collapse : collapse ; margin : 14 px 0 ; font-size : 13.5 px ; }
th { background : var ( - - panel2 ) ; color : var ( - - accent ) ; text-align : left ; padding : 9 px 12 px ; border : 1 px solid var ( - - border ) ; font-weight : 600 ; white-space : nowrap ; }
td { padding : 8 px 12 px ; border : 1 px solid var ( - - border ) ; vertical-align : top ; }
tr : nth-child ( even ) td { background : rgba ( 255 , 255 , 255 , 0.02 ) ; }
code { font-family : Consolas , "Courier New" , monospace ; background : var ( - - code - bg ) ; color : #9fd3ff ; padding : 1 px 6 px ; border-radius : 4 px ; font-size : 13 px ; }
pre { background : var ( - - code - bg ) ; border : 1 px solid var ( - - border ) ; border-radius : 8 px ; padding : 14 px 18 px ; overflow-x : auto ; margin : 12 px 0 ; }
pre code { background : none ; padding : 0 ; color : #c8d8e8 ; font-size : 13 px ; line-height : 1.6 ; }
. badge { display : inline-block ; padding : 1 px 10 px ; border-radius : 10 px ; font-size : 12 px ; font-weight : 600 ; margin-right : 6 px ; white-space : nowrap ; }
. p0 { background : rgba ( 255 , 92 , 92 , .15 ) ; color : var ( - - p0 ) ; border : 1 px solid var ( - - p0 ) ; }
. p1 { background : rgba ( 255 , 179 , 71 , .12 ) ; color : var ( - - p1 ) ; border : 1 px solid var ( - - p1 ) ; }
. p2 { background : rgba ( 111 , 191 , 115 , .12 ) ; color : var ( - - p2 ) ; border : 1 px solid var ( - - p2 ) ; }
. loc { color : var ( - - text - dim ) ; font-size : 12.5 px ; font-family : Consolas , monospace ; }
ul , ol { margin : 8 px 0 8 px 24 px ; }
li { margin : 5 px 0 ; }
. flow { display : flex ; align-items : center ; flex-wrap : wrap ; gap : 8 px ; margin : 14 px 0 ; font-size : 13 px ; }
. node { background : var ( - - panel2 ) ; border : 1 px solid var ( - - accent ) ; border-radius : 8 px ; padding : 8 px 14 px ; color : var ( - - text ) ; }
. node . green { border-color : var ( - - accent2 ) ; }
. node . orange { border-color : var ( - - p1 ) ; }
. arrow { color : var ( - - accent ) ; font-weight : bold ; }
. note { border-left : 3 px solid var ( - - accent2 ) ; background : rgba ( 55 , 200 , 160 , .06 ) ; padding : 12 px 18 px ; border-radius : 0 8 px 8 px 0 ; margin : 14 px 0 ; font-size : 14 px ; }
. warn { border-left : 3 px solid var ( - - p1 ) ; background : rgba ( 255 , 179 , 71 , .06 ) ; padding : 12 px 18 px ; border-radius : 0 8 px 8 px 0 ; margin : 14 px 0 ; font-size : 14 px ; }
. danger { border-left : 3 px solid var ( - - p0 ) ; background : rgba ( 255 , 92 , 92 , .07 ) ; padding : 12 px 18 px ; border-radius : 0 8 px 8 px 0 ; margin : 14 px 0 ; font-size : 14 px ; }
. grid2 { display : grid ; grid-template-columns : 1 fr 1 fr ; gap : 16 px ; }
. kpi { display : grid ; grid-template-columns : repeat ( 4 , 1 fr ) ; gap : 14 px ; margin : 18 px 0 ; }
. kpi . item { background : var ( - - panel ) ; border : 1 px solid var ( - - border ) ; border-radius : 10 px ; padding : 16 px ; text-align : center ; }
. kpi . num { font-size : 26 px ; font-weight : 700 ; color : var ( - - accent ) ; }
. kpi . lbl { font-size : 12.5 px ; color : var ( - - text - dim ) ; margin-top : 4 px ; }
. phase { border-left : 3 px solid var ( - - accent ) ; padding : 6 px 0 6 px 20 px ; margin : 18 px 0 ; position : relative ; }
. phase :: before { content : "" ; position : absolute ; left : -7 px ; top : 12 px ; width : 11 px ; height : 11 px ; border-radius : 50 % ; background : var ( - - accent ) ; }
. phase h4 { margin : 0 0 4 px ; color : var ( - - accent ) ; }
. small { font-size : 13 px ; color : var ( - - text - dim ) ; }
@ media ( max-width : 1000px ) { nav { display : none ; } main { padding : 24 px ; } . kpi { grid-template-columns : repeat ( 2 , 1 fr ) ; } . grid2 { grid-template-columns : 1 fr ; } }
< / style >
< / head >
< body >
< div class = "layout" >
< nav >
< h3 > 目录< / h3 >
< a href = "#s1" > 1 审查概述< / a >
< a href = "#s2" > 2 现状架构分析< / a >
< a href = "#s3" > 3 代码审查意见< / a >
< a href = "#s3-1" > 3.1 确定性缺陷(Bug 级)< / a >
< a href = "#s3-2" > 3.2 状态机与控制逻辑< / a >
< a href = "#s3-3" > 3.3 通信逻辑< / a >
< a href = "#s3-4" > 3.4 故障码体系< / a >
< a href = "#s3-5" > 3.5 数据管理与并发< / a >
< a href = "#s3-6" > 3.6 上位机与 Web 服务< / a >
< a href = "#s3-7" > 3.7 构建工程与配置管理< / a >
< a href = "#s4" > 4 架构优化方案< / a >
< a href = "#s4-1" > 4.1 目标分层架构< / a >
< a href = "#s4-2" > 4.2 状态机重构< / a >
< a href = "#s4-3" > 4.3 通信层重构< / a >
< a href = "#s4-4" > 4.4 故障码统一方案< / a >
< a href = "#s4-5" > 4.5 上位机/下位机管理< / a >
< a href = "#s5" > 5 设备上电管理与状态机转移重构详细设计< / a >
< a href = "#s6" > 6 特性一致性保障策略< / a >
< a href = "#s7" > 7 测试方案< / a >
< a href = "#s8" > 8 实施路线图< / a >
< a href = "#s9" > 9 附录:问题清单< / a >
< / nav >
< main >
< header class = "cover" >
< div class = "tag" > CODE REVIEW & ARCHITECTURE OPTIMIZATION< / div >
< h1 > pPowerManger 电源管理软件< br > 代码审查与架构优化报告< / h1 >
< div class = "meta" > 项目:H100PowerManger( UUV 复合能源功率管理) | 范围:src/pPowerManger、src/pPowerMangerHost、test、ci | 日期:2026-09-03 | 版本:V1.0< / div >
< / header >
<!-- ================= 1 ================= -->
< h2 id = "s1" > 1 审查概述< / h2 >
< p > pPowerManger 是 H100 水下无人航行器复合能源系统的电源管理进程,运行于 MOOS 中间件之上,承担上位机指令接收、下位机(CCU/配电)设备管理、14 工况状态机调度、故障检测与上报、数据持久化及本地 Web 监视等职责。本次审查覆盖下位机核心代码约 2.4 万行(不含第三方 mongoose 库约 2.8 万行),重点评估代码架构、控制逻辑、通信逻辑、故障管理与测试体系,并给出以保持外部特性严格一致为前提的优化方案。< / p >
< div class = "kpi" >
< div class = "item" > < div class = "num" > 14< / div > < div class = "lbl" > 工况状态(tinyfsm) < / div > < / div >
< div class = "item" > < div class = "num" > 4< / div > < div class = "lbl" > 并发线程(无统一模型)< / div > < / div >
< div class = "item" > < div class = "num" > 3< / div > < div class = "lbl" > 套互不统一的故障编码< / div > < / div >
< div class = "item" > < div class = "num" > 0< / div > < div class = "lbl" > 状态机 / 上位机测试用例< / div > < / div >
< / div >
< div class = "card" >
< h4 > 总体结论< / h4 >
< p > 现有代码功能可用,但工程化程度不足,核心问题集中在五条主线:< / p >
< ol >
< li > < b > 存在多个确定性缺陷< / b > :Lifting(起吊准备)状态永远无法到达;浮力调节状态反馈因赋值误写为比较而永不更新;MOOS 邮件偏斜检查导致整批邮件被丢弃等(见 3.1)。< / li >
< li > < b > 状态机退化< / b > :tinyfsm 仅被用作事件路由外壳,真正的转移逻辑集中在基类两个巨型 switch 中;无 guard 条件;故障检测与 FaultState 完全脱钩。< / li >
< li > < b > 通信层职责混杂< / b > :协议知识(JSON 键名、设备 ID、帧格式)散落在 Driver、UpperCommManager、udpComm 三处;4 个线程对共享数据加锁纪律不一致,存在数据竞争与忙等阻塞。< / li >
< li > < b > 故障码体系三分天下< / b > :电池故障表、配电故障表、0xLSSS 系统故障魔数三套编码并存,双故障存储导致单一事实源被破坏,上报与显示映射脱节。< / li >
< li > < b > 测试存在三大空白< / b > :状态机迁移、故障注入、上位机链路均无测试,重构缺乏安全网。< / li >
< / ol >
< p > 优化应以"先建测试基线、再做特性等价重构"为总原则,分四个阶段实施(见第 7、8 章)。< / p >
< / div >
<!-- ================= 2 ================= -->
< h2 id = "s2" > 2 现状架构分析< / h2 >
< h3 > 2.1 进程与通信拓扑< / h3 >
< div class = "flow" >
< span class = "node green" > Web 页面(浏览器)< / span > < span class = "arrow" > ⇄ WebSocket< / span >
< span class = "node green" > pPowerMangerHost(上位机桥)< / span > < span class = "arrow" > ⇄ MOOSDB (TCP:9000, JSON 字符串变量)< / span >
< span class = "node" > pPowerManger(本进程)< / span > < span class = "arrow" > ⇄ UDP 二进制帧 (5001↔7000)< / span >
< span class = "node orange" > pCCU / pCanBridge< / span > < span class = "arrow" > ⇄ CAN< / span >
< span class = "node orange" > 电池 / 燃料电池 / 配电设备< / span >
< / div >
< p > 另有两条旁路:pPowerManger 内嵌 HTTP 服务(端口 8000/8090)直读内部对象提供本地监视页面;上位机 HostSim 自带 Web 服务(端口 18080)+ SQLite 历史库。上位机与下位机之间< b > 不存在直接 socket 连接< / b > ,全部交互以 MOOSDB 上的 JSON 字符串变量为协议载体(< code > uPower_*_cmd< / code > 下行、< code > uPower_*_fb< / code > 上行)。< / p >
< h3 > 2.2 下位机内部模块< / h3 >
< table >
< tr > < th > 模块< / th > < th > 文件< / th > < th > 规模< / th > < th > 实际职责< / th > < th > 主要问题< / th > < / tr >
< tr > < td > 主应用< / td > < td class = "loc" > PowerManger.h/.cpp< / td > < td class = "loc" > ~680 行< / td > < td > MOOSApp 框架、线程拉起、全局对象持有< / td > < td > 上帝对象,持有全部管理器指针并被 FSM 反向穿透< / td > < / tr >
< tr > < td > 状态机< / td > < td class = "loc" > fsm/PowerManagerFsm.cpp + 14 状态< / td > < td class = "loc" > ~3700 行< / td > < td > 事件路由 + 设备指令处理 + 故障编码 + 状态拷贝< / td > < td > 上帝类;转移逻辑退化到基类 switch;无 guard< / td > < / tr >
< tr > < td > 上位机通信< / td > < td class = "loc" > UpperCommManager.cpp< / td > < td class = "loc" > 261 行< / td > < td > MOOS 邮件 JSON 解析、周期状态广播< / td > < td > 解析失败误报 Unknown key;无连接管理< / td > < / tr >
< tr > < td > 下位机通信< / td > < td class = "loc" > LowerCommManager.cpp< / td > < td class = "loc" > 890 行< / td > < td > UDP 监听线程、帧校验入队、状态注入、断路器闭环操作< / td > < td > 回调直改全局数据;忙等最长约 7 s;35 个操作函数重复< / td > < / tr >
< tr > < td > 协议编解码< / td > < td class = "loc" > udpcomm/udpComm.cpp, ccuUdpMsg.h< / td > < td class = "loc" > ~600 行< / td > < td > UDP 二进制帧打包/解析、校验< / td > < td > 硬编码帧长;死分支;每帧堆分配< / td > < / tr >
< tr > < td > 设备策略表< / td > < td class = "loc" > driver.cpp< / td > < td class = "loc" > 1094 行< / td > < td > 68 字段 DriverTable、9 张工况设备表、JSON 编解码< / td > < td > 9 张表约 600 行逐字段重复赋值;错误码形同虚设< / td > < / tr >
< tr > < td > 系统数据< / td > < td class = "loc" > systemData.h, pmSysvariable.h< / td > < td class = "loc" > ~2250 行< / td > < td > 协议镜像结构体 + 全局数据池单例< / td > < td > 头文件内实现;百字段全局池;锁纪律不一致< / td > < / tr >
< tr > < td > 故障码< / td > < td class = "loc" > faultCode.h< / td > < td class = "loc" > 125 行< / td > < td > 电池 54 项 + 配电 45 项故障表< / td > < td > 与第三套 0xLSSS 魔数并存;上报/显示映射脱节< / td > < / tr >
< tr > < td > 本地 Web< / td > < td class = "loc" > httpserver/( mongoose + 内嵌页面)< / td > < td class = "loc" > ~7000 行< / td > < td > dashboard/日志/测试注入/电池/燃料电池页面< / td > < td > 与上位机 Web 重复建设;HTTP 回调越权直改状态< / td > < / tr >
< / table >
< h3 > 2.3 线程模型< / h3 >
< table >
< tr > < th > 线程< / th > < th > 周期< / th > < th > 主要工作< / th > < th > 风险< / th > < / tr >
< tr > < td > MOOS 主线程< / td > < td > 4 Hz< / td > < td > OnNewMail → 指令解析 → FSM dispatch; Iterate → CycleTriggeredEvent → 故障检测< / td > < td > 断路器闭环忙等约 7 s 会卡住整个邮件循环< / td > < / tr >
< tr > < td > commLoop 线程< / td > < td > 1 Hz< / td > < td > 心跳/周期指令下发、队列清理< / td > < td > 内含 usleep/MOOSPause;与监听线程并发清队列< / td > < / tr >
< tr > < td > UDP 监听线程< / td > < td > 阻塞读< / td > < td > 收帧 → 校验入队 → 出队写 systemData / SQLite< / td > < td > 直接写业务数据;退出时可能挂在 recv 上< / td > < / tr >
< tr > < td > HTTP 线程< / td > < td > 事件驱动< / td > < td > Web 页面与 REST< / td > < td > /fcs/control 回调里加锁直改 PowerManger 状态< / td > < / tr >
< / table >
< p > 共享数据 < code > m_msCmd< / code > 、< code > m_depth< / code > 、< code > m_insData< / code > 、< code > m_thrustRpm< / code > 等由 MOOS 线程无锁写、其余线程读;< code > m_stateMutex< / code > 仅保护部分字段;< code > updateSystemData< / code > 持 < code > m_dataMutex< / code > 再取 < code > faultCodeMutex< / code > ,锁顺序仅靠注释约定(< span class = "loc" > PowerManger.h:92-93< / span > )。整体属"能跑但不具备可论证的线程安全性"。< / p >
<!-- ================= 3 ================= -->
< h2 id = "s3" > 3 代码审查意见< / h2 >
< h3 id = "s3-1" > 3.1 确定性缺陷(Bug 级,建议立即修复)< / h3 >
< p > 以下问题经逐行核对确认,均有明确代码证据,与架构无关,应在重构前先以最小改动修复并补充回归用例。< / p >
< table >
< tr > < th > #< / th > < th > 级别< / th > < th > 问题< / th > < th > 位置< / th > < th > 影响与修复建议< / th > < / tr >
< tr >
< td > B1< / td > < td > < span class = "badge p0" > P0< / span > < / td >
< td > < b > Lifting(起吊准备)状态永远无法到达< / b > : < code > handleWorkCmd< / code > 的 case 12 只有日志,缺少 < code > transit< Lifting> ()< / code > 与 < code > break< / code > ,直接坠入 default< / td >
< td class = "loc" > fsm/PowerManagerFsm.cpp:2934-2936< / td >
< td > 上位机"起吊准备"指令被静默吞掉,Lifting 为死状态。补 < code > transit< Lifting> (); break;< / code > 。< / td >
< / tr >
< tr >
< td > B2< / td > < td > < span class = "badge p0" > P0< / span > < / td >
< td > < b > 赋值误写为比较< / b > : < code > s.buoyage == 1;< / code > (共 4 处,值 1/3/2/0),语句无副作用< / td >
< td class = "loc" > fsm/PowerManagerFsm.cpp:1471,1475,1482,1486< / td >
< td > 浮力调节(buoyage)状态反馈永远不更新,上报恒为初值。改为 < code > =< / code > 。< / td >
< / tr >
< tr >
< td > B3< / td > < td > < span class = "badge p0" > P0< / span > < / td >
< td > < b > MOOS 邮件整批丢弃< / b > :OnNewMail 中任一邮件偏斜超差即 < code > return true< / code > ,同批其余邮件全部丢失< / td >
< td class = "loc" > PowerManger.cpp:121-125< / td >
< td > 网络抖动时会成片丢失上位机指令。改为跳过该条、继续处理后续邮件。< / td >
< / tr >
< tr >
< td > B4< / td > < td > < span class = "badge p1" > P1< / span > < / td >
< td > < b > JSON 键名不匹配导致指令静默丢失< / b > :发送端键 < code > "mastLiftingServo_uint8"< / code > ,接收端解析键 < code > "mastLiftingServo_uint8_uint8"< / code > < / td >
< td class = "loc" > driver.cpp:844 vs 924< / td >
< td > 桅杆升降舵指令永远解析失败。统一键名,并增加"未知键/未命中"告警日志。< / td >
< / tr >
< tr >
< td > B5< / td > < td > < span class = "badge p1" > P1< / span > < / td >
< td > < b > 故障检测函数重复执行< / b > : < code > basicNavigationFault(); extendedNavigationFault();< / code > 连续调用两遍< / td >
< td class = "loc" > fsm/PowerManagerFsm.cpp:52-55< / td >
< td > 4 Hz 下双倍开销,且对带副作用的故障逻辑是隐患。删除重复行。< / td >
< / tr >
< tr >
< td > B6< / td > < td > < span class = "badge p1" > P1< / span > < / td >
< td > < b > 三个断路器故障永不上报< / b > : fbReserved/tyzReserved/reserved 调用的是无 faultCode 参数的重载,disfaultMap 中 code 10-12 成死码< / td >
< td class = "loc" > systemData.h:1026-1028< / td >
< td > 补齐故障码入参,或删除死码并同步前端映射。< / td >
< / tr >
< tr >
< td > B7< / td > < td > < span class = "badge p1" > P1< / span > < / td >
< td > < b > 数组越界风险< / b > :故障注入 < code > batfaultMap[e.eventId].code< / code > 对来自外部的 eventId 无边界检查;< code > systemData.h:524< / code > 循环 < code > i< 2< / code > 越界访问 < code > reserved2[1]< / code > ( cppcheck error 级)< / td >
< td class = "loc" > fsm/PowerManagerFsm.cpp:3053; systemData.h:524< / td >
< td > 注入接口可被越界触发,属安全隐患。加边界校验;修正循环上界。< / td >
< / tr >
< tr >
< td > B8< / td > < td > < span class = "badge p1" > P1< / span > < / td >
< td > < b > TestEvent 故障注入 switch 缺 break< / b > : case 15 坠入 default;另有 < code > react(TestEvent)< / code > 直接构造 FaultEvent 调故障函数,绕过事件机制< / td >
< td class = "loc" > fsm/PowerManagerFsm.cpp:2988-2989, 3084-3092< / td >
< td > 注入 15 号故障时行为未定义。补 break,统一走事件派发。< / td >
< / tr >
< tr >
< td > B9< / td > < td > < span class = "badge p2" > P2< / span > < / td >
< td > < b > 未初始化返回< / b > ( cppcheck error 级):< code > uPlan_taskStart.h:29< / code > 返回未初始化 < code > mission< / code > ; < code > uExternComm_setDeviceSwitch.h:39< / code > 可能返回未初始化 < code > cmd.cmd< / code > ; < code > uPower_pmState.h:14< / code > 对含 < code > std::vector< / code > 的结构体 memset< / td >
< td class = "loc" > upmsg/ 多个头文件< / td >
< td > 协议层偶发脏数据。统一在结构体定义处给默认值,禁止对非 POD memset。< / td >
< / tr >
< tr >
< td > B10< / td > < td > < span class = "badge p2" > P2< / span > < / td >
< td > < b > 注释与代码不符< / b > : FaultState.cpp:46 注释"5 秒超时"实为 20 秒(:53),且与 Lifting.cpp:40、PowerManagerFsm.cpp 内 5 处同逻辑代码的 5 秒超时不一致;同一段超时代码共复制 7 份< / td >
< td class = "loc" > FaultState.cpp:46/53 等< / td >
< td > 误导维护且各副本超时阈值漂移。随重构统一为命名常量。< / td >
< / tr >
< tr >
< td > B11< / td > < td > < span class = "badge p1" > P1< / span > < / td >
< td > < b > 模板残留致每条正常指令误报 "Unhandled Mail"< / b > : OnNewMail 的 for 循环内保留了 MOOS 模板代码 < code > if(key=="FOO")...else if(key!="APPCAST_REQ") reportRunWarning(...)< / code > ,除 APPCAST_REQ 外的所有邮件(含已正常处理的 uPower_*_cmd/fb)都被计入 run warning< / td >
< td class = "loc" > PowerManger.cpp:142-146< / td >
< td > AppCast 告警计数被污染、日志刷屏。删除该模板残留,仅在 processUpperMsg 返回 false 时告警。< / td >
< / tr >
< tr >
< td > B12< / td > < td > < span class = "badge p1" > P1< / span > < / td >
< td > < b > 畸形 JSON 可致崩溃< / b > : < code > loadDriverIdFromJson< / code > 对 < code > root[field].asUInt()< / code > 无类型检查(< code > JSON_USE_EXCEPTION=1< / code > ),调用处无 try/catch; < code > SafeReadUInt< / code > 用 < code > std::cerr< / code > 报错而非日志框架< / td >
< td class = "loc" > driver.cpp:935-936; UpperCommManager.cpp:48< / td >
< td > 恶意/畸形指令使 jsoncpp 抛异常向上传播。统一走 SafeReadUInt 并捕获异常、改走日志。< / td >
< / tr >
< tr >
< td > B13< / td > < td > < span class = "badge p1" > P1< / span > < / td >
< td > < b > HTTP 服务在配置就绪前启动< / b > : < code > m_httpServer->start()< / code > 在构造函数调用,此时 mission 配置未加载、< code > m_db< / code > 为 nullptr、UDP 未 bind,控制接口(/fcs/control 等)已对外可访问< / td >
< td class = "loc" > PowerManger.cpp:83-87< / td >
< td > 存在"配置生效前已暴露控制面"窗口。将 start() 移到 OnStartUp 末尾(DB/端口就绪后)。< / td >
< / tr >
< tr >
< td > B14< / td > < td > < span class = "badge p2" > P2< / span > < / td >
< td > < b > 死代码与孤儿成员< / b > : < code > FsmLoop()< / code > /< code > _FsmCB< / code > 无任何线程启动;< code > m_deviceCmdQuenue< / code > 及 push/pop/isEmpty 三接口无消费者;< code > buildReport()< / code > 为 MOOS 模板占位;< code > SKEW_TOLERANCE 5< / code > 宏未用而 < code > m_skew=10< / code > 硬编码< / td >
< td class = "loc" > PowerManger.cpp:18/339; PowerManger.h:48/121< / td >
< td > 误导维护。随阶段 3 一并清理,SKEW 阈值统一为命名常量。< / td >
< / tr >
< / table >
< h3 id = "s3-2" > 3.2 状态机与控制逻辑< / h3 >
< h4 > 3.2.1 现状转移关系(经代码还原)< / h4 >
< table >
< tr > < th > 源状态< / th > < th > 事件< / th > < th > 目标状态< / th > < th > 说明< / th > < / tr >
< tr > < td > InitState< / td > < td > entry() 内直接切换< / td > < td > StandbyState< / td > < td > 初始化完成即转< / td > < / tr >
< tr > < td > 任意状态< / td > < td > MasterCommandEvent( workCMD 1~11) < / td > < td > Standby / ShoreBasedReady / WaterBasedReady / RemoteControl / Cruise / HighSpeed / FloatDown / FloatAdjust / UnderwaterSurvey / SurfaceSurvey / DJMode< / td > < td class = "loc" > PowerManagerFsm.cpp:2887-2941; workCMD=12 无效(B1) < / td > < / tr >
< tr > < td > 任意状态< / td > < td > TaskStartEvent< / td > < td > THROW_LOAD→FaultState; FLOAT_UP/SAT_COMM 等→FloatDown; RECYCLE/SAIL→CruiseMode; HOVER→FloatAdjust; TOUR→UnderwaterSurvey< / td > < td class = "loc" > PowerManagerFsm.cpp:948-985< / td > < / tr >
< tr > < td > 任意状态< / td > < td > TaskStopEvent< / td > < td > RemoteControl< / td > < td class = "loc" > 基类 react, cpp:13< / td > < / tr >
< tr > < td > StandbyState< / td > < td > StandbyEvent< / td > < td > StandbyState(自切换)< / td > < td > 语义存疑< / td > < / tr >
< tr > < td > FaultState< / td > < td > —(无显式出口)< / td > < td > —< / td > < td > 仅能借基类 handleWorkCmd 间接跳出< / td > < / tr >
< / table >
< h4 > 3.2.2 主要架构问题< / h4 >
< div class = "danger" >
< b > A1 状态机退化为"事件路由壳"。< / b > 真正的状态转移表不在各状态类中,而集中在基类 < code > handleWorkCmd< / code > 与 < code > react(TaskStartEvent)< / code > 两个巨型 switch;14 个状态类的 < code > react(MasterCommandEvent)< / code > 是完全相同的复制粘贴。tinyfsm 提供的 < code > transit< S> (action, condition)< / code > guard 重载(tinyfsm.hpp:151)全项目零使用——< b > 任何状态下都可经 workCMD 直接跳进任何工况< / b > (如巡航中直接切起吊),无任何合法性检查。
< / div >
< div class = "danger" >
< b > A2 故障检测与 FaultState 完全脱钩。< / b > 8 个 < code > xxxFault()< / code > 检测函数只写故障码集合,从不触发状态迁移;FaultState 唯一入口是"抛载任务"( TaskStartEvent::THROW_LOAD),即 3 级故障(如深度失效 0x1302)发生时系统仍停留在原工况。FaultState 无显式出口、无故障恢复确认、无故障码清除联动。FaultEvent 无任何 dispatch 点,基类 < code > react(FaultEvent)< / code > 为死代码。
< / div >
< div class = "warn" >
< b > A3 entry() 内长阻塞。< / b > WaterBasedReady.cpp:50 与 Lifting.cpp:10 在 entry() 中 < code > MOOSPause(10000)< / code > ; ShoreBasedReady.cpp:24-35 while 轮询等待约 10 s;断路器操作闭环在事件处理路径上忙等约 7 s。状态迁移期间 FSM 无法响应任何新事件,上位机指令被积压在邮件队列。
< / div >
< div class = "warn" >
< b > A4 上帝类与全局穿透。< / b > PowerManagerFsm.cpp( 3096 行)集中了 10 个高度重复的设备指令处理函数、10 个 getXxxStatus 状态拷贝函数、8 组故障编码函数(约 200 个 0xLSSS 魔法故障码)。状态代码经 < code > static PowerManger* pm< / code > 裸指针直接读写宿主几十个公有成员、直接操作断路器、直接 sendNotification,单向依赖原则被彻底打破。
< / div >
< ul >
< li > < b > A5 语义错位< / b > : FloatDown 实际承载"上浮/下潜/卫星通信/卫星标定"四种业务;< code > handelPowerCmd< / code > 、< code > underWterDevices< / code > 、< code > mergencyLithiumBattery< / code > 等拼写错误已进入接口。< / li >
< li > < b > A6 死代码< / b > : < code > PowerSystemState< / code > 枚举(hpp:21)、< code > DeviceCmd< / code > 事件(hpp:41)、< code > handleDeviceCmd< / code > 空壳(cpp:2966)、< code > m_currentDriver< / code > ( hpp:87)、< code > checkError()< / code > 被注释掏空(PowerManger.cpp:344-356)。< / li >
< li > < b > A7 日志混乱< / b > :基类兜底 react 用 < code > std::cout < < "No Idear"< / code > ( hpp:94);entry/exit 大量使用 RTTI demangle + ANSI 转义打印,嵌入式目标上属无效开销;< code > printState< / code > 用 < code > desp.find("故障")< / code > 字符串匹配决定日志级别(PowerManger.cpp:359),极其脆弱。< / li >
< / ul >
< h3 id = "s3-3" > 3.3 通信逻辑< / h3 >
< div class = "warn" >
< b > C1 上位机连接无管理。< / b > < code > LowerCommManager::m_connectState< / code > ( h:131)与 < code > PowerManger::connectState< / code > (h:136)声明后从未使用;上位机方向无心跳、无在线检测、无断线告警,仅靠 MOOS 库自动重连。下位机方向有 30 s 超时检测(systemData.h:752-761),但超时后仅置标志位,未与故障系统联动。
< / div >
< div class = "warn" >
< b > C2 协议知识三处散养。< / b > JSON 键名与设备 ID 映射(协议层知识)写在 Driver(业务策略层)里,与发送端 driver.cpp 各维护一份,已造成 B4 键名失配;UDP 帧格式在 udpComm,设备 ID 0-61 在 driver.cpp:908-930 与 DeviceId 枚举重复硬编码。< code > Driver< / code > 实例在 PowerManger 与 UpperCommManager 各持一份,表状态易不一致。
< / div >
< ul >
< li > < b > C3 校验与错误处理形同虚设< / b > : < code > loadDriverTableFromJson< / code > 的 error_code 每次被覆盖、从不检查,恒返回 0( driver.cpp:962-1093);< code > sendCcuSetParmCmd< / code > 无论发送成败都 return true( udpComm.cpp:609-619);帧长硬编码 18/6/8 且旁边就是 TODO( udpComm.cpp:467/491/515/539)。< / li >
< li > < b > C4 死分支与静默吞帧< / b > : < code > CCU_UDPMSG_START1 == DIS_MSG_HEAD1 == 0x40< / code > , udpComm.cpp:47-56 两个 else-if 完全等价;未知 id 走 default 后仍 return true( udpComm.cpp:344-347)。< / li >
< li > < b > C5 高频路径低效< / b > :每帧堆分配(udpComm.cpp:462 等 5 处)、每个 UDP 包 cout 打印一次(LowerCommManager.cpp:381)、发送函数失败后仍 return true( LowerCommManager.cpp:142-144)。< / li >
< li > < b > C6 废弃函数藏错误数据< / b > : < code > sendDisSysCommand< / code > 对 bus3 无视入参硬编码 < code > 0x55/0xAA< / code > ( LowerCommManager.cpp:183-189),若被误用将下发错误指令。< / li >
< li > < b > C7 双 Web 重复建设< / b > :下位机 httpserver( 8000)与上位机 HostSim Web( 18080)都用 mongoose + /ws + 内嵌页面提供状态监视;下位机 /fcs/control 在 HTTP 回调中直改状态(httpserver.cpp:150),绕过 MOOS 消息机制,属越权控制通道。< / li >
< / ul >
< h3 id = "s3-4" > 3.4 故障码体系< / h3 >
< table >
< tr > < th > 编码方案< / th > < th > 定义位置< / th > < th > 格式< / th > < th > 使用方< / th > < th > 问题< / th > < / tr >
< tr > < td > 电池故障表< / td > < td class = "loc" > faultCode.h:17-76, batfaultMap[54]< / td > < td > 单字节 0x01-0xC9,带 1-4 级< / td > < td > 仅故障注入使用< / td > < td > level 字段与描述矛盾(:35);注入越界风险(B7)< / td > < / tr >
< tr > < td > 配电故障表< / td > < td class = "loc" > faultCode.h:78-124, disfaultMap[45]< / td > < td > 十进制 1-45< / td > < td > 断路器故障上报< / td > < td > code 10-12 死码(B6) < / td > < / tr >
< tr > < td > 系统故障码< / td > < td class = "loc" > 无定义文件,散落在 PowerManagerFsm.cpp:1676 起< / td > < td > 16 位魔数 0xLSSS(高半字节=等级)< / td > < td > 8 个检测函数 + getFaultLevel< / td > < td > 约 200 个裸字面量无注释无枚举;等级提取依赖位运算巧合< / td > < / tr >
< / table >
< ul >
< li > < b > F1 双重故障存储< / b > :真实故障进 < code > SystemData::sysFaultCodes< / code > (set,自恢复即自动清除,无锁存/确认);注入故障进 < code > PowerManger::faultCode< / code > (vector,不去重、只能整体 clear)。< code > getFaultLevel()< / code > ( PowerManger.cpp:448)与上报用的 < code > data.getFaultCodeLevel()< / code > ( uPower_pmState.h:50)读的是< b > 不同集合< / b > ,同一件事故障等级可能不一致。< / li >
< li > < b > F2 死赋值< / b > : UpperCommManager.cpp:240 把 < code > getFaultCodes()< / code > 赋给 state,但 < code > uPower_pmState.h:41-73 buildMsg()< / code > 忽略该赋值,改用 < code > data.getFaultCode()< / code > 。< / li >
< li > < b > F3 映射脱节< / b > :故障码→中文描述的映射 C++ 侧仅用于注入,上报只发数字码;显示文本由前端 JS 重写一份(httpserver/fuelcell.h:834-840);0xLSSS 系统故障码< b > 没有任何文本映射< / b > 。< / li >
< li > < b > F4 条件编译陷阱(经核实比初版更严重)< / b > : < code > DEBUG< / code > 宏在全部构建中均未被定义(CMake 无 < code > -DDEBUG< / code > ),而 systemData.h:776 的 < code > #ifdef DEBUG< / code > 依赖它——因此燃料电池 4 级故障字 < code > fc_fault_level_1..4< / code > 在< b > 所有构建< / b > 下都永不更新(恒为 0),并非仅"发布版"; systemData.h:7 的 < code > #define DEBUFG< / code > 是拼写错误且定义了无用宏。同理 < code > #ifdef _DEBUG< / code > ( UpperCommManager.cpp 全篇)在 Linux 下也永不生效,其调试追踪成员均为死代码。< / li >
< li > < b > F5 无故障事件持久化< / b > :SQLite 仅随帧存原始故障字段,无独立故障事件表,故障发生/恢复时间不可追溯,无法支撑事后分析。< / li >
< / ul >
< h3 id = "s3-5" > 3.5 数据管理与并发< / h3 >
< ul >
< li > < b > D1 全局数据池< / b > : SystemData( systemData.h, 1219 行头文件内实现)是百字段单例,字段基本是 pmSysvariable 协议镜像的 float 转换副本,< code > updateSystemData()< / code > 逐字段拷贝数百行——协议每改一处需同步三处(pmSysvariable / systemData / updateSystemData)。构造函数 60+ 项手工初始化(:660-690)。< / li >
< li > < b > D2 锁纪律不一致< / b > :提供了部分加锁访问器(:694-733),但 FSM 大量裸读绕过锁(PowerManagerFsm.cpp:1813/1920/1930);TestEvent 直接无锁写 < code > fc_fault_level_1< / code > ( :3060);< code > msg_*_Update_time< / code > 无锁写、另一把锁下读(LowerCommManager.cpp:229-233)。< / li >
< li > < b > D3 整数截断风险< / b > :对 double 使用未限定的 < code > abs()< / code > ( systemData.h:757),可能解析为 C 的 < code > int abs< / code > 截断时间差,应使用 < code > std::fabs< / code > 。< / li >
< li > < b > D4 双重时间基准< / b > : commLoop 每 1 s < code > clearMsgQueue()< / code > ( LowerCommManager.cpp:124)与监听线程并发清队列,语义冗余且可能丢未消费帧。< / li >
< / ul >
< h3 id = "s3-6" > 3.6 上位机与 Web 服务< / h3 >
< p > 上位机 HostSim 的设计相对干净:网页指令经 WebSocket 入 < code > m_cmdQueue< / code > 、Iterate 中 < code > drainCommands()< / code > 转发 MOOSDB( HostSim.cpp:213-228);上行订阅 12 个反馈变量,dirty 后 < code > broadcast(buildSnapshot())< / code > 推送,并以 SQLite 表 < code > fb_log< / code > 保留 24 h 历史(FeedbackStore.cpp:62-69),提供 < code > /api/history< / code > 、< code > /api/rawlog< / code > 。可作为重构时"松耦合桥"的保留样板。< / p >
< ul >
< li > < b > H1 功能重叠< / b > :与下位机 httpserver 在状态监视上重复;建议下位机 Web 退化为纯调试用途(仅本机回环监听),状态监视统一归上位机。< / li >
< li > < b > H2 越权通道< / b > :下位机 /fcs/control 直改内部状态,破坏"一切控制经 MOOS 指令"的单入口原则,应迁移为向 MOOSDB 发指令或加鉴权。< / li >
< li > < b > H3 页面硬编码< / b > : 1912 行 data.h 等 7 个页面以 < code > R"(...)"< / code > 字符串嵌在头文件,无构建期资源管线(上位机已有 webassets_gen.h 方案可复用)。< / li >
< / ul >
< h3 id = "s3-7" > 3.7 构建工程与配置管理< / h3 >
< ul >
< li > < b > E1 编译告警默认关闭、无 sanitizer 门禁< / b > : CMakeLists 仅加 < code > -fPIC -g -Wdeprecated-declarations< / code > , < code > -Wall< / code > 需手动开 WALL_ON 且写法含语法错误(< code > "-Wall" -C++11< / code > , CMakeLists.txt:86);Release 依赖 CMake 默认 < code > -O3< / code > 未显式声明;无 ASan/UBSan/TSan 构建类型。建议:开启 < code > -Wall -Wextra< / code > 并清零告警,新增 sanitizer 构建目标,测试二进制输出到 build 目录。< / li >
< li > < b > E2 依赖不可复现、GLOB 反模式< / b > : < code > FILE(GLOB ...)< / code > 收集头/库目录(CMakeLists.txt:49/53),新增文件不触发重配;mongoose/sqlite3/loguru/jsoncpp 以源码 vendor 进 src 且无版本号与许可证记录;< code > CMAKE_MINIMUM_REQUIRED(VERSION 3.5)< / code > 过老。建议 GLOB 改显式列表,第三方库固定版本并登记 LICENSE。< / li >
< li > < b > E3 配置解析无校验< / b > : OnStartUp 用 < code > atoi< / code > 解析 ccuport/iport( PowerManger.cpp:229/233),非法值静默为 0,端口无 1-65535 范围检查;缺配置块仅 warning。建议显式校验非法值并告警。< / li >
< li > < b > E4 SQLite 高频路径重复 prepare< / b > : < code > insertGeneric< / code > 每次插入重拼 SQL 并 prepare( SQLite.cpp:610-625),< code > onFrame< / code > 每帧同样 prepare;建议缓存 prepared statement,故障事件表纳入 SCHEMA_VERSION 迁移机制。< / li >
< li > < b > E5 日志/错误输出三套并存< / b > : loguru、< code > std::cerr< / code > ( driver.cpp:887/904/957)、< code > std::cout< / code > ( LowerCommManager.cpp:373/381)混用,无法按级别关停。建议统一 loguru,cout/cerr 清零。< / li >
< / ul >
<!-- ================= 4 ================= -->
< h2 id = "s4" > 4 架构优化方案< / h2 >
< p > 优化总原则:< b > 外部特性严格不变< / b > ( MOOS 变量名与 JSON 契约、UDP 帧格式、Web 接口、故障码取值、状态迁移外部可观测序列均保持),< b > 内部结构彻底重整< / b > 。所有重构以第 6 章的一致性保障与第 7 章的测试基线为前提。< / p >
< h3 id = "s4-1" > 4.1 目标分层架构< / h3 >
< div class = "card" >
< pre > < code > ┌─────────────────────────────────────────────────────────────┐
│ 接口层 (Interface) UpperCommManager │ HttpServer(调试) │ ← 只做协议适配,不含业务判断
├─────────────────────────────────────────────────────────────┤
│ 应用层 (Application) PowerManagerApp │ ← 协调者:指令→校验→派发到领域服务
│ ├─ CommandService(指令接收、校验、应答) │
│ ├─ FaultManager (故障检测、锁存、上报、事件持久化) │
│ └─ StateMachineService(工况状态机,表驱动 + guard) │
├─────────────────────────────────────────────────────────────┤
│ 领域层 (Domain) DeviceRegistry │ PowerPolicy(Driver表) │ ← 纯 C++,无 MOOS/网络依赖,可单测
│ SystemState(线程安全的状态仓储) │
├─────────────────────────────────────────────────────────────┤
│ 基础设施层 (Infra) UdpLink(收发) │ ProtocolCodec(编解码) │ ← 唯一知道帧格式/JSON键名的地方
│ SQLiteStore │ MoosGateway │
└─────────────────────────────────────────────────────────────┘< / code > < / pre >
< / div >
< ul >
< li > < b > 依赖方向单向化< / b > :接口层 → 应用层 → 领域层 ← 基础设施层(依赖倒置)。状态机不再持有 < code > PowerManger*< / code > 裸指针,改为构造时注入 < code > ISystemState< / code > 、< code > IDeviceCommand< / code > 、< code > IFaultSink< / code > 三个窄接口。< / li >
< li > < b > 线程模型统一< / b > :确立"单线程事件循环 + 生产者队列"模型——UDP 监听线程、HTTP 线程只做< b > 收包→解析→投递事件队列< / b > ,所有业务处理(FSM dispatch、故障检测、状态写入)在 MOOS Iterate 所在线程串行执行,从根上消除数据竞争。断路器闭环操作改为异步状态(发送→等待反馈事件→超时事件),消灭 7 s 忙等。< / li >
< li > < b > Driver 拆分< / b > :模式设备策略表(领域层 PowerPolicy)与 JSON 键名/设备 ID 映射(基础设施层 ProtocolCodec)分离;Driver 单例化并归属 CommandService,消除双实例不一致。< / li >
< / ul >
< h3 id = "s4-2" > 4.2 状态机重构方案< / h3 >
< h4 > 4.2.1 转移表显式化< / h4 >
< p > 将隐藏在两个 switch 中的转移逻辑提取为< b > 静态转移表 + guard 函数< / b > ,作为唯一事实源,并据此生成文档与测试用例:< / p >
< pre > < code > // fsm/TransitionTable.h —— 唯一事实源(示例,迁移时须以现状代码逐条核对生成)
struct TransitionRule {
StateId from; // ANY 表示任意源状态
EventId event;
StateId to;
GuardFn guard; // nullptr 表示无条件(保持现状语义)
const char* comment; // 对应原代码位置,便于审计
};
static const TransitionRule kRules[] = {
{ ANY, EV_WORK_CMD, STANDBY, nullptr, "workCMD=1 Fsm.cpp:2891" },
{ ANY, EV_WORK_CMD, LIFTING, nullptr, "workCMD=12 Fsm.cpp:2934(修复B1后生效)" },
{ ANY, EV_TASK_START, FAULT, GuardThrowLoad, "THROW_LOAD Fsm.cpp:953" },
{ FAULT, EV_FAULT_CLEARED, REMOTE_CTRL, GuardFaultAcked, "新增:故障确认后恢复" },
...
};< / code > < / pre >
< p > guard 第一阶段全部保持现状(无条件),仅把"可迁移性"显式化;第二阶段再与总体组确认后补充工况互斥规则(如 HighSpeed 与 DJMode 互斥需先回 Standby)。< / p >
< h4 > 4.2.2 FaultState 与故障系统联动< / h4 >
< ol >
< li > < b > 入口< / b > : FaultManager 检测到 3 级及以上故障(或抛载任务)→ 投递 < code > FaultEvent< / code > → FSM 迁移 FaultState,保持现有"抛载进 FaultState"路径不变。< / li >
< li > < b > entry 动作异步化< / b > :断路器断开序列改为 FaultState 内部的子步骤计时状态(step + deadline),每拍 Iterate 推进,取代 MOOSPause(10000) 与 20 s 忙等;超时阈值统一为命名常量并修正注释。< / li >
< li > < b > 出口< / b > :显式定义恢复路径——故障码集合清空且收到上位机确认指令(workCMD=4 RemoteControl,保持现有指令集不变)→ 退出 FaultState。该路径当前已可经基类 handleWorkCmd 间接实现,重构只是将其显式化并纳入转移表,< b > 不改变外部行为< / b > 。< / li >
< / ol >
< h4 > 4.2.3 代码组织< / h4 >
< ul >
< li > PowerManagerFsm.cpp 拆分:< code > DeviceCommandHandlers.cpp< / code > (10 个设备指令函数,提取公共模板消除重复)、< code > StatusSnapshot.cpp< / code > ( 10 个 getXxxStatus)、< code > FaultEncoding.cpp< / code > (故障编码,配合 4.4 迁移到 FaultManager)。< / li >
< li > 14 个状态类删除重复的 < code > react(MasterCommandEvent)< / code > ,统一由基类按转移表处理;状态类只保留 entry/exit 与状态特有事件。< / li >
< li > 命名修正(handelPowerCmd→handlePowerCmd 等)仅在内部接口进行,MOOS 变量名、JSON 键名等外部标识< b > 一律不改< / b > 。< / li >
< / ul >
< h3 id = "s4-3" > 4.3 通信层重构方案< / h3 >
< h4 > 4.3.1 协议单一事实源< / h4 >
< p > 建立 < code > protocol/< / code > 目录,以一份定义文件同时生成/校验编解码两侧,杜绝键名失配(B4)类问题:< / p >
< ul >
< li > < code > protocol/MoosVariables.def< / code > :全部 MOOS 变量名 + JSON schema(键名、类型、取值范围),UpperCommManager 与 Driver 的 JSON 编解码均引用之;增加"未知键/解析失败"统一告警(修复 C3、B4 的静默丢失)。< / li >
< li > < code > protocol/DeviceId.h< / code > :设备 ID 唯一枚举,driver.cpp:908-930 的 0-61 硬编码与 DeviceId 枚举合并;UDP 帧长由结构体 sizeof 计算,删除 18/6/8 魔法数。< / li >
< li > 修正 udpComm.cpp:47-56 等价死分支与 default 静默吞帧:未知帧计入统计并日志告警。< / li >
< / ul >
< h4 > 4.3.2 上下行链路管理< / h4 >
< table >
< tr > < th > 链路< / th > < th > 现状< / th > < th > 优化后< / th > < th > 外部行为变化< / th > < / tr >
< tr > < td > 上位机(MOOS) < / td > < td > 无连接管理,1 s 周期广播状态< / td > < td > 启用已声明未使用的 connectState:以"周期广播正常发出 + 收到上位机任何邮件"维护在线标志;掉线仅记录与上报,不触发控制动作< / td > < td > 无(只新增可观测性)< / td > < / tr >
< tr > < td > 下位机(UDP) < / td > < td > 30 s 超时置 isTimeout,超时母线发清零指令< / td > < td > 超时事件接入 FaultManager(通信类故障码,新增码段,不占用现有码值);清零指令逻辑保持不变< / td > < td > 故障上报新增通信超时码(经总体确认后启用)< / td > < / tr >
< tr > < td > 断路器闭环< / td > < td > 事件路径忙等约 7 s + 600 ms 复位< / td > < td > 异步化:发送→注册期望反馈→超时定时器,状态机以子状态跟踪;指令时序与重发次数严格保持现状< / td > < td > 无(时序一致,仅不再阻塞线程)< / td > < / tr >
< tr > < td > Web 通道< / td > < td > 下位机 8000 全功能 + /fcs/control 越权直改< / td > < td > 下位机 Web 保留页面但控制类接口改为转发 MOOS 指令(等价于上位机下发);上位机 Web 方案不变< / td > < td > 接口路径与参数不变,仅内部实现改道< / td > < / tr >
< / table >
< h4 > 4.3.3 工程细节修复< / h4 >
< ul >
< li > 发送路径消除每帧堆分配(栈对象 + sendto);删除每包 cout( LowerCommManager.cpp:381),统一走 loguru 并按级别可关。< / li >
< li > UDP 监听线程退出改为 socket 超时 + quit 标志,保证进程可干净退出。< / li >
< li > 移除 < code > sendDisSysCommand< / code > 废弃函数(含 0x55/0xAA 硬编码),或修正后纳入 ProtocolCodec。< / li >
< / ul >
< h3 id = "s4-4" > 4.4 故障码统一方案< / h3 >
< div class = "card" >
< h4 > 目标:一套编码、一个存储、一处映射< / h4 >
< p > < b > 1. 统一故障码定义文件 < code > fault/FaultCodeDef.h< / code > < / b > (生成式,唯一事实源):< / p >
< pre > < code > // 每条故障一行:枚举名, 码值(保持现有取值不变), 等级, 来源域, 中文描述
FAULT_DEF(BAT_CELL_OVERVOLT_L1, 0x01, 1, DOMAIN_BATTERY, "单体过压1级")
FAULT_DEF(DIS_BREAKER_TRIP_MAIN, 1, 2, DOMAIN_DIS, "主断路器脱扣")
FAULT_DEF(SYS_DEPTH_INVALID_L3, 0x1302, 3, DOMAIN_SYSTEM, "深度数据失效")
// 0xLSSS 系统码约 200 个字面量全部收编为枚举,码值、等级语义与现状逐条核对一致< / code > < / pre >
< p > 由此单文件自动生成:C++ 枚举、码值→描述映射表(供上报与前端共用,替代 fuelcell.h:834 的 JS 重写)、测试用的完整性校验数据。电池 0x01-0xC9、配电 1-45、系统 0xLSSS 三段码值全部原样保留,< b > 上位机与前端所见故障码不变< / b > 。< / p >
< p > < b > 2. 合并故障存储为 FaultManager 单一集合< / b > : < / p >
< ul >
< li > 统一存放真实故障与注入故障(注入故障带 source 标记区分),上报接口(uPower_pmState)与等级查询(getFaultLevel)改为读同一集合,消除 F1/F2 的双源不一致;对外 JSON 字段名与格式不变。< / li >
< li > 锁存语义保持现状(自恢复即自动清除),但 FaultManager 内部记录"发生/恢复"事件并写入 SQLite 新增 < code > fault_event(ts, code, level, event, source)< / code > 表——< b > 新增表不影响现有表结构< / b > 。< / li >
< li > 故障码访问统一加 < code > faultCodeMutex< / code > ,消除 TestEvent 无锁写。< / li >
< / ul >
< p > < b > 3. 修复编码层缺陷< / b > :删除 systemData.h:776 的 < code > #ifdef DEBUG< / code > (发布版也更新燃料电池 4 级故障字)、修正 DEBUFG 拼写、补齐 B6 三个断路器的故障码入参、faultCode.h:35 的 level 字段与描述对齐。< / p >
< / div >
< h3 id = "s4-5" > 4.5 上位机 / 下位机管理方案< / h3 >
< table >
< tr > < th > 主题< / th > < th > 职责划分(优化后)< / th > < / tr >
< tr > < td > 控制通道< / td > < td > < b > 唯一入口< / b > :一切控制指令(网页、调试页、测试注入)最终都变为 MOOSDB 上的 < code > uPower_*_cmd< / code > JSON,由 CommandService 统一校验、应答、记录。下位机 HTTP 控制接口仅做转发。< / td > < / tr >
< tr > < td > 状态监视< / td > < td > 上位机 HostSim 为主(跨机、带历史库 /api/history);下位机 Web 退化为本机调试页(日志查看、测试注入保留),dashboard/data 等监视页标注"调试用"。< / td > < / tr >
< tr > < td > 指令校验< / td > < td > CommandService 对 workCMD/powerCMD/modCMD 做范围校验 + 当前状态下合法性校验(按转移表),非法指令回执拒绝原因(新增 JSON 字段,缺省不发送,兼容旧上位机)。< / td > < / tr >
< tr > < td > 指令清空哨兵< / td > < td > workCMD=99 等魔法哨兵改为命名常量 < code > CMD_NONE=99< / code > ,取值不变,仅消除魔法数。< / td > < / tr >
< tr > < td > 页面资源< / td > < td > 下位机 7 个硬编码页面迁移到 CMake 资源管线(复用上位机 webassets_gen.h 方案),HTML 独立成文件,便于前端维护;URL 与页面功能不变。< / td > < / tr >
< / table >
<!-- ================= 5(新增详细设计章) ================= -->
< h2 id = "s5" > 5 设备上电管理与状态机转移重构详细设计< / h2 >
< h3 > 5.1 总体设计判断< / h3 >
< p > 设备上电/管理与状态机转移是同一个问题的两面——< b > "声明"与"执行"混在一起< / b > 。9 张工况设备表(driver.cpp 约 600 行逐字段赋值)本质是"数据",却写成了代码;状态 entry() 里的上电时序本质是"流程",却硬编码成一串串 < code > setDeviceState + MOOSPause + operate_*_Breaker< / code > ;状态转移本质是"一张表",却隐式埋在 < code > handleWorkCmd< / code > /< code > react(TaskStartEvent)< / code > 两个 switch 里。重构的共同方向:< b > 把数据从代码里剥离出来,把流程交给统一执行器,把转移显式化< / b > 。< / p >
< h3 > 5.2 设备上电与管理重构< / h3 >
< h4 > 5.2.1 现状代码证据< / h4 >
< ul >
< li > < b > 工况表即代码< / b > : < code > Driver::getSTANDBYTable()< / code > ( driver.cpp:151-228)等 9 个函数,每个约 70 行 < code > driverTable->depth1 = 1;< / code > 式逐字段赋值,< code > getCRUISE/getHIGH_SPEED/getASCEND_DESCEND< / code > 内容几乎一致,且混用裸 0/1 与 < code > DRIVER_*< / code > 宏。< / li >
< li > < b > 上电时序硬编码且阻塞< / b > : < code > CruiseMode::entry()< / code > 的典型模式是:查当前设备状态 → < code > operate_HVA_actuatorCircuit_Breaker(0)< / code > (内部忙等最长 6 s)→ < code > MOOSPause(3)< / code > → 连续 6 次 < code > setDeviceState< / code > → 上报,每个状态 entry 都是这段逻辑的变体手写。< / li >
< li > < b > 断路器闭环时序散落< / b > : < code > operateBreakerGeneric< / code > ( LowerCommManager.cpp:843-890)的"首次发包 → 100 ms 等待 → 轮询反馈 → 200 ms 重发 → 6 s 超时 → 3× 200 ms 复位"被 35 个 < code > operate_*< / code > 函数复用,但在事件处理路径上同步执行,阻塞 MOOS 线程。< / li >
< li > < b > 校验逻辑十份复制< / b > : < code > WaterBasedReady::react(DevCmdExt)< / code > 中的校验链(Debug 模式豁免 → 深度上电保护 → 前视声纳先关机再断电并 MOOSPause(10000) → 写设备状态)在 10 个 < code > handleXxxCmd< / code > 中各有变体,细微偏差正是 B4(桅杆键名失配)类缺陷的温床。< / li >
< / ul >
< h4 > 5.2.2 目标结构< / h4 >
< div class = "card" >
< pre > < code > 现状(分散) 目标(收敛)
┌──────────────────────────────┐ ┌────────────────────────────────┐
│ 9 张工况表 = 600 行赋值函数 │ │ ① 设备描述表 DeviceDef │
│ entry() 硬编码时序+忙等6~20s │ 收敛 │ 68 设备 × 属性(域/断路器通道/ │
│ 10 个 handleXxxCmd 校验复制 │ ───────────► │ 保护规则/9 工况默认值) │
│ 35 个 operate_*_Breaker │ │ ② 功率时序执行器 PowerSequencer │
└──────────────────────────────┘ │ 声明式步骤,异步分步执行 │
│ ③ 统一指令管线:模式校验→深度 │
│ 保护→前置动作→执行→回执 │
│ ④ DeviceManager 唯一写口 │
│ m_subDisSysCmd 只经它修改 │
└────────────────────────────────┘< / code > < / pre >
< / div >
< h4 > 5.2.3 重构四件事< / h4 >
< ol >
< li > < b > 工况表从函数变成数据。< / b > 9 个 < code > getXxxTable()< / code > 函数替换为静态二维表 < code > kModeDeviceTable[9][68]< / code > (或按设备分组的声明式定义),查表替代函数调用。迁移时用程序把现有 9 个函数的输出快照为 golden data,重构后逐字节比对——这是"特性严格一致"最直接的保证。< / li >
< li > < b > 上电时序从阻塞硬编码变成声明式步骤 + 异步执行器。< / b > 每个工况声明一个步骤序列,例如 CruiseMode:< code > [条件:任一bowCover≠0] → 断启闭机构断路器(超时6s) → 延时3s → 批量下电bowCover1~6 → 上报< / code > 。PowerSequencer 每拍 Iterate 推进一步,< code > operateBreakerGeneric< / code > 的时序参数(100 ms 首等、200 ms 重发、6 s 超时、3×200 ms 复位)原样保留为步骤属性,但不再阻塞线程;entry() 只提交序列即返回,状态机始终可响应事件。< / li >
< li > < b > 设备指令校验收敛为一条管线。< / b > 所有设备指令走同一链路:模式校验(Normal 禁操作)→ 域保护(水下设备深度检查,Debug 豁免)→ 设备专属前置动作(forwardSonar 先关机再断电等,注册在设备描述表)→ 经 Sequencer 执行 → 回执。各状态只声明"本状态允许操作哪些设备域",不再各写一份校验。< / li >
< li > < b > 设备状态唯一写口。< / b > < code > m_subDisSysCmd< / code > 现在被 FSM、HTTP 回调、测试注入直接修改,重构后只能经 DeviceManager 写入;外部可观测的写序列(日志、上报、SQLite 落库)保持一致。< / li >
< / ol >
< h3 > 5.3 状态机转移重构< / h3 >
< h4 > 5.3.1 核心问题:转移关系隐式化< / h4 >
< p > 转移关系不存在于任何一张表中,而是从两个 switch 的行为里"涌现"出来的——这正是 case 12 漏写 < code > transit< / code > 导致 Lifting 不可达(B1)这类缺陷的结构性根源。重构目标是把转移机制从"隐式 switch"变为"显式表驱动": < / p >
< div class = "card" >
< pre > < code > 现状:隐式转移 目标:显式转移
MasterCommandEvent 到达 事件统一规范化
│ │
handleWorkCmd 巨型 switch 查转移表 TransitionRule[]
( 14 状态 × 14 份复制 react) (源状态×事件→目标,唯一事实源)
│ │
无 guard 直接 transit guard 校验
( case 12 漏写 = Lifting 死状态) (一期空 guard 记日志,二期补互斥)
│ │
entry() 阻塞上电 CHANING 过渡子状态
(期间 FSM 不响应任何事件) (上电序列异步执行,完成即落定)
│ │
FaultState 孤岛 FaultEvent 显式迁移
(故障检测不触发迁移,无出口) (3 级以上故障进入,确认后退出)< / code > < / pre >
< / div >
< h4 > 5.3.2 重构五步走< / h4 >
< ol >
< li > < b > 把转移关系从代码里"挖"出来变成表。< / b > 逐行核对 < code > handleWorkCmd< / code > ( workCMD 1~12)、< code > react(TaskStartEvent)< / code > ( THROW_LOAD/FLOAT_UP/RECYCLE/SAIL/HOVER/TOUR 等)、< code > react(TaskStopEvent)< / code > 与 InitState 自迁移,生成 < code > TransitionRule[]< / code > 静态表,每行标注原代码位置(如 < code > workCMD=5 → CruiseMode, Fsm.cpp:2903< / code > )。该表同时是实现、文档与测试基准。< code > handleWorkCmd< / code > 的 switch 删除,14 个状态中复制粘贴的 < code > react(MasterCommandEvent)< / code > 全部删除,统一由基类查表处理;workCMD=99 清空哨兵语义保留,仅改为命名常量。< / li >
< li > < b > guard 分两期落地。< / b > 一期所有 guard 为 nullptr(无条件),< b > 严格保持现状转移语义< / b > ——这是特性一致的红线;基类在每次迁移时记录"源状态→事件→目标"日志。二期依据日志与总体组确认应禁止的迁移(如巡航中直接切 DJMode),再逐步补 guard。重构本身不引入行为变化,行为收紧是另一个独立、可评审的决策。< / li >
< li > < b > entry() 阻塞上电改为 CHANING 过渡子状态。< / b > 现状代码已有雏形——每个 entry 开头 < code > workCondition = CHANING< / code > 、干完活才置为目标工况,只是"干活"方式是阻塞的。重构将其正式化:迁移发生时进入目标状态的过渡形态,上电序列交 PowerSequencer 异步执行,序列完成事件使状态落定;期间 FSM 照常响应事件(如 FaultEvent 可打断上电)。上位机看到的 workCondition 变化序列与现状完全一致。< / li >
< li > < b > FaultState 接入主转移图。< / b > 故障检测发现 3 级以上故障 → FaultManager 派发 FaultEvent → 转移表新增 < code > ANY × FaultEvent → FaultState< / code > 规则(保留现有 THROW_LOAD 入口不变);FaultState 增加显式出口:故障码集合清空 + 上位机确认(复用现有 workCMD=4 指令,不新增协议)→ RemoteControl; FaultState 内的断路器断开序列同样改为 Sequencer 步骤,消灭 20 s 忙等。< / li >
< li > < b > 状态类只做"差异"。< / b > 状态类只保留本状态特有内容:entry 提交的上电序列、特有事件处理(如 DJMode 专属指令)、exit 清理;公共行为上收基类。CruiseMode/HighSpeedMode 等任务态的共性(均响应 TaskStopEvent→RemoteControl)通过继承 MissionStateBase 表达,利用 tinyfsm 原生支持的状态继承。< / li >
< / ol >
< h3 > 5.4 两块重构的衔接与线程模型< / h3 >
< div class = "note" >
衔接点在 CHANING 子状态:< b > 状态机管"去哪里", PowerSequencer 管"到了之后按什么时序上电", DeviceManager 管"每一笔设备状态怎么写"< / b > 。三者通过事件队列串行协作,全部运行在 MOOS 主线程上,从根上消除现有四个线程交叉读写全局数据的问题。UDP 监听线程与 HTTP 线程退化为纯生产者:收包→解析→投递事件队列,不再直接触碰业务状态。
< / div >
< h3 > 5.5 迁移顺序与一致性验证< / h3 >
< ol >
< li > < b > 快照基线< / b > :采集 9 张工况表输出、标准指令脚本下的状态上报时序,作为 golden data; < / li >
< li > < b > 设备描述表与指令管线< / b > (风险最低,先行);< / li >
< li > < b > PowerSequencer 替换阻塞上电< / b > ; < / li >
< li > < b > 转移表替换 switch< / b > 。< / li >
< / ol >
< p > 每一步均以 golden data diff 为空为通过条件,diff 非空则不进入下一步。B1(Lifting 不可达)等已确认缺陷在挖掘转移表时即会暴露,按第 6 章约定单独登记为行为变更点修复。< / p >
<!-- ================= 6 ================= -->
< h2 id = "s6" > 6 特性一致性保障策略< / h2 >
< p > "优化后与原代码特性严格一致"需要通过可执行的基线来保证,而不是靠评审印象。建议按以下四层契约冻结外部行为:< / p >
< table >
< tr > < th > 契约层< / th > < th > 冻结内容< / th > < th > 验证手段< / th > < / tr >
< tr > < td > MOOS 接口契约< / td > < td > 全部订阅/发布变量名、JSON 键名与类型、周期广播频率(1 s)、应答时序< / td > < td > 契约测试:录制现有系统真实 MOOS 流量为"黄金报文集",重构后回放比对(键集合、类型、取值范围)< / td > < / tr >
< tr > < td > UDP 帧契约< / td > < td > 6 类反馈帧 ID、帧头 0x40 0x40、#pragma pack(1) 布局、校验算法、帧长< / td > < td > 字节级编解码往返测试(现有 udp_feeder/full_feeder 扩展为参数化用例);结构体 static_assert 尺寸断言< / td > < / tr >
< tr > < td > 行为契约< / td > < td > 状态迁移外部可观测序列(指令→状态上报序列)、断路器操作时序(重发间隔、6 s 超时、600 ms 复位)、故障码取值与上报内容< / td > < td > 黄金主测试:对现状系统跑标准指令脚本,录制 < code > uPower_currentState_st< / code > 等上报时序为基线;重构后同脚本回放,时序逐拍比对< / td > < / tr >
< tr > < td > Web/存储契约< / td > < td > URL 路由、REST 参数、SQLite 现有表结构< / td > < td > HTTP 接口冒烟用例;数据库 schema 比对脚本< / td > < / tr >
< / table >
< div class = "note" >
< b > 已知缺陷的处理约定< / b > : 3.1 节 B1-B10 属"现状特性"的一部分还是"应修复的缺陷",须逐项与总体确认。建议原则:外部可观测且显然错误的行为(如 buoyage 恒不更新、Lifting 不可达、桅杆指令丢失)按缺陷修复并在报告中显式登记"行为变更点";纯内部问题(重复调用、死代码)直接修复,行为不变。所有行为变更点单独列入变更清单,重构完成时逐项回归确认。
< / div >
<!-- ================= 7 ================= -->
< h2 id = "s7" > 7 测试方案< / h2 >
< h3 > 7.1 测试金字塔与补齐重点< / h3 >
< table >
< tr > < th > 层级< / th > < th > 现状< / th > < th > 补齐内容< / th > < th > 框架/工具< / th > < / tr >
< tr > < td > 单元测试< / td > < td > GTest 56 例:Driver 表、SystemData、故障码表、upmsg 往返、UDP 校验和、pack 布局< / td > < td > ① 状态机迁移测试:转移表全覆盖(每个规则至少 1 例)+ guard 边界;② FaultManager:检测→锁存→上报→清除全链路,含 B6/B7 回归;③ ProtocolCodec: JSON 键名完整性(防 B4 复发)、帧编解码参数化< / td > < td > GTest;领域层零 MOOS 依赖后可脱离 MOOSDB 运行< / td > < / tr >
< tr > < td > 集成测试< / td > < td > udp_feeder/full_feeder 字节级仿真 CCU+4 路配电 + check_db.py 验证落库< / td > < td > ① 故障注入 feeder:丢包、坏校验和、越界值、故障位置位、30 s 静默(超时路径);② MOOS 端到端:上位机指令→状态迁移→上报序列黄金比对(第 6 章基线);③ 断路器闭环时序仿真< / td > < td > Python 脚本 + 现有 feeder 扩展;MOOSDB 冒烟沿用< / td > < / tr >
< tr > < td > 系统/回归< / td > < td > CI 有构建+冒烟,但集成失败不阻塞(build-test.sh:181),已知 std::bad_alloc 被容忍< / td > < td > ① 集成失败纳入 ALL_OK 阻塞;② cppcheck/clang-tidy 接入 CI( error 级清零);③ 测试二进制输出改到 build 目录,清理 test/ 下 in-source 构建残留< / td > < td > Gitea Actions 现有流水线扩展< / td > < / tr >
< tr > < td > 上位机测试< / td > < td > 零< / td > < td > HostSim 指令队列 drain、FeedbackStore 落盘/24 h 清理、/api/history 时序提取、WS 快照广播< / td > < td > GTest(队列/存储)+ Python( HTTP/WS 接口)< / td > < / tr >
< / table >
< h3 > 7.2 关键专项测试< / h3 >
< ul >
< li > < b > 状态机迁移矩阵< / b > :以 4.2.1 转移表为基准自动生成用例骨架——14 状态 × 11 事件全组合标注"允许/拒绝/保持",重构前后两轮运行,输出 diff 必须为空(除已登记的行为变更点)。< / li >
< li > < b > 故障注入矩阵< / b > :覆盖三级来源——UDP 层(坏帧/丢帧/超时)、设备层(断路器脱扣、绝缘低、BMS 各级故障位)、系统层(深度失效、功率不足等 0xLSSS 码);断言故障码上报内容、FaultState 迁移、SQLite fault_event 落库三处一致。< / li >
< li > < b > 并发压力< / b > : UDP 监听线程 100 Hz 灌包 + MOOS 指令并发注入,ThreadSanitizer 构建跑 10 min,数据竞争报告清零;队列上限 100 的溢出行为固定为"丢最旧 + 计数告警"并测试。< / li >
< li > < b > 长稳< / b > : feeder 连续 24 h 正常流量 + 周期故障注入,监控内存(消除每帧 new/delete 后应无增长)、句柄、落库行数。< / li >
< / ul >
< h3 > 7.3 验收标准< / h3 >
< ol >
< li > 黄金报文比对:MOOS/UDP 契约测试 100% 通过,diff 为空;< / li >
< li > 状态迁移矩阵:除已登记变更点(B1 修复后 Lifting 可达等)外零差异;< / li >
< li > 新增单测覆盖率:fsm/、FaultManager、ProtocolCodec 行覆盖 ≥ 80%; < / li >
< li > cppcheck error 级清零,TSan 报告清零;< / li >
< li > CI 全链路(构建→单测→集成→黄金比对)绿灯且失败阻塞。< / li >
< / ol >
<!-- ================= 8 ================= -->
< h2 id = "s8" > 8 实施路线图< / h2 >
< div class = "phase" >
< h4 > 阶段 0:缺陷修复与测试基线(先行,约 2 周)< / h4 >
< p class = "small" > 最小改动修复 B1-B14 并逐项登记行为变更;开启 < code > -Wall -Wextra< / code > 清零告警、接入 sanitizer 构建目标与 cppcheck(error 级阻塞);配置项端口校验;搭建黄金报文基线与状态迁移矩阵录制工具。< b > 产出:行为基线库 + 变更清单 + 可运行的安全网。< / b > < / p >
< / div >
< div class = "phase" >
< h4 > 阶段 1:故障码与数据层统一(约 2-3 周)< / h4 >
< p class = "small" > 落地 FaultCodeDef.h 生成式定义;合并双故障存储为 FaultManager;新增 fault_event 持久化表;修复条件编译与死码。该阶段相对独立、风险最低,先行可立即改善可维护性。< b > 产出:统一故障子系统 + 故障注入测试矩阵。< / b > < / p >
< / div >
< div class = "phase" >
< h4 > 阶段 2:通信层与线程模型(约 3-4 周)< / h4 >
< p class = "small" > 建立 protocol/ 单一事实源;UDP 监听线程改为纯生产者;断路器闭环异步化;连接状态管理启用;下位机 HTTP 控制接口改道 MOOS。< b > 产出:单线程事件循环模型 + 契约测试全绿。< / b > < / p >
< / div >
< div class = "phase" >
< h4 > 阶段 3:状态机重构(约 3-4 周)< / h4 >
< p class = "small" > 转移表显式化;PowerManagerFsm 上帝类拆分;状态类去重;FaultState 出入路径显式化;entry 阻塞消除。每步以迁移矩阵 diff 为空为准入。< b > 产出:表驱动状态机 + 迁移矩阵零差异报告。< / b > < / p >
< / div >
< div class = "phase" >
< h4 > 阶段 4:上位机与收尾(约 2 周)< / h4 >
< p class = "small" > 上位机测试补齐;下位机页面资源管线迁移;长稳与并发压力测试;文档更新(转移表自动生成状态图)。< b > 产出:完整测试体系 + 验收报告。< / b > < / p >
< / div >
< div class = "warn" >
< b > 风险控制< / b > :每阶段结束必须 CI 全绿(含黄金比对)方可合并;协议/故障码/状态序列的任何外部可见变更必须已在变更清单登记并经确认;保持 pCCU、pMotor、pELoad 等周边进程接口不动,重构范围严格限定在 pPowerManger 与 pPowerMangerHost。
< / div >
<!-- ================= 9 ================= -->
< h2 id = "s9" > 9 附录:问题清单汇总< / h2 >
< table >
< tr > < th > 编号< / th > < th > 级别< / th > < th > 类别< / th > < th > 问题摘要< / th > < th > 位置< / th > < th > 处置< / th > < / tr >
< tr > < td > B1< / td > < td > < span class = "badge p0" > P0< / span > < / td > < td > Bug< / td > < td > Lifting 状态不可达(case 12 无 transit/break) < / td > < td class = "loc" > PowerManagerFsm.cpp:2934< / td > < td > 阶段0修复(行为变更登记)< / td > < / tr >
< tr > < td > B2< / td > < td > < span class = "badge p0" > P0< / span > < / td > < td > Bug< / td > < td > s.buoyage == 赋值误写为比较 × 4< / td > < td class = "loc" > PowerManagerFsm.cpp:1471-1486< / td > < td > 阶段0修复(行为变更登记)< / td > < / tr >
< tr > < td > B3< / td > < td > < span class = "badge p0" > P0< / span > < / td > < td > Bug< / td > < td > 邮件偏斜 return 导致整批丢弃< / td > < td class = "loc" > PowerManger.cpp:121-125< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B4< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > Bug< / td > < td > 桅杆升降舵 JSON 键名失配,指令静默丢失< / td > < td class = "loc" > driver.cpp:844/924< / td > < td > 阶段0修复(行为变更登记)< / td > < / tr >
< tr > < td > B5< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > Bug< / td > < td > 故障检测函数重复调用两遍< / td > < td class = "loc" > PowerManagerFsm.cpp:52-55< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B6< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > Bug< / td > < td > 3 个断路器故障码死码< / td > < td class = "loc" > systemData.h:1026-1028< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B7< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > Bug< / td > < td > 故障注入数组越界 + systemData.h:524 越界< / td > < td class = "loc" > PowerManagerFsm.cpp:3053< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B8< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > Bug< / td > < td > TestEvent switch 缺 break;绕过事件机制< / td > < td class = "loc" > PowerManagerFsm.cpp:3084< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B9< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > Bug< / td > < td > 协议结构体未初始化返回 / memset 非 POD< / td > < td class = "loc" > upmsg/ 多处< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B10< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 规范< / td > < td > 超时常量与注释不符(FaultState 20s vs 其余 5s),复制 7 份< / td > < td class = "loc" > FaultState.cpp:46/53 等< / td > < td > 阶段3统一< / td > < / tr >
< tr > < td > B11< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > Bug< / td > < td > 模板残留致每条正常指令误报 "Unhandled Mail" run warning< / td > < td class = "loc" > PowerManger.cpp:142-146< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B12< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > Bug< / td > < td > 畸形 JSON 类型致 jsoncpp 抛异常(asUInt 无类型检查、无 catch) < / td > < td class = "loc" > driver.cpp:935 / UpperCommManager.cpp:48< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B13< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > Bug< / td > < td > HTTP 服务在配置/DB 就绪前于构造函数启动,提前暴露控制面< / td > < td class = "loc" > PowerManger.cpp:83-87< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > B14< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 规范< / td > < td > FsmLoop/_FsmCB、m_deviceCmdQuenue 无消费者;buildReport 占位;SKEW_TOLERANCE 宏未用< / td > < td class = "loc" > PowerManger.h:48/121 等< / td > < td > 阶段3清理< / td > < / tr >
< tr > < td > A1< / td > < td > < span class = "badge p0" > P0< / span > < / td > < td > 架构< / td > < td > 状态机退化为路由壳,转移逻辑集中在基类 switch< / td > < td class = "loc" > fsm/ 全局< / td > < td > 阶段3重构< / td > < / tr >
< tr > < td > A2< / td > < td > < span class = "badge p0" > P0< / span > < / td > < td > 架构< / td > < td > 故障检测与 FaultState 脱钩;FaultState 无出口< / td > < td class = "loc" > PowerManagerFsm.cpp:948-953< / td > < td > 阶段1+3< / td > < / tr >
< tr > < td > A3< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 架构< / td > < td > entry() 内 MOOSPause/忙等最长 10-20 s< / td > < td class = "loc" > WaterBasedReady.cpp:50 等< / td > < td > 阶段3异步化< / td > < / tr >
< tr > < td > A4< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 架构< / td > < td > 3096 行上帝类;static pm 裸指针全局穿透< / td > < td class = "loc" > PowerManagerFsm.hpp:86< / td > < td > 阶段3拆分< / td > < / tr >
< tr > < td > A5-A7< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 规范< / td > < td > 语义错位/拼写错误/死代码/日志混乱< / td > < td class = "loc" > 多处< / td > < td > 阶段3清理< / td > < / tr >
< tr > < td > C1< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 通信< / td > < td > 上位机无连接管理,connectState 死字段< / td > < td class = "loc" > LowerCommManager.h:131< / td > < td > 阶段2启用< / td > < / tr >
< tr > < td > C2< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 通信< / td > < td > 协议知识三处散养;Driver 双实例< / td > < td class = "loc" > driver.cpp / UpperCommManager.h:93< / td > < td > 阶段2收敛< / td > < / tr >
< tr > < td > C3-C6< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 通信< / td > < td > 错误码虚设/死分支/硬编码帧长/废弃函数错误数据< / td > < td class = "loc" > udpComm.cpp / driver.cpp< / td > < td > 阶段2修复< / td > < / tr >
< tr > < td > C5< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 性能< / td > < td > 每帧堆分配、每包 cout< / td > < td class = "loc" > udpComm.cpp:462 等< / td > < td > 阶段2优化< / td > < / tr >
< tr > < td > C7< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 架构< / td > < td > 双 Web 重复建设;/fcs/control 越权直改状态< / td > < td class = "loc" > httpserver.cpp:150< / td > < td > 阶段2+4< / td > < / tr >
< tr > < td > F1< / td > < td > < span class = "badge p0" > P0< / span > < / td > < td > 故障< / td > < td > 双重故障存储,等级查询与上报读不同集合< / td > < td class = "loc" > PowerManger.cpp:448 / uPower_pmState.h:50< / td > < td > 阶段1合并< / td > < / tr >
< tr > < td > F2< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 故障< / td > < td > 上报死赋值< / td > < td class = "loc" > UpperCommManager.cpp:240< / td > < td > 阶段1清理< / td > < / tr >
< tr > < td > F3< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 故障< / td > < td > 三套编码并存;系统码无文本映射;前端 JS 重写映射< / td > < td class = "loc" > faultCode.h / fuelcell.h:834< / td > < td > 阶段1统一< / td > < / tr >
< tr > < td > F4< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 故障< / td > < td > DEBUG 宏全构建未定义,4 级故障字恒为 0;DEBUFG 拼写;_DEBUG 死代码< / td > < td class = "loc" > systemData.h:7,776 等< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > F5< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 故障< / td > < td > 无故障事件持久化表< / td > < td class = "loc" > SQLite 层< / td > < td > 阶段1新增< / td > < / tr >
< tr > < td > D1-D4< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 并发< / td > < td > 全局数据池/锁纪律不一致/abs 截断/并发清队列< / td > < td class = "loc" > systemData.h 等< / td > < td > 阶段2统一线程模型< / td > < / tr >
< tr > < td > H1-H3< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 上位机< / td > < td > 功能重叠/越权通道/页面硬编码< / td > < td class = "loc" > HostSim / httpserver< / td > < td > 阶段4< / td > < / tr >
< tr > < td > E1< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 构建< / td > < td > -Wall 未启用且写法错误;无 sanitizer;测试输出残留< / td > < td class = "loc" > CMakeLists.txt:80-86< / td > < td > 阶段0接入< / td > < / tr >
< tr > < td > E2< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 构建< / td > < td > GLOB 反模式;第三方库无版本/许可证记录;CMAKE 版本过老< / td > < td class = "loc" > CMakeLists.txt:9,49,53< / td > < td > 阶段0接入< / td > < / tr >
< tr > < td > E3< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > 配置< / td > < td > ccuport/iport atoi 无校验、无范围检查< / td > < td class = "loc" > PowerManger.cpp:229/233< / td > < td > 阶段0修复< / td > < / tr >
< tr > < td > E4< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 性能< / td > < td > SQLite insertGeneric/onFrame 高频重复 prepare< / td > < td class = "loc" > SQLite.cpp:610-625< / td > < td > 阶段1优化< / td > < / tr >
< tr > < td > E5< / td > < td > < span class = "badge p2" > P2< / span > < / td > < td > 规范< / td > < td > 日志/错误输出三套并存(loguru/cerr/cout) < / td > < td class = "loc" > driver.cpp / LowerCommManager.cpp< / td > < td > 阶段3统一< / td > < / tr >
< tr > < td > T1< / td > < td > < span class = "badge p0" > P0< / span > < / td > < td > 测试< / td > < td > 状态机/故障注入/上位机三大测试空白< / td > < td class = "loc" > test/< / td > < td > 阶段0起持续< / td > < / tr >
< tr > < td > T2< / td > < td > < span class = "badge p1" > P1< / span > < / td > < td > CI< / td > < td > 集成失败不阻塞;cppcheck 未接入;in-source 构建残留< / td > < td class = "loc" > ci/build-test.sh:181< / td > < td > 阶段0< / td > < / tr >
< / table >
< p class = "small" style = "margin-top:40px;border-top:1px solid var(--border);padding-top:16px;" >
本报告基于 2026-09-03 工作区代码静态审查生成,所有问题均标注文件与行号,关键缺陷(B1、B2、B3、B5)已经人工逐行复核。报告未对任何源代码做修改。
< / p >
< / main >
< / div >
< / body >
< / html >