- 新增 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 补充编译产物/临时文件忽略
375 lines
14 KiB
Python
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")
|