docs 新增 pPowerManger 代码审查与架构优化报告及电子负载 IT6000C 说明手册(PDF)
build-test-deploy / ci (push) Failing after 30s

This commit is contained in:
zjk
2026-09-09 20:52:15 +08:00
parent f7d2b98dc1
commit c61ac8ccb9
3 changed files with 651 additions and 0 deletions
@@ -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:15px;}
.layout{display:flex;max-width:1440px;margin:0 auto;}
nav{position:sticky;top:0;height:100vh;width:250px;flex-shrink:0;overflow-y:auto;padding:28px 18px;border-right:1px solid var(--border);background:var(--panel);}
nav h3{font-size:13px;color:var(--text-dim);letter-spacing:2px;margin-bottom:14px;}
nav a{display:block;color:var(--text-dim);text-decoration:none;padding:5px 10px;border-radius:6px;font-size:13.5px;border-left:2px solid transparent;}
nav a:hover{color:var(--accent);background:var(--panel2);}
main{flex:1;padding:44px 56px 120px;max-width:1140px;}
header.cover{border-bottom:1px solid var(--border);padding-bottom:30px;margin-bottom:36px;}
header.cover .tag{color:var(--accent);font-size:13px;letter-spacing:3px;}
header.cover h1{font-size:30px;margin:10px 0 8px;font-weight:600;}
header.cover .meta{color:var(--text-dim);font-size:13.5px;}
h2{font-size:22px;margin:52px 0 18px;padding-left:14px;border-left:4px solid var(--accent);font-weight:600;}
h3{font-size:17px;margin:30px 0 12px;color:var(--accent);font-weight:600;}
h4{font-size:15.5px;margin:20px 0 8px;color:var(--text);}
p{margin:10px 0;color:var(--text);}
.card{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:20px 24px;margin:16px 0;}
table{width:100%;border-collapse:collapse;margin:14px 0;font-size:13.5px;}
th{background:var(--panel2);color:var(--accent);text-align:left;padding:9px 12px;border:1px solid var(--border);font-weight:600;white-space:nowrap;}
td{padding:8px 12px;border:1px 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:1px 6px;border-radius:4px;font-size:13px;}
pre{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:14px 18px;overflow-x:auto;margin:12px 0;}
pre code{background:none;padding:0;color:#c8d8e8;font-size:13px;line-height:1.6;}
.badge{display:inline-block;padding:1px 10px;border-radius:10px;font-size:12px;font-weight:600;margin-right:6px;white-space:nowrap;}
.p0{background:rgba(255,92,92,.15);color:var(--p0);border:1px solid var(--p0);}
.p1{background:rgba(255,179,71,.12);color:var(--p1);border:1px solid var(--p1);}
.p2{background:rgba(111,191,115,.12);color:var(--p2);border:1px solid var(--p2);}
.loc{color:var(--text-dim);font-size:12.5px;font-family:Consolas,monospace;}
ul,ol{margin:8px 0 8px 24px;}
li{margin:5px 0;}
.flow{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:14px 0;font-size:13px;}
.node{background:var(--panel2);border:1px solid var(--accent);border-radius:8px;padding:8px 14px;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:3px solid var(--accent2);background:rgba(55,200,160,.06);padding:12px 18px;border-radius:0 8px 8px 0;margin:14px 0;font-size:14px;}
.warn{border-left:3px solid var(--p1);background:rgba(255,179,71,.06);padding:12px 18px;border-radius:0 8px 8px 0;margin:14px 0;font-size:14px;}
.danger{border-left:3px solid var(--p0);background:rgba(255,92,92,.07);padding:12px 18px;border-radius:0 8px 8px 0;margin:14px 0;font-size:14px;}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.kpi{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:18px 0;}
.kpi .item{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:16px;text-align:center;}
.kpi .num{font-size:26px;font-weight:700;color:var(--accent);}
.kpi .lbl{font-size:12.5px;color:var(--text-dim);margin-top:4px;}
.phase{border-left:3px solid var(--accent);padding:6px 0 6px 20px;margin:18px 0;position:relative;}
.phase::before{content:"";position:absolute;left:-7px;top:12px;width:11px;height:11px;border-radius:50%;background:var(--accent);}
.phase h4{margin:0 0 4px;color:var(--accent);}
.small{font-size:13px;color:var(--text-dim);}
@media (max-width:1000px){nav{display:none;}main{padding:24px;}.kpi{grid-template-columns:repeat(2,1fr);}.grid2{grid-template-columns:1fr;}}
</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 &amp; ARCHITECTURE OPTIMIZATION</div>
<h1>pPowerManger 电源管理软件<br>代码审查与架构优化报告</h1>
<div class="meta">项目:H100PowerManger(UUV 复合能源功率管理)&nbsp;|&nbsp;范围:src/pPowerManger、src/pPowerMangerHost、test、ci&nbsp;|&nbsp;日期:2026-09-03&nbsp;|&nbsp;版本: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&lt;Lifting&gt;()</code> 与 <code>break</code>,直接坠入 default</td>
<td class="loc">fsm/PowerManagerFsm.cpp:2934-2936</td>
<td>上位机"起吊准备"指令被静默吞掉,Lifting 为死状态。补 <code>transit&lt;Lifting&gt;(); 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&lt;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&lt;S&gt;(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 &lt;&lt; "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>