#include "CCU.h" #include "MBUtils.h" #include "../pPowerManger/logc/loguru.hpp" #include "protocol/Frame.h" #include "protocol/CanBms.h" #include #include #include using namespace ccu; using namespace std; //--------------------------------------------------------- // Constructor / Destructor CCU::CCU() {} CCU::~CCU() { if (m_web) { m_web->stop(); delete m_web; m_web = nullptr; } if (m_coord) { delete m_coord; m_coord = nullptr; } if (m_bridge) { delete m_bridge; m_bridge = nullptr; } if (m_fcLink) { m_fcLink->stop(); delete m_fcLink; m_fcLink = nullptr; } if (m_pmLink) { m_pmLink->stop(); delete m_pmLink; m_pmLink = nullptr; } if (m_rcuLink) { m_rcuLink->stop(); delete m_rcuLink; m_rcuLink = nullptr; } if (m_db) { m_db->close(); delete m_db; m_db = nullptr; } if (m_snap) { delete m_snap; m_snap = nullptr; } if (m_sysData) { delete m_sysData; m_sysData = nullptr; } } //--------------------------------------------------------- // OnStartUp:读取配置并初始化各组件 bool CCU::OnStartUp() { AppCastingMOOSApp::OnStartUp(); STRING_LIST sParams; m_MissionReader.EnableVerbatimQuoting(false); if (!m_MissionReader.GetConfiguration(GetAppName(), sParams)) reportConfigWarning("No config block found for " + GetAppName()); STRING_LIST::iterator p; for (p = sParams.begin(); p != sParams.end(); p++) { string orig = *p; string line = *p; string param = stripBlankEnds(tolower(biteStringX(line, '='))); string value = stripBlankEnds(line); bool handled = true; if (param == "fc_local_port") m_fcLocalPort = atol(value.c_str()); else if (param == "fc_remote_ip") m_fcHost = value; else if (param == "fc_remote_port") m_fcPort = atol(value.c_str()); else if (param == "pm_local_port") m_pmLocalPort = atol(value.c_str()); else if (param == "pm_remote_ip") m_pmHost = value; else if (param == "pm_remote_port") m_pmPort = atol(value.c_str()); else if (param == "rcu_enable") m_rcuEnable = (tolower(value) == "true" || value == "1"); else if (param == "rcu_local_port") m_rcuLocalPort = atol(value.c_str()); else if (param == "rcu_remote_ip") m_rcuHost = value; else if (param == "rcu_remote_port") m_rcuPort = atol(value.c_str()); else if (param == "dbpath") m_dbPath = value; else if (param == "logpath") m_logPath = value; else if (param == "bcu_can_channel") m_bcuCanChannel = value; else if (param == "web_port") m_webPort = atoi(value.c_str()); else if (param == "web_enable") { m_webEnable = (tolower(value) == "true" || value == "1"); } else handled = false; if (!handled) reportUnhandledConfigWarning(orig); } registerVariables(); // 日志文件 if (m_logPath.empty()) m_logPath = "pCCU.log"; loguru::add_file(m_logPath.c_str(), loguru::Append, loguru::Verbosity_MAX); LOG_F(INFO, "pCCU log path: %s", m_logPath.c_str()); // 数据库 m_db = new DbStore(m_dbPath); if (!m_db->open()) { LOG_F(ERROR, "open database failed: %s", m_db->lastError().c_str()); delete m_db; m_db = nullptr; } else { LOG_F(INFO, "database path: %s", m_dbPath.c_str()); } // 系统数据快照 m_sysData = new SystemData(); // 配电桥接器(须在链路 start 前完成依赖注入,避免接收线程空指针) m_bridge = new DisBridge(); // FC 链路 m_fcLink = new FcLinkManager(m_fcLocalPort, m_fcHost, m_fcPort); m_fcLink->setLogSink(m_db); m_fcLink->setOnMessage([this](Message* msg, const std::vector& frame) { handleFcMessage(msg, frame); }); m_fcLink->setOnRawFrame([](int dir, const std::vector&, bool) { // 原始帧已通过 LogSink 落库;如需 Web 实时原始报文可在此扩展 }); // PM 链路 m_pmLink = new PmLinkManager(m_pmLocalPort, m_pmHost, m_pmPort); m_pmLink->setLogSink(m_db); m_pmLink->setOnMessage([this](Message* msg, const std::vector& frame) { // 配电指令(0x0001~0x0004,Sum8 短帧)优先桥接转发真实CCU; // 注册表按帧总长区分配电指令与 PM 操控/参数指令(共用 id) if (m_bridge && m_bridge->onPmFrame(msg, frame)) return; handlePmMessage(msg, frame); }); m_pmLink->setOnRawFrame([](int, const std::vector&, bool) { }); // 真实 CCU 链路(配电桥接,rcu_enable 开启时创建) if (m_rcuEnable) { m_rcuLink = new RcuLinkManager(m_rcuLocalPort, m_rcuHost, m_rcuPort); m_rcuLink->setLogSink(m_db); m_rcuLink->setOnMessage([this](Message* msg, const std::vector& frame) { handleRcuMessage(msg, frame); }); m_rcuLink->setOnRawFrame([](int, const std::vector&, bool) { }); } // 注入桥接依赖(m_rcuLink 可为空:未启用桥接时相关入口自动失效) m_bridge->setup(m_pmLink, m_rcuLink, m_sysData); // 启动链路 if (!m_fcLink->start()) LOG_F(ERROR, "FC link start failed"); if (!m_pmLink->start()) LOG_F(ERROR, "PM link start failed"); if (m_rcuLink && !m_rcuLink->start()) LOG_F(ERROR, "RCU link start failed on port %ld", m_rcuLocalPort); // 锂电池:CAN 总线(BMS 协议),数据经 pCanBridge 以 CAN_0x* 消息透传, // 在 OnNewMail 中订阅解码(见 registerVariables/handleCanMessage)。 // 电源协调器(FC 联动 + 协调算法占位) m_coord = new PowerCoordinator(); m_coord->setup(m_sysData, m_fcLink, m_pmLink); // 快照构建器(链路就绪后创建) m_snap = new SnapshotBuilder(m_sysData, m_fcLink, m_pmLink, m_rcuLink, m_db); // 网页 if (m_webEnable) { m_web = new WebServer(); m_web->setOnOpen([this]() { return buildSnapshot(); }); m_web->setApiHandler([this](const std::string& uri, const std::string& query) { return handleApi(uri, query); }); if (!m_web->start(m_webPort)) { LOG_F(ERROR, "web server start failed on port %d", m_webPort); delete m_web; m_web = nullptr; } } if (m_rcuLink) { LOG_F(INFO, "pCCU started: fc_link local=%ld -> %s:%ld, pm_link local=%ld -> %s:%ld, " "rcu_link local=%ld -> %s:%ld (配电桥接), " "battery=BMS/CAN via pCanBridge(CAN_0x*)", m_fcLocalPort, m_fcHost.c_str(), m_fcPort, m_pmLocalPort, m_pmHost.c_str(), m_pmPort, m_rcuLocalPort, m_rcuHost.c_str(), m_rcuPort); } else { LOG_F(INFO, "pCCU started: fc_link local=%ld -> %s:%ld, pm_link local=%ld -> %s:%ld, " "rcu_link disabled, " "battery=BMS/CAN via pCanBridge(CAN_0x*)", m_fcLocalPort, m_fcHost.c_str(), m_fcPort, m_pmLocalPort, m_pmHost.c_str(), m_pmPort); } return true; } //--------------------------------------------------------- // OnConnectToServer bool CCU::OnConnectToServer() { registerVariables(); return true; } //--------------------------------------------------------- // registerVariables void CCU::registerVariables() { AppCastingMOOSApp::RegisterVariables(); // 订阅 pCanBridge 透传的全部 CAN 帧(变量名 CAN_0x%08X,二进制数据域)。 // 注意:MOOS 通配订阅必须用三参数重载(变量模式 + 来源模式), // 单参数 Register("CAN_0x*") 不会做通配匹配。 Register("CAN_0x*", "*", 0); // 运行状态变量 Register("CCU_FC_LINK_STATE", 0); Register("CCU_PM_LINK_STATE", 0); Register("CCU_RCU_LINK_STATE", 0); } //--------------------------------------------------------- // OnNewMail bool CCU::OnNewMail(MOOSMSG_LIST &NewMail) { AppCastingMOOSApp::OnNewMail(NewMail); MOOSMSG_LIST::iterator p; for (p = NewMail.begin(); p != NewMail.end(); p++) { CMOOSMsg &msg = *p; // pCanBridge 透传的 CAN 帧(变量名前缀 CAN_0x) if (msg.m_sKey.rfind("CAN_0x", 0) == 0) { handleCanMessage(msg); continue; } } return true; } //--------------------------------------------------------- // handleCanMessage:解码 pCanBridge 透传的 CAN 帧 // // 消息格式(pCanBridge 约定): // m_sKey = "CAN_0x%08X"(CAN ID,扩展帧) // m_sVal = 二进制数据域(dlc 字节) // m_dfVal2 = 原始帧信息字节(bit7 FF / bit6 RTR / bit3~0 DLC) void CCU::handleCanMessage(CMOOSMsg& msg) { if (!m_sysData) return; // 从变量名解析 CAN ID(跳过前缀 "CAN_0x" 共 6 字符) uint32_t canId = 0; if (std::sscanf(msg.m_sKey.c_str() + 6, "%x", &canId) != 1) return; m_sysData->incCanFrameCount(); // 远程帧无数据域,直接跳过 if (msg.IsBinary() && msg.GetBinaryDataSize() > 0) { unsigned int n = msg.GetBinaryDataSize(); unsigned char* d = msg.GetBinaryData(); if (!d) return; // 合并语义:先取该节点当前快照 -> 解码部分更新 -> 写回 // (每个功能码报文只更新自己的字段,不能整体覆盖) uint8_t addr = bcuAddrOf(canId); if (!addr) return; BcuNodeStatus node = m_sysData->bcuNode(addr); if (decodeCanBcuFrame(canId, d, static_cast(n), addr, node)) { node.valid = true; node.lastRxTime = MOOSTime(false); m_sysData->updateBcuNode(addr, node); // 锂电池解析数据落库(bcu_node 表,每帧一行,含节点最新合成状态) if (m_db) m_db->insertBcuNode(addr, bcuFuncOf(canId), node); // 节流日志:1s 一条,便于联调观察 double now = MOOSTime(); if (now - m_lastBmsLog >= 1.0) { m_lastBmsLog = now; LOG_F(INFO, "[BMS/CAN] 节点%d u=%.1fV i=%.1fA soc=%.1f%% alarm=%02X self=%d vMax=%.3fV", addr, node.totalVoltage, node.current, node.soc, node.alarmCode, node.selfCheck, node.maxCellVoltage); } } } } //--------------------------------------------------------- // Iterate:周期任务(AppTick 次/秒) bool CCU::Iterate() { AppCastingMOOSApp::Iterate(); double now = MOOSTime(); // 周期(1Hz)整合 FC 状态并发送 PM 状态报文给控制主机 if (now - m_lastStatusTx >= 1.0) { sendPmStatus(); m_lastStatusTx = now; } // BCU 断路器控制指令下发(网页 -> 队列 -> MOOS CAN_TX_*) sendPendingBcuCtrl(); // 电源协调器:协调算法占位 if (m_coord) m_coord->tick(now); // 周期(1Hz)推送网页快照 if (m_web) { static double lastWebPush = 0; if (now - lastWebPush >= 1.0) { lastWebPush = now; m_web->broadcast(buildSnapshot()); } } AppCastingMOOSApp::PostReport(); return true; } //--------------------------------------------------------- // handleFcMessage:处理 FC 链路收到的消息 void CCU::handleFcMessage(Message* msg, const std::vector& frame) { if (!msg) return; switch (msg->id()) { case 0x0002: { // FC 状态反馈 FcStatusValue v; if (msg->decode(frame, static_cast(&v))) { m_sysData->updateFcStatus(v); LOG_F(INFO, "[FC] status: mode=%d status=%d fault_level=%d", v.fc_mode, v.fc_status, v.fc_fault_level); } else { LOG_F(ERROR, "[FC] status decode failed"); } break; } default: LOG_F(WARNING, "[FC] unhandled msg id 0x%04X", msg->id()); break; } } //--------------------------------------------------------- // handlePmMessage:处理 PM 链路收到的消息 void CCU::handlePmMessage(Message* msg, const std::vector& frame) { if (!msg) return; // 配电指令已在入口由 DisBridge 处理(见 OnStartUp 的 onMessage 接线), // 到达此处的均为 PM 协议消息(Sum32 校验) switch (msg->id()) { case 0x0001: { // PM 操控指令 -> 转发给 FC PmControlValue c; if (msg->decode(frame, static_cast(&c))) { m_sysData->updatePmControl(c); LOG_F(INFO, "[PM] control: mode=%d cmd=%d power=%d", c.mode, c.cmd, c.outputPower); // 映射并转发到 FC(0827协议:姿态仅数据1有效,数据2字节预留填0) FcControlValue f; f.mode = c.mode; f.cmd = c.cmd; f.outputPower = c.outputPower; f.pitch1 = c.pitch; f.roll1 = c.roll; f.emergencyAllow = c.emergencyAllow; f.depth = c.depth; f.supplyCmd = c.supplyCmd; f.reservedCmd5 = c.reservedCmd5; f.reservedCmd6 = c.reservedCmd6; f.heartbeat = c.heartbeat; m_sysData->updateFcControl(f); m_fcLink->sendMessage(0x0001, &f); LOG_F(INFO, "[FC] forward control cmd=%d power=%d", f.cmd, f.outputPower); // 锂电池启停/功率指令:BMS CAN 协议未定义控制报文,暂不支持 if (c.insBatCmd != 0 || c.dynBatCmd != 0 || c.dynBatPower != 0) { LOG_F(WARNING, "[PM] battery cmd ignored (insBat=0x%02X dynBat=0x%02X dynPwr=%u): " "BMS CAN 协议未定义控制报文", c.insBatCmd, c.dynBatCmd, c.dynBatPower); } } else { LOG_F(ERROR, "[PM] control decode failed"); } break; } case 0x0002: { // PM 参数设定 -> 回参数反馈 PmParamSetValue p; if (msg->decode(frame, static_cast(&p))) { m_sysData->updatePmParamSet(p); LOG_F(INFO, "[PM] param set received"); // 当前阶段直接回成功 PmParamSetFbValue fb; fb.flag = 0x10; // 设定成功 fb.failCode = 0; m_sysData->updatePmParamFb(fb); m_pmLink->sendMessage(0x0003, &fb); LOG_F(INFO, "[PM] param set feedback sent"); } else { LOG_F(ERROR, "[PM] param set decode failed"); } break; } default: LOG_F(WARNING, "[PM] unhandled msg id 0x%04X", msg->id()); break; } } //--------------------------------------------------------- // handleRcuMessage:处理真实 CCU 链路收到的消息(配电桥接) // // 配电反馈(0x0005~0x0008)与真实CCU状态报文(0x0004)由 DisBridge // 处理(原帧转发 pPowerManger / 仅解码显示);其余(如配电指令 // 回显)仅记录,不转发。 void CCU::handleRcuMessage(Message* msg, const std::vector& frame) { if (!msg) return; if (m_bridge && m_bridge->onRcuFrame(msg, frame)) return; LOG_F(WARNING, "[RCU] unhandled msg id 0x%04X, len=%zu", msg->id(), frame.size()); } //--------------------------------------------------------- // sendPmStatus:整合最新 FC 状态,编码 PM 状态报文发送 void CCU::sendPmStatus() { if (!m_pmLink || !m_sysData) return; const FcStatusValue& fc = m_sysData->fcStatus(); PmStatusValue s; s.fc_mode = fc.fc_mode; s.fc_status = fc.fc_status; s.fault_level_1 = fc.fault_level_1; s.fault_level_2 = fc.fault_level_2; s.fault_level_3 = fc.fault_level_3; s.fault_level_4 = fc.fault_level_4; s.total_generation_time = fc.total_generation_time; s.fc_fault_level = fc.fc_fault_level; s.output_power_limit = 0; // FC 状态未含此字段,置 0 s.generation_power = fc.generation_power; s.hydrogen_capacity = fc.hydrogen_capacity; s.liquid_oxygen_capacity = fc.liquid_oxygen_capacity; s.fc1_min_cell_voltage = fc.fc1_min_cell_voltage; s.fc1_min_cell_pos = fc.fc1_min_cell_pos; s.fc1_avg_cell_voltage = fc.fc1_avg_cell_voltage; s.fc2_min_cell_voltage = fc.fc2_min_cell_voltage; s.fc2_min_cell_pos = fc.fc2_min_cell_pos; s.fc2_avg_cell_voltage = fc.fc2_avg_cell_voltage; s.palladium_temp = fc.palladium_temp; s.buffer_tank_pressure = fc.buffer_tank_pressure; s.flue_total_emission = fc.flue_total_emission; s.flue_pressure = fc.flue_pressure; s.reactor_pressure = fc.reactor_pressure; s.electric_valve_open = fc.electric_valve_open; s.main_pipe_pressure = 0; s.aux_pipe_pressure = 0; s.dcdc1_in_voltage = fc.dcdc1_in_voltage; s.dcdc1_in_current = fc.dcdc1_in_current; s.dcdc2_in_voltage = fc.dcdc2_in_voltage; s.dcdc2_in_current = fc.dcdc2_in_current; s.dcdc_out_voltage = fc.dcdc_out_voltage; s.dcdc_out_current = fc.dcdc_out_current; s.dcdc_ctrl_voltage = fc.dcdc_ctrl_voltage; s.dcdc_aux_voltage = fc.dcdc_aux_voltage; s.methanol_total_use = fc.methanol_total_use; s.methanol_feed = fc.methanol_feed; s.oxygen_side_water_level = fc.oxygen_side_water_level; s.hydrogen_side_water_level = fc.hydrogen_side_water_level; s.ballast_water_level = fc.ballast_water_level; s.exhaust_inlet_pressure = fc.exhaust_inlet_pressure; s.exhaust_outlet_pressure = fc.exhaust_outlet_pressure; s.exhaust_run_freq = fc.exhaust_run_freq; s.exhaust_inlet_temp = fc.exhaust_inlet_temp; s.exhaust_outlet_temp = fc.exhaust_outlet_temp; s.exhaust_water_in_pressure = fc.exhaust_water_in_pressure; s.exhaust_water_out_pressure = fc.exhaust_water_out_pressure; s.tank_lo2_pressure = fc.tank_lo2_pressure; s.tank_co2_pressure = fc.tank_co2_pressure; s.tank_lo2_level = fc.tank_lo2_level; s.alloy_h2_flow = fc.alloy_h2_flow; s.fc_h2_flow = fc.fc_h2_flow; s.fc_o2_flow = fc.fc_o2_flow; s.emergency_float_depth = fc.emergency_float_depth; s.emergency_float_time = fc.emergency_float_time; s.cabin_pressure1 = fc.cabin_pressure1; s.cabin_pressure2 = fc.cabin_pressure2; s.cabin_temp1 = fc.cabin_temp1; s.cabin_temp2 = fc.cabin_temp2; s.cabin_humidity1 = fc.cabin_humidity1; s.cabin_humidity2 = fc.cabin_humidity2; s.h2_concentration1 = fc.h2_concentration1; s.h2_concentration2 = fc.h2_concentration2; s.h2_concentration3 = fc.h2_concentration3; s.o2_concentration1 = fc.o2_concentration1; s.o2_concentration2 = fc.o2_concentration2; s.ch3oh_concentration1 = fc.ch3oh_concentration1; s.ch3oh_concentration2 = fc.ch3oh_concentration2; s.flame_detector1 = fc.flame_detector1; s.flame_detector2 = fc.flame_detector2; // 通信心跳自增 s.heartbeat = m_pmHeartbeat++; s.emergency_cmd = fc.emergency_cmd; m_sysData->updatePmStatus(s); m_pmLink->sendMessage(0x0004, &s); LOG_F(INFO, "[PM] status sent: mode=%d status=%d heartbeat=%d", s.fc_mode, s.fc_status, s.heartbeat); } //--------------------------------------------------------- // buildSnapshot std::string CCU::buildSnapshot() { if (m_snap) return m_snap->build(); return "{}"; } //--------------------------------------------------------- // handleApi:/api/logs、/api/bcu_ctrl std::string CCU::handleApi(const std::string& uri, const std::string& query) { if (uri == "/api/logs") { if (m_snap) return m_snap->buildLogs(50); return "[]"; } if (uri == "/api/bcu_ctrl") { return handleBcuCtrlApi(query); } return ""; } //--------------------------------------------------------- // handleBcuCtrlApi:BCU 断路器控制指令下发 // // GET /api/bcu_ctrl?addr=5&action=1 action: 1 闭合 / 0 断开(总正+总负) // GET /api/bcu_ctrl?addr=5&pos=1&neg=0 也可单独指定总正/总负 // Web 线程不可直接 Notify,指令入队后由 Iterate(MOOS 线程)下发。 std::string CCU::handleBcuCtrlApi(const std::string& query) { if (!m_sysData) return "{\"ok\":false,\"error\":\"not ready\"}"; // 简易 query 解析(k=v&k=v) auto getParam = [&query](const char* key, int& out) -> bool { std::string k = std::string(key) + "="; size_t p = query.find(k); if (p == std::string::npos) return false; size_t e = query.find('&', p); std::string v = query.substr(p + k.size(), (e == std::string::npos) ? std::string::npos : e - p - k.size()); out = atoi(v.c_str()); return true; }; int addr = 0, action = -1, pos = -1, neg = -1; getParam("addr", addr); getParam("action", action); getParam("pos", pos); getParam("neg", neg); if (addr < kBcuAddrMin || addr > kBcuAddrMax) return "{\"ok\":false,\"error\":\"bad addr\"}"; if (pos < 0) pos = action; // 未单独指定时跟随 action if (neg < 0) neg = action; if (action < 0 && (pos < 0 || neg < 0)) return "{\"ok\":false,\"error\":\"missing action\"}"; if (action > 1 || pos > 1 || neg > 1) return "{\"ok\":false,\"error\":\"bad value\"}"; BcuCtrlCmd c; c.addr = static_cast(addr); c.pos = static_cast(pos); c.neg = static_cast(neg); if (!m_sysData->pushBcuCtrlCmd(c)) return "{\"ok\":false,\"error\":\"queue full\"}"; LOG_F(INFO, "[BCU/CAN] 断路器指令入队: 节点%d 总正=%d 总负=%d", addr, pos, neg); char buf[96]; std::snprintf(buf, sizeof(buf), "{\"ok\":true,\"addr\":%d,\"pos\":%d,\"neg\":%d}", addr, pos, neg); return buf; } //--------------------------------------------------------- // sendPendingBcuCtrl:出队控制指令,编码 0x10XX81FF 经 MOOS 下发 // 链路:MOOS CAN_TX_0x*(m_sSrcAux=目标通道) -> pCanBridge 订阅 // -> 按通道路由 CanEndpoint(TCP) -> CANET -> CAN 总线 void CCU::sendPendingBcuCtrl() { if (!m_sysData) return; BcuCtrlCmd c; while (m_sysData->popBcuCtrlCmd(c)) { uint8_t data[8]; buildBcuRelayCtrlData(c.pos, c.neg, data); uint32_t canId = bcuCtrlCanId(c.addr); char key[32]; std::snprintf(key, sizeof(key), "CAN_TX_0x%08X", canId); // 二进制构造同 pCanBridge 上行发布(MOOS_BINARY_STRING) CMOOSMsg msg(MOOS_NOTIFY, key, static_cast(sizeof(data)), data); // 目标通道经 m_sSrcAux 指定(与上行 CAN_0x* 的通道标注对称) msg.SetSourceAux(m_bcuCanChannel); bool ok = m_Comms.Post(msg); m_sysData->noteBcuCtrlSent(c, ok); LOG_F(INFO, "[BCU/CAN] 断路器指令 0x%08X 节点%d 总正=%d 总负=%d 通道=%s %s", canId, c.addr, c.pos, c.neg, m_bcuCanChannel.c_str(), ok ? "已投递MOOS" : "投递失败"); } } //--------------------------------------------------------- // buildReport bool CCU::buildReport() { m_msgs << "============================================" << "\n"; m_msgs << "pCCU 复合管控器" << "\n"; m_msgs << "============================================" << "\n"; if (m_fcLink) { m_msgs << "FC 链路 local:" << m_fcLink->localPort() << " rx:" << m_fcLink->rxCount() << " tx:" << m_fcLink->txCount() << " err:" << m_fcLink->errorCount() << "\n"; } if (m_pmLink) { m_msgs << "PM 链路 local:" << m_pmLink->localPort() << " rx:" << m_pmLink->rxCount() << " tx:" << m_pmLink->txCount() << " err:" << m_pmLink->errorCount() << "\n"; } if (m_rcuLink) { m_msgs << "RCU 链路 local:" << m_rcuLink->localPort() << " -> " << m_rcuLink->rcuHost() << ":" << m_rcuLink->rcuPort() << " rx:" << m_rcuLink->rxCount() << " tx:" << m_rcuLink->txCount() << " err:" << m_rcuLink->errorCount() << "\n"; if (m_sysData) { const RealCcuState& st = m_sysData->realCcu(); m_msgs << "配电桥接: 反馈转发 " << st.fwdFbOk << " 成功 / " << st.fwdFbErr << " 失败, 指令转发 " << st.fwdCmdOk << " 成功 / " << st.fwdCmdErr << " 失败\n"; } } if (m_sysData) { m_msgs << "锂电池: BMS/CAN (经 pCanBridge CAN_0x* 订阅)\n"; m_msgs << "CAN 帧接收计数:" << m_sysData->canFrameCount() << "\n"; m_msgs << "BMS 报文接收计数:" << m_sysData->bmsStatusCount() << "\n"; m_msgs << "FC 状态接收次数:" << m_sysData->fcStatusCount() << "\n"; m_msgs << "PM 指令接收次数:" << m_sysData->pmControlCount() << "\n"; const BcuCtrlStat& bc = m_sysData->bcuCtrlStat(); m_msgs << "断路器指令下发:" << bc.sentCount << " 成功 / " << bc.errCount << " 失败 (最近: 节点" << static_cast(bc.last.addr) << " 总正=" << static_cast(bc.last.pos) << " 总负=" << static_cast(bc.last.neg) << ")\n"; } if (m_db) { m_msgs << "数据库记录数:" << m_db->count() << "\n"; m_msgs << "数据库BMS记录数:" << m_db->countBcu() << "\n"; } return true; }