Files
H100PowerManger/test/disSysTest/app/panels.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

441 lines
19 KiB
Python

"""数据驱动的状态/操作面板构建器。
由 BusSpec / CmdSpec 声明式描述每个母线的字段、显示方式与控制项,
build_bus_page / update_bus_page 负责创建与刷新界面,
避免每个母线重复手写布局与 if/elif 更新逻辑。
"""
import tkinter as tk
from dataclasses import dataclass
from tkinter import ttk
from . import protocol
from .protocol import BREAKER_ON, BREAKER_OFF, BREAKER_FAULT
# 配色
GREEN = "#2e7d32"
RED = "#d32f2f"
BLACK = "#212121"
GRAY = "#9e9e9e"
def breaker_color(raw):
if raw == BREAKER_ON:
return GREEN
if raw == BREAKER_OFF:
return BLACK
if raw == BREAKER_FAULT:
return RED
return GRAY
def bool_ok(raw, ok=BREAKER_ON):
return raw == ok
def make_light(parent, size=18):
canvas = tk.Canvas(parent, width=size, height=size, bg="white",
highlightthickness=0)
canvas.create_oval(2, 2, size - 2, size - 2, fill=GRAY, outline="black")
return canvas
def set_light(canvas, color):
canvas.delete("all")
canvas.create_oval(2, 2, int(canvas["width"]) - 2,
int(canvas["height"]) - 2, fill=color, outline="black")
# ------------------------------------------------------------------
@dataclass
class BusSpec:
key: str # 总线标识
title: str # 页标题
msg_cls: type
breakers: list # [(label, field)]
voltages: list # [(label, field, scale, unit)]
currents: list # [(label, field, scale, unit)]
booleans: list # [(label, field)] ok=BREAKER_ON 为正常
derived: list # [(label, getter)] getter(msg)->(text, color)
HIGH_VOL_BUS = BusSpec(
key="high_vol",
title="动力母线状态",
msg_cls=protocol.MsgDisHighVolBusFb,
breakers=[
("燃料电池断路器", "fuelCellCircuitBreaker"),
("动力锂电池断路器", "powerLithiumBatteryCircuitBreaker"),
("推进电机断路器", "propulsionMotorCircuitBreaker"),
("锂电池组仪表断路器", "lithiumBatteryGroupInstrumentCircuitBreaker"),
("DC/DC模块断路器", "dcDc5ModuleCircuitBreaker"),
("艏高压配电盒断路器", "bowHighVoltageDistributionBoxCircuitBreaker"),
("艉FT设备45断路器", "sternFTDevice45CircuitBreaker"),
("艉舵机开关1断路器", "sternRudderSwitch1CircuitBreaker"),
("艉舵机开关2断路器", "sternRudderSwitch2CircuitBreaker"),
("FB备用断路器", "fbReservedCircuitBreaker"),
("TYZ备用断路器", "tyzReservedCircuitBreaker"),
("备用断路器", "reservedCircuitBreaker"),
],
voltages=[
("动力母线电压", "powerBusVoltage", 0.1, "V"),
("A母线电压", "busbarAVoltage", 0.1, "V"),
],
currents=[
("动力母线电流", "powerBusCurrent", 0.1, "A"),
("A汇流排电流", "busbarACurrent", 0.1, "A"),
("DC/DC5模块电流", "dcDc5ModuleCurrent", 0.1, "A"),
("推进电机控制箱电流", "propulsionMotorControlBoxCurrent", 0.1, "A"),
("锂电池组(仪表)电流", "lithiumBatteryGroupMeterCurrent", 0.1, "A"),
("艏部高压配电箱电流", "bowHighVoltageDistributionBoxCurrent", 0.1, "A"),
("艉部FT装置4/5电流", "sternFTDevice45Current", 0.1, "A"),
("TYZ预留开关电流", "tyzReservedSwitchCurrent", 0.1, "A"),
("艉舵开关1电流", "sternRudderSwitch1Current", 0.1, "A"),
("艉舵开关2电流", "sternRudderSwitch2Current", 0.1, "A"),
("FB预留开关电流", "fbReservedSwitchCurrent", 0.1, "A"),
("备用电流1", "reservedCurrent1", 0.1, "A"),
("备用电流2", "reservedCurrent2", 0.1, "A"),
("备用电流3", "reservedCurrent3", 0.1, "A"),
],
booleans=[
("仪表失电信号", "meterPowerLossSignal"),
("应急失电信号", "emergencyPowerLossSignal"),
("动力母线绝缘", "powerBusInsulationStatus"),
("艏高压绝缘", "aBusInsulationStatus"),
],
derived=[
("冷却水压力", lambda m: (f"{m.coolWaterPressure * 0.1:.1f} kPa", BLACK)),
("DC/DC温度", lambda m: (f"{m.dcdcTemperature - 40} ℃", BLACK)),
],
)
HIGH_A_VOL_BUS = BusSpec(
key="high_a_vol",
title="艏部高压状态",
msg_cls=protocol.MsgDisHighAVolBusFb,
breakers=[
("艏FT设备123断路器", "bowFTDevice123CircuitBreaker"),
("执行机构断路器", "actuatorCircuitBreaker"),
("桅舵机控制箱断路器", "mastSteeringGearControlBoxCircuitBreaker"),
("XCZ断路器", "xczCircuitBreaker"),
("艏舵控制箱断路器", "bowRudderControlBoxCircuitBreaker"),
("开盖启动气缸断路器", "openWaterCoverStartCylinderCircuitBreaker"),
],
voltages=[
("汇流排B电压", "busbarBVoltage", 0.1, "V"),
],
currents=[
("汇流排B电流", "busbarBCurrent", 0.1, "A"),
("艏部FT设备123电流", "bowFTDevice123Current", 0.1, "A"),
("启闭机构电流", "actuatorCurrent", 0.1, "A"),
("桅杆舵机控制箱电流", "mastSteeringGearControlBoxCurrent", 0.1, "A"),
("XCZ电流", "xczCurrent", 0.1, "A"),
("艏舵控制箱电流", "bowRudderControlBoxCurrent", 0.1, "A"),
("敞水盖启动电缸电流", "openWaterCoverStartCylinderCurrent", 0.1, "A"),
],
booleans=[
("仪表电源失电信号", "instrumentPowerFailureSignal"),
("应急电源失电信号", "emergencyPowerFailureSignal"),
("进水报警", "waterIngressionAlarm"),
],
derived=[],
)
LOW_MAIN_BUS = BusSpec(
key="low_main",
title="仪表母线状态",
msg_cls=protocol.MsgDisLowMainBusFb,
breakers=[
("锂电池组仪表断路器", "lithiumBatteryGroupInstrumentCircuitBreaker"),
("艏低压配电盒断路器", "bowLowVoltageDistributionBoxCircuitBreaker"),
("单元4仪表48V断路器", "unit4InstrumentDC48VCircuitBreaker"),
("DC/C1直流配电板断路器", "dcC1DCDistributionPanelCircuitBreaker"),
("备用断路器1", "reservedCircuitBreaker1"),
("备用断路器2", "reservedCircuitBreaker2"),
("应急锂电池组2断路器", "emergencyLithiumBatteryGroup2CircuitBreaker"),
],
voltages=[
("仪表母线电压", "instrumentBusbarVoltage", 0.1, "V"),
("应急2汇流排电压", "emergency2BusbarVoltage", 0.1, "V"),
],
currents=[
("艏部低压配电箱电流", "bowLowVoltageDistributionBoxCurrent", 0.1, "A"),
("锂电池组(仪表)电流", "lithiumBatteryGroupInstrumentCurrent", 0.1, "A"),
("DC-C1仪表DC48V电流", "dcC1InstrumentDC48VCurrent", 0.1, "A"),
("应急锂电池组2电流", "emergencyLithiumBatteryGroup2Current", 0.1, "A"),
("单元4仪表48V电流", "unit4InstrumentDC48VCurrent", 0.1, "A"),
("复合能源管控应急DC48V电流", "compositeEnergyManagementSystemEmergencyDC48VCurrent", 0.1, "A"),
("燃料电池安保应急DC48V电流", "fuelCellSecuritySystemEmergencyDC48VCurrent", 0.1, "A"),
("PLC控制电源电流", "plcControlPowerCurrent", 0.1, "A"),
("备用电流1", "reservedCurrent1", 0.1, "A"),
("备用电流2", "reservedCurrent2", 0.1, "A"),
],
booleans=[
("仪表电源失电信号", "instrumentPowerFailureSignal"),
("应急电源失电信号", "emergencyPowerFailureSignal"),
("仪表汇流排绝缘低", "instrumentBusbarInsulationLow"),
],
derived=[],
)
LOW_BUS = BusSpec(
key="low_bus",
title="艏部低压状态",
msg_cls=protocol.MsgDisLowBusFb,
breakers=[
("单元1断路器", "unit1CircuitBreaker"),
("单元2断路器", "unit2CircuitBreaker"),
("单元3断路器", "unit3CircuitBreaker"),
("单元5断路器", "unit5CircuitBreaker"),
("艏PZ设备断路器", "bowPZDeviceCircuitBreaker"),
("备用断路器1", "reservedCircuitBreaker1"),
("备用断路器2", "reservedCircuitBreaker2"),
("应急锂电池组1断路器", "emergencyLithiumBatteryGroup1CircuitBreaker"),
],
voltages=[
("仪表母排电压", "instrumentBusbarVoltage", 0.1, "V"),
("应急1母线电压", "emergency1BusbarVoltage", 0.1, "V"),
],
currents=[
("仪表母排电流", "instrumentBusbarCurrent", 0.1, "A"),
("单元1电流", "unit1Current", 0.1, "A"),
("单元2电流", "unit2Current", 0.1, "A"),
("单元3电流", "unit3Current", 0.1, "A"),
("单元5电流", "unit5Current", 0.1, "A"),
("艏PZ设备电流", "bowPZDeviceCurrent", 0.1, "A"),
("应急锂电池组1电流", "emergencyLithiumBatteryGroup1Current", 0.1, "A"),
],
booleans=[
("仪表电失电信号", "instrumentPowerFailureSignal"),
("应急电失电信号", "emergencyPowerFailureSignal"),
("浸水报警", "waterIngressionAlarm"),
],
derived=[],
)
BUS_SPECS = {spec.key: spec for spec in (HIGH_VOL_BUS, HIGH_A_VOL_BUS,
LOW_MAIN_BUS, LOW_BUS)}
# ------------------------------------------------------------------
# 状态页构建 / 刷新
# ------------------------------------------------------------------
def build_bus_page(parent, spec):
"""构建一个母线状态页,返回用于刷新的 refs 字典。"""
page = ttk.Frame(parent, padding=6)
page.grid_columnconfigure(0, weight=1)
# ---- 顶部:电压 + 断路器 ----
top = ttk.LabelFrame(page, text="电压与断路器状态", padding=8)
top.grid(row=0, column=0, sticky="nsew", pady=(0, 6))
for c in range(3):
top.grid_columnconfigure(c, weight=1)
voltage_widgets = {}
for i, (label, field, scale, unit) in enumerate(spec.voltages):
sub = ttk.Frame(top)
sub.grid(row=0, column=i, padx=10, pady=4, sticky="w")
ttk.Label(sub, text=f"{label}({unit}):", width=20, anchor="w").pack(side="left")
value_lbl = ttk.Label(sub, text="--", width=10, anchor="e", relief="groove")
value_lbl.pack(side="right")
voltage_widgets[field] = {"widget": value_lbl, "scale": scale, "unit": unit}
ttk.Label(top, text="断路器状态:",
font=("Arial", 10, "bold")).grid(row=1, column=0,
columnspan=6, sticky="w", pady=(10, 4))
breaker_lights = {}
field_for_label = {label: f for label, f in spec.breakers}
for i, (label, _f) in enumerate(spec.breakers):
bf = tk.Frame(top, relief="solid", borderwidth=1, padx=6, pady=2)
bf.grid(row=2 + i // 3, column=i % 3, padx=6, pady=4, sticky="w")
ttk.Label(bf, text=label, width=20, anchor="w").pack(side="left")
light = make_light(bf)
light.pack(side="right")
breaker_lights[label] = light
# ---- 下部:电流 / 布尔量 / 派生量 ----
group = ttk.LabelFrame(page, text="系统状态", padding=8)
group.grid(row=1, column=0, sticky="nsew")
page.grid_rowconfigure(1, weight=1)
for c in range(3):
group.grid_columnconfigure(c, weight=1)
values = {}
booleans = {}
derived = {}
items = []
for it in spec.currents:
items.append(("value", it))
for it in spec.booleans:
items.append(("bool", it))
for it in spec.derived:
items.append(("derived", it))
for i, (kind, it) in enumerate(items):
f = ttk.Frame(group)
f.grid(row=i // 3, column=i % 3, sticky="w", padx=8, pady=3)
if kind == "value":
label, field, scale, unit = it
ttk.Label(f, text=f"{label}({unit}):", width=26, anchor="w").pack(side="left")
v = ttk.Label(f, text="--", width=10, anchor="e", relief="groove")
v.pack(side="right")
values[field] = {"widget": v, "scale": scale, "unit": unit}
elif kind == "bool":
label, field = it
ttk.Label(f, text=f"{label}:", width=26, anchor="w").pack(side="left")
v = ttk.Label(f, text="--", width=10, anchor="e", relief="groove")
v.pack(side="right")
booleans[field] = {"widget": v}
else:
label, getter = it
ttk.Label(f, text=f"{label}:", width=26, anchor="w").pack(side="left")
v = ttk.Label(f, text="--", width=10, anchor="e", relief="groove")
v.pack(side="right")
derived[label] = {"widget": v, "getter": getter}
return {
"frame": page,
"field_for_label": field_for_label,
"breaker_lights": breaker_lights,
"voltage_widgets": voltage_widgets,
"values": values,
"booleans": booleans,
"derived": derived,
}
def update_bus_page(spec, refs, msg):
"""用最新反馈消息刷新状态页。msg 为 None 时跳过。"""
if msg is None:
return
for label, light in refs["breaker_lights"].items():
field = refs["field_for_label"][label]
set_light(light, breaker_color(getattr(msg, field)))
for field, info in refs["voltage_widgets"].items():
v = getattr(msg, field) * info["scale"]
info["widget"].config(text=f"{v:.1f} {info['unit']}")
for field, info in refs["values"].items():
v = getattr(msg, field) * info["scale"]
info["widget"].config(text=f"{v:.1f} {info['unit']}")
for field, info in refs["booleans"].items():
ok = bool_ok(getattr(msg, field))
info["widget"].config(text="正常" if ok else "故障",
foreground=GREEN if ok else RED)
for label, info in refs["derived"].items():
text, color = info["getter"](msg)
info["widget"].config(text=text, foreground=color)
# ------------------------------------------------------------------
# 控制面板
# ------------------------------------------------------------------
@dataclass
class CmdSpec:
key: str
title: str
msg_cls: type
dest: str # 'c1' / 'c2'
actions: list # [(label, field)]
HVB_CMD = CmdSpec("hvb", "动力母线配电指令", protocol.MsgDisHVBCmd, "c1", [
("燃料电池断路器", "fuelCellCircuitBreaker"),
("动力锂电池断路器", "powerLithiumBatteryCircuitBreaker"),
("推进电机断路器", "propulsionMotorCircuitBreaker"),
("锂电池组仪表断路器", "lithiumBatteryGroupInstrumentCircuitBreaker"),
("DC/DC模块断路器", "dcDc5ModuleCircuitBreaker"),
("艏高压配电盒断路器", "bowHighVoltageDistributionBoxCircuitBreaker"),
("艉FT设备45断路器", "sternFTDevice45CircuitBreaker"),
("艉舵机开关1断路器", "sternRudderSwitch1CircuitBreaker"),
("艉舵机开关2断路器", "sternRudderSwitch2CircuitBreaker"),
("FB备用断路器", "fbReservedCircuitBreaker"),
("TYZ备用断路器", "tyzReservedCircuitBreaker"),
("备用断路器", "reservedCircuitBreaker"),
("DC/DC模块启动", "dcdc5Module"),
("冷却系统启动", "coolingSystem"),
])
HVBA_CMD = CmdSpec("hvba", "艏部高压配电指令", protocol.MsgDisHVBACmd, "c1", [
("艏FT设备123断路器", "bowFTDevice123CircuitBreaker"),
("执行机构断路器", "actuatorCircuitBreaker"),
("桅舵机控制箱断路器", "mastSteeringGearControlBoxCircuitBreaker"),
("XCZ断路器", "xczCircuitBreaker"),
("艏舵控制箱断路器", "bowRudderControlBoxCircuitBreaker"),
("开盖启动气缸断路器", "openWaterCoverStartCylinderCircuitBreaker"),
])
LVMB_CMD = CmdSpec("lvmb", "仪表母线配电指令", protocol.MsgDisLVMBCmd, "c2", [
("锂电池组仪表断路器", "lithiumBatteryGroupInstrumentCircuitBreaker"),
("艏低压配电盒断路器", "bowLowVoltageDistributionBoxCircuitBreaker"),
("单元4仪表48V断路器", "unit4InstrumentDC48VCircuitBreaker"),
("DC/C1直流配电板断路器", "dcC1DCDistributionPanelCircuitBreaker"),
("备用断路器1", "reservedCircuitBreaker1"),
("备用断路器2", "reservedCircuitBreaker2"),
("应急锂电池组2断路器", "emergencyLithiumBatteryGroup2CircuitBreaker"),
])
LVB_CMD = CmdSpec("lvb", "艏部低压配电指令", protocol.MsgDisLVBCmd, "c2", [
("单元1断路器", "unit1CircuitBreaker"),
("单元2断路器", "unit2CircuitBreaker"),
("单元3断路器", "unit3CircuitBreaker"),
("单元5断路器", "unit5CircuitBreaker"),
("艏PZ设备断路器", "bowPZDeviceCircuitBreaker"),
("备用断路器1", "reservedCircuitBreaker1"),
("备用断路器2", "reservedCircuitBreaker2"),
("应急锂电池组1断路器", "emergencyLithiumBatteryGroup1CircuitBreaker"),
])
CMD_SPECS = {spec.key: spec for spec in (HVB_CMD, HVBA_CMD, LVMB_CMD, LVB_CMD)}
def build_control_panel(parent, spec, on_cmd):
"""构建控制面板。on_cmd(spec, field, state)。"""
frame = ttk.LabelFrame(parent, text=spec.title, padding=10)
frame.pack(fill="x", pady=5)
for i, (label, field) in enumerate(spec.actions):
row, col = i // 3, i % 3
bf = ttk.Frame(frame)
bf.grid(row=row, column=col, padx=10, pady=2, sticky="w")
ttk.Label(bf, text=label, width=20).pack(side="left")
ttk.Button(bf, text="合", width=3,
command=lambda f=field: on_cmd(spec, f, BREAKER_ON)).pack(side="left", padx=2)
ttk.Button(bf, text="分", width=3,
command=lambda f=field: on_cmd(spec, f, BREAKER_OFF)).pack(side="left", padx=2)
return frame
# ------------------------------------------------------------------
# DC/DC 故障字显示(供状态页与独立弹窗复用)
# ------------------------------------------------------------------
FAULT1_LABELS = ["过温关机", "输出过流", "输出过压", "输出欠压",
"输入过压", "输入欠压", "输出短路保护", "内部故障"]
FAULT2_LABELS = ["通信故障", "工作状态", "冷却泵1故障", "冷却泵2故障",
"冷却水泄漏", "冷却泵1运行状态", "冷却泵2运行状态", "保留"]
def build_dcdc_display(parent, title="DC/DC 故障字"):
"""构建 DC/DC 故障字灯组,返回 refs={"words": {1: [canvas...], 2: [...]}}。"""
frame = ttk.LabelFrame(parent, text=title, padding=8)
frame.grid_columnconfigure(0, weight=1)
words = {1: [], 2: []}
for wi, labels in ((1, FAULT1_LABELS), (2, FAULT2_LABELS)):
ttk.Label(frame, text=f"DC/DC故障字{wi}:",
font=("Microsoft YaHei", 9, "bold")).grid(
row=(wi - 1) * 2, column=0, sticky="w", pady=(6, 2))
for i, label in enumerate(labels):
pf = tk.Frame(frame, borderwidth=1, relief="solid", padx=4, pady=2)
pf.grid(row=(wi - 1) * 2 + 1 + i // 4, column=i % 4, padx=5, pady=3, sticky="w")
ttk.Label(pf, text=label).pack(side="left", padx=(0, 6))
light = make_light(pf, 18)
light.pack(side="right")
words[wi].append(light)
return {"widget": frame, "words": words}
def update_dcdc_display(refs, word1, word2):
for i in range(8):
set_light(refs["words"][1][i], RED if (word1 & (1 << i)) else GREEN)
set_light(refs["words"][2][i], RED if (word2 & (1 << i)) else GREEN)