新增复合管控器(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:
@@ -0,0 +1,374 @@
|
||||
"""配电系统 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")
|
||||
Reference in New Issue
Block a user