0827协议修订:姿态数据2改预留 + 状态帧新增剩余发电量 + CCU地址可配置
build-test-deploy / ci (push) Successful in 1h13m13s

FC协议(0827 docx):
- 控制指令域:字节12,13/16,17由纵倾/横倾姿态数据2改为预留(编码恒0、解码忽略)
- 状态反馈域:字节147,148由预留12改为剩余发电量(MWh),贯通JSON/网页显示
- PM→CCU链路:姿态字段4xuchar改为2xshort(数据1),结构体大小与线上布局不变
- 同步更新:CCU转发映射、SnapshotBuilder、pCCU网页、fuelcell网页、SQLite列描述、单测/模拟器
- 修正集成测试遗留断言(20B→24B、心跳d[19]→d[23])并增强预留字节校验

CCU地址可配置:
- pPowerManger 支持 moos 配置项 ccuhost/ccuport(默认127.0.0.1:7000)
- build-board.sh 增加 --ccu-host/--ccu-port 参数

文档:FC协议文档更新为0827版,清理过期协议docx
This commit is contained in:
zjk
2026-08-28 00:09:17 +08:00
parent c8becd1f6c
commit 2590282aed
28 changed files with 199 additions and 91 deletions
+7 -9
View File
@@ -52,8 +52,8 @@ static void testFrameLengths() {
// 2. FC 控制指令编解码往返
static void testFcControlRoundTrip() {
FcControlValue v;
v.mode = 2; v.cmd = 2; v.outputPower = 100; v.pitch1 = 35; v.pitch2 = -21;
v.roll1 = -12; v.roll2 = 48;
v.mode = 2; v.cmd = 2; v.outputPower = 100; v.pitch1 = 35;
v.roll1 = -12;
v.emergencyAllow = 0x03; v.depth = 150;
v.supplyCmd = 1; v.reservedCmd5 = 5; v.reservedCmd6 = 6; v.heartbeat = 7;
@@ -64,11 +64,11 @@ static void testFcControlRoundTrip() {
CHECK(frame[0] == 0x40 && frame[1] == 0x40);
CHECK(frame[4] == 0x18 && frame[5] == 0x00); // 域字节数 0x0018 小端
// 字节位置校验(数据域起点=帧头6):姿态各 int16 小端
// 字节位置校验(数据域起点=帧头6):姿态数据1 int16 小端,数据2字节预留恒0
CHECK(frame[6 + 3] == 0x23 && frame[6 + 4] == 0x00); // 纵倾数据1 = 35
CHECK(frame[6 + 5] == 0xEB && frame[6 + 6] == 0xFF); // 纵倾数据2 = -21 (0xFFEB)
CHECK(frame[6 + 5] == 0x00 && frame[6 + 6] == 0x00); // 预留(0827协议,原纵倾数据2)
CHECK(frame[6 + 7] == 0xF4 && frame[6 + 8] == 0xFF); // 横倾数据1 = -12 (0xFFF4)
CHECK(frame[6 + 9] == 0x30 && frame[6 + 10] == 0x00); // 横倾数据2 = 48
CHECK(frame[6 + 9] == 0x00 && frame[6 + 10] == 0x00); // 预留(0827协议,原横倾数据2)
FcControlValue out;
CHECK(msg.decode(frame, static_cast<void*>(&out)));
@@ -76,9 +76,7 @@ static void testFcControlRoundTrip() {
CHECK(out.cmd == 2);
CHECK(out.outputPower == 100);
CHECK(out.pitch1 == 35);
CHECK(out.pitch2 == -21);
CHECK(out.roll1 == -12);
CHECK(out.roll2 == 48);
CHECK(out.emergencyAllow == 0x03);
CHECK(out.depth == 150);
CHECK(out.supplyCmd == 1);
@@ -101,7 +99,7 @@ static void testFcStatusRoundTrip() {
v.methanol_total_use = 100;
v.cabin_temp1 = 25; v.h2_concentration1 = 500;
v.heartbeat = 9; v.emergency_cmd = 0x01;
v.reserved[0] = 0xAAAA; v.reserved[11] = 0xBBBB;
v.reserved[0] = 0xAAAA; v.remaining_generation = 0xBBBB;
FcStatusMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
@@ -125,7 +123,7 @@ static void testFcStatusRoundTrip() {
CHECK(out.methanol_total_use == 100);
CHECK(out.cabin_temp1 == 25 && out.h2_concentration1 == 500);
CHECK(out.heartbeat == 9 && out.emergency_cmd == 0x01);
CHECK(out.reserved[0] == 0xAAAA && out.reserved[11] == 0xBBBB);
CHECK(out.reserved[0] == 0xAAAA && out.remaining_generation == 0xBBBB);
}
//--------------------------------------------------------------------------
+16 -6
View File
@@ -9,7 +9,7 @@
校验:
1. FC 状态收到并整合,pCCU 周期发送 PM 状态(0x0004, 248B)到 16003
2. PM 操控(0x0001)转发为 FC 控制(0x0001, 20B)到 16001
2. PM 操控(0x0001)转发为 FC 控制(0x0001, 24B)到 16001
3. PM 参数设定(0x0002)触发参数反馈(0x0003, 12B)到 16003
4. SQLite 记录了收/发原始帧
5. Web 页面可访问
@@ -62,6 +62,7 @@ def fc_status_frame():
p[17] = 70 # 液氧
struct.pack_into("<H", p, 24, 350) # 钯膜温度
struct.pack_into("<H", p, 68, 2500) # 舱室温度1
struct.pack_into("<H", p, 140, 4321) # 剩余发电量 MWh (字节147,148)
p[142] = 5 # 心跳
p[143] = 0x01 # 应急指令
return frame(0x0002, bytes(p), checksum=False)
@@ -74,6 +75,8 @@ def pm_control_frame():
p[8] = 2 # 模式 自动
p[9] = 2 # 操控 启动
p[10] = 120 # 输出功率 0.1kW
struct.pack_into("<h", p, 11, 123) # 纵倾姿态数据1 12.3°
struct.pack_into("<h", p, 13, -45) # 横倾姿态数据1 -4.5°
p[15] = 0x03 # 应急允许
struct.pack_into("<H", p, 16, 150) # 潜深
p[21] = 0x10 # 仪表锂电启动
@@ -173,14 +176,21 @@ def main():
else:
ok = False
# 校验 2:PM 操控转发为 FC 控制(20B)
fc_ctl = [d for d in fc_ctl_results if len(d) == 20]
print(f"[FC 控制转发] 收到 {len(fc_ctl)} 帧 (期望>=1, 每帧20B)")
# 校验 2:PM 操控转发为 FC 控制(24B,0827协议:姿态数据1转发,数据2字节预留恒0)
fc_ctl = [d for d in fc_ctl_results if len(d) == 24]
print(f"[FC 控制转发] 收到 {len(fc_ctl)} 帧 (期望>=1, 每帧24B)")
if fc_ctl:
d = fc_ctl[0]
cmd = d[7]; power = d[8]; hb = d[19]
print(f" -> cmd={cmd}, outputPower={power}, heartbeat={hb}")
cmd = d[7]; power = d[8]; hb = d[23]
pitch = struct.unpack_from("<h", d, 9)[0] # 字节10,11
roll = struct.unpack_from("<h", d, 13)[0] # 字节14,15
resv1 = struct.unpack_from("<H", d, 11)[0] # 字节12,13 预留
resv2 = struct.unpack_from("<H", d, 15)[0] # 字节16,17 预留
print(f" -> cmd={cmd}, outputPower={power}, pitch={pitch}, roll={roll}, "
f"resv1={resv1}, resv2={resv2}, heartbeat={hb}")
ok = ok and (cmd == 2) and (power == 120) and (hb == 7)
ok = ok and (pitch == 123) and (roll == -45)
ok = ok and (resv1 == 0) and (resv2 == 0)
else:
ok = False
+3 -1
View File
@@ -120,6 +120,8 @@ class FcSim:
struct.pack_into("<H", p, 116, int(900 + 100 * math.sin(self.t / 4)))
p[142] = self.heartbeat & 0xFF # 通信心跳 (字节149)
p[143] = 0 # 应急指令 (字节150)
# 剩余发电量 MWh (字节147-148, 0827协议由预留12改为):随发电缓慢消耗
struct.pack_into("<H", p, 140, int(500 - self.gen_time / 360.0))
return frame(0x0002, bytes(p), checksum=False)
def tick(self, dt):
@@ -156,7 +158,7 @@ class PmSim:
p[8] = 2 # 模式: 自动
p[9] = 2 # 操控: 启动
p[10] = self.power # 输出功率 (0.1kW)
# 纵倾姿态 (数据1=低8位/数据2=高8位, int16, 0.1°) / 横倾姿态
# 纵倾姿态数据1 / 横倾姿态数据1 (int16, 0.1°, 0827协议数据2字节预留)
pitch = int((3.0 + 1.5 * math.sin(time.time() / 8)) * 10)
roll = int((-1.0 + 0.5 * math.cos(time.time() / 6)) * 10)
struct.pack_into("<h", p, 11, pitch)