Files
H100PowerManger/test/disSysTest/app/protocol.py
T
zjk 54c32a180a 新增复合管控器(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 补充编译产物/临时文件忽略
2026-08-26 22:43:10 +08:00

375 lines
14 KiB
Python

"""配电系统 UDP 协议封装(声明式消息定义)。
字段顺序、大小与 C++ 侧 pmSysvariable.h / ccuUdpMsg.h 严格对齐。
帧格式(配电消息,单字节校验):
start1(0x40) start2(0x40) | id(uint16 LE) | length(uint16 LE) | body | checkCode(和校验 &0xFF)
说明:
- 字段表(FIELDS)是唯一数据源,length、pack/unpack 均由此推导,
避免手工维护长度字段与 C++ 结构体大小不一致。
- unpack 采用固定偏移解析 + 起始字节/最小长度/校验和校验。
"""
import struct
DIS_START1 = 0x40
DIS_START2 = 0x40
# 断路器状态定义(与 C++ 一致)
BREAKER_ON = 0x55 # 合
BREAKER_OFF = 0xAA # 分
BREAKER_FAULT = 0x5A # 故障
class Field:
"""协议字段定义。fmt 支持 'B'(uchar) / 'H'(ushort LE)。"""
__slots__ = ("name", "fmt", "size")
def __init__(self, name, fmt):
self.name = name
self.fmt = fmt
self.size = struct.calcsize(fmt)
class Message:
"""声明式消息基类:由 FIELDS 自动生成 pack/unpack 与长度。"""
MSG_ID = 0x0000
CN = "未知消息"
FIELDS = () # tuple[Field]
def __init__(self):
self.start1 = DIS_START1
self.start2 = DIS_START2
self.msg_id = self.MSG_ID
self.length = self.body_size()
self.checkCode = 0
for f in self.FIELDS:
setattr(self, f.name, 0)
# ---- 派生属性 -------------------------------------------------
@classmethod
def body_size(cls):
return sum(f.size for f in cls.FIELDS)
@classmethod
def field_names(cls):
return [f.name for f in cls.FIELDS]
def to_dict(self):
return {f.name: getattr(self, f.name) for f in self.FIELDS}
def __repr__(self):
parts = [f"{self.__class__.__name__}(id=0x{self.MSG_ID:04X}, len={self.length}"]
for f in self.FIELDS:
parts.append(f"{f.name}={getattr(self, f.name)}")
return ", ".join(parts) + ")"
# ---- 打包/解包 ------------------------------------------------
def pack(self):
body = b""
for f in self.FIELDS:
body += struct.pack("<" + f.fmt, getattr(self, f.name))
self.length = self.body_size()
header = struct.pack("<BBHH", DIS_START1, DIS_START2, self.MSG_ID, self.length)
data = header + body
self.checkCode = sum(data) & 0xFF
return data + struct.pack("<B", self.checkCode)
def unpack(self, data):
if len(data) < 7:
return False
if data[0] != DIS_START1 or data[1] != DIS_START2:
return False
if struct.unpack_from("<H", data, 2)[0] != self.MSG_ID:
return False
expected = 6 + self.body_size() + 1
if len(data) < expected:
return False
self.start1, self.start2 = data[0], data[1]
self.msg_id = struct.unpack_from("<H", data, 2)[0]
self.length = struct.unpack_from("<H", data, 4)[0]
offset = 6
for f in self.FIELDS:
if f.size == 1:
setattr(self, f.name, data[offset])
offset += 1
else:
setattr(self, f.name, struct.unpack_from("<H", data, offset)[0])
offset += 2
self.checkCode = data[-1]
checksum = sum(data[:-1]) & 0xFF
return checksum == data[-1]
# ---------------------------------------------------------------
# 高压母线反馈 0x0005 body=53
# ---------------------------------------------------------------
class MsgDisHighVolBusFb(Message):
MSG_ID = 0x0005
CN = "动力母线反馈"
FIELDS = (
Field("fuelCellCircuitBreaker", "B"),
Field("powerLithiumBatteryCircuitBreaker", "B"),
Field("propulsionMotorCircuitBreaker", "B"),
Field("lithiumBatteryGroupInstrumentCircuitBreaker", "B"),
Field("dcDc5ModuleCircuitBreaker", "B"),
Field("bowHighVoltageDistributionBoxCircuitBreaker", "B"),
Field("sternFTDevice45CircuitBreaker", "B"),
Field("fbReservedCircuitBreaker", "B"),
Field("sternRudderSwitch1CircuitBreaker", "B"),
Field("sternRudderSwitch2CircuitBreaker", "B"),
Field("tyzReservedCircuitBreaker", "B"),
Field("reservedCircuitBreaker", "B"),
Field("dcDcFaultWord1", "B"),
Field("dcDcFaultWord2", "B"),
Field("powerBusInsulationStatus", "B"),
Field("aBusInsulationStatus", "B"),
Field("meterPowerLossSignal", "B"),
Field("emergencyPowerLossSignal", "B"),
Field("dcdcTemperature", "B"),
Field("powerBusVoltage", "H"),
Field("dcDc5ModuleCurrent", "H"),
Field("powerBusCurrent", "H"),
Field("busbarAVoltage", "H"),
Field("propulsionMotorControlBoxCurrent", "H"),
Field("busbarACurrent", "H"),
Field("lithiumBatteryGroupMeterCurrent", "H"),
Field("bowHighVoltageDistributionBoxCurrent", "H"),
Field("sternFTDevice45Current", "H"),
Field("tyzReservedSwitchCurrent", "H"),
Field("sternRudderSwitch1Current", "H"),
Field("reservedCurrent1", "H"),
Field("sternRudderSwitch2Current", "H"),
Field("reservedCurrent2", "H"),
Field("fbReservedSwitchCurrent", "H"),
Field("reservedCurrent3", "H"),
Field("coolWaterPressure", "H"),
)
# ---------------------------------------------------------------
# 高压A母线反馈 0x0006 body=25
# ---------------------------------------------------------------
class MsgDisHighAVolBusFb(Message):
MSG_ID = 0x0006
CN = "艏部高压反馈"
FIELDS = (
Field("bowFTDevice123CircuitBreaker", "B"),
Field("actuatorCircuitBreaker", "B"),
Field("mastSteeringGearControlBoxCircuitBreaker", "B"),
Field("xczCircuitBreaker", "B"),
Field("bowRudderControlBoxCircuitBreaker", "B"),
Field("openWaterCoverStartCylinderCircuitBreaker", "B"),
Field("instrumentPowerFailureSignal", "B"),
Field("emergencyPowerFailureSignal", "B"),
Field("waterIngressionAlarm", "B"),
Field("busbarBVoltage", "H"),
Field("busbarBCurrent", "H"),
Field("bowFTDevice123Current", "H"),
Field("actuatorCurrent", "H"),
Field("mastSteeringGearControlBoxCurrent", "H"),
Field("xczCurrent", "H"),
Field("bowRudderControlBoxCurrent", "H"),
Field("openWaterCoverStartCylinderCurrent", "H"),
)
# ---------------------------------------------------------------
# 仪表主母线反馈 0x0007 body=34
# ---------------------------------------------------------------
class MsgDisLowMainBusFb(Message):
MSG_ID = 0x0007
CN = "仪表主母线反馈"
FIELDS = (
Field("lithiumBatteryGroupInstrumentCircuitBreaker", "B"),
Field("bowLowVoltageDistributionBoxCircuitBreaker", "B"),
Field("unit4InstrumentDC48VCircuitBreaker", "B"),
Field("dcC1DCDistributionPanelCircuitBreaker", "B"),
Field("reservedCircuitBreaker1", "B"),
Field("reservedCircuitBreaker2", "B"),
Field("emergencyLithiumBatteryGroup2CircuitBreaker", "B"),
Field("instrumentPowerFailureSignal", "B"),
Field("emergencyPowerFailureSignal", "B"),
Field("instrumentBusbarInsulationLow", "B"),
Field("instrumentBusbarVoltage", "H"),
Field("bowLowVoltageDistributionBoxCurrent", "H"),
Field("dcC1InstrumentDC48VCurrent", "H"),
Field("reservedCurrent1", "H"),
Field("emergencyLithiumBatteryGroup2Current", "H"),
Field("lithiumBatteryGroupInstrumentCurrent", "H"),
Field("unit4InstrumentDC48VCurrent", "H"),
Field("reservedCurrent2", "H"),
Field("emergency2BusbarVoltage", "H"),
Field("compositeEnergyManagementSystemEmergencyDC48VCurrent", "H"),
Field("fuelCellSecuritySystemEmergencyDC48VCurrent", "H"),
Field("plcControlPowerCurrent", "H"),
)
# ---------------------------------------------------------------
# 仪表汇流排反馈 0x0008 body=29
# ---------------------------------------------------------------
class MsgDisLowBusFb(Message):
MSG_ID = 0x0008
CN = "艏部低压反馈"
FIELDS = (
Field("unit1CircuitBreaker", "B"),
Field("unit2CircuitBreaker", "B"),
Field("unit3CircuitBreaker", "B"),
Field("unit5CircuitBreaker", "B"),
Field("bowPZDeviceCircuitBreaker", "B"),
Field("reservedCircuitBreaker1", "B"),
Field("reservedCircuitBreaker2", "B"),
Field("emergencyLithiumBatteryGroup1CircuitBreaker", "B"),
Field("instrumentPowerFailureSignal", "B"),
Field("emergencyPowerFailureSignal", "B"),
Field("waterIngressionAlarm", "B"),
Field("instrumentBusbarVoltage", "H"),
Field("instrumentBusbarCurrent", "H"),
Field("unit1Current", "H"),
Field("unit2Current", "H"),
Field("unit3Current", "H"),
Field("unit5Current", "H"),
Field("bowPZDeviceCurrent", "H"),
Field("emergencyLithiumBatteryGroup1Current", "H"),
Field("emergency1BusbarVoltage", "H"),
)
# ---------------------------------------------------------------
# 动力母线配电指令 0x0001 body=14
# ---------------------------------------------------------------
class MsgDisHVBCmd(Message):
MSG_ID = 0x0001
CN = "动力母线配电指令"
FIELDS = (
Field("fuelCellCircuitBreaker", "B"),
Field("powerLithiumBatteryCircuitBreaker", "B"),
Field("propulsionMotorCircuitBreaker", "B"),
Field("lithiumBatteryGroupInstrumentCircuitBreaker", "B"),
Field("dcDc5ModuleCircuitBreaker", "B"),
Field("bowHighVoltageDistributionBoxCircuitBreaker", "B"),
Field("sternFTDevice45CircuitBreaker", "B"),
Field("fbReservedCircuitBreaker", "B"),
Field("sternRudderSwitch1CircuitBreaker", "B"),
Field("sternRudderSwitch2CircuitBreaker", "B"),
Field("tyzReservedCircuitBreaker", "B"),
Field("reservedCircuitBreaker", "B"),
Field("dcdc5Module", "B"),
Field("coolingSystem", "B"),
)
# ---------------------------------------------------------------
# 动力汇流排A配电指令 0x0002 body=6
# ---------------------------------------------------------------
class MsgDisHVBACmd(Message):
MSG_ID = 0x0002
CN = "艏部高压配电指令"
FIELDS = (
Field("bowFTDevice123CircuitBreaker", "B"),
Field("actuatorCircuitBreaker", "B"),
Field("mastSteeringGearControlBoxCircuitBreaker", "B"),
Field("xczCircuitBreaker", "B"),
Field("bowRudderControlBoxCircuitBreaker", "B"),
Field("openWaterCoverStartCylinderCircuitBreaker", "B"),
)
# ---------------------------------------------------------------
# 仪表主母线配电指令 0x0003 body=7
# ---------------------------------------------------------------
class MsgDisLVMBCmd(Message):
MSG_ID = 0x0003
CN = "仪表主母线配电指令"
FIELDS = (
Field("lithiumBatteryGroupInstrumentCircuitBreaker", "B"),
Field("bowLowVoltageDistributionBoxCircuitBreaker", "B"),
Field("unit4InstrumentDC48VCircuitBreaker", "B"),
Field("dcC1DCDistributionPanelCircuitBreaker", "B"),
Field("reservedCircuitBreaker1", "B"),
Field("reservedCircuitBreaker2", "B"),
Field("emergencyLithiumBatteryGroup2CircuitBreaker", "B"),
)
# ---------------------------------------------------------------
# 仪表汇流排配电指令 0x0004 body=8
# ---------------------------------------------------------------
class MsgDisLVBCmd(Message):
MSG_ID = 0x0004
CN = "艏部低压配电指令"
FIELDS = (
Field("unit1CircuitBreaker", "B"),
Field("unit2CircuitBreaker", "B"),
Field("unit3CircuitBreaker", "B"),
Field("unit5CircuitBreaker", "B"),
Field("bowPZDeviceCircuitBreaker", "B"),
Field("reservedCircuitBreaker1", "B"),
Field("reservedCircuitBreaker2", "B"),
Field("emergencyLithiumBatteryGroup1CircuitBreaker", "B"),
)
# ---------------------------------------------------------------
# 消息注册表
# ---------------------------------------------------------------
FB_MESSAGES = {c.MSG_ID: c for c in (MsgDisHighVolBusFb, MsgDisHighAVolBusFb,
MsgDisLowMainBusFb, MsgDisLowBusFb)}
CMD_MESSAGES = {c.MSG_ID: c for c in (MsgDisHVBCmd, MsgDisHVBACmd,
MsgDisLVMBCmd, MsgDisLVBCmd)}
ALL_MESSAGES = {**FB_MESSAGES, **CMD_MESSAGES}
def msg_id_of(data):
"""从原始帧提取消息 ID。数据不合法时返回 None。"""
if len(data) < 6:
return None
if data[0] != DIS_START1 or data[1] != DIS_START2:
return None
return struct.unpack_from("<H", data, 2)[0]
def make_message(msg_id):
cls = ALL_MESSAGES.get(msg_id)
return cls() if cls else None
def self_test():
"""pack/unpack 往返 + 长度断言。返回 (ok, 报告)。"""
lines = []
ok = True
for cls in (MsgDisHighVolBusFb, MsgDisHighAVolBusFb, MsgDisLowMainBusFb,
MsgDisLowBusFb, MsgDisHVBCmd, MsgDisHVBACmd, MsgDisLVMBCmd,
MsgDisLVBCmd):
m = cls()
data = m.pack()
expected = 6 + cls.body_size() + 1
frame_ok = len(data) == expected
len_ok = m.length == cls.body_size()
m2 = cls()
round_ok = m2.unpack(data)
# 翻转一个字节应导致校验失败
bad = bytearray(data)
bad[6] ^= 0xFF
neg_ok = not m2.unpack(bytes(bad))
status = "OK" if (frame_ok and len_ok and round_ok and neg_ok) else "FAIL"
if status == "FAIL":
ok = False
lines.append(f" [{status}] {cls.__name__}: id=0x{cls.MSG_ID:04X} "
f"body={cls.body_size()}B frame={len(data)}B "
f"length_field={m.length} roundtrip={round_ok} neg={neg_ok}")
return ok, "\n".join(lines)
if __name__ == "__main__":
ok, report = self_test()
print(report)
print("PROTOCOL SELF-TEST:", "PASS" if ok else "FAIL")