Files
H100PowerManger/test/pccu/pccuTest.cpp
T
zjk 0639b5904d pCCU 新增真实CCU配电桥接(rcu链路原帧透传+网页显示)并适配板卡端口拓扑
- protocol/DisProtocol: 配电协议8条消息(指令0x0001~4/反馈0x0005~8, Sum8),复用pmSysvariable结构体+static_assert核对
- ChecksumPolicy 新增 Sum8;Frame 支持 1 字节校验和
- MessageRegistry 支持 (id+帧总长) 二维查找:配电指令与PM操控指令共用id按帧长区分(帧头length字段不可信)
- LinkManager: 按实际帧长分发 + sendRaw原帧透传 + 非本协议帧头落库节流告警;UdpEndpoint sendTo 加锁
- comm/RcuLinkManager: 真实CCU链路;core/DisBridge: 配电桥接器(反馈转pPowerManger+解码显示,指令转真实CCU,0x0004仅显示不转发)
- 网页新增"真实CCU"页签全字段展示;SnapshotBuilder realCcu 节 + rcu 链路描述
- h100.moos: 端口腾挪(真实CCU固定上报5001归pCCU,pPowerManger iport=5002),rcu_remote=192.168.0.140:7000
- 测试: pccuTest 新增配电/Sum8/长度分发/RCU注册表用例(176项);集成测试新增桥接字节一致+不误转发用例
2026-09-01 15:06:11 +08:00

541 lines
23 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/DisProtocol.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/comm/RcuLinkManager.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 BCU 报文解码(docs/04KT38电池BCU-MBMS通信(CAN)定义.docx,
// 测试向量为实车抓包数据与协议文档示例值)
static void testCanBmsDecode() {
uint8_t addr = 0;
// 0x10010000(节点01,实车抓包):14 da 17 70 00 c9 31 01
// 累加电压 0x14DA=5338 -> 533.8V;回路电流 0x1770=6000 -> 600.0-600=0.0A
// SOC 0x00C9=201 -> 20.1%;告警码 0x31;自检状态 0x01
BcuNodeStatus n1;
const uint8_t d0[8] = {0x14,0xDA,0x17,0x70,0x00,0xC9,0x31,0x01};
CHECK(decodeCanBcuFrame(0x10010000, d0, 8, addr, n1));
CHECK(addr == 1);
CHECK(n1.totalVoltage == 533.8);
CHECK(n1.current == 0.0);
CHECK(n1.soc == 20.1);
CHECK(n1.alarmCode == 0x31);
CHECK(n1.selfCheck == 0x01);
// 回路电流:充电 -25A -> raw 0x1676=5750 -> 575.0-600=-25.0A
const uint8_t d0n[8] = {0x14,0xDA,0x16,0x76,0x00,0xC9,0x31,0x00};
decodeCanBcuFrame(0x10010000, d0n, 8, addr, n1);
CHECK(n1.current == -25.0);
// 0x10010001(节点01)单体电压:0e a8 05 0e a0 03 0e a4
// 最高 0x0EA8=3752 -> 3.752V (#5);最低 0x0EA0=3744 -> 3.744V (#3)
// 平均 0x0EA4=3748 -> 3.748V;部分更新,概要字段保留
const uint8_t dv[8] = {0x0E,0xA8,0x05,0x0E,0xA0,0x03,0x0E,0xA4};
CHECK(decodeCanBcuFrame(0x10010001, dv, 8, addr, n1));
CHECK(n1.maxCellVoltage == 3.752);
CHECK(n1.maxCellVoltageNo == 5);
CHECK(n1.minCellVoltage == 3.744);
CHECK(n1.minCellVoltageNo == 3);
CHECK(n1.avgCellVoltage == 3.748);
CHECK(n1.totalVoltage == 533.8); // 同节点部分更新,前一帧字段不被清除
CHECK(n1.rxMask == ((1u << BCU_FUNC_BASE) | (1u << BCU_FUNC_CELL_V)));
// 0x10010002(节点01)单体温度:3c 01 2d 02 33
// 最高 0x3C=60-40=20℃ (#1);最低 0x2D=45-40=5℃ (#2);平均 0x33=51-40=11℃
// (实测固件平均温度与最高/最低一致均带 -40℃ 偏移,协议文档"偏移0"有误:
// 实车 0x10020002 原始 68/69 减 40 后为 28/29℃,落在最低28~最高30区间内)
const uint8_t dt[8] = {0x3C,0x01,0x2D,0x02,0x33,0x00,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10010002, dt, 8, addr, n1));
CHECK(n1.maxCellTemp == 20.0);
CHECK(n1.maxCellTempNo == 1);
CHECK(n1.minCellTemp == 5.0);
CHECK(n1.minCellTempNo == 2);
CHECK(n1.avgCellTemp == 11.0);
// 0x10010003(节点01)继电器:01 01 -> 正/负极均闭合
const uint8_t dr[8] = {0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10010003, dr, 8, addr, n1));
CHECK(n1.posRelay == 1 && n1.negRelay == 1);
// 0x10010006(节点01,实车抓包):07 d0 07 d0 14 da 00 00
// 正/负极绝缘 2000kΩ;端口电压 0x14DA -> 533.8V;外侧电压 0.0V
const uint8_t di[8] = {0x07,0xD0,0x07,0xD0,0x14,0xDA,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10010006, di, 8, addr, n1));
CHECK(n1.posInsulationKohm == 2000.0);
CHECK(n1.negInsulationKohm == 2000.0);
CHECK(n1.portVoltage == 533.8);
CHECK(n1.posRelayOuterVoltage == 0.0);
// 0x10010010(节点01)告警位:byte0=0x01 -> BCU绝缘2级;byte1=0x04 -> 烟雾报警1级
const uint8_t da[8] = {0x01,0x04,0x00,0x00,0x00,0x00,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10010010, da, 8, addr, n1));
CHECK(n1.alarmBits[0] == 0x01 && n1.alarmBits[1] == 0x04);
const char* texts[8] = {nullptr};
int cnt = bcuAlarmTexts(n1, texts, 8);
CHECK(cnt == 2);
CHECK(cnt > 0 && std::string(texts[0]) == "BCU绝缘2级");
CHECK(cnt > 1 && std::string(texts[1]) == "BCU烟雾报警1级");
// 无告警
const uint8_t dz[8] = {0,0,0,0,0,0,0,0};
decodeCanBcuFrame(0x10010010, dz, 8, addr, n1);
CHECK(bcuAlarmTexts(n1, texts, 8) == 0);
// 节点分离:0x10020000(节点02)解析到独立状态
BcuNodeStatus n2;
const uint8_t d02[8] = {0x0D,0x40,0x17,0x70,0x01,0x2C,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10020000, d02, 8, addr, n2));
CHECK(addr == 2);
CHECK(n2.totalVoltage == 339.2);
CHECK(n2.soc == 30.0);
CHECK(n1.alarmCode == 0x31); // 节点01 快照不受节点02 帧影响
// 非 BCU 报文 / 预留功能码 / 地址越界 / 下发指令 均不命中
const uint8_t dx[8] = {0,0,0,0,0,0,0,0};
CHECK(!decodeCanBcuFrame(0x10010035, dx, 8, addr, n1)); // 未定义功能码
CHECK(!decodeCanBcuFrame(0x10010004, dx, 8, addr, n1)); // Reserve
CHECK(!decodeCanBcuFrame(0x10010005, dx, 8, addr, n1)); // Reserve
CHECK(!decodeCanBcuFrame(0x10370000, dx, 8, addr, n1)); // 地址 0x37 越界
CHECK(!decodeCanBcuFrame(0x10000000, dx, 8, addr, n1)); // 地址 0 非法
CHECK(!decodeCanBcuFrame(0x100181FF, dx, 8, addr, n1)); // MBMS 下发指令
CHECK(!decodeCanBcuFrame(0x10010000, nullptr, 0, addr, n1));
CHECK(isCanBcuId(0x10010000) && isCanBcuId(0x10010001) &&
isCanBcuId(0x10010002) && isCanBcuId(0x10010003) &&
isCanBcuId(0x10010006) && isCanBcuId(0x10010010));
CHECK(!isCanBcuId(0x10010004) && !isCanBcuId(0x10010005) &&
!isCanBcuId(0x100181FF) && !isCanBcuId(0x10370000));
}
//--------------------------------------------------------------------------
// 9. 配电协议帧长度与 Sum8 校验
static void testDisFrameLengths() {
DisHighVolBusCmdMessage hvCmd; CHECK(hvCmd.totalLength() == 21); // 6+14+1
DisHighAVolBusCmdMessage hvaCmd; CHECK(hvaCmd.totalLength() == 13); // 6+6+1
DisHighBVolBusCmdMessage hvbCmd; CHECK(hvbCmd.totalLength() == 14); // 6+7+1
DisLowBusCmdMessage lvCmd; CHECK(lvCmd.totalLength() == 15); // 6+8+1
DisHighVolBusFbMessage hvFb; CHECK(hvFb.totalLength() == 60); // 6+53+1
DisHighAVolBusFbMessage hvaFb; CHECK(hvaFb.totalLength() == 32); // 6+25+1
DisHighBVolBusFbMessage hvbFb; CHECK(hvbFb.totalLength() == 41); // 6+34+1
DisLowBusFbMessage lvFb; CHECK(lvFb.totalLength() == 36); // 6+29+1
}
//--------------------------------------------------------------------------
// 10. 配电指令/反馈编解码往返(Sum8 校验,篡改检测)
static void testDisRoundTrip() {
// 指令:高压母线(含启动类字节)
disHighVolBusCmd hv;
hv.fuelCellCircuitBreaker = DIS_BREAKER_ON;
hv.powerLithiumBatteryCircuitBreaker = DIS_BREAKER_OFF;
hv.dcdc5Module = 1; hv.coolingSystem = 0;
DisHighVolBusCmdMessage hvMsg;
std::vector<uint8_t> hf = hvMsg.encode(hv);
CHECK(hf.size() == 21);
// Sum8 校验和位于帧尾 1 字节
unsigned sum = 0;
for (size_t i = 0; i + 1 < hf.size(); ++i) sum += hf[i];
CHECK(hf.back() == (sum & 0xFF));
disHighVolBusCmd hvOut;
CHECK(hvMsg.decode(hf, static_cast<void*>(&hvOut)));
CHECK(hvOut.fuelCellCircuitBreaker == DIS_BREAKER_ON);
CHECK(hvOut.powerLithiumBatteryCircuitBreaker == DIS_BREAKER_OFF);
CHECK(hvOut.dcdc5Module == 1 && hvOut.coolingSystem == 0);
// 篡改数据域 -> Sum8 失败
std::vector<uint8_t> bad = hf; bad[8] ^= 0xFF;
CHECK(!hvMsg.decode(bad, static_cast<void*>(&hvOut)));
// 指令:低压主母线 / 仪表汇流排
disLowMainBusCmd hvb; hvb.dcC1DCDistributionPanelCircuitBreaker = DIS_BREAKER_ON;
DisHighBVolBusCmdMessage hvbMsg;
std::vector<uint8_t> bf = hvbMsg.encode(hvb);
CHECK(bf.size() == 14);
disLowMainBusCmd hvbOut;
CHECK(hvbMsg.decode(bf, static_cast<void*>(&hvbOut)));
CHECK(hvbOut.dcC1DCDistributionPanelCircuitBreaker == DIS_BREAKER_ON);
disLowBusCmd lv; lv.unit2CircuitBreaker = DIS_BREAKER_ON;
DisLowBusCmdMessage lvMsg;
std::vector<uint8_t> lf = lvMsg.encode(lv);
CHECK(lf.size() == 15);
disLowBusCmd lvOut;
CHECK(lvMsg.decode(lf, static_cast<void*>(&lvOut)));
CHECK(lvOut.unit2CircuitBreaker == DIS_BREAKER_ON);
// 反馈:高压母线A(断路器+u16 电气量抽查)
disHighAVolBusState hva;
hva.bowFTDevice123CircuitBreaker = DIS_BREAKER_ON;
hva.busbarBVoltage = 0x1234; hva.bowFTDevice123Current = 567;
DisHighAVolBusFbMessage hvaMsg;
std::vector<uint8_t> af = hvaMsg.encode(hva);
CHECK(af.size() == 32);
disHighAVolBusState hvaOut;
CHECK(hvaMsg.decode(af, static_cast<void*>(&hvaOut)));
CHECK(hvaOut.bowFTDevice123CircuitBreaker == DIS_BREAKER_ON);
CHECK(hvaOut.busbarBVoltage == 0x1234);
CHECK(hvaOut.bowFTDevice123Current == 567);
CHECK(isDisCmdMessage(&hvMsg) && isDisFbMessage(&hvaMsg));
CHECK(!isDisMessage(nullptr));
}
//--------------------------------------------------------------------------
// 11. 注册表 (id+帧长) 二维分发:配电指令与 PM 指令共用 id 不串扰
static void testRegistryLengthDispatch() {
MessageRegistry reg;
registerPmMessages(reg); // 0x0001(45B) 0x0002(28B) 0x0003(12B) 0x0004(248B)
registerDisCmdMessages(reg); // 0x0001(21B) 0x0002(13B) 0x0003(14B) 0x0004(15B)
CHECK(reg.size() == 8);
// 精确长度命中
CHECK(reg.find(0x0001, 45)->name() == std::string("pm_control"));
CHECK(reg.find(0x0001, 21)->name() == std::string("dis_hv_cmd"));
CHECK(reg.find(0x0002, 28)->name() == std::string("pm_param_set"));
CHECK(reg.find(0x0002, 13)->name() == std::string("dis_hva_cmd"));
CHECK(reg.find(0x0004, 248)->name() == std::string("pm_status"));
CHECK(reg.find(0x0004, 15)->name() == std::string("dis_lv_cmd"));
// LinkManager 收帧分发:配电指令短帧 -> dis 消息;PM 长帧 -> pm 消息
LinkManager lm("test_pm", 0);
registerPmMessages(lm.registry());
registerDisCmdMessages(lm.registry());
std::string gotName;
lm.setOnMessage([&](Message* m, const std::vector<uint8_t>&) {
gotName = m ? m->name() : "";
});
disHighVolBusCmd hv; hv.fuelCellCircuitBreaker = DIS_BREAKER_ON;
DisHighVolBusCmdMessage hvMsg;
CHECK(lm.handleIncoming(hvMsg.encode(hv)));
CHECK(gotName == "dis_hv_cmd");
PmControlValue pc; pc.mode = 2;
PmControlMessage pcMsg;
CHECK(lm.handleIncoming(pcMsg.encode(pc)));
CHECK(gotName == "pm_control");
}
//--------------------------------------------------------------------------
// 12. RCU 链路注册表:配电反馈 + 真实CCU 状态报文可识别
static void testRcuRegistry() {
RcuLinkManager rcu(0, "127.0.0.1", 1);
// 反馈命中
disHighVolBusState hv; hv.powerBusVoltage = 0x0FA0;
DisHighVolBusFbMessage hvMsg;
std::vector<uint8_t> f = hvMsg.encode(hv);
std::string gotName;
rcu.setOnMessage([&](Message* m, const std::vector<uint8_t>&) {
gotName = m ? m->name() : "";
});
CHECK(rcu.handleIncoming(f));
CHECK(gotName == "dis_hv_fb");
// 真实CCU 状态报文(PM 状态消息复用)
PmStatusValue st; st.heartbeat = 9;
PmStatusMessage stMsg;
CHECK(rcu.handleIncoming(stMsg.encode(st)));
CHECK(gotName == "pm_status");
}
//--------------------------------------------------------------------------
int main() {
testFrameLengths();
testFcControlRoundTrip();
testFcStatusRoundTrip();
testPmControlRoundTrip();
testPmStatusRoundTrip();
testChecksum();
testLinkDispatch();
testDbStore();
testCanBmsDecode();
testDisFrameLengths();
testDisRoundTrip();
testRegistryLengthDispatch();
testRcuRegistry();
std::printf("\n==== pccuTest: %d passed, %d failed ====\n", g_pass, g_fail);
return g_fail == 0 ? 0 : 1;
}