锂电池改用 BCU-MBMS CAN 协议按节点解析 + bcu_node 解析数据落库 + 板卡对时脚本

- pCCU/CanBms:按《04KT38电池BCU-MBMS通信(CAN)定义》重写解码,
  BmsStatus 单状态模型改为 BcuNodeStatus 多节点模型(0x10XX00YY,
  节点地址 01~36h),支持 0x0000 电压/电流/SOC/告警码、0x0001 单体
  电压、0x0002 单体温度、0x0003 继电器、0x0006 绝缘/端口电压、
  0x0010 告警位(附录1 中文码表)六类报文
- 平均单体温度偏移修正:协议文档 BYTE5 写"偏移0"有误,实测固件与
  最高/最低一致均带 -40℃ 偏移(实车 0x10020002 原始 68/69 减 40 后
  为 28/29℃,落在最低28~最高30区间内,按文档直读则超出物理范围)
- pCCU/DbStore:新增 bcu_node 解析数据表,锂电池 BMS 报文每帧落一行
  节点合成状态(原 comm_log 仅原始帧,BMS/CAN 帧此前不落 pCCU 库),
  buildReport 增加 BMS 记录数
- 快照/网页:BCU 按节点分组展示(告警位解析中文含义、数据 age),
  PM 状态报文锂电池/应急电池卡片
- pPowerManger:新增 iport 本地输入端口配置(UDP bind 延迟到
  OnStartUp 读取配置后执行,保证 iport/ccuhost/ccuport 生效),
  各 mission 文件补充注释
- test:CAN BCU 解码用例按新协议/新偏移更新(实车抓包 + 文档示例值),
  133 项全部通过
- docs:删除旧 BMS 协议(xlsx/20230324docx),归档 04KT38 BCU-MBMS
  CAN 定义、配电控制器通信协议 20260824、配电系统 CAN 通讯协议
- scripts:新增 sync-board-time.sh,223 板卡(RK3588)时间校正
  (本机为基准 + RTT 折半补偿对时,尽力写 RTC,支持 status/--time)
This commit is contained in:
zjk
2026-09-01 12:58:33 +08:00
parent 88bed850ee
commit 5b8f0b1421
27 changed files with 894 additions and 170 deletions
+121 -1
View File
@@ -65,7 +65,45 @@ bool DbStore::open() {
");")) return false;
exec("CREATE INDEX IF NOT EXISTS idx_comm_log_time ON comm_log(time);");
return prepareInsert();
// bcu_node:锂电池 BCU 节点解析数据(每收到一帧 BMS 报文落一行,
// 记录该节点合并后的最新状态;BMS/CAN 协议字段固定故用结构化表)
if (!exec(
"CREATE TABLE IF NOT EXISTS bcu_node ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" time REAL NOT NULL,"
" addr INTEGER NOT NULL," // BCU 节点地址 1~54
" func INTEGER NOT NULL," // 触发本行的报文功能码
" total_voltage REAL," // 累加电压 V
" current REAL," // 回路电流 A(放电为正)
" soc REAL," // SOC %
" alarm_code INTEGER," // 告警码(原始值)
" self_check INTEGER," // 自检状态(原始值)
" max_cell_voltage REAL," // 最高单体电压 V
" max_cell_voltage_no INTEGER,"
" min_cell_voltage REAL," // 最低单体电压 V
" min_cell_voltage_no INTEGER,"
" avg_cell_voltage REAL," // 单体平均电压 V
" max_cell_temp REAL," // 最高单体温度 ℃
" max_cell_temp_no INTEGER,"
" min_cell_temp REAL," // 最低单体温度 ℃
" min_cell_temp_no INTEGER,"
" avg_cell_temp REAL," // 单体平均温度 ℃
" pos_relay INTEGER," // 正极继电器 1闭合/0断开
" neg_relay INTEGER," // 负极继电器 1闭合/0断开
" pos_insulation REAL," // 正极绝缘阻抗 kΩ
" neg_insulation REAL," // 负极绝缘阻抗 kΩ
" port_voltage REAL," // 正负极端口电压 V
" pos_relay_outer_voltage REAL," // 正极继电器外侧电压 V
" alarm_bits TEXT," // 告警位 byte0~5 十六进制
" alarm_extra_byte6 INTEGER,"
" alarm_extra_byte7 INTEGER,"
" rx_mask INTEGER," // 收到过的功能码位图
" last_rx_time REAL" // 节点最近收帧时刻(MOOSTime)
");")) return false;
exec("CREATE INDEX IF NOT EXISTS idx_bcu_node_time ON bcu_node(time);");
exec("CREATE INDEX IF NOT EXISTS idx_bcu_node_addr ON bcu_node(addr);");
return prepareInsert() && prepareBcuInsert();
}
bool DbStore::prepareInsert() {
@@ -79,12 +117,33 @@ bool DbStore::prepareInsert() {
return true;
}
bool DbStore::prepareBcuInsert() {
const char* sql =
"INSERT INTO bcu_node (time, addr, func, total_voltage, current, soc, "
"alarm_code, self_check, "
"max_cell_voltage, max_cell_voltage_no, min_cell_voltage, min_cell_voltage_no, avg_cell_voltage, "
"max_cell_temp, max_cell_temp_no, min_cell_temp, min_cell_temp_no, avg_cell_temp, "
"pos_relay, neg_relay, pos_insulation, neg_insulation, port_voltage, pos_relay_outer_voltage, "
"alarm_bits, alarm_extra_byte6, alarm_extra_byte7, rx_mask, last_rx_time) "
"VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,"
"?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29);";
if (sqlite3_prepare_v2(m_db, sql, -1, &m_stmtBcuInsert, nullptr) != SQLITE_OK) {
m_lastError = sqlite3_errmsg(m_db);
return false;
}
return true;
}
void DbStore::close() {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_stmtInsert) {
sqlite3_finalize(m_stmtInsert);
m_stmtInsert = nullptr;
}
if (m_stmtBcuInsert) {
sqlite3_finalize(m_stmtBcuInsert);
m_stmtBcuInsert = nullptr;
}
if (m_db) {
sqlite3_close(m_db);
m_db = nullptr;
@@ -119,6 +178,55 @@ void DbStore::onRawFrame(int direction, uint16_t msgId, const std::string& link,
}
}
void DbStore::insertBcuNode(uint8_t addr, uint8_t func, const BcuNodeStatus& v) {
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_db || !m_stmtBcuInsert) return;
char bits[16];
std::snprintf(bits, sizeof(bits), "%02X%02X%02X%02X%02X%02X",
v.alarmBits[0], v.alarmBits[1], v.alarmBits[2],
v.alarmBits[3], v.alarmBits[4], v.alarmBits[5]);
sqlite3_reset(m_stmtBcuInsert);
sqlite3_clear_bindings(m_stmtBcuInsert);
// 时间:与 comm_log 一致用 unix 时间(秒)
int i = 1;
sqlite3_bind_double(m_stmtBcuInsert, i++, static_cast<double>(::time(nullptr)));
sqlite3_bind_int(m_stmtBcuInsert, i++, addr);
sqlite3_bind_int(m_stmtBcuInsert, i++, func);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.totalVoltage);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.current);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.soc);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.alarmCode);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.selfCheck);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.maxCellVoltage);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.maxCellVoltageNo);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.minCellVoltage);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.minCellVoltageNo);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.avgCellVoltage);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.maxCellTemp);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.maxCellTempNo);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.minCellTemp);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.minCellTempNo);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.avgCellTemp);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.posRelay);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.negRelay);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.posInsulationKohm);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.negInsulationKohm);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.portVoltage);
sqlite3_bind_double(m_stmtBcuInsert, i++, v.posRelayOuterVoltage);
sqlite3_bind_text(m_stmtBcuInsert, i++, bits, -1, SQLITE_TRANSIENT);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.alarmExtraByte6);
sqlite3_bind_int(m_stmtBcuInsert, i++, v.alarmExtraByte7);
sqlite3_bind_int64(m_stmtBcuInsert, i++, static_cast<sqlite3_int64>(v.rxMask));
sqlite3_bind_double(m_stmtBcuInsert, i++, v.lastRxTime);
int rc = sqlite3_step(m_stmtBcuInsert);
if (rc != SQLITE_DONE) {
m_lastError = sqlite3_errmsg(m_db);
}
}
std::vector<CommLogRow> DbStore::queryRecent(const std::string& link, int direction,
uint16_t msgId, int limit) {
std::lock_guard<std::mutex> lock(m_mutex);
@@ -175,4 +283,16 @@ long long DbStore::count() const {
return n;
}
long long DbStore::countBcu() const {
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_db) return 0;
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(m_db, "SELECT COUNT(*) FROM bcu_node;", -1, &stmt, nullptr) != SQLITE_OK)
return 0;
long long n = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) n = sqlite3_column_int64(stmt, 0);
sqlite3_finalize(stmt);
return n;
}
} // namespace ccu