新增复合管控器(pCCU) + 惯导数据解析 + 设备控制页增强 + 0824协议文档归档

- 新增 pCCU 复合管控器程序(src/pCCU):FC/PM 双链路(UDP)、协议编解码器
  (FcProtocol/PmProtocol/Frame/Message/MessageRegistry)、快照与落库(DbStore)、
  内置 WebServer;挂入 src/CMakeLists.txt 与板卡 mission(h100.moos)
- 惯导/深度数据:新增 uDevice_insData 消息解析,接入 UpperCommManager,
  pPowerMangerHost 注册监视,UpmsgTest 覆盖
- pPowerMangerHost 设备控制页增强(web/index.html)
- 部署/运维脚本改为 4 个 systemd 服务(moosdb/pPowerManger/pPowerMangerHost/pCCU):
  deploy/clean-data/fetch-data 同步适配
- 协议文档:归档 0824 控制主机与复合管控器通信协议 xlsx 及关联 docx,移除旧 md
- 测试:新增 pccu 单元/集成测试与 disSysTest Python 配电仿真
- 清理:移除未使用的 CCU 状态调试打印 coutMsg;.gitignore 补充编译产物/临时文件忽略
This commit is contained in:
zjk
2026-08-26 22:43:10 +08:00
parent e0488d513b
commit 54c32a180a
79 changed files with 8731 additions and 1398 deletions
+42
View File
@@ -0,0 +1,42 @@
#ifndef PCCU_FC_LINK_MANAGER_H
#define PCCU_FC_LINK_MANAGER_H
#include "LinkManager.h"
#include "../protocol/FcProtocol.h"
namespace ccu {
//============================================================================
// FcLinkManager:面向燃料电池控制器的链路。
//
// - 本地监听:燃料电池状态反馈端口(默认 6000)
// - 发送目标:燃料电池控制器(默认 192.168.1.162:7000)
// 端口/地址均可由 .moos 配置覆盖。
//============================================================================
class FcLinkManager : public LinkManager {
public:
FcLinkManager(long localPort,
const std::string& fcHost, long fcPort)
: LinkManager("fc", localPort), m_fcHost(fcHost), m_fcPort(fcPort) {
registerFcMessages(registry());
}
void setFcAddress(const std::string& host, long port) {
m_fcHost = host; m_fcPort = port;
}
const std::string& fcHost() const { return m_fcHost; }
long fcPort() const { return m_fcPort; }
protected:
std::string defaultRemoteHost() const override { return m_fcHost; }
long defaultRemotePort() const override { return m_fcPort; }
private:
std::string m_fcHost;
long m_fcPort;
};
} // namespace ccu
#endif // PCCU_FC_LINK_MANAGER_H
+108
View File
@@ -0,0 +1,108 @@
#include "LinkManager.h"
#include "../protocol/Frame.h"
#include "MOOS/libMOOS/Utils/MOOSUtilityFunctions.h"
#include "loguru.hpp"
namespace ccu {
LinkManager::LinkManager(const std::string& linkName, long localPort)
: m_linkName(linkName), m_localPort(localPort) {}
LinkManager::~LinkManager() {
stop();
}
bool LinkManager::start() {
if (m_running) return true;
if (!m_endpoint.init(m_localPort)) {
LOG_F(ERROR, "[%s] bind failed on port %ld", m_linkName.c_str(), m_localPort);
return false;
}
m_running = true;
m_rxThread = std::thread([this]() { rxThreadFunc(); });
LOG_F(INFO, "[%s] listening on local port %ld", m_linkName.c_str(), m_localPort);
return true;
}
void LinkManager::stop() {
if (!m_running) return;
m_running = false;
if (m_rxThread.joinable()) m_rxThread.join();
}
bool LinkManager::sendMessage(uint16_t id, const void* value) {
return sendMessageTo(id, value, defaultRemoteHost(), defaultRemotePort());
}
bool LinkManager::sendMessageTo(uint16_t id, const void* value,
const std::string& host, long port) {
if (host.empty() || port <= 0) return false;
Message* msg = m_registry.find(id);
if (!msg) {
LOG_F(ERROR, "[%s] unknown message id 0x%04X", m_linkName.c_str(), id);
return false;
}
std::vector<uint8_t> frame = msg->encode(value);
if (!m_endpoint.sendTo(host, port, frame)) {
LOG_F(ERROR, "[%s] send msg 0x%04X to %s:%ld failed",
m_linkName.c_str(), id, host.c_str(), port);
return false;
}
++m_txCount;
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());
++m_errorCount;
return false;
}
Message* msg = m_registry.find(id);
if (!msg) {
LOG_F(WARNING, "[%s] unknown msg id 0x%04X, len=%zu",
m_linkName.c_str(), id, frame.size());
++m_errorCount;
return false;
}
bool checksumOk = msg->checksum().verify(frame);
++m_rxCount;
m_lastRxTime = MOOSTime(false);
// 原始帧落库 + 回调
if (m_logSink) m_logSink->onRawFrame(0, id, m_linkName, frame, checksumOk);
if (m_onRawFrame) m_onRawFrame(0, frame, checksumOk);
if (!checksumOk) {
LOG_F(WARNING, "[%s] checksum error msg 0x%04X", m_linkName.c_str(), id);
++m_errorCount;
return false;
}
// 解码交给上层(CCU 层知道具体消息类型)
if (m_onMessage) m_onMessage(msg, frame);
return true;
}
void LinkManager::rxThreadFunc() {
uint8_t buf[2048];
while (m_running) {
int n = m_endpoint.receive(buf, sizeof(buf));
if (n > 0) {
std::vector<uint8_t> frame(buf, buf + n);
handleIncoming(frame);
} else {
// 无数据,短暂休眠避免忙等
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}
} // namespace ccu
+112
View File
@@ -0,0 +1,112 @@
#ifndef PCCU_LINK_MANAGER_H
#define PCCU_LINK_MANAGER_H
#include <cstdint>
#include <string>
#include <atomic>
#include <thread>
#include <functional>
#include "UdpEndpoint.h"
#include "../protocol/MessageRegistry.h"
#include "../protocol/Message.h"
namespace ccu {
// 落库接口:由 DbStore 实现,供链路层记录原始帧(实现收发数据入库)
class ILinkLogSink {
public:
virtual ~ILinkLogSink() = default;
// direction: 0=收 1=发;data 为原始完整帧;checksumOk 为校验结果
virtual void onRawFrame(int direction, uint16_t msgId, const std::string& link,
const std::vector<uint8_t>& data, bool checksumOk) = 0;
};
//============================================================================
// LinkManager:单条 UDP 链路的管理者。
//
// 职责:
// - 绑定本地端口并维护独立接收线程
// - 持有该链路的 MessageRegistry(收发共用,id 按链路隔离)
// - 收帧:解析 id -> 查注册表 -> 校验 -> 回调 onMessage(携带消息对象与原始帧)
// - 发帧:编码 -> 发送 -> 落库
//
// 派生类(FcLinkManager / PmLinkManager)仅需提供链路名与默认收发地址。
//============================================================================
class LinkManager {
public:
LinkManager(const std::string& linkName, long localPort);
virtual ~LinkManager();
// 启动:绑定端口 + 启动接收线程。返回是否成功。
bool start();
void stop();
bool isRunning() const { return m_running; }
// 注册表(收发共用)
MessageRegistry& registry() { return m_registry; }
const MessageRegistry& registry() const { return m_registry; }
// 收帧回调:msg 为注册表中命中的消息对象(可调用 decode 解码),frame 为原始完整帧
void setOnMessage(std::function<void(Message* msg, const std::vector<uint8_t>& frame)> cb) {
m_onMessage = std::move(cb);
}
// 原始帧回调(用于 Web/调试展示原始报文)
void setOnRawFrame(std::function<void(int direction, const std::vector<uint8_t>& frame, bool checksumOk)> cb) {
m_onRawFrame = std::move(cb);
}
// 落库接口(可空)
void setLogSink(ILinkLogSink* sink) { m_logSink = sink; }
// 编码并发送一条已注册消息;返回是否成功
bool sendMessage(uint16_t id, const void* value);
// 编码并发送一条已注册消息,同时指定目标地址(覆盖默认)
bool sendMessageTo(uint16_t id, const void* value,
const std::string& host, long port);
// 处理一帧收到的原始数据(供接收线程调用,也可测试时手动喂入)
bool handleIncoming(const std::vector<uint8_t>& frame);
// 链路状态:最近一次收到消息的时间(MOOSTime),0 表示从未收到
double lastRxTime() const { return m_lastRxTime; }
long localPort() const { return m_localPort; }
const std::string& linkName() const { return m_linkName; }
// 收发统计
unsigned long rxCount() const { return m_rxCount; }
unsigned long txCount() const { return m_txCount; }
unsigned long errorCount() const { return m_errorCount; }
protected:
// 派生类提供发送默认目标
virtual std::string defaultRemoteHost() const { return ""; }
virtual long defaultRemotePort() const { return 0; }
private:
void rxThreadFunc();
std::string m_linkName;
long m_localPort;
UdpEndpoint m_endpoint;
MessageRegistry m_registry;
std::atomic<bool> m_running{false};
std::thread m_rxThread;
std::function<void(Message*, const std::vector<uint8_t>&)> m_onMessage;
std::function<void(int, const std::vector<uint8_t>&, bool)> m_onRawFrame;
ILinkLogSink* m_logSink = nullptr;
double m_lastRxTime = 0;
unsigned long m_rxCount = 0;
unsigned long m_txCount = 0;
unsigned long m_errorCount = 0;
};
} // namespace ccu
#endif // PCCU_LINK_MANAGER_H
+42
View File
@@ -0,0 +1,42 @@
#ifndef PCCU_PM_LINK_MANAGER_H
#define PCCU_PM_LINK_MANAGER_H
#include "LinkManager.h"
#include "../protocol/PmProtocol.h"
namespace ccu {
//============================================================================
// PmLinkManager:面向控制主机 pPowerManger 的链路。
//
// - 本地监听:控制主机指令端口(默认 7000)
// - 发送目标:控制主机(默认 192.168.0.140:5001)
// 端口/地址均可由 .moos 配置覆盖。
//============================================================================
class PmLinkManager : public LinkManager {
public:
PmLinkManager(long localPort,
const std::string& pmHost, long pmPort)
: LinkManager("pm", localPort), m_pmHost(pmHost), m_pmPort(pmPort) {
registerPmMessages(registry());
}
void setPmAddress(const std::string& host, long port) {
m_pmHost = host; m_pmPort = port;
}
const std::string& pmHost() const { return m_pmHost; }
long pmPort() const { return m_pmPort; }
protected:
std::string defaultRemoteHost() const override { return m_pmHost; }
long defaultRemotePort() const override { return m_pmPort; }
private:
std::string m_pmHost;
long m_pmPort;
};
} // namespace ccu
#endif // PCCU_PM_LINK_MANAGER_H
+57
View File
@@ -0,0 +1,57 @@
#include "UdpEndpoint.h"
#include <iostream>
namespace ccu {
UdpEndpoint::UdpEndpoint() : m_socket(nullptr), m_localPort(0) {}
UdpEndpoint::~UdpEndpoint() {
if (m_socket) {
delete m_socket;
m_socket = nullptr;
}
}
bool UdpEndpoint::init(long localPort) {
m_localPort = localPort;
m_socket = new XPCUdpSocket(localPort);
try {
m_socket->vBindSocket();
} catch (const std::exception& e) {
std::cerr << "[UdpEndpoint] bind failed on port " << localPort
<< ": " << e.what() << std::endl;
delete m_socket;
m_socket = nullptr;
return false;
} catch (...) {
std::cerr << "[UdpEndpoint] bind failed on port " << localPort
<< ": unknown error" << std::endl;
delete m_socket;
m_socket = nullptr;
return false;
}
return true;
}
bool UdpEndpoint::sendTo(const std::string& host, long port, const std::vector<uint8_t>& data) {
if (!m_socket || data.empty()) return false;
try {
m_socket->iSendMessageTo(const_cast<uint8_t*>(data.data()),
static_cast<int>(data.size()),
port, host);
return true;
} catch (...) {
return false;
}
}
int UdpEndpoint::receive(uint8_t* buf, size_t len) {
if (!m_socket || !buf || len == 0) return 0;
try {
return m_socket->iRecieveMessage(buf, static_cast<int>(len));
} catch (...) {
return 0;
}
}
} // namespace ccu
+46
View File
@@ -0,0 +1,46 @@
#ifndef PCCU_UDP_ENDPOINT_H
#define PCCU_UDP_ENDPOINT_H
#define UNIX
#include "MOOS/libMOOS/Comms/XPCUdpSocket.h"
#include <cstdint>
#include <string>
#include <vector>
namespace ccu {
//============================================================================
// UdpEndpoint:单个 UDP 套接字的封装。
//
// 职责:
// - 绑定本地端口
// - 向指定远端 (host:port) 发送数据报
// - 阻塞接收数据报
// 不关心协议内容,仅负责字节的收发。
//============================================================================
class UdpEndpoint {
public:
UdpEndpoint();
~UdpEndpoint();
// 绑定本地端口;返回是否成功(端口占用/权限失败返回 false)
bool init(long localPort);
// 向指定远端发送完整数据报
bool sendTo(const std::string& host, long port, const std::vector<uint8_t>& data);
// 阻塞接收一帧;返回接收字节数,0 或负值表示无数据/错误
int receive(uint8_t* buf, size_t len);
bool isOpen() const { return m_socket != nullptr; }
long localPort() const { return m_localPort; }
private:
XPCUdpSocket* m_socket = nullptr;
long m_localPort = 0;
};
} // namespace ccu
#endif // PCCU_UDP_ENDPOINT_H