- CanBms: bcuCtrlCanId(0x10XX81FF) + buildBcuRelayCtrlData(02 00 总正 总负 FF*4) - SystemData: BcuCtrlCmd 下行队列(Web线程入队->Iterate/MOOS线程出队Notify) + 下发统计 - CCU: /api/bcu_ctrl(addr/action 或 pos/neg, 校验地址01h~36h) + Iterate 经 MOOS CAN_TX_0x* 下发 - pCanBridge 变双向透传: 订阅 CAN_TX_0x*,CanEndpoint::sendFrame(13字节CANET帧,TCP写,互斥保护) - 网页: 每BCU节点断路器闭合/断开按钮(confirm确认) + 下发统计卡片 - 测试: pccuTest 控制帧编码用例(183项);集成测试新增假CANET服务器验证完整下行链路(闭合/断开/非法地址)
561 lines
24 KiB
C++
561 lines
24 KiB
C++
//============================================================================
|
||
// 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");
|
||
}
|
||
|
||
//--------------------------------------------------------------------------
|
||
// 13. BCU 断路器控制帧(0x10XX81FF 下行)
|
||
static void testBcuCtrlFrame() {
|
||
// CAN ID:0x10 XX 81 FF,XX=节点地址
|
||
CHECK(bcuCtrlCanId(1) == 0x100181FFu);
|
||
CHECK(bcuCtrlCanId(54) == 0x103681FFu);
|
||
|
||
// 数据域:02 00 总正 总负 FF*4
|
||
uint8_t d[8];
|
||
buildBcuRelayCtrlData(1, 1, d);
|
||
CHECK(d[0] == 0x02 && d[1] == 0x00);
|
||
CHECK(d[2] == 0x01 && d[3] == 0x01);
|
||
CHECK(d[4] == 0xFF && d[5] == 0xFF && d[6] == 0xFF && d[7] == 0xFF);
|
||
buildBcuRelayCtrlData(0, 0, d);
|
||
CHECK(d[2] == 0x00 && d[3] == 0x00);
|
||
buildBcuRelayCtrlData(1, 0, d);
|
||
CHECK(d[2] == 0x01 && d[3] == 0x00);
|
||
}
|
||
|
||
//--------------------------------------------------------------------------
|
||
int main() {
|
||
testFrameLengths();
|
||
testFcControlRoundTrip();
|
||
testFcStatusRoundTrip();
|
||
testPmControlRoundTrip();
|
||
testPmStatusRoundTrip();
|
||
testChecksum();
|
||
testLinkDispatch();
|
||
testDbStore();
|
||
testCanBmsDecode();
|
||
testDisFrameLengths();
|
||
testDisRoundTrip();
|
||
testRegistryLengthDispatch();
|
||
testRcuRegistry();
|
||
testBcuCtrlFrame();
|
||
|
||
std::printf("\n==== pccuTest: %d passed, %d failed ====\n", g_pass, g_fail);
|
||
return g_fail == 0 ? 0 : 1;
|
||
} |