Files
H100PowerManger/src/pCCU/store/DbStore.cpp
T
zjk 5b8f0b1421 锂电池改用 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)
2026-09-01 12:58:33 +08:00

298 lines
12 KiB
C++

#include "DbStore.h"
#include "../protocol/Message.h"
#include "sqlite3.h"
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <ctime>
namespace ccu {
namespace {
std::string toHex(const std::vector<uint8_t>& data) {
static const char* hex = "0123456789ABCDEF";
std::string s;
s.reserve(data.size() * 2);
for (uint8_t b : data) {
s += hex[(b >> 4) & 0xF];
s += hex[b & 0xF];
}
return s;
}
} // namespace
DbStore::DbStore(const std::string& dbPath) : m_dbPath(dbPath) {}
DbStore::~DbStore() {
close();
}
bool DbStore::exec(const char* sql) {
char* err = nullptr;
int rc = sqlite3_exec(m_db, sql, nullptr, nullptr, &err);
if (rc != SQLITE_OK) {
m_lastError = err ? err : "sqlite error";
sqlite3_free(err);
return false;
}
return true;
}
bool DbStore::open() {
if (m_db) return true;
if (sqlite3_open(m_dbPath.c_str(), &m_db) != SQLITE_OK) {
m_lastError = sqlite3_errmsg(m_db);
sqlite3_close(m_db);
m_db = nullptr;
return false;
}
sqlite3_busy_timeout(m_db, 2000);
exec("PRAGMA journal_mode=WAL;");
exec("PRAGMA synchronous=NORMAL;");
// comm_log:收发全部原始帧
if (!exec(
"CREATE TABLE IF NOT EXISTS comm_log ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" time REAL NOT NULL,"
" link TEXT NOT NULL,"
" direction INTEGER NOT NULL," // 0=收 1=发
" msg_id INTEGER NOT NULL,"
" msg_name TEXT NOT NULL,"
" length INTEGER NOT NULL,"
" checksum_ok INTEGER NOT NULL,"
" hex TEXT NOT NULL"
");")) return false;
exec("CREATE INDEX IF NOT EXISTS idx_comm_log_time ON comm_log(time);");
// 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() {
const char* sql =
"INSERT INTO comm_log (time, link, direction, msg_id, msg_name, length, checksum_ok, hex) "
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);";
if (sqlite3_prepare_v2(m_db, sql, -1, &m_stmtInsert, nullptr) != SQLITE_OK) {
m_lastError = sqlite3_errmsg(m_db);
return false;
}
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;
}
}
void DbStore::onRawFrame(int direction, uint16_t msgId, const std::string& link,
const std::vector<uint8_t>& data, bool checksumOk) {
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_db || !m_stmtInsert) return;
// 消息名:尝试从链路名无法直接获取,这里用 msgId 的十六进制作为名称的一部分
char name[32];
std::snprintf(name, sizeof(name), "msg_0x%04X", msgId);
std::string hex = toHex(data);
sqlite3_reset(m_stmtInsert);
sqlite3_clear_bindings(m_stmtInsert);
// 时间:使用当前 unix 时间(秒),供网页展示
sqlite3_bind_double(m_stmtInsert, 1, static_cast<double>(::time(nullptr)));
sqlite3_bind_text(m_stmtInsert, 2, link.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int(m_stmtInsert, 3, direction);
sqlite3_bind_int(m_stmtInsert, 4, msgId);
sqlite3_bind_text(m_stmtInsert, 5, name, -1, SQLITE_TRANSIENT);
sqlite3_bind_int(m_stmtInsert, 6, static_cast<int>(data.size()));
sqlite3_bind_int(m_stmtInsert, 7, checksumOk ? 1 : 0);
sqlite3_bind_text(m_stmtInsert, 8, hex.c_str(), -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(m_stmtInsert);
if (rc != SQLITE_DONE) {
m_lastError = sqlite3_errmsg(m_db);
}
}
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);
std::vector<CommLogRow> rows;
if (!m_db) return rows;
if (limit <= 0) limit = 100;
std::string sql = "SELECT id, time, link, direction, msg_id, msg_name, length, checksum_ok, hex "
"FROM comm_log WHERE 1=1";
if (!link.empty()) {
sql += " AND link='" + link + "'";
}
if (direction >= 0) {
sql += " AND direction=" + std::to_string(direction);
}
if (msgId != 0) {
char buf[16];
std::snprintf(buf, sizeof(buf), "%u", msgId);
sql += " AND msg_id=" + std::string(buf);
}
sql += " ORDER BY id DESC LIMIT " + std::to_string(limit) + ";";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
m_lastError = sqlite3_errmsg(m_db);
return rows;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
CommLogRow r;
r.id = sqlite3_column_int64(stmt, 0);
r.time = sqlite3_column_double(stmt, 1);
r.link = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
r.direction = sqlite3_column_int(stmt, 3);
r.msgId = static_cast<uint16_t>(sqlite3_column_int(stmt, 4));
r.msgName = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 5));
r.length = sqlite3_column_int(stmt, 6);
r.checksumOk = sqlite3_column_int(stmt, 7);
r.hex = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 8));
rows.push_back(std::move(r));
}
sqlite3_finalize(stmt);
return rows;
}
long long DbStore::count() 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 comm_log;", -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;
}
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