pCCU 新增真实CCU配电桥接(rcu链路原帧透传+网页显示)并适配板卡端口拓扑

- protocol/DisProtocol: 配电协议8条消息(指令0x0001~4/反馈0x0005~8, Sum8),复用pmSysvariable结构体+static_assert核对
- ChecksumPolicy 新增 Sum8;Frame 支持 1 字节校验和
- MessageRegistry 支持 (id+帧总长) 二维查找:配电指令与PM操控指令共用id按帧长区分(帧头length字段不可信)
- LinkManager: 按实际帧长分发 + sendRaw原帧透传 + 非本协议帧头落库节流告警;UdpEndpoint sendTo 加锁
- comm/RcuLinkManager: 真实CCU链路;core/DisBridge: 配电桥接器(反馈转pPowerManger+解码显示,指令转真实CCU,0x0004仅显示不转发)
- 网页新增"真实CCU"页签全字段展示;SnapshotBuilder realCcu 节 + rcu 链路描述
- h100.moos: 端口腾挪(真实CCU固定上报5001归pCCU,pPowerManger iport=5002),rcu_remote=192.168.0.140:7000
- 测试: pccuTest 新增配电/Sum8/长度分发/RCU注册表用例(176项);集成测试新增桥接字节一致+不误转发用例
This commit is contained in:
zjk
2026-09-01 15:06:11 +08:00
parent 5b8f0b1421
commit 0639b5904d
28 changed files with 1609 additions and 53 deletions
+31 -2
View File
@@ -54,17 +54,46 @@ bool LinkManager::sendMessageTo(uint16_t id, const void* value,
return true;
}
bool LinkManager::sendRaw(const std::vector<uint8_t>& frame) {
if (frame.empty()) return false;
const std::string& host = defaultRemoteHost();
long port = defaultRemotePort();
if (host.empty() || port <= 0) return false;
if (!m_endpoint.sendTo(host, port, frame)) {
LOG_F(ERROR, "[%s] send raw frame (%zu B) to %s:%ld failed",
m_linkName.c_str(), frame.size(), host.c_str(), port);
return false;
}
++m_txCount;
uint16_t id = 0;
Frame::parseId(frame, id);
if (m_logSink) m_logSink->onRawFrame(1, id, m_linkName, frame, true);
if (m_onRawFrame) m_onRawFrame(1, frame, true);
return true;
}
bool LinkManager::handleIncoming(const std::vector<uint8_t>& frame) {
if (frame.size() < FRAME_HEADER_LEN) return false;
uint16_t id = 0;
if (!Frame::parseId(frame, id)) {
LOG_F(WARNING, "[%s] bad frame header", m_linkName.c_str());
// 非本协议帧(如真实CCU 混发的旧协议 0x20 0x20 状态帧、无效帧):
// 原样落库便于排查,告警按 10s 节流避免刷屏
++m_errorCount;
if (m_logSink) m_logSink->onRawFrame(0, 0, m_linkName, frame, false);
double now = MOOSTime(false);
if (now - m_lastBadHdrLog >= 10.0) {
m_lastBadHdrLog = now;
LOG_F(WARNING, "[%s] 非本协议帧头(%02X %02X, %zu B),已忽略(10s节流)",
m_linkName.c_str(), frame[0], frame[1], frame.size());
}
return false;
}
Message* msg = m_registry.find(id);
// 优先按 id + 实际帧总长精确匹配(配电指令 0x0001~0x0004 与 PM 指令
// 共用 id,仅帧长不同;帧头 length 字段不可信,须用实际收到的字节数),
// 未命中回退该 id 的第一条(保持旧行为:decode 失败报错)
Message* msg = m_registry.find(id, frame.size());
if (!msg) {
LOG_F(WARNING, "[%s] unknown msg id 0x%04X, len=%zu",
m_linkName.c_str(), id, frame.size());
+8 -1
View File
@@ -48,7 +48,9 @@ public:
MessageRegistry& registry() { return m_registry; }
const MessageRegistry& registry() const { return m_registry; }
// 收帧回调:msg 为注册表中命中的消息对象(可调用 decode 解码),frame 为原始完整帧
// 收帧回调:msg 为注册表中命中的消息对象(可调用 decode 解码),frame 为原始完整帧。
// 注意:同 id 不同长度的消息(如配电指令与 PM 指令)按帧总长精确区分,
// 回调携带的 msg 即精确命中的对象;长度未命中时回退该 id 的第一条。
void setOnMessage(std::function<void(Message* msg, const std::vector<uint8_t>& frame)> cb) {
m_onMessage = std::move(cb);
}
@@ -68,6 +70,10 @@ public:
bool sendMessageTo(uint16_t id, const void* value,
const std::string& host, long port);
// 原样发送一帧到默认远端(桥接透传用,不做编码);
// 消息 id 从帧头解析,收发计数与落库口径与 sendMessage 一致
bool sendRaw(const std::vector<uint8_t>& frame);
// 处理一帧收到的原始数据(供接收线程调用,也可测试时手动喂入)
bool handleIncoming(const std::vector<uint8_t>& frame);
@@ -102,6 +108,7 @@ private:
ILinkLogSink* m_logSink = nullptr;
double m_lastRxTime = 0;
double m_lastBadHdrLog = 0; // 非本协议帧头告警节流
unsigned long m_rxCount = 0;
unsigned long m_txCount = 0;
unsigned long m_errorCount = 0;
+8
View File
@@ -3,6 +3,7 @@
#include "LinkManager.h"
#include "../protocol/PmProtocol.h"
#include "../protocol/DisProtocol.h"
namespace ccu {
@@ -11,6 +12,12 @@ namespace ccu {
//
// - 本地监听:控制主机指令端口(默认 7000)
// - 发送目标:控制主机(默认 192.168.0.140:5001)
//
// 注册的消息:
// - PM 协议 0x0001~0x0004(Sum32)
// - 配电指令 0x0001~0x0004(Sum8,13~21B 短帧):pPowerManger 下发的
// 配电指令与其操控/参数指令共用 id,仅帧长不同,由注册表按
// (id+帧长) 区分,DisBridge 据此原桥接转发真实CCU
// 端口/地址均可由 .moos 配置覆盖。
//============================================================================
@@ -20,6 +27,7 @@ public:
const std::string& pmHost, long pmPort)
: LinkManager("pm", localPort), m_pmHost(pmHost), m_pmPort(pmPort) {
registerPmMessages(registry());
registerDisCmdMessages(registry());
}
void setPmAddress(const std::string& host, long port) {
+51
View File
@@ -0,0 +1,51 @@
#ifndef PCCU_RCU_LINK_MANAGER_H
#define PCCU_RCU_LINK_MANAGER_H
#include "LinkManager.h"
#include "../protocol/PmProtocol.h"
#include "../protocol/DisProtocol.h"
namespace ccu {
//============================================================================
// RcuLinkManager:面向真实 CCU 设备的链路(配电桥接)。
//
// - 本地监听:rcu_local_port(默认 7100),接收真实CCU 上报的数据
// - 发送目标:真实CCU(默认占位 192.168.0.200:7000,.moos 可配)
//
// 注册的消息:
// - 配电反馈 0x0005~0x0008(Sum8):真实CCU 上报的配电状态,
// 由 DisBridge 原帧转发给 pPowerManger 并解码显示
// - 配电指令 0x0001~0x0004(Sum8):真实CCU 的指令回显仅识别,不转发
// - PM 消息 0x0001~0x0004(Sum32):真实CCU 状态报文 0x0004 仅解码
// 显示,不转发(避免与 pCCU 自身模拟的状态报文冲突)
//============================================================================
class RcuLinkManager : public LinkManager {
public:
RcuLinkManager(long localPort,
const std::string& rcuHost, long rcuPort)
: LinkManager("rcu", localPort), m_rcuHost(rcuHost), m_rcuPort(rcuPort) {
registerDisFbMessages(registry());
registerDisCmdMessages(registry());
registerPmMessages(registry());
}
void setRcuAddress(const std::string& host, long port) {
m_rcuHost = host; m_rcuPort = port;
}
const std::string& rcuHost() const { return m_rcuHost; }
long rcuPort() const { return m_rcuPort; }
protected:
std::string defaultRemoteHost() const override { return m_rcuHost; }
long defaultRemotePort() const override { return m_rcuPort; }
private:
std::string m_rcuHost;
long m_rcuPort;
};
} // namespace ccu
#endif // PCCU_RCU_LINK_MANAGER_H
+1
View File
@@ -36,6 +36,7 @@ bool UdpEndpoint::init(long localPort) {
bool UdpEndpoint::sendTo(const std::string& host, long port, const std::vector<uint8_t>& data) {
if (!m_socket || data.empty()) return false;
try {
std::lock_guard<std::mutex> lock(m_sendMutex);
m_socket->iSendMessageTo(const_cast<uint8_t*>(data.data()),
static_cast<int>(data.size()),
port, host);
+4
View File
@@ -4,6 +4,7 @@
#define UNIX
#include "MOOS/libMOOS/Comms/XPCUdpSocket.h"
#include <cstdint>
#include <mutex>
#include <string>
#include <vector>
@@ -17,6 +18,8 @@ namespace ccu {
// - 向指定远端 (host:port) 发送数据报
// - 阻塞接收数据报
// 不关心协议内容,仅负责字节的收发。
// 发送可能来自多个线程(MOOS 主线程 + 各链路接收线程的桥接转发),
// sendTo 内部加锁串行化。
//============================================================================
class UdpEndpoint {
@@ -39,6 +42,7 @@ public:
private:
XPCUdpSocket* m_socket = nullptr;
long m_localPort = 0;
std::mutex m_sendMutex; // 串行化并发 sendTo
};
} // namespace ccu