From 67e732045acfbd4858445c549a7afb83cdf2191b Mon Sep 17 00:00:00 2001 From: zjk <1553836110@qq.com> Date: Tue, 1 Sep 2026 21:23:52 +0800 Subject: [PATCH] =?UTF-8?q?pCanBridge=20=E6=89=A9=E5=B1=95=E5=A4=9A?= =?UTF-8?q?=E9=80=9A=E9=81=93=E6=94=AF=E6=8C=81=EF=BC=9A=E6=AF=8F=E9=80=9A?= =?UTF-8?q?=E9=81=93=E7=8B=AC=E7=AB=8BTCP=E9=93=BE=E8=B7=AF=20+=20?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E8=A1=8C=E5=AF=B9=E7=A7=B0=E9=80=9A=E9=81=93?= =?UTF-8?q?=E6=A0=87=E6=B3=A8(m=5FsSrcAux)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pCanBridge: - 配置改为 channel = <通道名>,,<工作端口> 重复行(兼容旧 can_host/can_port 单通道回退), 每通道一条独立 CanEndpoint(TCP Client) + 接收线程,对应 CANET 的 CAN0=4001..CAN7=4008 - 上行 CAN_0x%08X 不变,m_sSrcAux=通道名区分来源通道 - 下行按 m_sSrcAux 路由到对应通道;回退规则:default_channel > 单通道 > 多通道告警丢弃 - buildReport 逐通道统计(连接/收发帧数/错误/重连) pCCU: - 新增 bcu_can_channel 配置(默认 CAN0),BCU 断路器下行 Post 前 SetSourceAux 指定目标通道, key 保持 CAN_TX_0x%08X(与上行通道标注对称,key 格式不变) 配置/文档:pCanBridge.moos、CanBridge_Info、pCCU.moos、missions/h100.moos、 test/pccu/pccu_it.moos 同步新格式。 验证:伪造 CANET Server 冒烟通过(多通道建链/上行解析发布/SQLite channel 列/ 下行通道路由与回退丢弃/srcAux 经 MOOSDB 往返); pccu 集成测试 PASSED(BCU 断路器下行 3 帧经新路由全部正确); pccuTest 183/183、PowerMangerTests 64/64 通过。 --- missions/h100.moos | 15 +- src/pCCU/CCU.cpp | 11 +- src/pCCU/CCU.h | 3 + src/pCCU/pCCU.moos | 4 + src/pCanBridge/CanBridge.cpp | 220 +++++++++++++++++++++++------- src/pCanBridge/CanBridge.h | 75 +++++++--- src/pCanBridge/CanBridge_Info.cpp | 27 ++-- src/pCanBridge/pCanBridge.moos | 36 ++--- test/pccu/pccu_it.moos | 5 +- 9 files changed, 282 insertions(+), 114 deletions(-) diff --git a/missions/h100.moos b/missions/h100.moos index 26875a2..cec1897 100644 --- a/missions/h100.moos +++ b/missions/h100.moos @@ -84,13 +84,14 @@ ProcessConfig = pCanBridge AppTick = 4 CommsTick = 4 - // CAN->MOOSDB 透传:USBCAN-8E-U/CANET(TCP Server 模式) - // 工作端口:CAN0=4001,CAN1=4002,...,CAN7=4008 - can_host = 192.168.0.222 - can_port = 4001 - // 通道名(写入消息 m_sSrcAux 辅助字段) - can_channel_name = CAN0 + // CAN<->MOOSDB 多通道透传:CANET(TCP Server 模式) + // 每通道一条独立 TCP 连接;工作端口:CAN0=4001,CAN1=4002,...,CAN7=4008 + channel = CAN0,192.168.0.222,4001 + // channel = CAN1,192.168.0.222,4002 - // CAN 帧 SQLite 落库路径(can_frame 表) + // CAN_TX 下行默认通道(pCCU 未指定 m_sSrcAux 时使用;可省略) + default_channel = CAN0 + + // CAN 帧 SQLite 落库路径(can_frame 表,channel 列区分通道) dbpath = /root/work/h100/data/pCanBridge_data.db } diff --git a/src/pCCU/CCU.cpp b/src/pCCU/CCU.cpp index 1fa1f5c..beaf124 100644 --- a/src/pCCU/CCU.cpp +++ b/src/pCCU/CCU.cpp @@ -58,6 +58,7 @@ bool CCU::OnStartUp() { 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"); @@ -574,7 +575,8 @@ std::string CCU::handleBcuCtrlApi(const std::string& query) { //--------------------------------------------------------- // sendPendingBcuCtrl:出队控制指令,编码 0x10XX81FF 经 MOOS 下发 -// 链路:MOOS CAN_TX_0x* -> pCanBridge 订阅 -> CanEndpoint(TCP) -> USBCAN +// 链路:MOOS CAN_TX_0x*(m_sSrcAux=目标通道) -> pCanBridge 订阅 +// -> 按通道路由 CanEndpoint(TCP) -> CANET -> CAN 总线 void CCU::sendPendingBcuCtrl() { if (!m_sysData) return; @@ -589,10 +591,13 @@ void CCU::sendPendingBcuCtrl() { // 二进制构造同 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", - canId, c.addr, c.pos, c.neg, ok ? "已投递MOOS" : "投递失败"); + 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" : "投递失败"); } } diff --git a/src/pCCU/CCU.h b/src/pCCU/CCU.h index 84b3380..8d8be83 100644 --- a/src/pCCU/CCU.h +++ b/src/pCCU/CCU.h @@ -85,6 +85,9 @@ private: int m_webPort = 8080; // 与 pPowerManger(8090)/pPowerMangerHost(18080) 错开 bool m_webEnable = true; + // BCU 断路器控制帧下发的目标 CAN 通道(经 pCanBridge 的 m_sSrcAux 路由) + std::string m_bcuCanChannel = "CAN0"; + // CAN BMS 日志节流(避免 60帧/s 刷爆日志) double m_lastBmsLog = 0; diff --git a/src/pCCU/pCCU.moos b/src/pCCU/pCCU.moos index a4cc6be..699eb08 100644 --- a/src/pCCU/pCCU.moos +++ b/src/pCCU/pCCU.moos @@ -45,6 +45,10 @@ ProcessConfig = pCCU // CAN 总线 BMS 协议(docs/BMS_协议字段定义.xlsx), // 经 pCanBridge 发布的 CAN_0x* 消息透传,无需额外配置。 + //======== BCU 断路器控制下发 ======== + // 下行 CAN_TX_0x* 消息的目标通道(经 m_sSrcAux 由 pCanBridge 路由) + bcu_can_channel = CAN0 + //======== 存储与日志 ======== // 数据库路径(SQLite) dbpath = pccu_data.db diff --git a/src/pCanBridge/CanBridge.cpp b/src/pCanBridge/CanBridge.cpp index 6a024a4..f564398 100644 --- a/src/pCanBridge/CanBridge.cpp +++ b/src/pCanBridge/CanBridge.cpp @@ -14,11 +14,14 @@ namespace canbridge { CanBridge::CanBridge() {} CanBridge::~CanBridge() { - if (m_endpoint) { - m_endpoint->stop(); - delete m_endpoint; - m_endpoint = nullptr; + for (size_t i = 0; i < m_channels.size(); ++i) { + if (m_channels[i].endpoint) { + m_channels[i].endpoint->stop(); + delete m_channels[i].endpoint; + m_channels[i].endpoint = nullptr; + } } + m_channels.clear(); if (m_db) { m_db->close(); delete m_db; @@ -27,7 +30,11 @@ CanBridge::~CanBridge() { } //--------------------------------------------------------- -// OnStartUp:读取配置并启动 CAN 链路 +// OnStartUp:读取配置并启动各通道 CAN 链路 +// +// 通道配置(可重复多行): +// channel = <通道名>,<转换器IP>,<工作端口> +// 兼容旧单通道配置:can_host / can_port / can_channel_name bool CanBridge::OnStartUp() { AppCastingMOOSApp::OnStartUp(); @@ -37,6 +44,11 @@ bool CanBridge::OnStartUp() { if (!m_MissionReader.GetConfiguration(GetAppName(), sParams)) reportConfigWarning("No config block found for " + GetAppName()); + // 旧单通道配置缓存(存在 can_host 时回退使用) + bool hasLegacy = false; + string legacyHost, legacyName = "CAN0"; + long legacyPort = 0; + STRING_LIST::iterator p; for (p = sParams.begin(); p != sParams.end(); p++) { string orig = *p; @@ -45,19 +57,55 @@ bool CanBridge::OnStartUp() { string value = stripBlankEnds(line); bool handled = true; - if (param == "can_host") m_canHost = value; - else if (param == "can_port") m_canPort = atol(value.c_str()); - else if (param == "can_channel_name") m_channelName = value; - else if (param == "dbpath") m_dbPath = value; + if (param == "channel") { + // channel = <通道名>,,<端口> + string name = stripBlankEnds(biteStringX(value, ',')); + string host = stripBlankEnds(biteStringX(value, ',')); + string port = stripBlankEnds(value); + ChannelCfg cfg; + cfg.name = name; + cfg.host = host; + cfg.port = atol(port.c_str()); + if (cfg.name.empty() || cfg.host.empty() || cfg.port <= 0) { + reportConfigWarning("bad channel line (want name,host,port): " + orig); + } else { + Channel ch; + ch.cfg = cfg; + m_channels.push_back(ch); + } + } + else if (param == "default_channel") m_defaultChannel = value; + else if (param == "dbpath") m_dbPath = value; + // 旧单通道配置(向后兼容) + else if (param == "can_host") { legacyHost = value; hasLegacy = true; } + else if (param == "can_port") { legacyPort = atol(value.c_str()); hasLegacy = true; } + else if (param == "can_channel_name") { legacyName = value; } else handled = false; if (!handled) reportUnhandledConfigWarning(orig); } + // 无 channel 行但有旧配置 -> 构造单通道 + if (m_channels.empty() && hasLegacy && + !legacyHost.empty() && legacyPort > 0) { + ChannelCfg cfg; + cfg.name = legacyName; + cfg.host = legacyHost; + cfg.port = legacyPort; + Channel ch; + ch.cfg = cfg; + m_channels.push_back(ch); + reportConfigWarning("legacy can_host/can_port config used; " + "prefer 'channel = name,host,port' lines"); + } + registerVariables(); - // 数据库(CAN 帧落库) + if (m_channels.empty()) + reportRunWarning("no CAN channel configured"); + + // 数据库(CAN 帧落库,channel 列区分通道) m_db = new CanDbStore(m_dbPath); if (!m_db->open()) { reportRunWarning("CAN db open failed: " + m_dbPath + " (" + m_db->lastError() + ")"); @@ -65,19 +113,26 @@ bool CanBridge::OnStartUp() { m_db = nullptr; } - // CAN 链路(TCP Client,收帧回调在接收线程中触发) - m_endpoint = new CanEndpoint(); - m_endpoint->configure(m_canHost, m_canPort); - m_endpoint->setFrameCallback( - [this](uint8_t fi, uint32_t id, const uint8_t* data, int dlc) { - handleFrame(fi, id, data, dlc); - }); - if (!m_endpoint->start()) { - reportRunWarning("CAN endpoint start failed: " + m_canHost); + // 各通道 CAN 链路(TCP Client,收帧回调在各自接收线程中触发) + for (size_t i = 0; i < m_channels.size(); ++i) { + Channel& ch = m_channels[i]; + ch.endpoint = new CanEndpoint(); + ch.endpoint->configure(ch.cfg.host, ch.cfg.port); + const string name = ch.cfg.name; // 按通道捕获 + ch.endpoint->setFrameCallback( + [this, name](uint8_t fi, uint32_t id, const uint8_t* data, int dlc) { + handleFrame(name, fi, id, data, dlc); + }); + if (!ch.endpoint->start()) + reportRunWarning("CAN endpoint start failed: " + ch.cfg.name + + " (" + ch.cfg.host + ")"); } - cout << "pCanBridge started: " << m_canHost << ":" << m_canPort - << " (" << m_channelName << "), db: " << m_dbPath << endl; + cout << "pCanBridge started with " << m_channels.size() << " channel(s):"; + for (size_t i = 0; i < m_channels.size(); ++i) + cout << " " << m_channels[i].cfg.name << "=" + << m_channels[i].cfg.host << ":" << m_channels[i].cfg.port; + cout << ", db: " << m_dbPath << endl; return true; } @@ -91,7 +146,7 @@ bool CanBridge::OnConnectToServer() { //--------------------------------------------------------- // registerVariables:订阅下行指令(CAN_TX_0x*,pCCU 发布的 -// BCU 断路器控制帧等)。 +// BCU 断路器控制帧等,通道经 m_sSrcAux 指定)。 void CanBridge::registerVariables() { AppCastingMOOSApp::RegisterVariables(); @@ -114,43 +169,93 @@ bool CanBridge::OnNewMail(MOOSMSG_LIST &NewMail) { } //--------------------------------------------------------- -// handleTxMessage:MOOS 下行消息 -> CANET 13 字节帧 -> USBCAN +// findChannel:按通道名查找运行通道 + +CanBridge::Channel* CanBridge::findChannel(const std::string& name) { + for (size_t i = 0; i < m_channels.size(); ++i) { + if (m_channels[i].cfg.name == name && m_channels[i].endpoint) + return &m_channels[i]; + } + return nullptr; +} + +//--------------------------------------------------------- +// resolveTxChannel:下行目标通道解析(回退规则) +// 1) srcAux 非空:命中通道则用之,否则告警丢弃 +// 2) srcAux 为空:default_channel 命中则用之 +// 3) 仍无:仅单通道时用该通道,多通道告警丢弃 + +CanBridge::Channel* CanBridge::resolveTxChannel(const std::string& srcAux) { + if (!srcAux.empty()) { + Channel* ch = findChannel(srcAux); + if (!ch) + reportRunWarning("CAN_TX unknown channel: '" + srcAux + "'"); + return ch; + } + + if (!m_defaultChannel.empty()) { + Channel* ch = findChannel(m_defaultChannel); + if (ch) return ch; + } + + if (m_channels.size() == 1) + return m_channels[0].endpoint ? &m_channels[0] : nullptr; + + reportRunWarning("CAN_TX no channel (empty srcAux) with " + + to_string(m_channels.size()) + " channels, dropped"); + return nullptr; +} + +//--------------------------------------------------------- +// handleTxMessage:MOOS 下行消息 -> CANET 13 字节帧 -> 对应通道 // -// m_sKey = "CAN_TX_0x%08X"(CAN ID) -// m_sVal = 二进制数据域(dlc<=8) +// m_sKey = "CAN_TX_0x%08X"(CAN ID) +// m_sVal = 二进制数据域(dlc<=8) +// m_sSrcAux = 目标通道名(空时按回退规则) // 帧信息:id>0x7FF 视为扩展帧(bit7=1),DLC=数据字节数。 void CanBridge::handleTxMessage(CMOOSMsg& msg) { - if (!m_endpoint) return; + ++m_txCount; uint32_t id = 0; // "CAN_TX_0x" 前缀长 9 字符(注意不是 8) int sr = std::sscanf(msg.m_sKey.c_str() + 9, "%x", &id); if (sr != 1) { + ++m_txDropCount; reportRunWarning("CAN_TX bad key: " + msg.m_sKey); return; } if (!msg.IsBinary() || msg.GetBinaryDataSize() <= 0 || msg.GetBinaryDataSize() > 8) { + ++m_txDropCount; reportRunWarning("CAN_TX bad data size: " + msg.m_sKey); return; } + Channel* ch = resolveTxChannel(msg.GetSourceAux()); + if (!ch) { + ++m_txDropCount; + return; + } + const unsigned char* d = msg.GetBinaryData(); - if (!d) return; + if (!d) { + ++m_txDropCount; + return; + } int dlc = static_cast(msg.GetBinaryDataSize()); uint8_t frameInfo = static_cast(((id > 0x7FF) ? 0x80 : 0x00) | dlc); - if (m_endpoint->sendFrame(frameInfo, id, d, dlc)) { - ++m_txCount; + if (ch->endpoint->sendFrame(frameInfo, id, d, dlc)) { char hex[32] = {0}; for (int i = 0; i < dlc && i < 8; ++i) std::snprintf(hex + i * 2, 3, "%02X", d[i]); - std::cout << "[CAN-TX] 0x" << std::hex << id << std::dec - << " dlc=" << dlc << " data=" << hex << std::endl; + std::cout << "[CAN-TX " << ch->cfg.name << "] 0x" << std::hex << id + << std::dec << " dlc=" << dlc << " data=" << hex << std::endl; } else { - reportRunWarning("CAN_TX send failed: " + msg.m_sKey + - (m_endpoint->isConnected() ? "" : " (未连接)")); + reportRunWarning("CAN_TX send failed on " + ch->cfg.name + ": " + + msg.m_sKey + + (ch->endpoint->isConnected() ? "" : " (未连接)")); } } @@ -166,12 +271,12 @@ bool CanBridge::Iterate() { //--------------------------------------------------------- // handleFrame:CAN 帧 -> CMOOSMsg(二进制) -> MOOSDB // -// 在 CanEndpoint 接收线程中调用;m_Comms::Post 线程安全。 +// 在各通道 CanEndpoint 接收线程中调用;m_Comms::Post 线程安全。 -void CanBridge::handleFrame(uint8_t frameInfo, uint32_t id, - const uint8_t* data, int dlc) { +void CanBridge::handleFrame(const std::string& channel, uint8_t frameInfo, + uint32_t id, const uint8_t* data, int dlc) { // 落库(WAL + 预处理语句,~60帧/秒无压力) - if (m_db) m_db->onFrame(m_channelName, frameInfo, id, data, dlc); + if (m_db) m_db->onFrame(channel, frameInfo, id, data, dlc); char key[32]; std::snprintf(key, sizeof(key), "CAN_0x%08X", id); @@ -180,32 +285,41 @@ void CanBridge::handleFrame(uint8_t frameInfo, uint32_t id, CMOOSMsg msg(MOOS_NOTIFY, key, static_cast(dlc), const_cast(data)); - msg.SetSourceAux(m_channelName); // 通道 -> m_sSrcAux + msg.SetSourceAux(channel); // 通道 -> m_sSrcAux msg.SetDoubleAux(static_cast(frameInfo)); // FF/RTR/DLC -> m_dfVal2 m_Comms.Post(msg); - ++m_notifyCount; } //--------------------------------------------------------- -// buildReport:AppCasting 报告 +// buildReport:AppCasting 报告(逐通道) bool CanBridge::buildReport() { m_msgs << "============================================" << "\n"; - m_msgs << "pCanBridge CAN<->MOOSDB 双向透传" << "\n"; + m_msgs << "pCanBridge CAN<->MOOSDB 多通道双向透传" << "\n"; m_msgs << "============================================" << "\n"; - if (m_endpoint) { - m_msgs << "目标: " << m_endpoint->host() << ":" - << m_endpoint->port() << " (" << m_channelName << ")" << "\n"; - m_msgs << "连接状态: " << (m_endpoint->isConnected() ? "已连接" : "断开") << "\n"; - m_msgs << "收帧数: " << m_endpoint->frameCount() << "\n"; - m_msgs << "发布数: " << m_notifyCount << "\n"; - m_msgs << "下行帧数: " << m_endpoint->txCount() << " (错误 " - << m_endpoint->txErrorCount() << ")" << "\n"; - m_msgs << "MOOS下发数: " << m_txCount << "\n"; - m_msgs << "错误数: " << m_endpoint->errorCount() << "\n"; - m_msgs << "重连次数: " << m_endpoint->reconnectCount() << "\n"; - m_msgs << "最近收帧: " << m_endpoint->lastFrameTime() << "\n"; + m_msgs << "通道数: " << m_channels.size() << "\n"; + if (!m_defaultChannel.empty()) + m_msgs << "默认通道: " << m_defaultChannel << "\n"; + m_msgs << "MOOS下发数: " << m_txCount << " (丢弃 " << m_txDropCount << ")\n"; + + for (size_t i = 0; i < m_channels.size(); ++i) { + const Channel& ch = m_channels[i]; + m_msgs << "--------------------------------------------" << "\n"; + m_msgs << "[" << ch.cfg.name << "] " << ch.cfg.host << ":" + << ch.cfg.port << "\n"; + if (ch.endpoint) { + m_msgs << " 连接状态: " + << (ch.endpoint->isConnected() ? "已连接" : "断开") << "\n"; + m_msgs << " 收帧数: " << ch.endpoint->frameCount() << "\n"; + m_msgs << " 下行帧数: " << ch.endpoint->txCount() + << " (错误 " << ch.endpoint->txErrorCount() << ")\n"; + m_msgs << " 错误数: " << ch.endpoint->errorCount() << "\n"; + m_msgs << " 重连次数: " << ch.endpoint->reconnectCount() << "\n"; + m_msgs << " 最近收帧: " << ch.endpoint->lastFrameTime() << "\n"; + } } + + m_msgs << "============================================" << "\n"; if (m_db) { m_msgs << "数据库: " << m_dbPath << (m_db->isOpen() ? " (已打开)" : " (未打开)") << "\n"; diff --git a/src/pCanBridge/CanBridge.h b/src/pCanBridge/CanBridge.h index 5d404f2..b18c5c4 100644 --- a/src/pCanBridge/CanBridge.h +++ b/src/pCanBridge/CanBridge.h @@ -6,14 +6,21 @@ #include "CanEndpoint.h" #include "CanDbStore.h" #include +#include +#include namespace canbridge { //============================================================================ -// CanBridge:CAN <-> MOOSDB 双向透传桥(MOOS 应用外壳)。 +// CanBridge:CAN <-> MOOSDB 多通道双向透传桥(MOOS 应用外壳)。 // -// 数据流(上行): -// USBCAN-8E-U (TCP Server, 默认 192.168.0.222:4001 = CAN0) +// 通道配置(.moos 中可重复多行): +// channel = <通道名>,<转换器IP>,<工作端口> +// 如 channel = CAN0,192.168.0.222,4001(CANET: CAN0=4001..CAN7=4008) +// 每通道一条独立 TCP 连接 + 独立接收线程。 +// +// 数据流(上行,每通道一条 TCP 连接): +// CANET 工作端口 (TCP Server) // -> CanEndpoint(TCP Client,13 字节切帧) // -> handleFrame() 解析 // -> Notify 到 MOOSDB @@ -21,16 +28,24 @@ namespace canbridge { // 数据流(下行,BCU 断路器/继电器控制等): // MOOS "CAN_TX_0x%08X"(二进制数据域,dlc<=8) // -> OnNewMail 解析 CAN ID 与数据 -// -> CanEndpoint::sendFrame(同一 TCP 连接写出 13 字节帧) -// -> USBCAN -> CAN 总线 +// -> 按消息 m_sSrcAux(目标通道名)路由到对应通道的 CanEndpoint +// -> CanEndpoint::sendFrame(该通道 TCP 连接写出 13 字节帧) +// -> CANET -> CAN 总线 // -// MOOS 消息映射: -// m_sKey = "CAN_0x%08X" CAN ID(完整 4 字节,标准/扩展帧同格式) -// m_sVal = 二进制 data(dlc 字节,MOOS_BINARY_STRING) -// m_sSrcAux = 通道名(默认 "CAN0",配置项 can_channel_name) -// m_dfVal2 = 原始帧信息字节 byte0(bit7 FF / bit6 RTR / bit3~0 DLC) +// MOOS 消息映射(上行/下行对称,通道一律走 m_sSrcAux): +// 上行 m_sKey = "CAN_0x%08X" CAN ID(完整 4 字节,标准/扩展帧同格式) +// 上行 m_sVal = 二进制 data(dlc 字节,MOOS_BINARY_STRING) +// 上行 m_sSrcAux = 通道名(如 "CAN0") +// 上行 m_dfVal2 = 原始帧信息字节 byte0(bit7 FF / bit6 RTR / bit3~0 DLC) +// 下行 m_sKey = "CAN_TX_0x%08X",m_sVal = 二进制 data,m_sSrcAux = 目标通道名 // m_nID 不使用(MOOS 内部消息序号) // +// 下行通道回退规则(m_sSrcAux 为空时): +// 1) 配置了 default_channel 且命中 -> 用该通道 +// 2) 仅配置了单通道 -> 用该通道 +// 3) 多通道且无 default_channel -> 告警丢弃 +// m_sSrcAux 非空但未命中任何通道 -> 告警丢弃 +// // 每帧(上行)同时落库 SQLite(can_frame 表,配置项 dbpath); // 下行帧仅日志记录(can_frame 表无方向列,保持旧库兼容)。 //============================================================================ @@ -49,23 +64,41 @@ protected: void registerVariables(); private: - // 收帧回调(CanEndpoint 接收线程调用) - void handleFrame(uint8_t frameInfo, uint32_t id, const uint8_t* data, int dlc); + // 通道配置(.moos 重复行 channel = name,host,port) + struct ChannelCfg { + std::string name; + std::string host; + long port = 0; + }; - // 下行指令处理(OnNewMail 调用):CAN_TX_0x%08X -> CanEndpoint 发送 + // 运行中的通道(配置 + 独立 TCP 连接) + struct Channel { + ChannelCfg cfg; + CanEndpoint* endpoint = nullptr; + }; + + // 收帧回调(CanEndpoint 接收线程调用) + void handleFrame(const std::string& channel, uint8_t frameInfo, + uint32_t id, const uint8_t* data, int dlc); + + // 下行指令处理(OnNewMail 调用):CAN_TX_0x%08X -> 按通道路由发送 void handleTxMessage(CMOOSMsg& msg); + // 按通道名查找运行通道;未命中返回 nullptr + Channel* findChannel(const std::string& name); + + // 下行目标通道解析(含回退规则);返回通道指针,nullptr 表示丢弃 + Channel* resolveTxChannel(const std::string& srcAux); + // 配置 - std::string m_canHost = "192.168.0.222"; - long m_canPort = 4001; - std::string m_channelName = "CAN0"; // 写入 m_sSrcAux - std::string m_dbPath = "pCanBridge_data.db"; // SQLite 路径(moos 配置项 dbpath) + std::vector m_channels; + std::string m_defaultChannel; // default_channel(可空) + std::string m_dbPath = "pCanBridge_data.db"; // SQLite 路径(moos 配置项 dbpath) // 组件 - CanEndpoint* m_endpoint = nullptr; - CanDbStore* m_db = nullptr; - std::atomic m_notifyCount{0}; - std::atomic m_txCount{0}; + CanDbStore* m_db = nullptr; + std::atomic m_txCount{0}; // MOOS 下行接收总数 + std::atomic m_txDropCount{0}; // 下行因通道解析失败丢弃数 }; } // namespace canbridge diff --git a/src/pCanBridge/CanBridge_Info.cpp b/src/pCanBridge/CanBridge_Info.cpp index 30a1af9..c6c07af 100644 --- a/src/pCanBridge/CanBridge_Info.cpp +++ b/src/pCanBridge/CanBridge_Info.cpp @@ -14,11 +14,12 @@ using namespace std; void showSynopsis() { blk("SYNOPSIS: "); blk("------------------------------------ "); - blk(" The pCanBridge application bridges CAN bus frames from a "); - blk(" USBCAN-8E-U / CANET Ethernet-CAN converter (TCP Server mode) "); - blk(" into the MOOSDB. Each received CAN frame is parsed from the "); + blk(" The pCanBridge application bridges CAN bus frames between "); + blk(" multi-channel CANET Ethernet-CAN converters (TCP Server mode) "); + blk(" and the MOOSDB. Each channel uses its own TCP connection "); + blk(" (CANET working port). Every CAN frame is parsed from the "); blk(" 13-byte CANET wire format and transparently published as a "); - blk(" binary MOOS message. "); + blk(" binary MOOS message (channel tagged via m_sSrcAux). "); blk(" "); } @@ -61,12 +62,13 @@ void showExampleConfigAndExit() { blk(" AppTick = 4 "); blk(" CommsTick = 4 "); blk(" "); - blk(" // USBCAN-8E-U/CANET 目标地址(TCP Server 模式) "); - blk(" can_host = 192.168.0.222 // 转换器 IP "); - blk(" can_port = 4001 // 工作端口: CAN0=4001 CAN1=4002.. "); + blk(" // CANET 通道(可重复多行):通道名,IP,工作端口 "); + blk(" // 工作端口: CAN0=4001 CAN1=4002 ... CAN7=4008 "); + blk(" channel = CAN0,192.168.0.222,4001 "); + blk(" channel = CAN1,192.168.0.222,4002 "); blk(" "); - blk(" // 通道名(写入消息的 m_sSrcAux 辅助字段) "); - blk(" can_channel_name = CAN0 "); + blk(" // CAN_TX 下行默认通道(m_sSrcAux 为空时使用,可省略) "); + blk(" default_channel = CAN0 "); blk(" "); blk(" // CAN 帧 SQLite 落库路径(can_frame 表) "); blk(" dbpath = pCanBridge_data.db "); @@ -85,14 +87,17 @@ void showInterfaceAndExit() { blk(" "); blk("SUBSCRIPTIONS: "); blk("------------------------------------ "); - blk(" (none: publish-only application) "); + blk(" CAN_TX_0x%08X = 下行 CAN 帧(每帧一条二进制消息) "); + blk(" m_sVal: 二进制 data(dlc<=8 字节, MOOS_BINARY_STRING) "); + blk(" m_sSrcAux: 目标通道名(如 CAN0;空时按回退规则: "); + blk(" default_channel > 单通道 > 多通道告警丢弃) "); blk(" "); blk("PUBLICATIONS: "); blk("------------------------------------ "); blk(" CAN_0x%08X = CAN 帧(每帧一条二进制消息) "); blk(" 变量名: CAN ID 完整 4 字节十六进制, 如 CAN_0x10010001 "); blk(" m_sVal: 二进制 data(dlc 字节, MOOS_BINARY_STRING) "); - blk(" m_sSrcAux: 通道名(默认 CAN0) "); + blk(" m_sSrcAux: 通道名(如 CAN0) "); blk(" m_dfVal2: 原始帧信息字节 byte0 "); blk(" bit7=FF(1扩展/0标准) bit6=RTR(1远程/0数据) "); blk(" bit3~0=DLC "); diff --git a/src/pCanBridge/pCanBridge.moos b/src/pCanBridge/pCanBridge.moos index 74fafc7..8f44a19 100644 --- a/src/pCanBridge/pCanBridge.moos +++ b/src/pCanBridge/pCanBridge.moos @@ -1,13 +1,14 @@ //============================================================================ -// pCanBridge 配置示例 +// pCanBridge 配置示例(多通道) // -// 拓扑(双向透传): -// 上行:USBCAN-8E-U(CANET TCP Server,192.168.0.222:4001=CAN0) -// -> pCanBridge(TCP Client) -> MOOS "CAN_0x%08X"(二进制 data) -// 下行:MOOS "CAN_TX_0x%08X"(二进制 data,dlc<=8,如 BCU 断路器控制 -// 帧 0x10XX81FF,由 pCCU 发布)-> pCanBridge -> USBCAN -> CAN 总线 +// 拓扑(双向透传,每通道一条独立 TCP 连接): +// 上行:CANET 工作端口(TCP Server)-> pCanBridge(TCP Client) +// -> MOOS "CAN_0x%08X"(二进制 data,m_sSrcAux=通道名) +// 下行:MOOS "CAN_TX_0x%08X"(二进制 data,m_sSrcAux=目标通道, +// 如 BCU 断路器控制帧 0x10XX81FF,由 pCCU 发布) +// -> pCanBridge 按通道路由 -> CANET -> CAN 总线 // 变量名 CAN_0x%08X = CAN ID;m_sVal = 二进制 data; -// m_sSrcAux = 通道名;m_dfVal2 = 原始帧信息字节(FF/RTR/DLC) +// m_sSrcAux = 通道名;上行 m_dfVal2 = 原始帧信息字节(FF/RTR/DLC) //============================================================================ ProcessConfig = pCanBridge @@ -15,17 +16,20 @@ ProcessConfig = pCanBridge AppTick = 4 CommsTick = 4 - //======== 目标 USBCAN-8E-U 地址(TCP Server 模式) ======== - // 转换器 IP - can_host = 192.168.0.222 - // 工作端口:CAN0=4001,CAN1=4002,...,CAN7=4008 - can_port = 4001 + //======== 通道配置(可重复多行) ======== + // 格式:channel = <通道名>,<转换器IP>,<工作端口> + // CANET 工作端口:CAN0=4001,CAN1=4002,...,CAN7=4008 + channel = CAN0,192.168.0.222,4001 + // channel = CAN1,192.168.0.222,4002 + // channel = CAN2,192.168.0.222,4003 + // ... - //======== 通道标识 ======== - // 写入消息 m_sSrcAux 辅助字段,用于下游区分通道 - can_channel_name = CAN0 + //======== 下行默认通道(可空) ======== + // CAN_TX 消息 m_sSrcAux 为空时:配置了本项则用它; + // 未配置且仅单通道则用该通道;多通道则告警丢弃。 + default_channel = CAN0 //======== 存储 ======== - // CAN 帧 SQLite 落库路径(can_frame 表) + // CAN 帧 SQLite 落库路径(can_frame 表,channel 列区分通道) dbpath = pCanBridge_data.db } diff --git a/test/pccu/pccu_it.moos b/test/pccu/pccu_it.moos index 8e5de0d..95ffa23 100644 --- a/test/pccu/pccu_it.moos +++ b/test/pccu/pccu_it.moos @@ -38,8 +38,7 @@ ProcessConfig = pCanBridge CommsTick = 4 // 测试用 CANET TCP Server(本脚本模拟,捕获 pCanBridge 下行帧) - can_host = 127.0.0.1 - can_port = 16006 - can_channel_name = CAN0 + channel = CAN0,127.0.0.1,16006 + default_channel = CAN0 dbpath = /tmp/pccu_it_can.db } \ No newline at end of file