Files
H100PowerManger/docs/pPowerManger_代码审查与架构优化报告.html

652 lines
70 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>