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:
+8
-7
@@ -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
@@ -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" : "投递失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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 ");
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user