- 新增 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 补充编译产物/临时文件忽略
118 lines
3.5 KiB
Python
118 lines
3.5 KiB
Python
"""UDP 通信层:socket 管理、收发线程、按消息 ID 分发。
|
||
|
||
线程安全:start/stop/reconfigure 可随时调用;
|
||
收包在独立线程解析,解析成功后回调 on_feedback / on_command,
|
||
原始帧经 on_raw 回调(用于转发/透传)。
|
||
"""
|
||
|
||
import socket
|
||
import threading
|
||
import time
|
||
|
||
from . import protocol
|
||
|
||
|
||
class UdpComm:
|
||
def __init__(self, logger=None):
|
||
self.logger = logger
|
||
self.sock = None
|
||
self.running = False
|
||
self._thread = None
|
||
self._lock = threading.Lock()
|
||
# 回调
|
||
self.on_feedback = None # callable(msg, addr)
|
||
self.on_command = None # callable(msg, addr)
|
||
self.on_raw = None # callable(data, addr)
|
||
self.on_unknown = None # callable(data, addr) 解析失败/未知ID
|
||
# 统计
|
||
self.rx_count = 0
|
||
self.tx_count = 0
|
||
self.last_rx_time = None
|
||
|
||
def bind(self, ip, port):
|
||
"""创建并绑定监听 socket(带超时 + 地址复用)。失败抛 OSError。"""
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||
s.settimeout(0.1)
|
||
s.bind((ip, int(port)))
|
||
with self._lock:
|
||
old = self.sock
|
||
self.sock = s
|
||
if old:
|
||
old.close()
|
||
return self
|
||
|
||
@property
|
||
def bound(self):
|
||
return self.sock is not None
|
||
|
||
def start(self):
|
||
if self.running:
|
||
return
|
||
if self.sock is None:
|
||
raise RuntimeError("socket 未绑定")
|
||
self.running = True
|
||
self._thread = threading.Thread(target=self._recv_loop, daemon=True,
|
||
name="udp-recv")
|
||
self._thread.start()
|
||
|
||
def stop(self):
|
||
self.running = False
|
||
t = self._thread
|
||
self._thread = None
|
||
if t:
|
||
t.join(timeout=1.5)
|
||
|
||
def _recv_loop(self):
|
||
while self.running:
|
||
try:
|
||
data, addr = self.sock.recvfrom(2048)
|
||
except socket.timeout:
|
||
continue
|
||
except OSError:
|
||
break
|
||
self.rx_count += 1
|
||
self.last_rx_time = time.time()
|
||
try:
|
||
self._dispatch(data, addr)
|
||
except Exception as e:
|
||
if self.logger:
|
||
self.logger.error(f"分发消息异常: {e}")
|
||
|
||
def _dispatch(self, data, addr):
|
||
if self.on_raw:
|
||
self.on_raw(data, addr)
|
||
msg_id = protocol.msg_id_of(data)
|
||
cls = protocol.ALL_MESSAGES.get(msg_id)
|
||
msg = None
|
||
if cls:
|
||
msg = cls()
|
||
if not msg.unpack(data):
|
||
msg = None
|
||
if msg is None:
|
||
if self.on_unknown:
|
||
self.on_unknown(data, addr)
|
||
return
|
||
if msg_id in protocol.CMD_MESSAGES:
|
||
if self.on_command:
|
||
self.on_command(msg, addr)
|
||
else:
|
||
if self.on_feedback:
|
||
self.on_feedback(msg, addr)
|
||
|
||
def sendto(self, data, ip, port):
|
||
"""发送原始字节。返回 True/False。"""
|
||
if self.sock is None:
|
||
return False
|
||
try:
|
||
self.sock.sendto(data, (ip, int(port)))
|
||
self.tx_count += 1
|
||
return True
|
||
except OSError as e:
|
||
if self.logger:
|
||
self.logger.error(f"发送到 {ip}:{port} 失败: {e}")
|
||
return False
|
||
|
||
def send_message(self, msg, ip, port):
|
||
return self.sendto(msg.pack(), ip, port)
|