diff --git a/DLPilot/src/app_gui.py b/DLPilot/src/app_gui.py index e8dc28e..3a67935 100644 --- a/DLPilot/src/app_gui.py +++ b/DLPilot/src/app_gui.py @@ -33,8 +33,14 @@ MONO = ('Consolas', 12) BOLD = ('Microsoft YaHei UI', 13, 'bold') TITLE_FONT = ('Microsoft YaHei UI', 18, 'bold') +# 电源自检状态位配色: 灰=未检测, 绿=正常回包, 红=错误/超时 +CHIP_IDLE = ('gray78', 'gray34') +CHIP_IDLE_TEXT = ('#333333', '#E8E8E8') +CHIP_OK = '#2FA574' +CHIP_ERR = '#D9534F' + APP_NAME = 'DLPilot' -APP_VERSION = '0.3.3' +APP_VERSION = '0.3.4' REPO_URL = 'https://gitea.hulk.wang/hulk/LT8619C_Debug_Tools' @@ -73,8 +79,8 @@ class App(ctk.CTk): ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('hulk.DLPilot') except Exception: pass - self.geometry('1440x920') - self.minsize(1180, 800) + self.geometry('1440x1010') # 右列新增电源检查卡, 窗口加高 + self.minsize(1180, 860) ic = get_icon_path() if ic: try: @@ -93,6 +99,8 @@ class App(ctk.CTk): self._dlpc_connected = False self._poll_job = None self._tl_order = [] # 温度查询应答顺序: 'DMD'/'LAMP' 先到先得 + self._pd_seq = [] # 电源自检待执行步骤: [(命令, kind), ...] + self._pd_timer = None # 自检单步超时定时器 self._build() self._load_displays() # 主窗口按 ESC 也能关闭所有投屏(投屏窗口失焦时的兜底出口) @@ -319,6 +327,31 @@ class App(ctk.CTk): self._dlpc_log('帧格式: D5 01 LEN <命令> CRC / B5 TYPE LEN CRC, ' '累加和校验; 全部命令见 README"光机控制"') + # -- 电源检查 -- + card, c = self._card(col, '电源检查 — PD 自检 / 功率读数') + card.grid(row=2, column=0, sticky='ew', pady=(10, 0)) + rowp1 = ctk.CTkFrame(c, fg_color='transparent') + rowp1.pack(fill='x') + ctk.CTkButton(rowp1, text='电源自检', width=90, + command=self._pd_selftest).pack(side='left') + ctk.CTkLabel(rowp1, text='协议:').pack(side='left', padx=(10, 4)) + self.chip_pd = ctk.CTkLabel(rowp1, text='PD', width=60, height=26, + corner_radius=6, fg_color=CHIP_IDLE, + text_color=CHIP_IDLE_TEXT) + self.chip_pd.pack(side='left') + self.var_fpdo = tk.StringVar(value='FPDO: —') + ctk.CTkLabel(rowp1, textvariable=self.var_fpdo, anchor='w', + text_color='#3B8ED0').pack(side='left', padx=(12, 0)) + rowp2 = ctk.CTkFrame(c, fg_color='transparent') + rowp2.pack(fill='x', pady=(6, 0)) + ctk.CTkButton(rowp2, text='功率检测', width=90, + command=self._pwr_query).pack(side='left') + self.var_pwr = tk.StringVar(value='电压: — 电流: — 功率: —') + ctk.CTkLabel(rowp2, textvariable=self.var_pwr, height=28, + corner_radius=6, fg_color=('#F2F2F2', '#181818'), + anchor='w').pack(side='left', fill='x', expand=True, + padx=(8, 0)) + # -- 光机命令 -- # 标题行右侧: 电流实时读数 + 查电流按钮(okR..G..B.. 应答解析) cur_frame = ctk.CTkFrame(self, fg_color='transparent') @@ -329,7 +362,7 @@ class App(ctk.CTk): command=self._query_current).pack(side='left', padx=(6, 0)) card, c = self._card(col,'光机命令 — 预设 / 自定义 / TEC', right_widget=cur_frame) - card.grid(row=2, column=0, sticky='ew', pady=(10, 0)) + card.grid(row=3, column=0, sticky='ew', pady=(10, 0)) row1 = ctk.CTkFrame(c, fg_color='transparent') row1.pack(fill='x') for name, cmd in (('开光机', 'M730S0'), ('关光机', 'M730S1'), @@ -386,7 +419,7 @@ class App(ctk.CTk): # -- 快捷滑条 -- card, c = self._card(col,'快捷滑条 — 光强 / 定时投光 / TEC 温度') - card.grid(row=3, column=0, sticky='ew', pady=(10, 0)) + card.grid(row=4, column=0, sticky='ew', pady=(10, 0)) rowa = ctk.CTkFrame(c, fg_color='transparent') rowa.pack(fill='x') ctk.CTkLabel(rowa, text='光强(54-800):').pack(side='left') @@ -733,6 +766,9 @@ class App(ctk.CTk): elif kind == 'current': self.var_current.set(text) self._dlpc_log(text) + if (self._pd_seq and kind == 'event' + and text.startswith('← ')): + self._pd_on_reply(text) # 电源自检: 应答路由到状态机 except queue.Empty: pass connected = self.dlpc.is_connected() @@ -814,6 +850,80 @@ class App(ctk.CTk): if cmd: self._dlpc_send(cmd) + # -- 电源检查: 自检序列 + 功率读数 -- + + def _pd_selftest(self): + """电源自检: 按序自动执行 M301/M302(电压检测命令待固件定义后在序列追加), + 每步应答刷新状态位: 绿=正常回包, 红=错误/超时, 灰=未检测""" + if not self.dlpc.is_connected(): + self.lbl_dlpc_st.configure(text='未连接串口', text_color='#E07A3F') + return + if self._pd_seq: + return # 进行中, 忽略重复触发 + self._pd_seq = [('M301', 'pd'), ('M302', 'fpdo')] + self._pd_chip_reset() + self._dlpc_log('电源自检: 开始') + self._pd_step_next() + + def _pd_chip_reset(self): + self.chip_pd.configure(fg_color=CHIP_IDLE, + text_color=CHIP_IDLE_TEXT, text='PD') + self.var_fpdo.set('FPDO: —') + + def _pd_step_next(self): + if not self._pd_seq: + self._dlpc_log('电源自检: 完成') + return + self.dlpc.send(self._pd_seq[0][0]) + self._pd_timer = self.after(3000, lambda: self._pd_step_done(False, '超时')) + + def _pd_on_reply(self, line): + self._pd_step_done(line.startswith('← ok'), line) + + def _pd_step_done(self, ok, line=''): + if not self._pd_seq: + return + if self._pd_timer: + self.after_cancel(self._pd_timer) + self._pd_timer = None + _cmd, kind = self._pd_seq.pop(0) + payload = '' + if ok: + payload = line[2:].split(' [')[0].strip() # 去 '← ' 与尾部 hex + payload = payload[2:].strip() if payload[:2].lower() == 'ok' else '' + if kind == 'pd': + self.chip_pd.configure(fg_color=CHIP_OK if ok else CHIP_ERR, + text_color='#FFFFFF', + text=payload if ok and payload else 'PD') + elif kind == 'fpdo': + mv = re.search(r'(\d+(?:\.\d+)?)\s*V', payload, re.I) + ma = re.search(r'(\d+(?:\.\d+)?)\s*A', payload, re.I) + if not ok: + self.var_fpdo.set('FPDO: 应答错误') + elif mv and ma: # 电压 + 电源宣称最大电流 -> FPDO 功率 + v, a = float(mv.group(1)), float(ma.group(1)) + self.var_fpdo.set(f'FPDO: {v:g}V / {a:g}A ({v * a:.0f}W)') + elif payload: + self.var_fpdo.set(f'FPDO: {payload}') + if ok: + self._pd_step_next() + else: # 出错即中止, 后续状态位保持未检测灰 + self._pd_seq.clear() + why = '超时无应答' if not line.startswith('← ') else '应答错误' + self._dlpc_log(f'电源自检: {why}, 序列中止') + + def _pwr_query(self): + """功率检测: 命令待固件实现, 先占位(定义后在此发送并接 _pwr_update)""" + if not self.dlpc.is_connected(): + self.lbl_dlpc_st.configure(text='未连接串口', text_color='#E07A3F') + return + self._dlpc_log('功率检测: 命令待固件实现') + + def _pwr_update(self, volts, amps): + """功率读数框更新入口: 电压/电流到手后换算功率并刷新""" + self.var_pwr.set(f'电压: {volts:.1f} V 电流: {amps:.2f} A ' + f'功率: {volts * amps:.1f} W') + def _light_entry_apply(self, _evt=None): """滑条数值输入框回车: 校验并限幅 54-800, 同步回滑条""" try: @@ -879,9 +989,11 @@ class App(ctk.CTk): self._dlpc_send(f'M8S{v}') def destroy(self): - try: # 取消待触发的轮询回调, 避免销毁后 Tcl 报错 + try: # 取消待触发的轮询/自检超时回调, 避免销毁后 Tcl 报错 if self._poll_job: self.after_cancel(self._poll_job) + if self._pd_timer: + self.after_cancel(self._pd_timer) except Exception: pass try: # 两种退出路径(关窗/--seconds 自动关)都收掉串口线程