Files
H100PowerManger/test/pccu/pccuTest.cpp
T
zjk 88bed850ee
build-test-deploy / ci (push) Failing after 1h9m57s
pCCU 锂电池通信切换为 CAN 总线(BMS 协议,经 pCanBridge 透传)
协议层:
- 新增 protocol/CanBms.{h,cpp}:按 docs/BMS_协议字段定义.xlsx 解码
  0x10010000(总电压/SOC/故障码/状态位)、0x10010005(最大电流限制/
  总电流/最高单体电压)、0x10010006(总电压校验)。
  缩放用除法保证与十进制字面量严格一致;总电流按 2 字节 s16 实现
  (xlsx 标注 3 字节与其自身示例/实车帧矛盾,已在代码注释说明)。
- 删除 UDP 锂电池协议:protocol/BatProtocol.{h,cpp}、
  comm/BatLinkManager.h(动力/仪表双链路、自检/接触器/功率指令)。

pCCU 主体:
- CCU 订阅 pCanBridge 发布的 CAN_0x* 二进制消息并解码 BMS 报文;
  SystemData 双电池槽位改为单一 BmsStatusValue + CAN 帧计数;
  PM 电池启停/功率指令改为告警忽略(BMS CAN 协议未定义控制报文)。
- 修复两个本地联调发现的阻断性 bug:
  1) MOOS 通配订阅必须用三参数 Register("CAN_0x*","*",0),
     单参数形式不做通配匹配;
  2) 变量名前缀匹配偏移(compare 少比 1 字符 / sscanf +5 指向 'x'),
     改为 rfind 前缀判断 + +6 偏移。
- PowerCoordinator/CoordFsm 剥离电池接触器/切换时序(依赖已删除的
  UDP 指令),保留 FC 联动与协调策略占位(CoordSwitchingState 改为
  通用的 CoordBusyState)。

网页:
- 动力/仪表锂电池两个标签页合并为"锂电池BMS(CAN)":总电压/总电流/
  SOC/最大电流限制/最高单体电压/校验帧/故障码高亮/在线状态,
  附报文来源说明;链路状态页改为 CAN 链路行(pCanBridge)。

配置与测试:
- pCCU.moos / missions/h100.moos 移除电池 UDP 配置;
- pccuTest:删除旧电池编解码用例,新增 testCanBmsDecode
  (向量取自实车抓包:533.8V/20.1%/49/3.752V/-25.0A)103/103 通过;
- 集成测试改为纯 FC/PM 流程,本地实测 PASSED(含 Web 页面校验)。
2026-08-31 21:42:26 +08:00

352 lines
14 KiB
C++
Raw 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.
//============================================================================
// pccuTest:pCCU 协议编解码 / 校验和 / 帧长度 / 链路分发 / 数据库 单元测试
// 无第三方测试库,独立可执行。参照 test/until/testSqlitdb.cpp 的轻量风格。
//============================================================================
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
#include <iostream>
#include "../../src/pCCU/protocol/FieldCodec.h"
#include "../../src/pCCU/protocol/Frame.h"
#include "../../src/pCCU/protocol/ChecksumPolicy.h"
#include "../../src/pCCU/protocol/MessageRegistry.h"
#include "../../src/pCCU/protocol/FcProtocol.h"
#include "../../src/pCCU/protocol/PmProtocol.h"
#include "../../src/pCCU/protocol/CanBms.h"
#include "../../src/pCCU/comm/LinkManager.h"
#include "../../src/pCCU/comm/FcLinkManager.h"
#include "../../src/pCCU/comm/PmLinkManager.h"
#include "../../src/pCCU/store/DbStore.h"
using namespace ccu;
static int g_fail = 0;
static int g_pass = 0;
#define CHECK(cond) do { \
if (cond) { ++g_pass; } \
else { ++g_fail; std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); } \
} while (0)
//--------------------------------------------------------------------------
// 1. 帧长度必须与协议文档一致
static void testFrameLengths() {
FcControlMessage fcm;
FcStatusMessage fsm;
PmControlMessage pcm;
PmParamSetMessage pps;
PmParamSetFbMessage ppf;
PmStatusMessage psm;
CHECK(fcm.totalLength() == 24); // FC 控制指令 24B(1.1.1:域字节数0x0018)
CHECK(fsm.totalLength() == 150); // FC 状态反馈 150B
CHECK(pcm.totalLength() == 45); // PM 操控指令 45B
CHECK(pps.totalLength() == 28); // PM 参数设定 28B
CHECK(ppf.totalLength() == 12); // PM 参数反馈 12B
CHECK(psm.totalLength() == 248); // PM 状态报文 248B
}
//--------------------------------------------------------------------------
// 2. FC 控制指令编解码往返
static void testFcControlRoundTrip() {
FcControlValue v;
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;
FcControlMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
CHECK(frame.size() == 24);
CHECK(frame[0] == 0x40 && frame[1] == 0x40);
CHECK(frame[4] == 0x18 && frame[5] == 0x00); // 域字节数 0x0018 小端
// 字节位置校验(数据域起点=帧头6):姿态数据1 int16 小端,数据2字节预留恒0
CHECK(frame[6 + 3] == 0x23 && frame[6 + 4] == 0x00); // 纵倾数据1 = 35
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] == 0x00 && frame[6 + 10] == 0x00); // 预留(0827协议,原横倾数据2)
FcControlValue out;
CHECK(msg.decode(frame, static_cast<void*>(&out)));
CHECK(out.mode == 2);
CHECK(out.cmd == 2);
CHECK(out.outputPower == 100);
CHECK(out.pitch1 == 35);
CHECK(out.roll1 == -12);
CHECK(out.emergencyAllow == 0x03);
CHECK(out.depth == 150);
CHECK(out.supplyCmd == 1);
CHECK(out.reservedCmd5 == 5 && out.reservedCmd6 == 6);
CHECK(out.heartbeat == 7);
}
//--------------------------------------------------------------------------
// 3. FC 状态编解码往返(关键字段抽查)
static void testFcStatusRoundTrip() {
FcStatusValue v;
v.fc_mode = 2; v.fc_status = 6; v.fc_fault_level = 0;
v.fault_level_1 = 0x0102; v.fault_level_2 = 0x0304;
v.fault_level_3 = 0x0506; v.fault_level_4 = 0x0708;
v.total_generation_time = 12345;
v.generation_power = 23456;
v.hydrogen_capacity = 80; v.liquid_oxygen_capacity = 70;
v.palladium_temp = 350; v.buffer_tank_pressure = 200;
v.dcdc1_in_voltage = 12; v.dcdc_out_current = 33;
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.remaining_generation = 0xBBBB;
FcStatusMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
CHECK(frame.size() == 150);
// 字节位置校验(对照 0821 docx):发电功率在字节21-22(offset 14),心跳在字节149(offset 142)
CHECK(frame[6 + 12] == 0); // 故障等级 offset12
CHECK(frame[6 + 14] == 0xA0 && frame[6 + 15] == 0x5B); // 发电功率 23456 -> 0x5BA0 小端
CHECK(frame[6 + 142] == 9); // 心跳 offset142
CHECK(frame[6 + 143] == 0x01);// 应急指令 offset143
FcStatusValue out;
CHECK(msg.decode(frame, static_cast<void*>(&out)));
CHECK(out.fc_mode == 2 && out.fc_status == 6);
CHECK(out.fault_level_1 == 0x0102 && out.fault_level_4 == 0x0708);
CHECK(out.total_generation_time == 12345);
CHECK(out.generation_power == 23456);
CHECK(out.hydrogen_capacity == 80 && out.liquid_oxygen_capacity == 70);
CHECK(out.palladium_temp == 350);
CHECK(out.dcdc1_in_voltage == 12 && out.dcdc_out_current == 33);
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.remaining_generation == 0xBBBB);
}
//--------------------------------------------------------------------------
// 4. PM 操控指令编解码往返
static void testPmControlRoundTrip() {
PmControlValue v;
v.year = 2026; v.month = 8; v.day = 26; v.hour = 17; v.minute = 30; v.second = 5;
v.millisecond10 = 3;
v.mode = 2; v.cmd = 2; v.outputPower = 120;
v.pitch = 35; v.roll = -12;
v.emergencyAllow = 0x03; v.depth = 200;
v.supplyCmd = 1; v.reservedCmd5 = 5; v.reservedCmd6 = 6;
v.insBatCmd = 0x10; v.dynBatCmd = 0x20; v.dynBatPower = 300;
v.heartbeat = 11; v.hostState = 0xAA;
v.reserved1 = 0x11223344; v.reserved2 = 0x55667788;
PmControlMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
CHECK(frame.size() == 45);
PmControlValue out;
CHECK(msg.decode(frame, static_cast<void*>(&out)));
CHECK(out.year == 2026 && out.month == 8 && out.day == 26);
CHECK(out.hour == 17 && out.minute == 30 && out.second == 5);
CHECK(out.millisecond10 == 3);
CHECK(out.mode == 2 && out.cmd == 2 && out.outputPower == 120);
CHECK(out.pitch == 35 && out.roll == -12);
CHECK(out.depth == 200);
CHECK(out.insBatCmd == 0x10 && out.dynBatCmd == 0x20 && out.dynBatPower == 300);
CHECK(out.heartbeat == 11 && out.hostState == 0xAA);
CHECK(out.reserved1 == 0x11223344 && out.reserved2 == 0x55667788);
}
//--------------------------------------------------------------------------
// 5. PM 状态报文编解码往返(抽查关键字段,覆盖各分组)
static void testPmStatusRoundTrip() {
PmStatusValue v;
v.fc_mode = 2; v.fc_status = 6;
v.fault_level_1 = 1; v.fault_level_2 = 2; v.fault_level_3 = 3; v.fault_level_4 = 4;
v.total_generation_time = 500; v.fc_fault_level = 0;
v.output_power_limit = 30000; v.generation_power = 100;
v.hydrogen_capacity = 80; v.liquid_oxygen_capacity = 70;
v.main_pipe_pressure = 500; v.aux_pipe_pressure = 400;
v.dcdc_out_voltage = 240; v.dcdc_out_current = 100;
v.cabin_temp1 = 2500; v.cabin_temp2 = 2400;
v.h2_concentration1 = 300; v.o2_concentration1 = 200;
v.emergency_battery1_voltage = 24; v.emergency_battery2_voltage = 26;
v.ins_soc = 60; v.dyn_soc = 55;
v.ins_voltage_link = 4800; v.dyn_voltage_link = 4900;
v.ins_current = 120; v.dyn_current = 130;
v.device_online_flag1 = 0x0F; v.device_online_flag2 = 0x07;
v.heartbeat = 42; v.emergency_cmd = 0x01;
v.dyn_alarm_flag[0] = 0xAA; v.ins_alarm_flag[5] = 0x55;
PmStatusMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
CHECK(frame.size() == 248);
PmStatusValue out;
CHECK(msg.decode(frame, static_cast<void*>(&out)));
CHECK(out.fc_mode == 2 && out.fc_status == 6);
CHECK(out.fault_level_1 == 1 && out.fault_level_4 == 4);
CHECK(out.total_generation_time == 500 && out.fc_fault_level == 0);
CHECK(out.output_power_limit == 30000 && out.generation_power == 100);
CHECK(out.hydrogen_capacity == 80 && out.liquid_oxygen_capacity == 70);
CHECK(out.main_pipe_pressure == 500 && out.aux_pipe_pressure == 400);
CHECK(out.dcdc_out_voltage == 240 && out.dcdc_out_current == 100);
CHECK(out.cabin_temp1 == 2500 && out.h2_concentration1 == 300);
CHECK(out.emergency_battery1_voltage == 24 && out.emergency_battery2_voltage == 26);
CHECK(out.ins_soc == 60 && out.dyn_soc == 55);
CHECK(out.ins_voltage_link == 4800 && out.dyn_voltage_link == 4900);
CHECK(out.device_online_flag1 == 0x0F && out.device_online_flag2 == 0x07);
CHECK(out.heartbeat == 42 && out.emergency_cmd == 0x01);
CHECK(out.dyn_alarm_flag[0] == 0xAA && out.ins_alarm_flag[5] == 0x55);
}
//--------------------------------------------------------------------------
// 6. PM 校验和:正确帧通过,篡改字节校验失败
static void testChecksum() {
PmControlValue v;
v.mode = 2; v.cmd = 3; v.heartbeat = 1;
PmControlMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
PmControlValue tmp;
CHECK(msg.decode(frame, static_cast<void*>(&tmp))); // 校验通过
// 篡改数据域中间字节
std::vector<uint8_t> bad = frame;
bad[10] ^= 0xFF; // 数据域某字节
CHECK(!msg.decode(bad, static_cast<void*>(&tmp)));
// 篡改帧头长度
std::vector<uint8_t> badLen = frame;
badLen[4] = 0x01; // length 低字节改小 -> 0x012D != 45
CHECK(!msg.decode(badLen, static_cast<void*>(&tmp)));
}
//--------------------------------------------------------------------------
// 7. 链路分发:FC 链路收到状态帧 -> 回调解码
static void testLinkDispatch() {
// 注册表按链路隔离:FC 与 PM 共用 0x0001/0x0002 不应冲突
MessageRegistry fcReg;
registerFcMessages(fcReg);
MessageRegistry pmReg;
registerPmMessages(pmReg);
CHECK(fcReg.size() == 2);
CHECK(pmReg.size() == 4);
CHECK(fcReg.find(0x0001)->id() == 0x0001);
CHECK(pmReg.find(0x0001)->id() == 0x0001); // id 重叠但隔离
// 用 LinkManager 的 handleIncoming 模拟收帧
FcStatusValue fv;
fv.fc_status = 6; fv.heartbeat = 8;
FcStatusMessage fsMsg;
std::vector<uint8_t> frame = fsMsg.encode(fv);
LinkManager lm("test_fc", 0);
registerFcMessages(lm.registry());
bool got = false;
lm.setOnMessage([&](Message* m, const std::vector<uint8_t>& f) {
if (m && m->id() == 0x0002) {
FcStatusValue out;
if (m->decode(f, static_cast<void*>(&out))) {
got = (out.fc_status == 6 && out.heartbeat == 8);
}
}
});
CHECK(lm.handleIncoming(frame));
CHECK(got);
}
//--------------------------------------------------------------------------
// 8. 数据库落库与查询
static void testDbStore() {
std::string path = "test_pccu.db";
std::remove(path.c_str());
{
DbStore db(path);
CHECK(db.open());
std::vector<uint8_t> frame = {0x40,0x40,0x01,0x00,0x14,0x00, 1,2,3,4,5,6,7,8,9,10,11,12,13,14};
db.onRawFrame(0, 0x0001, "fc", frame, true);
db.onRawFrame(1, 0x0004, "pm", frame, false);
CHECK(db.count() == 2);
auto rows = db.queryRecent("", -1, 0, 10);
CHECK(rows.size() == 2);
CHECK(rows[0].msgId == 0x0004 || rows[0].msgId == 0x0001);
CHECK(rows[0].checksumOk == 0); // 最近一条是 pm 的 false
auto fcRows = db.queryRecent("fc", 0, 0, 10);
CHECK(fcRows.size() == 1);
CHECK(fcRows[0].msgId == 0x0001 && fcRows[0].direction == 0);
db.close();
}
std::remove(path.c_str());
}
//--------------------------------------------------------------------------
// 9. CAN BMS 报文解码(docs/BMS_协议字段定义.xlsx,测试向量为实车抓包数据)
static void testCanBmsDecode() {
BmsStatusValue v;
// 0x10010000:14 da 17 70 00 c9 31 01(实车抓包)
// 总电压 0x14DA=5338 -> 533.8V;SOC 0x00C9=201 -> 20.1%
// 故障码 0x31=49;状态位 0x01
const uint8_t d0[8] = {0x14,0xDA,0x17,0x70,0x00,0xC9,0x31,0x01};
CHECK(decodeCanBmsFrame(0x10010000, d0, 8, v));
CHECK(v.totalVoltage == 533.8);
CHECK(v.soc == 20.1);
CHECK(v.faultCode == 0x31);
CHECK(v.statusBits == 0x01);
// 文档示例帧:14 d9 => 533.7V
const uint8_t d0doc[8] = {0x14,0xD9,0x17,0x70,0x00,0xC9,0x31,0x01};
CHECK(decodeCanBmsFrame(0x10010000, d0doc, 8, v));
CHECK(v.totalVoltage == 533.7);
// 0x10010005:07 d0 00 00 0e a8 00 00
// 最大电流限制 0x07D0=2000 -> 200.0A;总电流 00 00 00 -> 0.0A
// 最高单体电压 0x0EA8=3752 -> 3.752V
const uint8_t d5[8] = {0x07,0xD0,0x00,0x00,0x0E,0xA8,0x00,0x00};
CHECK(decodeCanBmsFrame(0x10010005, d5, 8, v));
CHECK(v.maxCurrentLimit == 200.0);
CHECK(v.totalCurrent == 0.0);
CHECK(v.maxCellVoltage == 3.752);
// 总电流负数:s16 补码 FF 06 = -250 -> -25.0A(xlsx 负数示例)
const uint8_t d5n[8] = {0x07,0xD0,0xFF,0x06,0x0E,0xA8,0x00,0x00};
CHECK(decodeCanBmsFrame(0x10010005, d5n, 8, v));
CHECK(v.totalCurrent == -25.0);
// 0x10010006:07 d0 07 d0 14 da 00 00 -> 总电压校验 0x14DA=533.8V
const uint8_t d6[8] = {0x07,0xD0,0x07,0xD0,0x14,0xDA,0x00,0x00};
CHECK(decodeCanBmsFrame(0x10010006, d6, 8, v));
CHECK(v.totalVoltageChk == 533.8);
// 非 BMS 报文 ID 不命中
const uint8_t dx[8] = {0,0,0,0,0,0,0,0};
CHECK(!decodeCanBmsFrame(0x10010035, dx, 8, v));
CHECK(!decodeCanBmsFrame(0x10010000, nullptr, 0, v));
CHECK(isCanBmsId(0x10010000) && isCanBmsId(0x10010005) && isCanBmsId(0x10010006));
CHECK(!isCanBmsId(0x10010001));
}
//--------------------------------------------------------------------------
int main() {
testFrameLengths();
testFcControlRoundTrip();
testFcStatusRoundTrip();
testPmControlRoundTrip();
testPmStatusRoundTrip();
testChecksum();
testLinkDispatch();
testDbStore();
testCanBmsDecode();
std::printf("\n==== pccuTest: %d passed, %d failed ====\n", g_pass, g_fail);
return g_fail == 0 ? 0 : 1;
}