pCanBridge 扩展多通道支持:每通道独立TCP链路 + 上下行对称通道标注(m_sSrcAux)

pCanBridge:
- 配置改为 channel = <通道名>,<IP>,<工作端口> 重复行(兼容旧 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 通过。
This commit is contained in:
zjk
2026-09-01 21:23:52 +08:00
parent 90caacf9a2
commit 67e732045a
9 changed files with 282 additions and 114 deletions
+8 -7
View File
@@ -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
}
+8 -3
View File
@@ -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<unsigned int>(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" : "投递失败");
}
}
+3
View File
@@ -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;
+4
View File
@@ -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
+167 -53
View File
@@ -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 = <通道名>,<IP>,<端口>
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<int>(msg.GetBinaryDataSize());
uint8_t frameInfo = static_cast<uint8_t>(((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<unsigned int>(dlc),
const_cast<uint8_t*>(data));
msg.SetSourceAux(m_channelName); // 通道 -> m_sSrcAux
msg.SetSourceAux(channel); // 通道 -> m_sSrcAux
msg.SetDoubleAux(static_cast<double>(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";
+54 -21
View File
@@ -6,14 +6,21 @@
#include "CanEndpoint.h"
#include "CanDbStore.h"
#include <atomic>
#include <string>
#include <vector>
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<Channel> 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<unsigned long> m_notifyCount{0};
std::atomic<unsigned long> m_txCount{0};
CanDbStore* m_db = nullptr;
std::atomic<unsigned long> m_txCount{0}; // MOOS 下行接收总数
std::atomic<unsigned long> m_txDropCount{0}; // 下行因通道解析失败丢弃数
};
} // namespace canbridge
+16 -11
View File
@@ -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 ");
+20 -16
View File
@@ -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
}
+2 -3
View File
@@ -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
}