新增 pCanBridge:USBCAN-8E-U CAN->MOOSDB 透传桥 + SQLite 落库

新应用 src/pCanBridge(独立 MOOS App,单通道):
- CanEndpoint:TCP Client 连接 CANET/USBCAN-8E-U 工作端口
  (目标 IP/端口可配置,默认 192.168.0.222:4001=CAN0),
  接收线程流式缓冲按 13 字节切帧,过滤无效 DLC(>8),
  断线自动重连(非阻塞 connect+超时 / SO_RCVTIMEO 读超时)。
- 帧协议(CANET-8E-U 手册 8.1):byte0=帧信息(bit7 FF/bit6 RTR/
  bit3~0 DLC),byte1~4=ID 大端,byte5~12=数据。
- MOOS 映射:变量名 CAN_0x%08X=CAN ID(不缩减),m_sVal=二进制
  data(逐帧透传不限流),m_sSrcAux=通道名(can_channel_name),
  m_dfVal2=原始帧信息字节(用于区分标准/扩展、数据/远程帧)。
- CanDbStore:每帧落库 SQLite(can_frame 表:time/channel/
  frame_info/can_id/dlc/hex,含 time、can_id 索引),参考 pCCU
  DbStore:预处理语句 + WAL + synchronous=NORMAL,路径由 dbpath
  配置;复用 pPowerManger 的 sqlite3 amalgamation。
- AppCast 输出连接状态/收帧/发布/落库统计。

配套:
- missions/h100.moos 新增 pCanBridge 配置块(板卡 dbpath 指向
  data 目录)。
- build-board.sh / deploy.sh 支持 pCanBridge:产物部署 +
  pCanBridge.service 安装,服务列表加入第 5 个服务。
- .gitignore 忽略 bin/pCanBridge 与本机默认数据库。

已验证(板卡 192.168.0.223 实测):服务 active,连接设备正常,
~60 帧/s 透传,落库 9000+ 行 integrity ok、无丢帧。
This commit is contained in:
zjk
2026-08-31 19:14:59 +08:00
parent 7e4d08808a
commit b547772709
16 changed files with 1048 additions and 17 deletions
+169
View File
@@ -0,0 +1,169 @@
#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() {
if (m_endpoint) {
m_endpoint->stop();
delete m_endpoint;
m_endpoint = nullptr;
}
if (m_db) {
m_db->close();
delete m_db;
m_db = nullptr;
}
}
//---------------------------------------------------------
// OnStartUp:读取配置并启动 CAN 链路
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());
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 == "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;
else handled = false;
if (!handled)
reportUnhandledConfigWarning(orig);
}
registerVariables();
// 数据库(CAN 帧落库)
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,收帧回调在接收线程中触发)
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);
}
cout << "pCanBridge started: " << m_canHost << ":" << m_canPort
<< " (" << m_channelName << "), db: " << m_dbPath << endl;
return true;
}
//---------------------------------------------------------
// OnConnectToServer
bool CanBridge::OnConnectToServer() {
registerVariables();
return true;
}
//---------------------------------------------------------
// registerVariables:纯发布应用,无需注册订阅变量
void CanBridge::registerVariables() {
AppCastingMOOSApp::RegisterVariables();
}
//---------------------------------------------------------
// OnNewMail:当前无订阅指令,预留
bool CanBridge::OnNewMail(MOOSMSG_LIST &NewMail) {
AppCastingMOOSApp::OnNewMail(NewMail);
MOOSMSG_LIST::iterator p;
for (p = NewMail.begin(); p != NewMail.end(); p++) {
CMOOSMsg &msg = *p;
(void)msg;
}
return true;
}
//---------------------------------------------------------
// Iterate:周期任务
bool CanBridge::Iterate() {
AppCastingMOOSApp::Iterate();
AppCastingMOOSApp::PostReport();
return true;
}
//---------------------------------------------------------
// handleFrame:CAN 帧 -> CMOOSMsg(二进制) -> MOOSDB
//
// 在 CanEndpoint 接收线程中调用;m_Comms::Post 线程安全。
void CanBridge::handleFrame(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);
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(m_channelName); // 通道 -> m_sSrcAux
msg.SetDoubleAux(static_cast<double>(frameInfo)); // FF/RTR/DLC -> m_dfVal2
m_Comms.Post(msg);
++m_notifyCount;
}
//---------------------------------------------------------
// buildReport:AppCasting 报告
bool CanBridge::buildReport() {
m_msgs << "============================================" << "\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->errorCount() << "\n";
m_msgs << "重连次数: " << m_endpoint->reconnectCount() << "\n";
m_msgs << "最近收帧: " << m_endpoint->lastFrameTime() << "\n";
}
if (m_db) {
m_msgs << "数据库: " << m_dbPath
<< (m_db->isOpen() ? " (已打开)" : " (未打开)") << "\n";
m_msgs << "落库记录数: " << m_db->count() << "\n";
}
return true;
}
} // namespace canbridge