pCCU 锂电池通信切换为 CAN 总线(BMS 协议,经 pCanBridge 透传)
build-test-deploy / ci (push) Failing after 1h9m57s

协议层:
- 新增 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 页面校验)。
This commit is contained in:
zjk
2026-08-31 21:42:26 +08:00
parent ee8819c610
commit 88bed850ee
23 changed files with 424 additions and 2115 deletions
+40 -239
View File
@@ -15,11 +15,10 @@
#include "../../src/pCCU/protocol/MessageRegistry.h"
#include "../../src/pCCU/protocol/FcProtocol.h"
#include "../../src/pCCU/protocol/PmProtocol.h"
#include "../../src/pCCU/protocol/BatProtocol.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/BatLinkManager.h"
#include "../../src/pCCU/store/DbStore.h"
using namespace ccu;
@@ -290,244 +289,50 @@ static void testDbStore() {
}
//--------------------------------------------------------------------------
// 9. 锂电池帧长度必须与协议文档一致(docs/锂电池协议20230324.docx)
static void testBatFrameLengths() {
BatSelfCheckMessage bsc;
BatControlMessage bcm;
BatStatusMessage bsm;
BatPackAlarmMessage bam;
// 9. CAN BMS 报文解码(docs/BMS_协议字段定义.xlsx,测试向量为实车抓包数据)
static void testCanBmsDecode() {
BmsStatusValue v;
CHECK(bsc.totalLength() == 12); // 自检指令:6+2+4
CHECK(bcm.totalLength() == 27); // 设备控制指令:6+17+4
CHECK(bsm.totalLength() == 90); // 电池组状态:6+80+4
CHECK(bam.totalLength() == 78); // 电池包报警:6+68+4
}
// 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);
//--------------------------------------------------------------------------
// 10. 锂电池自检/控制指令编解码往返
static void testBatControlRoundTrip() {
// 自检指令
BatSelfCheckValue sc;
sc.selfCheck = BAT_SC_START;
sc.selfCheckReset = BAT_SCR_RESET;
BatSelfCheckMessage scMsg;
std::vector<uint8_t> scFrame = scMsg.encode(sc);
CHECK(scFrame.size() == 12);
CHECK(scFrame[0] == 0x40 && scFrame[1] == 0x40);
CHECK(scFrame[2] == 0x00 && scFrame[3] == 0x00); // id 0x0000 小端
CHECK(scFrame[6] == 0x01 && scFrame[7] == 0x55);
BatSelfCheckValue scOut;
CHECK(scMsg.decode(scFrame, static_cast<void*>(&scOut)));
CHECK(scOut.selfCheck == BAT_SC_START && scOut.selfCheckReset == BAT_SCR_RESET);
// 文档示例帧: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);
// 设备控制指令
BatControlValue v;
v.year = 2026; v.month = 8; v.day = 30; v.hour = 11; v.minute = 20; v.second = 33;
v.ms10 = 4;
v.workCondition = BAT_WC_SEA_TRIAL;
v.busContactor = BAT_CTR_CLOSE;
v.posContactor = BAT_CTR_OPEN;
v.diodeContactor = BAT_CTR_CLOSE;
v.powerKw = 350;
v.reserved = 0x1234;
v.heartbeat = 66;
// 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);
BatControlMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
CHECK(frame.size() == 27);
CHECK(frame[2] == 0x01 && frame[3] == 0x00); // id 0x0001 小端
CHECK(frame[6 + 8] == BAT_WC_SEA_TRIAL); // 工况
CHECK(frame[6 + 9] == BAT_CTR_CLOSE); // 母线接触器
CHECK(frame[6 + 10] == BAT_CTR_OPEN); // 正极接触器
CHECK(frame[6 + 11] == BAT_CTR_CLOSE); // 二极管接触器
CHECK(frame[6 + 12] == 0x5E && frame[6 + 13] == 0x01); // 350 = 0x015E 小端
CHECK(frame[6 + 14] == 0x34 && frame[6 + 15] == 0x12); // 预留 0x1234 小端
CHECK(frame[6 + 16] == 66); // 心跳
// 总电流负数: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);
BatControlValue out;
CHECK(msg.decode(frame, static_cast<void*>(&out)));
CHECK(out.year == 2026 && out.month == 8 && out.day == 30);
CHECK(out.hour == 11 && out.minute == 20 && out.second == 33 && out.ms10 == 4);
CHECK(out.workCondition == BAT_WC_SEA_TRIAL);
CHECK(out.busContactor == BAT_CTR_CLOSE);
CHECK(out.posContactor == BAT_CTR_OPEN);
CHECK(out.diodeContactor == BAT_CTR_CLOSE);
CHECK(out.powerKw == 350);
CHECK(out.reserved == 0x1234);
CHECK(out.heartbeat == 66);
}
// 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);
//--------------------------------------------------------------------------
// 11. 锂电池电池组状态(0x0003)编解码往返(关键字段抽查)
static void testBatStatusRoundTrip() {
BatStatusValue v;
v.year = 2026; v.month = 3; v.day = 24; v.hour = 10; v.minute = 0; v.second = 1;
v.ms10 = 9;
v.workCondition = BAT_WC_WORKSHOP;
v.selfCheckState = BAT_SCST_OK;
v.powerCfgState = 0x22;
v.busContactorState = BAT_BUSST_CLOSED;
v.posDiodeState = 0x32; // 二极管闭合完成(3?) 高4位=3 断开完成; 低4位=2 正极闭合完成
v.emergencyState = 0x1C;
v.soc = 855; // 85.5%
v.energy = 1234; // 123.4 kWh
v.maxDischargePower = 5000; // 500.0 kW
v.maxChargePower = 2000;
v.currentAccessPower = 1500; // 150.0 kW
v.packOnlineFlag = 0x00FFFFFF;
v.packAccessFlag = 0x000000FF;
v.relayPosCharge = 0x12;
v.relayPreNeg = 0x11;
v.voltageLoad = 5400; // 54.00 V
v.voltagePack = 5500;
v.current = -200; // -10 A(充电)
v.insulationPos = 50; // 500 kΩ
v.insulationNeg = 60;
v.masterTemp1 = 1; v.masterTemp2 = 2;
v.packVoltageMid = 540; // 54.0 V
v.packVoltageMin = 535;
v.packVoltageMinNo = 3;
v.packVoltageMax = 545;
v.packVoltageMaxNo = 7;
v.packDeltaMax = 200; // 20.0 mV
v.packDeltaMaxNo = 5;
v.packDeltaMaxVoltage = 5450;
v.cellTempAvg = 65; // 25 ℃
v.cellTempMin = 55;
v.cellTempMinNo = 2;
v.cellTempMax = 70;
v.cellTempMaxNo = 4;
v.alarmFlag[0] = 0x42; v.alarmFlag[5] = 0xC1;
v.alarmPosFlag = 0x00FF00AA;
v.chargeStatus = 0x20;
v.reserved = 0x5A5A;
v.heartbeat = 88;
BatStatusMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
CHECK(frame.size() == 90);
CHECK(frame[2] == 0x03 && frame[3] == 0x00); // id 0x0003
CHECK(frame[4] == 0x5A && frame[5] == 0x00); // 域字节数=整包长度 90=0x005A
CHECK(frame[6 + 9] == BAT_SCST_OK); // 自检状态 offset9
CHECK(frame[6 + 11] == BAT_BUSST_CLOSED); // 母线接触器状态 offset11
CHECK(frame[6 + 12] == 0x32); // 正极/二极管 offset12
CHECK(frame[6 + 14] == 0x57 && frame[6 + 15] == 0x03); // SOC 855=0x0357 小端
CHECK(frame[6 + 40] == 0x38 && frame[6 + 41] == 0xFF); // current -200=0xFF38 小端
CHECK(frame[6 + 79] == 88); // 心跳 offset79
BatStatusValue out;
CHECK(msg.decode(frame, static_cast<void*>(&out)));
CHECK(out.year == 2026 && out.month == 3 && out.day == 24);
CHECK(out.selfCheckState == BAT_SCST_OK);
CHECK(out.powerCfgState == 0x22);
CHECK(out.busContactorState == BAT_BUSST_CLOSED);
CHECK(out.posDiodeState == 0x32);
CHECK(batPosStateOf(out.posDiodeState) == BAT_POSST_CLOSED);
CHECK(out.emergencyState == 0x1C);
CHECK(out.soc == 855 && out.energy == 1234);
CHECK(out.maxDischargePower == 5000 && out.maxChargePower == 2000);
CHECK(out.currentAccessPower == 1500);
CHECK(out.packOnlineFlag == 0x00FFFFFF && out.packAccessFlag == 0x000000FF);
CHECK(out.relayPosCharge == 0x12 && out.relayPreNeg == 0x11);
CHECK(out.voltageLoad == 5400 && out.voltagePack == 5500);
CHECK(out.current == -200);
CHECK(out.insulationPos == 50 && out.insulationNeg == 60);
CHECK(out.packVoltageMid == 540 && out.packVoltageMin == 535 && out.packVoltageMinNo == 3);
CHECK(out.packVoltageMax == 545 && out.packVoltageMaxNo == 7);
CHECK(out.packDeltaMax == 200 && out.packDeltaMaxNo == 5 && out.packDeltaMaxVoltage == 5450);
CHECK(out.cellTempAvg == 65 && out.cellTempMax == 70 && out.cellTempMaxNo == 4);
CHECK(out.alarmFlag[0] == 0x42 && out.alarmFlag[5] == 0xC1);
CHECK(out.alarmPosFlag == 0x00FF00AA);
CHECK(out.chargeStatus == 0x20 && out.reserved == 0x5A5A);
CHECK(out.heartbeat == 88);
}
//--------------------------------------------------------------------------
// 12. 锂电池电池包报警(0x0004)编解码往返
static void testBatPackAlarmRoundTrip() {
BatPackAlarmValue v;
v.year = 2026; v.month = 3; v.day = 24; v.hour = 9; v.minute = 1; v.second = 2;
v.ms10 = 5;
v.packNo = 12;
v.alarmFlag[0] = 0x45; v.alarmFlag[3] = 0x82;
v.soc = 700; // 70.0%
v.relayPosNeg = 0x12; // 正极接通 + 负极断开
v.voltageLoad = 530; // 53.0 V
v.voltagePack = 528;
v.current = 400; // 20 A(放电)
v.insulationPos = 100;
v.insulationNeg = 110;
v.tempMaxNo = 6; v.tempMax1 = 75;
v.tempMin1 = 50;
v.tempAvg = 62;
v.balanceState = 1;
v.voltMaxNo = 9; v.voltMax1 = 41; // 4.1 mV*10 => 0.1mV 精度
v.voltMin1 = 33;
v.voltMinNo = 11;
v.voltAvg = 38;
BatPackAlarmMessage msg;
std::vector<uint8_t> frame = msg.encode(v);
CHECK(frame.size() == 78);
CHECK(frame[2] == 0x04 && frame[3] == 0x00); // id 0x0004
CHECK(frame[6 + 8] == 12); // 电池包号
CHECK(frame[6 + 9] == 0x45); // 报警标识字1
CHECK(frame[6 + 17] == 0x12); // 正极/负极继电器
CHECK(frame[6 + 66] == 0x26 && frame[6 + 67] == 0x00); // 平均电压 38=0x0026 小端
BatPackAlarmValue out;
CHECK(msg.decode(frame, static_cast<void*>(&out)));
CHECK(out.packNo == 12);
CHECK(out.alarmFlag[0] == 0x45 && out.alarmFlag[3] == 0x82);
CHECK(out.soc == 700);
CHECK(out.relayPosNeg == 0x12);
CHECK(out.voltageLoad == 530 && out.voltagePack == 528);
CHECK(out.current == 400);
CHECK(out.insulationPos == 100 && out.insulationNeg == 110);
CHECK(out.tempMaxNo == 6 && out.tempMax1 == 75);
CHECK(out.tempMin1 == 50 && out.tempAvg == 62);
CHECK(out.balanceState == 1);
CHECK(out.voltMaxNo == 9 && out.voltMax1 == 41);
CHECK(out.voltMin1 == 33 && out.voltMinNo == 11);
CHECK(out.voltAvg == 38);
}
//--------------------------------------------------------------------------
// 13. 锂电池链路分发与注册表隔离
static void testBatLinkDispatch() {
MessageRegistry batReg;
registerBatMessages(batReg);
CHECK(batReg.size() == 4);
CHECK(batReg.find(0x0000) != nullptr);
CHECK(batReg.find(0x0001) != nullptr);
CHECK(batReg.find(0x0003) != nullptr);
CHECK(batReg.find(0x0004) != nullptr);
// 锂电池链路收帧:0x0003 状态解码
BatStatusValue v;
v.soc = 999; v.heartbeat = 21;
BatStatusMessage bsm;
std::vector<uint8_t> frame = bsm.encode(v);
BatLinkManager blm(BatRole::Dyn, 0, "192.168.100.137", 7000);
CHECK(blm.linkName() == "bat_dyn");
bool got = false;
blm.setOnMessage([&](Message* m, const std::vector<uint8_t>& f) {
if (m && m->id() == 0x0003) {
BatStatusValue out;
if (m->decode(f, static_cast<void*>(&out))) {
got = (out.soc == 999 && out.heartbeat == 21);
}
}
});
CHECK(blm.handleIncoming(frame));
CHECK(got);
// 校验失败帧拒收
std::vector<uint8_t> bad = frame;
bad[10] ^= 0xFF;
CHECK(!blm.handleIncoming(bad));
// 非 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));
}
//--------------------------------------------------------------------------
@@ -540,11 +345,7 @@ int main() {
testChecksum();
testLinkDispatch();
testDbStore();
testBatFrameLengths();
testBatControlRoundTrip();
testBatStatusRoundTrip();
testBatPackAlarmRoundTrip();
testBatLinkDispatch();
testCanBmsDecode();
std::printf("\n==== pccuTest: %d passed, %d failed ====\n", g_pass, g_fail);
return g_fail == 0 ? 0 : 1;