From 57dd677271a04ab569ba0101a149ad2498b89ce6 Mon Sep 17 00:00:00 2001 From: zhaojingkui <1553836110@qq.com> Date: Tue, 21 Jan 2025 19:34:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/msg/uPower_fcsState_st.json | 38 +-- data/msg/uPower_liBatState_st.json | 22 +- src/pPowerManger/PowerManger.cpp | 22 +- src/pPowerManger/ccuUdpMsg.h | 4 + test/model/untitled.slx | Bin 0 -> 77597 bytes test/model/untitled.slx.original | Bin 0 -> 61108 bytes test/scripts/TestGUI.py | 46 +++ test/scripts/{ => back}/gui.py | 0 test/scripts/{ => back}/guiEle.py | 0 test/scripts/{ => back}/guiInput.py | 0 test/scripts/{ => back}/test.py | 0 test/scripts/back/testGui.py | 44 +++ test/scripts/{ => back}/until.py | 0 test/scripts/ccuSys.py | 20 ++ test/scripts/disSys.py | 502 ++++++++++++++++++++++++++++ test/scripts/logWin.py | 78 +++++ test/scripts/udpComm.py | 106 ++++++ 17 files changed, 844 insertions(+), 38 deletions(-) create mode 100644 test/model/untitled.slx create mode 100644 test/model/untitled.slx.original create mode 100644 test/scripts/TestGUI.py rename test/scripts/{ => back}/gui.py (100%) rename test/scripts/{ => back}/guiEle.py (100%) rename test/scripts/{ => back}/guiInput.py (100%) rename test/scripts/{ => back}/test.py (100%) create mode 100644 test/scripts/back/testGui.py rename test/scripts/{ => back}/until.py (100%) create mode 100644 test/scripts/ccuSys.py create mode 100644 test/scripts/disSys.py create mode 100644 test/scripts/logWin.py create mode 100644 test/scripts/udpComm.py diff --git a/data/msg/uPower_fcsState_st.json b/data/msg/uPower_fcsState_st.json index d23d9f2..cb8b10a 100644 --- a/data/msg/uPower_fcsState_st.json +++ b/data/msg/uPower_fcsState_st.json @@ -1,30 +1,30 @@ { "backpressure1_fp32": -1.6155215737897056e+25, "backpressure2_fp32": 1.9618178500547439e-44, - "ch4oConcent1_fp32": 3.7196912217041245e-12, - "ch4oConcent2_fp32": 1.1664828806579075e-38, - "ch4o_fp32": 1.1754855226419623e-38, - "excTemp_fp32": 1.1664828806579075e-38, - "faultCode1_uint16": 29888, - "faultCode2_uint16": 49624, + "ch4oConcent1_fp32": 1687.0, + "ch4oConcent2_fp32": 1.1726195870227251e-38, + "ch4o_fp32": 1.1753983618774813e-38, + "excTemp_fp32": 1.1726195870227251e-38, + "faultCode1_uint16": 38848, + "faultCode2_uint16": 21499, "faultCode3_uint16": 0, - "fcuCurIns_fp32": -1775767650304.0, - "fcuCurPower_fp32": 7.894814654516588e-39, + "fcuCurIns_fp32": 2.1062722967662487e+22, + "fcuCurPower_fp32": 7.9041248815135621e-39, "fcuCur_fp32": 1.7755460477889746e-38, - "fcuFaultLeve_uint8": 194, + "fcuFaultLeve_uint8": 226, "fcuOutIns_fp32": 0.0, "fcuOutPower_fp32": 2.152394441202919e-42, "fcuState_uint8": 124, "fcuVolIns_fp32": 0.0, - "fcuVolPower_fp32": 1.1664542941692352e-38, - "fcuVol_fp32": 1.1754855226419623e-38, + "fcuVolPower_fp32": 1.1725886183266636e-38, + "fcuVol_fp32": 1.1753983618774813e-38, "flameDet_uint8": 0, - "h2Concent1_fp32": 3.8200553831302386e-12, - "h2Concent2_fp32": 1.1664828806579075e-38, - "o2Concent1_fp32": 4.5144443738820428e-14, - "o2Concent2_fp32": 1.1664828806579075e-38, - "o2Level_fp32": 1.1664542941692352e-38, - "o2_fp32": -1075184933011456.0, - "powLimit_fp32": 1.166482740528061e-38, - "powerGen_fp32": -1075322371964928.0 + "h2Concent1_fp32": 1715.25, + "h2Concent2_fp32": 1.1726195870227251e-38, + "o2Concent1_fp32": 19.4140625, + "o2Concent2_fp32": 1.1726195870227251e-38, + "o2Level_fp32": 1.1725886183266636e-38, + "o2_fp32": -1.5729414295823943e+36, + "powLimit_fp32": 1.1726194468928787e-38, + "powerGen_fp32": -1.5732659481360528e+36 } \ No newline at end of file diff --git a/data/msg/uPower_liBatState_st.json b/data/msg/uPower_liBatState_st.json index 1f59671..031c1ab 100644 --- a/data/msg/uPower_liBatState_st.json +++ b/data/msg/uPower_liBatState_st.json @@ -6,16 +6,16 @@ "insBatSoc_uint8": 0, "insBatTotalElect_uint16": 2, "insChargeState_uint8": 71, - "insCurSocHold1_uint8": 41, - "insCurSocHold2_uint8": 204, - "insCurSocHold3_uint8": 4, + "insCurSocHold1_uint8": 65, + "insCurSocHold2_uint8": 221, + "insCurSocHold3_uint8": 175, "insCurrent_uint16": 0, - "insLinkVol_uint16": 52163, + "insLinkVol_uint16": 56547, "insNegRelayState_uint8": 0, - "insNegRes_uint16": 52265, - "insPackVol_uint16": 32516, + "insNegRes_uint16": 56641, + "insPackVol_uint16": 32687, "insPosRelayState_uint8": 0, - "insPosRes_uint16": 18488, + "insPosRes_uint16": 38968, "insfaultCode_uint8": 176, "powBatCurElect_uint16": 0, "powBatSoc_uint8": 0, @@ -27,12 +27,12 @@ "powCurToInsLimit1_uint8": 48, "powCurToinsLimit2_uint8": 0, "powCur_uint16": 15376, - "powLinkVol_uint16": 32516, + "powLinkVol_uint16": 32687, "powNegRelayState_uint8": 0, - "powNegRes_uint16": 32516, + "powNegRes_uint16": 32687, "powPosRelayState_uint8": 0, - "powPosRes_uint16": 52247, + "powPosRes_uint16": 56623, "powerBatLimit_uint16": 0, "powerPackVol_uint16": 0, - "powfaultCode_uint8": 68 + "powfaultCode_uint8": 148 } \ No newline at end of file diff --git a/src/pPowerManger/PowerManger.cpp b/src/pPowerManger/PowerManger.cpp index 9fcabfc..ff70386 100644 --- a/src/pPowerManger/PowerManger.cpp +++ b/src/pPowerManger/PowerManger.cpp @@ -104,8 +104,7 @@ bool PowerManger::Iterate() updateTime(); //2. 运行状态机 m_fsm.run(m_pmState,this); - cout <<"CURRENT STATE:" << m_pmState.name << " | CURRENT EVENT :" << m_pmEvent << endl; - + // cout <<"CURRENT STATE:" << m_pmState.name << " | CURRENT EVENT :" << m_pmEvent << endl; AppCastingMOOSApp::Iterate(); //3. 输出报告 buildPowerSysReport(); @@ -799,14 +798,20 @@ int PowerManger::handleInite_s1(FSM_State &fsm, void* pData) { cout << fsm.name << endl; PowerManger* pm = (PowerManger*)pData; - //1. 检查复合管控器连接 double StartTime = pm->GetAppStartTime(); while (!pm->m_connected) { + + disHighVolBusCmd a; + a.fuelCellCircuitBreaker = DIS_BREAKER_OFF; + a.propulsionMotorCircuitBreaker = DIS_BREAKER_OFF; + pm->m_udpComm.sendDisSysCmd(a); + + MOOSPause(1000); pm->updateTime(); double dft = abs(pm->m_current_time - StartTime); - if(dft > 1) + if(dft > 10) { pm->m_pmState = pm->m_Fault_s99; break; @@ -835,18 +840,19 @@ int PowerManger::handleInite_s1(FSM_State &fsm, void* pData) // 首先闭合应急电源开关 //TODO 检查并配置当前配电装置状态 disHighVolBusCmd a; - a.fuelCellCircuitBreaker = 0xAA; - a.propulsionMotorCircuitBreaker = 0xAA; + a.fuelCellCircuitBreaker = DIS_BREAKER_OFF; + a.propulsionMotorCircuitBreaker = DIS_BREAKER_OFF; pm->m_udpComm.sendDisSysCmd(a); MOOSPause(1000); - // pm->workCondition = ""; return 0; } int PowerManger::handleFault_s99(FSM_State &fsm, void* pData) { - cout << fsm.name << endl; + // cout << fsm.name << endl; + PowerManger* pm = (PowerManger*)pData; + pm->printState(fsm,"故障"); return 0; } diff --git a/src/pPowerManger/ccuUdpMsg.h b/src/pPowerManger/ccuUdpMsg.h index 20f7f47..46607ad 100644 --- a/src/pPowerManger/ccuUdpMsg.h +++ b/src/pPowerManger/ccuUdpMsg.h @@ -21,6 +21,10 @@ #define DIS_HVBUSB_CMD_ID 0x0003 #define DIS_LVBUS_CMD_ID 0x0004 +#define DIS_BREAKER_ON 0x55 +#define DIS_BREAKER_OFF 0xAA +#define DIS_FAULTOFF 0x5A + typedef unsigned int Checksum; typedef unsigned char cChecksum; diff --git a/test/model/untitled.slx b/test/model/untitled.slx new file mode 100644 index 0000000000000000000000000000000000000000..6657b567a75679d6062fa0995f1c7a07e290c3a9 GIT binary patch literal 77597 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%0ABNLPpMf}EBg3sr>V%1#9JCQ;KanJqCn7|t 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 Ol<4E#@7~44Nc=zBVBC=a literal 0 HcmV?d00001 diff --git a/test/model/untitled.slx.original b/test/model/untitled.slx.original new file mode 100644 index 0000000000000000000000000000000000000000..4a8ec29d29ac6b33dbd749d0cc32717330d6a9b9 GIT binary patch literal 61108 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 literal 0 HcmV?d00001 diff --git a/test/scripts/TestGUI.py b/test/scripts/TestGUI.py new file mode 100644 index 0000000..b224d1b --- /dev/null +++ b/test/scripts/TestGUI.py @@ -0,0 +1,46 @@ +import socket +import struct +import tkinter as tk +import threading +from udpmsg import * +from datetime import datetime +from disSys import * +from logWin import * +from udpComm import * + + +# 创建UDP通信套接字 +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +# 目标IP地址和端口号 +target_ip = 'localhost' +target_port = 5001 +server_address = (target_ip, target_port) +sock.bind(server_address) +# 要发送的消息 +# 创建主窗口 +root = tk.Tk() +root.title("能源管理系统调试") +# 创建消息显示窗口 +logFrame = tk.LabelFrame(root, text="消息显示") +logFrame.grid(row=1, columnspan=3 ,column=0, sticky='nsw') +logwin = logWin(logFrame) +logwin.grid(row=0, column=0, sticky='nesw') +# 创建配电窗口 +disSysFrame = tk.LabelFrame(root, text="配电系统") +disSysFrame.grid(row=0, column=0, sticky='ne') +disSysWin = disSys(disSysFrame) +disSysWin.grid(row=0, column=0, sticky='ne') + +# 创建并启动接收和发送消息的线程 +thread = threading.Thread(target=receive_messages, args=(sock, logwin, disSysWin)) +thread.daemon = True +thread.start() +thread2 = threading.Thread(target=send_messages, args=(sock, logwin, disSysWin)) +thread2.daemon = True +thread2.start() + + +# 运行主循环 +root.mainloop() +# 关闭套接字 +sock.close() \ No newline at end of file diff --git a/test/scripts/gui.py b/test/scripts/back/gui.py similarity index 100% rename from test/scripts/gui.py rename to test/scripts/back/gui.py diff --git a/test/scripts/guiEle.py b/test/scripts/back/guiEle.py similarity index 100% rename from test/scripts/guiEle.py rename to test/scripts/back/guiEle.py diff --git a/test/scripts/guiInput.py b/test/scripts/back/guiInput.py similarity index 100% rename from test/scripts/guiInput.py rename to test/scripts/back/guiInput.py diff --git a/test/scripts/test.py b/test/scripts/back/test.py similarity index 100% rename from test/scripts/test.py rename to test/scripts/back/test.py diff --git a/test/scripts/back/testGui.py b/test/scripts/back/testGui.py new file mode 100644 index 0000000..527f5a6 --- /dev/null +++ b/test/scripts/back/testGui.py @@ -0,0 +1,44 @@ +import socket +import struct +import tkinter as tk +import threading +from udpmsg import * +from datetime import datetime +from tkinter import font +from disSys import * +from guiEle import ElementDisplayApp +from until import * +from guiInput import InputGUI + +# 创建UDP套接字 +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +server_address = ('localhost', 5001) +sock.bind(server_address) + +# 创建主窗口 +root = tk.Tk() +root.title("能源管理系统调试") + +disSysWin = disSys(root) +disSysWin.grid(row=0, column=0, sticky='ne') + + +# 创建一个文本框用于显示消息 +custom_font = font.Font(family="Arial", size=12, weight="normal") +text_box = tk.Text(root, height=10, width=50, font=custom_font) +text_box.grid(row=6, column=0, sticky='ne') + +# 创建并启动接收和发送消息的线程 +# thread = threading.Thread(target=receive_messages, args=(sock, text_box,eleplay)) +# thread.daemon = True +# thread.start() + +# thread2 = threading.Thread(target=send_messages, args=(sock, eleplay)) +# thread2.daemon = True +# thread2.start() + +# 运行主循环 +root.mainloop() + +# 关闭套接字 +sock.close() diff --git a/test/scripts/until.py b/test/scripts/back/until.py similarity index 100% rename from test/scripts/until.py rename to test/scripts/back/until.py diff --git a/test/scripts/ccuSys.py b/test/scripts/ccuSys.py new file mode 100644 index 0000000..ece51a8 --- /dev/null +++ b/test/scripts/ccuSys.py @@ -0,0 +1,20 @@ +import socket +import struct +import tkinter as tk +import threading +from udpmsg import * +from datetime import datetime +from tkinter import font +from guiEle import ElementDisplayApp +from until import * +from guiInput import InputGUI +from tkinter import Frame + + +class ccuSys(Frame): + def __init__(self,root): + super().__init__(root) + self.createWidgets() + + def createWidgets(self): + pass \ No newline at end of file diff --git a/test/scripts/disSys.py b/test/scripts/disSys.py new file mode 100644 index 0000000..f0ce0f6 --- /dev/null +++ b/test/scripts/disSys.py @@ -0,0 +1,502 @@ +import socket +import struct +import tkinter as tk +import threading +from udpmsg import * +from datetime import datetime +from tkinter import font +from tkinter import Frame +from tkinter.ttk import * + +class disSys(Frame): + def __init__(self,root): + super().__init__(root) + # 当前接受的消息,用于显示和更新 + self.disHVB = msg_disHighVolBusFbMsg() + self.disHVBA = msg_disHighAVolBusFbMsg() + self.disHVBB = msg_disHighBVolBusFbMsg() + self.disLVB = msg_disLowBusFbMsg() + # 初始化列框架列表 + self.col_frames = [] + + self.createWidgets() + self.root = root + self.root.after(2000, self.update_disp) + def createWidgets(self): + # 定义每组的颜色 + self.colors = ["#FFC0CB", "#ADD8E6", "#90EE90", "#FFD700"] + # 创建自定义字体 + self.custom_font = font.Font(family="Arial", size=12, weight='normal') + # 创建4个Frame来分组显示元素的值 + # 断路器的frame + title=list(['高压母线断路器','高电压A母线断路器','高电压B母线断路器','低电压母线断路器']) + self.frames = [] + for i in range(4): + frame = tk.Frame(self, bg=self.colors[i], padx=10, pady=10) + # frame.pack(padx=10, pady=10, expand=True, fill='both') + title_label = tk.Label(frame, text=title[i],bg=self.colors[i],relief='solid' ,font=("Arial", 16, "bold")) + title_label.grid(row=0, column=0, columnspan=4, pady=(2, 2), sticky="nsew") + self.frames.append(frame) + self.frames[0].grid(row=0, column=0, sticky='nsew') + self.frames[1].grid(row=0, column=1, sticky='nsew') + self.frames[2].grid(row=1, column=0, sticky='nsew') + self.frames[3].grid(row=1, column=1, sticky='nsew') +################################################故障选择######################################################### + inputFrame = tk.LabelFrame(self, bg='Azure',text='故障选择',font=self.custom_font ,padx=10, pady=10) + inputFrame.grid(row=0, column=2, columnspan=2, padx=2, pady=2, sticky="nsew") + # # 添加输入框来更新值 TODO:添加输入框,更新故障信息 + self.checkboxes = [] + self.checkboxesVar = [] + self.faultDict1 = {0:'动力母线绝缘低', + 1:'A汇流排绝缘低', + 2:'仪表电源失电', + 3:'应急电源失电', + 4:'DC/DC模块过温'} + self.faultDict2 = {0:'仪表电源失电', + 1:'应急电源失电', + 2:'浸水报警'} + self.faultDict3 = {0:'仪表电源失电', + 1:'应急电源失电', + 2:'仪表汇流排绝缘低'} + self.faultDict4 = {0:'仪表失电', + 1:'浸水报警'} + spl_linenum = 1 + for i, dictionary in enumerate([self.faultDict1, self.faultDict2, self.faultDict3, self.faultDict4]): + self.checkboxes.append([]) + self.checkboxesVar.append([]) + spl = Separator(inputFrame, orient='vertical') + spl.grid(row=0, column=spl_linenum+i*2, rowspan=5, pady=(2, 2), sticky="nsew") + for j, (key, value) in enumerate(dictionary.items()): + checkboxVar = tk.IntVar() + checkbox = tk.Checkbutton(inputFrame, text=value, variable=checkboxVar,font=self.custom_font,anchor="w", + command=lambda i=i, j=j: self.cheak_fault(i, j)) + checkbox.grid(row=j, column=i*2, padx=1, pady=1, sticky="nsew") + self.checkboxes[i].append(checkbox) + self.checkboxesVar[i].append(checkboxVar) +################################################电路状态######################################################### + dispFrame = tk.LabelFrame(self, bg='Azure',text='电路状态',font=self.custom_font ,padx=10, pady=10) + dispFrame.grid(row=1, column=2, columnspan=2, padx=2, pady=2, sticky="nsew") # 调整输入框的上下边距并居中显示 + value = '关闭' + self.labelDcDc = tk.Label(dispFrame, text=f"{'DC/DC模块'}:{value}", font=self.custom_font, relief='groove' ,anchor="w") + self.labelDcDc.grid(row=0, column=0, padx=1, pady=1, sticky="nsew") + self.DcDcbutton = tk.Button(dispFrame, text="TURN", command=self.toggle_DcDc) + self.DcDcbutton.grid(row=0, column=1, padx=1, pady=1, sticky="nsew") + + self.labelWaterInput = tk.Label(dispFrame, text=f"{'冷却水进水阀'}:{value}", font=self.custom_font, relief='groove' ,anchor="w") + self.labelWaterInput.grid(row=1, column=0, padx=1, pady=1, sticky="nsew") + self.buttonWaterInput = tk.Button(dispFrame, text="TURN", command=self.toggle_WaterInput) + self.buttonWaterInput.grid(row=1, column=1, padx=1, pady=1, sticky="nsew") + + self.labelWaterOutput = tk.Label(dispFrame, text=f"{'冷却水出水阀'}:{value}", font=self.custom_font, relief='groove' ,anchor="w") + self.labelWaterOutput.grid(row=2, column=0, padx=1, pady=1, sticky="nsew") + self.buttonWaterOutput = tk.Button(dispFrame, text="TURN", command=self.toggle_WaterOutput) + self.buttonWaterOutput.grid(row=2, column=1, padx=1, pady=1, sticky="nsew") + + self.buttonCurrenStateWin = tk.Button(dispFrame, text="显示电路状态", command=self.show_CurrentState) + self.buttonCurrenStateWin.grid(row=0, rowspan=3 , columnspan=2 , column=3,padx=10, pady=1, sticky="nsew") +################################################断路器操作######################################################### + # 定义四个字典来存储元素的值 + self.dict1={'燃料电池断路器':'闭合', + '动力锂电池断路器':'闭合', + '推进电机断路器':'闭合', + '仪表锂电池断路器':'闭合', + 'DC/DC5模块断路器':'闭合', + '艏部高压配电断路器':'闭合', + '艏部FT4/5断路器':'闭合', + '艉舵1/2断路器':'闭合', + '艉舵3/4断路器':'闭合', + 'FB预留断路器':'闭合', + 'TYZ预留断路器':'闭合', + '预留断路器':'闭合'} + self.dict2 = {'艏部FT1-3断路器':'闭合', + '启闭机构断路器':'闭合', + '桅杆舵机断路器':'闭合', + 'XCZ断路器':'闭合', + '艏舵断路器':'闭合', + '敞开电缸断路器':'闭合'} + self.dict3 = {'仪表锂电池断路器':'闭合', + '艏部低压断路器':'闭合', + 'UNIT4断路器':'闭合', + 'DC/DC1断路器':'闭合', + '预留断路器1':'闭合', + '预留断路器2':'闭合', + '艉部应急电池开关':'闭合'} + self.dict4 = {'UNIT1断路器':'闭合', + 'UNIT2断路器':'闭合', + 'UNIT3断路器':'闭合', + 'UNIT5断路器':'闭合', + '艏部抛载断路器':'闭合', + '预留断路器1':'闭合', + '预留断路器2':'断开', + '艏部应急电池开关':'闭合'} + self.labels = [] + for i, dictionary in enumerate([self.dict1, self.dict2, self.dict3, self.dict4]): + self.labels.append([]) + for j, (key, value) in enumerate(dictionary.items()): + # 根据元素的值选择颜色 + if value == "闭合": + color = "green" + else: + color = "red" + label = tk.Label(self.frames[i], text=f"{key}: {value}", fg=color, font=self.custom_font, relief='groove' ,anchor="w") + if (j%2==0): + label.grid(row=j//2 + 1, column=j%2, padx=1, pady=1,sticky="nsew") + else: + label.grid(row=j//2 + 1, column=j%2+1, padx=1, pady=1,sticky="nsew") + self.labels[i].append(label) + # 添加按钮来改变字典中的值 + button = tk.Button(self.frames[i], text="TURN", command=lambda i=i, j=j: self.toggle_value(i, j)) + if (j%2==0): + button.grid(row=j//2+1, column=j%2+1, padx=1, pady=1) + else: + button.grid(row=j//2+1, column=j%2+2, padx=1, pady=1, sticky="nsew") + self.update_disp() + + # 创建保存电流及电压信息的表格 + self.disHVBData = [] + self.disHVBAData = [] + self.disHVBBData = [] + self.disLVBData = [] + + def update_disp(self): + for i, dictionary in enumerate([self.dict1, self.dict2, self.dict3, self.dict4]): + for j, (key, value) in enumerate(dictionary.items()): + # 根据元素的值选择颜色 + if value == "闭合": + color = "green" + else: + color = "red" + self.labels[i][j].config(text=f"{key}: {value}", fg=color) + + + def toggle_value(self, i, j): + dictionary = [self.dict1, self.dict2, self.dict3, self.dict4][i] + key = list(dictionary.keys())[j] + if dictionary[key] == "闭合": + dictionary[key] = "断开" + else: + dictionary[key] = "闭合" + print(f'{key}操作') + self.update_disp() + + def toggle_DcDc(self): + if (self.labelDcDc.cget("text")[-1] == '开'): + self.labelDcDc.config(text=f"{'DC/DC模块'}:关闭") + else: + self.labelDcDc.config(text=f"{'DC/DC模块'}:打开") + print('DC/DC模块操作') + + def toggle_WaterInput(self): + if (self.labelWaterInput.cget("text")[-1] == '开'): + self.labelWaterInput.config(text=f"{'冷却水进水阀'}:关闭") + else: + self.labelWaterInput.config(text=f"{'冷却水进水阀'}:打开") + print('冷却水进水阀操作') + + def toggle_WaterOutput(self): + if (self.labelWaterOutput.cget("text")[-1] == '开'): + self.labelWaterOutput.config(text=f"{'冷却水出水阀'}:关闭") + else: + self.labelWaterOutput.config(text=f"{'冷却水出水阀'}:打开") + print('冷却水出水阀操作') + + def cheak_fault(self,i,j): + #TODO:实现故障更新和选择 + print(self.checkboxes[i][j].cget("text"),self.checkboxesVar[i][j].get()) + # print(self.checkboxesVar[i][j].get()) + + def update_currentState(self): + # 更新所有数值显示 + for struct, name, entry in self.state_entries: + entry.delete(0, tk.END) + entry.insert(0, str(getattr(struct, name))) + + print('电路状态已更新') + def _create_scrollable_column(self, parent_frame, title, data_struct, state_font): + """创建带滚动条的列""" + col_frame = tk.LabelFrame(parent_frame, text=title, font=state_font, + bd=2, relief=tk.GROOVE, padx=5, pady=5) + col_frame.grid(row=0, column=len(self.col_frames), padx=5, pady=5, sticky="nsew") + col_frame.rowconfigure(0, weight=1) + col_frame.columnconfigure(0, weight=1) + self.col_frames.append(col_frame) + + # 创建滚动条容器 + canvas = tk.Canvas(col_frame) + v_scrollbar = tk.Scrollbar(col_frame, orient="vertical", command=canvas.yview) + h_scrollbar = tk.Scrollbar(col_frame, orient="horizontal", command=canvas.xview) + + # 创建可滚动内容框架 + scrollable_frame = tk.Frame(canvas) + canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") + + # 配置滚动区域 + def _configure_scrollregion(e): + canvas.configure(scrollregion=canvas.bbox("all")) + canvas.config(height=min(e.height, 600)) + canvas.yview_moveto(0) + canvas.xview_moveto(0) + + scrollable_frame.bind("", _configure_scrollregion) + + # 配置滚动条 + canvas.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set) + h_scrollbar.config(command=canvas.xview) + v_scrollbar.config(command=canvas.yview) + + # 布局 + canvas.grid(row=0, column=0, sticky="nsew") + v_scrollbar.grid(row=0, column=1, sticky="ns") + h_scrollbar.grid(row=1, column=0, sticky="ew") + + # 添加数据项 + for name, value in data_struct.__dict__.items(): + row_frame = tk.Frame(scrollable_frame) + row_frame.pack(fill=tk.X, padx=5, pady=2) + + name_label = tk.Label(row_frame, text=name, font=state_font, width=30, anchor="w") + name_label.pack(side=tk.LEFT, padx=5) + + entry = tk.Entry(row_frame, font=state_font, width=20) + entry.pack(side=tk.RIGHT, padx=5, fill=tk.X, expand=True) + entry.insert(0, str(value)) + entry.config(state='readonly') + + self.state_entries.append((data_struct, name, entry)) + + def show_CurrentState(self): + """显示当前电路状态窗口""" + # 初始化窗口 + self.currentStateWin = tk.Toplevel(self) + self.currentStateWin.title("电路状态") + self.currentStateWin.geometry("1400x800") + self.currentStateWin.minsize(1000, 600) + self.currentStateWin.protocol("WM_DELETE_WINDOW", self.on_close_currentState) + + # 创建主容器 + main_frame = tk.Frame(self.currentStateWin) + main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + # 配置网格布局 + for i in range(4): + main_frame.columnconfigure(i, weight=1) + + # 创建自定义字体 + state_font = font.Font(family="Arial", size=10, weight='bold') + + # 初始化状态条目列表 + self.state_entries = [] + self.col_frames = [] + + # 创建各状态列 + self._create_scrollable_column(main_frame, "高压母线", self.disHVB, state_font) + self._create_scrollable_column(main_frame, "高电压A母线", self.disHVBA, state_font) + self._create_scrollable_column(main_frame, "高电压B母线", self.disHVBB, state_font) + self._create_scrollable_column(main_frame, "低电压母线", self.disLVB, state_font) + + # 配置主框架布局权重 + main_frame.rowconfigure(1, weight=1) + + # 添加底部按钮容器 + btn_frame = tk.Frame(main_frame) + btn_frame.grid(row=2, column=0, columnspan=4, pady=10, sticky="sew") + + # 添加更新按钮 + update_btn = tk.Button(btn_frame, text="立即更新", font=state_font, + command=self.update_currentState) + update_btn.pack(fill=tk.X, padx=5, pady=5) + # update_btn.pack(side=tk.LEFT, padx=5) + + # 启动定时更新 + self.update_interval = 1000 # 5秒 + self.schedule_update() + + def on_close_currentState(self): + """窗口关闭回调""" + if hasattr(self, 'update_job'): + self.currentStateWin.after_cancel(self.update_job) + self.currentStateWin.destroy() + + def schedule_update(self): + """安排定时更新""" + self.update_currentState() + self.update_job = self.currentStateWin.after(self.update_interval, self.schedule_update) + def updata_data(self): + if (self.dict1['燃料电池断路器']=='闭合'): + self.disHVB.DCB1 = 0x55 + else: + self.disHVB.DCB1 = 0xAA + + if (self.dict1['动力锂电池断路器']=='闭合'): + self.disHVB.DCB2 = 0x55 + else: + self.disHVB.DCB2 = 0xAA + + if (self.dict1['推进电机断路器']=='闭合'): + self.disHVB.ZK1 = 0x55 + else: + self.disHVB.ZK1 = 0xAA + + if (self.dict1['仪表锂电池断路器']=='闭合'): + self.disHVB.ZK2 = 0x55 + else: + self.disHVB.ZK2 = 0xAA + + if (self.dict1['DC/DC5模块断路器']=='闭合'): + self.disHVB.ZK3 = 0x55 + else: + self.disHVB.ZK3 = 0xAA + + if (self.dict1['艏部高压配电断路器']=='闭合'): + self.disHVB.ZK4 = 0x55 + else: + self.disHVB.ZK4 = 0xAA + + if (self.dict1['艏部FT4/5断路器']=='闭合'): + self.disHVB.ZK5 = 0x55 + else: + self.disHVB.ZK5 = 0xAA + + if (self.dict1['艉舵1/2断路器']=='闭合'): + self.disHVB.ZK6 = 0x55 + else: + self.disHVB.ZK6 = 0xAA + + if (self.dict1['艉舵3/4断路器']=='闭合'): + self.disHVB.ZK7 = 0x55 + else: + self.disHVB.ZK7 = 0xAA + + if (self.dict1['FB预留断路器']=='闭合'): + self.disHVB.ZK8 = 0x55 + else: + self.disHVB.ZK8 = 0xAA + + if (self.dict1['TYZ预留断路器']=='闭合'): + self.disHVB.ZK9 = 0x55 + else: + self.disHVB.ZK9 = 0xAA + + if (self.dict1['预留断路器']=='闭合'): + self.disHVB.ZK10 = 0x55 + else: + self.disHVB.ZK10 = 0xAA + ###################################################### + if (self.dict2['艏部FT1-3断路器']=='闭合'): + self.disHVBA.bowFTDevice123CircuitBreaker = 0x55 + else: + self.disHVBA.bowFTDevice123CircuitBreaker = 0xAA + + if (self.dict2['启闭机构断路器']=='闭合'): + self.disHVBA.actuatorCircuitBreaker = 0x55 + else: + self.disHVBA.actuatorCircuitBreaker = 0xAA + + if (self.dict2['桅杆舵机断路器']=='闭合'): + self.disHVBA.mastSteeringGearControlBoxCircuitBreaker = 0x55 + else: + self.disHVBA.mastSteeringGearControlBoxCircuitBreaker = 0xAA + + if (self.dict2['XCZ断路器']=='闭合'): + self.disHVBA.xczCircuitBreaker = 0x55 + else: + self.disHVBA.xczCircuitBreaker = 0xAA + + if (self.dict2['艏舵断路器']=='闭合'): + self.disHVBA.bowRudderControlBoxCircuitBreaker = 0x55 + else: + self.disHVBA.bowRudderControlBoxCircuitBreaker = 0xAA + + if (self.dict2['敞开电缸断路器']=='闭合'): + self.disHVBA.openWaterCoverStartCylinderCircuitBreaker = 0x55 + else: + self.disHVBA.openWaterCoverStartCylinderCircuitBreaker = 0xAA + + #################################################################### + if (self.dict3['仪表锂电池断路器']=='闭合'): + self.disHVBB.lithiumBatteryGroupInstrumentCircuitBreaker = 0x55 + else: + self.disHVBB.lithiumBatteryGroupInstrumentCircuitBreaker = 0xAA + + if (self.dict3['艏部低压断路器']=='闭合'): + self.disHVBB.bowLowVoltageDistributionCircuitBreaker = 0x55 + else: + self.disHVBB.bowLowVoltageDistributionCircuitBreaker = 0xAA + + if (self.dict3['UNIT4断路器']=='闭合'): + self.disHVBB.unit4CircuitBreaker = 0x55 + else: + self.disHVBB.unit4CircuitBreaker = 0xAA + + if (self.dict3['DC/DC1断路器']=='闭合'): + self.disHVBB.dcdc1CircuitBreaker = 0x55 + else: + self.disHVBB.dcdc1CircuitBreaker = 0xAA + + if (self.dict3['预留断路器1']=='闭合'): + self.disHVBB.reservedCircuitBreaker1 = 0x55 + else: + self.disHVBB.reservedCircuitBreaker1 = 0xAA + + if (self.dict3['预留断路器2']=='闭合'): + self.disHVBB.reservedCircuitBreaker2 = 0x55 + else: + self.disHVBB.reservedCircuitBreaker2 = 0xAA + + if (self.dict3['艉部应急电池开关']=='闭合'): + self.disHVBB.emergencyLithiumBatteryGroup2CircuitBreaker = 0x55 + else: + self.disHVBB.emergencyLithiumBatteryGroup2CircuitBreaker = 0xAA + + ###################################################################### + if (self.dict4['UNIT1断路器']=='闭合'): + self.disLVB.unit1CircuitBreaker = 0x55 + else: + self.disLVB.unit1CircuitBreaker = 0xAA + + if (self.dict4['UNIT2断路器']=='闭合'): + self.disLVB.unit2CircuitBreaker = 0x55 + else: + self.disLVB.unit2CircuitBreaker = 0xAA + + if (self.dict4['UNIT3断路器']=='闭合'): + self.disLVB.unit3CircuitBreaker = 0x55 + else: + self.disLVB.unit3CircuitBreaker = 0xAA + + if (self.dict4['UNIT5断路器']=='闭合'): + self.disLVB.unit5CircuitBreaker = 0x55 + else: + self.disLVB.unit5CircuitBreaker = 0xAA + + if (self.dict4['预留断路器1']=='闭合'): + self.disLVB.reservedCircuitBreaker1 = 0x55 + else: + self.disLVB.reservedCircuitBreaker1 = 0xAA + + if (self.dict4['预留断路器2']=='闭合'): + self.disLVB.reservedCircuitBreaker2 = 0x55 + else: + self.disLVB.reservedCircuitBreaker2 = 0xAA + + if (self.dict4['艏部抛载断路器']=='闭合'): + self.disLVB.bowPZDeviceCircuitBreaker = 0x55 + else: + self.disLVB.bowPZDeviceCircuitBreaker = 0xAA + + if (self.dict4['艏部应急电池开关']=='闭合'): + self.disLVB.emergencyLithiumBatteryGroup1CircuitBreaker = 0x55 + else: + self.disLVB.emergencyLithiumBatteryGroup1CircuitBreaker = 0xAA + + a = self.disHVB.pack() + b = self.disHVBA.pack() + c = self.disHVBB.pack() + d = self.disLVB.pack() + return a,b,c,d + +if __name__ == "__main__": + root = tk.Tk() + app = disSys(root) + app.pack() + root.mainloop() diff --git a/test/scripts/logWin.py b/test/scripts/logWin.py new file mode 100644 index 0000000..122ac58 --- /dev/null +++ b/test/scripts/logWin.py @@ -0,0 +1,78 @@ +import socket +import struct +import tkinter as tk +import threading +from udpmsg import * +from datetime import datetime +from tkinter import font, filedialog +from tkinter import Frame +from tkinter.ttk import * + +class logWin(Frame): + def __init__(self, root): + super().__init__(root) + self.root = root + custom_font = font.Font(family="Arial", size=12, weight="normal") + + # 设置 state 为 tk.DISABLED 禁用编辑功能 + self.text_box = tk.Text(root, height=10, width=50, font=custom_font, state=tk.DISABLED) + self.text_box.grid(row=0, column=0, columnspan=2, sticky='nsew') + + # 创建垂直滚动条 + scrollbar = tk.Scrollbar(root, command=self.text_box.yview) + scrollbar.grid(row=0, column=2, sticky='ns') + + # 将滚动条与 Text 组件关联 + self.text_box.config(yscrollcommand=scrollbar.set) + + # 创建保存按钮 + save_button = tk.Button(root, text="保存日志", command=self.save_log) + save_button.grid(row=1, column=0, sticky='ew') + + # 创建清空按钮 + clear_button = tk.Button(root, text="清空日志", command=self.clear_log) + clear_button.grid(row=1, column=1, sticky='ew') + + # 配置行和列的权重,使组件能够随窗口大小变化而调整 + root.grid_rowconfigure(0, weight=1) + root.grid_columnconfigure(0, weight=1) + root.grid_columnconfigure(1, weight=1) + + def display_log(self, log_message): + # 启用编辑功能 + self.text_box.config(state=tk.NORMAL) + # 将日志信息添加到 Text 组件中 + self.text_box.insert(tk.END, log_message + "\n") + # 滚动到 Text 组件的底部,以显示最新的日志信息 + self.text_box.see(tk.END) + # 禁用编辑功能 + self.text_box.config(state=tk.DISABLED) + + def save_log(self): + # 打开文件选择对话框 + file_path = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]) + if file_path: + # 获取文本框中的内容 + log_content = self.text_box.get("1.0", tk.END) + # 将内容写入文件 + with open(file_path, "w") as file: + file.write(log_content) + # 显示保存成功的提示 + tk.messagebox.showinfo("保存成功", "日志已成功保存到文件。") + + def clear_log(self): + # 启用编辑功能 + self.text_box.config(state=tk.NORMAL) + # 清空 Text 组件中的内容 + self.text_box.delete("1.0", tk.END) + # 禁用编辑功能 + self.text_box.config(state=tk.DISABLED) + +if __name__ == "__main__": + root = tk.Tk() + root.title("能源管理系统调试") + log_win = logWin(root) + # 假设您有一个日志消息需要显示 + for i in range(100): + log_win.display_log("这是一条日志信息" + str(i)) + root.mainloop() \ No newline at end of file diff --git a/test/scripts/udpComm.py b/test/scripts/udpComm.py new file mode 100644 index 0000000..fdf1463 --- /dev/null +++ b/test/scripts/udpComm.py @@ -0,0 +1,106 @@ +import socket +import struct +import tkinter as tk +import threading +from udpmsg import * +from datetime import datetime +from tkinter import font +import threading +from disSys import * +from logWin import * + +target_ip = '127.0.0.1' +target_port = 5000 + +def receive_messages(sock, logwin:logWin, disSysWin:disSys): + while True: + current_time = datetime.now() + formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S") + data, address = sock.recvfrom(4096) + headBuf = data[:6] + header = struct.unpack('接收到来自 {address} | 高压母线配电指令:\n") + logWin.display_log(logwin,f"{formatted_time}->接收到来自 {address} | 高压母线配电指令:") + if(msg.fuelCellCircuitBreaker==0x55): + disSysWin.dict1['燃料电池断路器']='闭合' + elif(msg.fuelCellCircuitBreaker==0xAA): + disSysWin.dict1['燃料电池断路器']='断开' + + if(msg.powerLithiumBatteryCircuitBreaker==0x55): + disSysWin.dict1['动力锂电池断路器']='闭合' + elif(msg.powerLithiumBatteryCircuitBreaker==0xAA): + disSysWin.dict1['动力锂电池断路器']='断开' + + if(msg.propulsionMotorCircuitBreaker==0x55): + disSysWin.dict1['推进电机断路器']='闭合' + elif(msg.propulsionMotorCircuitBreaker==0xAA): + disSysWin.dict1['推进电机断路器']='断开' + + if(msg.lithiumBatteryGroupInstrumentCircuitBreaker==0x55): + disSysWin.dict1['仪表锂电池断路器']='闭合' + elif(msg.lithiumBatteryGroupInstrumentCircuitBreaker==0xAA): + disSysWin.dict1['仪表锂电池断路器']='断开' + + if(msg.dcDc5ModuleCircuitBreaker==0x55): + disSysWin.dict1['DC/DC5模块断路器']='闭合' + elif(msg.dcDc5ModuleCircuitBreaker==0xAA): + disSysWin.dict1['DC/DC5模块断路器']='断开' + + if(msg.bowHighVoltageDistributionBoxCircuitBreaker==0x55): + disSysWin.dict1['艏部高压配电断路器']='闭合' + elif(msg.bowHighVoltageDistributionBoxCircuitBreaker==0xAA): + disSysWin.dict1['艏部高压配电断路器']='断开' + + if(msg.sternFTDevice45CircuitBreaker==0x55): + disSysWin.dict1['艏部FT4/5断路器']='闭合' + elif(msg.sternFTDevice45CircuitBreaker==0xAA): + disSysWin.dict1['艏部FT4/5断路器']='断开' + + if(msg.sternRudderSwitch1CircuitBreaker==0x55): + disSysWin.dict1['艉舵1/2断路器']='闭合' + elif(msg.sternRudderSwitch1CircuitBreaker==0xAA): + disSysWin.dict1['艉舵1/2断路器']='断开' + + if(msg.sternRudderSwitch2CircuitBreaker==0x55): + disSysWin.dict1['艉舵3/4断路器']='闭合' + elif(msg.sternRudderSwitch2CircuitBreaker==0xAA): + disSysWin.dict1['艉舵3/4断路器']='断开' + + if(msg.fbReservedCircuitBreaker==0x55): + disSysWin.dict1['FB预留断路器']='闭合' + elif(msg.fbReservedCircuitBreaker==0xAA): + disSysWin.dict1['FB预留断路器']='断开' + + if(msg.tyzReservedCircuitBreaker==0x55): + disSysWin.dict1['TYZ预留断路器']='闭合' + elif(msg.tyzReservedCircuitBreaker==0xAA): + disSysWin.dict1['TYZ预留断路器']='断开' + + if(msg.reservedCircuitBreaker==0x55): + disSysWin.dict1['预留断路器']='闭合' + elif(msg.reservedCircuitBreaker==0xAA): + disSysWin.dict1['预留断路器']='断开' + disSysWin.update_disp() + + +def send_messages(sock, logwin:logWin, disSysWin:disSys): + while True: + # 配电消息反馈 + a,b,c,d = disSysWin.updata_data() + sock.sendto(a, (target_ip, target_port)) + sock.sendto(b, (target_ip, target_port)) + sock.sendto(c, (target_ip, target_port)) + sock.sendto(d, (target_ip, target_port)) + + #TODO复合管控消息反馈 + + + threading.Event().wait(1) # 每隔1秒发送一次消息