新增复合管控器(pCCU) + 惯导数据解析 + 设备控制页增强 + 0824协议文档归档

- 新增 pCCU 复合管控器程序(src/pCCU):FC/PM 双链路(UDP)、协议编解码器
  (FcProtocol/PmProtocol/Frame/Message/MessageRegistry)、快照与落库(DbStore)、
  内置 WebServer;挂入 src/CMakeLists.txt 与板卡 mission(h100.moos)
- 惯导/深度数据:新增 uDevice_insData 消息解析,接入 UpperCommManager,
  pPowerMangerHost 注册监视,UpmsgTest 覆盖
- pPowerMangerHost 设备控制页增强(web/index.html)
- 部署/运维脚本改为 4 个 systemd 服务(moosdb/pPowerManger/pPowerMangerHost/pCCU):
  deploy/clean-data/fetch-data 同步适配
- 协议文档:归档 0824 控制主机与复合管控器通信协议 xlsx 及关联 docx,移除旧 md
- 测试:新增 pccu 单元/集成测试与 disSysTest Python 配电仿真
- 清理:移除未使用的 CCU 状态调试打印 coutMsg;.gitignore 补充编译产物/临时文件忽略
This commit is contained in:
zjk
2026-08-26 22:43:10 +08:00
parent e0488d513b
commit 54c32a180a
79 changed files with 8731 additions and 1398 deletions
+178
View File
@@ -0,0 +1,178 @@
#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);");
return prepareInsert();
}
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;
}
void DbStore::close() {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_stmtInsert) {
sqlite3_finalize(m_stmtInsert);
m_stmtInsert = 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);
}
}
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;
}
} // namespace ccu