Files
H100PowerManger/src/pCanBridge/CanBridge.cpp
T
zjk 67e732045a 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 通过。
2026-09-01 21:23:52 +08:00

332 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "CanBridge.h"
#include "CanBridge_Info.h"
#include "MBUtils.h"
#include "MOOS/libMOOS/Comms/MOOSMsg.h"
#include <cstdio>
using namespace std;
namespace canbridge {
//---------------------------------------------------------
// Constructor / Destructor
CanBridge::CanBridge() {}
CanBridge::~CanBridge() {
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;
m_db = nullptr;
}
}
//---------------------------------------------------------
// OnStartUp:读取配置并启动各通道 CAN 链路
//
// 通道配置(可重复多行):
// channel = <通道名>,<转换器IP>,<工作端口>
// 兼容旧单通道配置:can_host / can_port / can_channel_name
bool CanBridge::OnStartUp() {
AppCastingMOOSApp::OnStartUp();
STRING_LIST sParams;
m_MissionReader.EnableVerbatimQuoting(false);
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;
string line = *p;
string param = stripBlankEnds(tolower(biteStringX(line, '=')));
string value = stripBlankEnds(line);
bool handled = true;
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();
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() + ")");
delete m_db;
m_db = nullptr;
}
// 各通道 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 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;
}
//---------------------------------------------------------
// OnConnectToServer
bool CanBridge::OnConnectToServer() {
registerVariables();
return true;
}
//---------------------------------------------------------
// registerVariables:订阅下行指令(CAN_TX_0x*,pCCU 发布的
// BCU 断路器控制帧等,通道经 m_sSrcAux 指定)。
void CanBridge::registerVariables() {
AppCastingMOOSApp::RegisterVariables();
// 通配订阅必须用三参数重载(变量模式 + 来源模式)
Register("CAN_TX_0x*", "*", 0);
}
//---------------------------------------------------------
// OnNewMail:下行指令 CAN_TX_0x%08X(二进制数据域)
bool CanBridge::OnNewMail(MOOSMSG_LIST &NewMail) {
AppCastingMOOSApp::OnNewMail(NewMail);
MOOSMSG_LIST::iterator p;
for (p = NewMail.begin(); p != NewMail.end(); p++) {
CMOOSMsg &msg = *p;
if (msg.m_sKey.rfind("CAN_TX_0x", 0) == 0)
handleTxMessage(msg);
}
return true;
}
//---------------------------------------------------------
// 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_sSrcAux = 目标通道名(空时按回退规则)
// 帧信息:id>0x7FF 视为扩展帧(bit7=1),DLC=数据字节数。
void CanBridge::handleTxMessage(CMOOSMsg& msg) {
++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) {
++m_txDropCount;
return;
}
int dlc = static_cast<int>(msg.GetBinaryDataSize());
uint8_t frameInfo = static_cast<uint8_t>(((id > 0x7FF) ? 0x80 : 0x00) | dlc);
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 " << ch->cfg.name << "] 0x" << std::hex << id
<< std::dec << " dlc=" << dlc << " data=" << hex << std::endl;
} else {
reportRunWarning("CAN_TX send failed on " + ch->cfg.name + ": " +
msg.m_sKey +
(ch->endpoint->isConnected() ? "" : " (未连接)"));
}
}
//---------------------------------------------------------
// Iterate:周期任务
bool CanBridge::Iterate() {
AppCastingMOOSApp::Iterate();
AppCastingMOOSApp::PostReport();
return true;
}
//---------------------------------------------------------
// handleFrame:CAN 帧 -> CMOOSMsg(二进制) -> MOOSDB
//
// 在各通道 CanEndpoint 接收线程中调用;m_Comms::Post 线程安全。
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(channel, frameInfo, id, data, dlc);
char key[32];
std::snprintf(key, sizeof(key), "CAN_0x%08X", id);
// 二进制构造:m_cDataType = MOOS_BINARY_STRING
CMOOSMsg msg(MOOS_NOTIFY, key,
static_cast<unsigned int>(dlc),
const_cast<uint8_t*>(data));
msg.SetSourceAux(channel); // 通道 -> m_sSrcAux
msg.SetDoubleAux(static_cast<double>(frameInfo)); // FF/RTR/DLC -> m_dfVal2
m_Comms.Post(msg);
}
//---------------------------------------------------------
// buildReport:AppCasting 报告(逐通道)
bool CanBridge::buildReport() {
m_msgs << "============================================" << "\n";
m_msgs << "pCanBridge CAN<->MOOSDB 多通道双向透传" << "\n";
m_msgs << "============================================" << "\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";
m_msgs << "落库记录数: " << m_db->count() << "\n";
}
return true;
}
} // namespace canbridge