- 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)
298 lines
12 KiB
C++
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
|