锂电池通信(docs/锂电池协议20230324.docx): - 协议层 BatProtocol:自检0x0000(2B)/设备控制0x0001(17B)/电池组状态0x0003(80B)/ 电池包报警0x0004(68B),uint32字节和校验、小端 - 链路层 BatLinkManager:动力/仪表锂电池独立UDP链路,本机监听与远端IP端口 均由.moos配置(dyn_bat_*/ins_bat_*/bat_work_condition) - SystemData/SnapshotBuilder/网页:电池数据按动力/仪表归类展示(运行状态/接触器 电气量/电池包电压温度/报警),收发帧落库SQLite,网页新增电池标签页 操作->指令下发(PowerCoordinator): - 操作接口:自检/上下电(母线接触器55H接通77H断开,含预充)/功率设定(0~500kW)/ 电池切换(低压->高压6步、高压->低压5步,按协议时序) - 步骤引擎:每电池独立步骤队列,下发->状态反馈确认->超时兜底;周期1s心跳控制帧 - PM操控指令insBatCmd/dynBatCmd/dynBatPower自动映射为电池操作 电源协调状态机骨架(CoordFsm, TinyFSM): - 事件Tick/PmControl/StepDone + 状态Normal/Switching/Fault,react均为TODO占位 - 当前未接线(无start/dispatch调用,运行行为与接入前一致),CoordFsm.hpp头部附接线指南 测试与部署: - pccuTest新增电池协议编解码/帧长/链路分发用例(175项通过);集成测试覆盖电池 心跳、启停映射(0x10->0x55/0x20->0x77)、状态落库;同步pccu_it.moos与ws_check - h100.moos与pCCU.moos增加锂电池链路配置;归档锂电池协议文档
551 lines
22 KiB
C++
551 lines
22 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/BatProtocol.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;
|
||
|
||
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. 锂电池帧长度必须与协议文档一致(docs/锂电池协议20230324.docx)
|
||
static void testBatFrameLengths() {
|
||
BatSelfCheckMessage bsc;
|
||
BatControlMessage bcm;
|
||
BatStatusMessage bsm;
|
||
BatPackAlarmMessage bam;
|
||
|
||
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
|
||
}
|
||
|
||
//--------------------------------------------------------------------------
|
||
// 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);
|
||
|
||
// 设备控制指令
|
||
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;
|
||
|
||
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); // 心跳
|
||
|
||
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);
|
||
}
|
||
|
||
//--------------------------------------------------------------------------
|
||
// 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));
|
||
}
|
||
|
||
//--------------------------------------------------------------------------
|
||
int main() {
|
||
testFrameLengths();
|
||
testFcControlRoundTrip();
|
||
testFcStatusRoundTrip();
|
||
testPmControlRoundTrip();
|
||
testPmStatusRoundTrip();
|
||
testChecksum();
|
||
testLinkDispatch();
|
||
testDbStore();
|
||
testBatFrameLengths();
|
||
testBatControlRoundTrip();
|
||
testBatStatusRoundTrip();
|
||
testBatPackAlarmRoundTrip();
|
||
testBatLinkDispatch();
|
||
|
||
std::printf("\n==== pccuTest: %d passed, %d failed ====\n", g_pass, g_fail);
|
||
return g_fail == 0 ? 0 : 1;
|
||
} |