diff --git a/HDMI-Tool/README.md b/HDMI-Tool/README.md index 1e0f5e4..4eced41 100644 --- a/HDMI-Tool/README.md +++ b/HDMI-Tool/README.md @@ -63,9 +63,10 @@ HDMI-Tool/ 点图案按钮全屏投放,按 **ESC** 或点击退出;R/G/B 纯色按钮(55/AA/FF,按通道着色) 用于 RGB 位交换类测试;自定义图片点齿轮按钮配置(图片自动复制到 exe 旁); 4. **光机控制**:右列选串口与参数(波特率/数据位/校验/停止位,默认 115200 8-N-1)→ 连接 → - 快捷按钮/预设下拉/自定义输入(回车即发);"查DMD温度""查LAMP温度"读温度; - 快捷滑条:光强拖动后"下发 M731S",定时秒数后"开光机(定时)"(0 秒=不限时 M730S0), - TEC 凝露点滑条/输入框(200-350 即 20.0-35.0°C)后"下发 M8S";数值框输入后回车自动限幅同步; + 快捷按钮/预设下拉/自定义输入(回车即发);"查DMD温度""查LAMP温度"读温度 + (应答实时填入按钮旁的显示框);快捷滑条:光强拖动后"下发 M731S", + 定时毫秒后"开光机(定时)"(0=不限时 M730S0),TEC 凝露点滑条/输入框 + (200-350 即 20.0-35.0°C)后"下发 M8S";数值框输入后回车自动限幅同步; 5. 测试卡区域、自定义栏位与串口记忆(含参数)自动保存在 exe 旁的 `pattern_slots.json`。 ## 4. 典型测试场景 diff --git a/HDMI-Tool/release/HDMI-Tool.exe b/HDMI-Tool/release/HDMI-Tool.exe index 67bb048..3d1e237 100644 Binary files a/HDMI-Tool/release/HDMI-Tool.exe and b/HDMI-Tool/release/HDMI-Tool.exe differ diff --git a/HDMI-Tool/release/README.md b/HDMI-Tool/release/README.md index 1e0f5e4..4eced41 100644 --- a/HDMI-Tool/release/README.md +++ b/HDMI-Tool/release/README.md @@ -63,9 +63,10 @@ HDMI-Tool/ 点图案按钮全屏投放,按 **ESC** 或点击退出;R/G/B 纯色按钮(55/AA/FF,按通道着色) 用于 RGB 位交换类测试;自定义图片点齿轮按钮配置(图片自动复制到 exe 旁); 4. **光机控制**:右列选串口与参数(波特率/数据位/校验/停止位,默认 115200 8-N-1)→ 连接 → - 快捷按钮/预设下拉/自定义输入(回车即发);"查DMD温度""查LAMP温度"读温度; - 快捷滑条:光强拖动后"下发 M731S",定时秒数后"开光机(定时)"(0 秒=不限时 M730S0), - TEC 凝露点滑条/输入框(200-350 即 20.0-35.0°C)后"下发 M8S";数值框输入后回车自动限幅同步; + 快捷按钮/预设下拉/自定义输入(回车即发);"查DMD温度""查LAMP温度"读温度 + (应答实时填入按钮旁的显示框);快捷滑条:光强拖动后"下发 M731S", + 定时毫秒后"开光机(定时)"(0=不限时 M730S0),TEC 凝露点滑条/输入框 + (200-350 即 20.0-35.0°C)后"下发 M8S";数值框输入后回车自动限幅同步; 5. 测试卡区域、自定义栏位与串口记忆(含参数)自动保存在 exe 旁的 `pattern_slots.json`。 ## 4. 典型测试场景 diff --git a/HDMI-Tool/src/app_gui.py b/HDMI-Tool/src/app_gui.py index 5c0e216..92bc80f 100644 --- a/HDMI-Tool/src/app_gui.py +++ b/HDMI-Tool/src/app_gui.py @@ -15,9 +15,10 @@ HDMI-Tool 主界面 — CustomTkinter 双栏仪表盘 import ctypes import os import queue +import re import time import tkinter as tk -from tkinter import filedialog, messagebox, ttk +from tkinter import filedialog, messagebox import customtkinter as ctk @@ -73,7 +74,7 @@ class App(ctk.CTk): self.dlpc = dlpc_rs485.DLPC485(self._dlpc_on_event, self._dlpc_on_status) self._dlpc_connected = False self._poll_job = None - self._tv_style() + self._tl_order = [] # 温度查询应答顺序: 'DMD'/'LAMP' 先到先得 self._build() self._load_displays() if dlpc_rs485.HAS_SERIAL: @@ -84,32 +85,8 @@ class App(ctk.CTk): # ============================================================ # 布局 # ============================================================ - def _tv_style(self): - """内嵌 ttk.Treeview 的暗/亮配色(跟随外观切换)""" - self._style = ttk.Style(self) - try: - self._style.theme_use('clam') - except Exception: - pass - self._apply_tv_colors() - - def _apply_tv_colors(self): - dark = ctk.get_appearance_mode() == 'Dark' - s = self._style - s.configure('HDMI.Treeview', - background='#181818' if dark else '#F4F4F4', - fieldbackground='#181818' if dark else '#F4F4F4', - foreground='#DADADA' if dark else '#1A1A1A', - rowheight=24, borderwidth=0) - s.configure('HDMI.Treeview.Heading', - background='#242424' if dark else '#E8E8E8', - foreground='#B0B0B0' if dark else '#444444', - relief='flat') - s.map('HDMI.Treeview', background=[('selected', '#2A5D8F')]) - def _set_appearance(self, mode): ctk.set_appearance_mode(mode) - self._apply_tv_colors() def _card(self, parent, title, button=None, command=None, right_label=None): """卡片容器: 圆角边框 + 标题行(可带右侧小按钮/右对齐标签), 返回 (卡片, 内容 frame) @@ -163,9 +140,9 @@ class App(ctk.CTk): ).pack(expand=True) def _build_left(self, col): - col.grid_rowconfigure(1, weight=3) # 支持的模式 + col.grid_rowconfigure(1, weight=2) # 支持的模式(与 EDID 信息等高) col.grid_rowconfigure(2, weight=2) # EDID 信息 - col.grid_rowconfigure(4, weight=1) # EDID 原始数据 + col.grid_rowconfigure(4, weight=0) # EDID 原始数据(固定高, 压缩) col.grid_columnconfigure(0, weight=1) # -- 显示器选择区域 -- @@ -199,18 +176,10 @@ class App(ctk.CTk): self.txt_modes.configure(state='disabled') # -- EDID 信息 -- - card, c = self._card(col,'EDID 信息') + card, c = self._card(col, 'EDID 信息') card.grid(row=2, column=0, sticky='nsew', pady=(10, 0)) - self.tv_info = ttk.Treeview(c, columns=('k', 'v'), show='headings', - height=8, style='HDMI.Treeview') - self.tv_info.heading('k', text='项目') - self.tv_info.heading('v', text='值') - self.tv_info.column('k', width=150, anchor='w') - self.tv_info.column('v', anchor='w') - sb = ttk.Scrollbar(c, orient='vertical', command=self.tv_info.yview) - self.tv_info.configure(yscrollcommand=sb.set) - self.tv_info.pack(side='left', fill='both', expand=True) - sb.pack(side='right', fill='y') + self.edid_scroll = ctk.CTkScrollableFrame(c, height=150) + self.edid_scroll.pack(fill='both', expand=True) # -- 测试图案 -- card, c = self._card(col,'测试图案 — 黑底 1:1 居中, 按 ESC 退出') @@ -255,11 +224,11 @@ class App(ctk.CTk): text_color='gray50').pack(side='left', padx=4) # -- EDID 原始数据 -- - card, c = self._card(col,'EDID 原始数据', + card, c = self._card(col, 'EDID 原始数据', button='逐字节解析', command=self._show_edid_fields) - card.grid(row=4, column=0, sticky='nsew', pady=(10, 0)) - self.txt_hex = ctk.CTkTextbox(c, font=MONO, wrap='none') - self.txt_hex.pack(fill='both', expand=True) + card.grid(row=4, column=0, sticky='ew', pady=(10, 0)) + self.txt_hex = ctk.CTkTextbox(c, font=MONO, wrap='none', height=130) + self.txt_hex.pack(fill='x') self.txt_hex.configure(state='disabled') def _build_right(self, col): @@ -322,13 +291,15 @@ class App(ctk.CTk): for name, cmd in (('开光机', 'M730S0'), ('关光机', 'M730S1'), ('查电流', 'M732'), ('查版本', 'M999'), ('软件重启', 'M888'), ('开TEC', 'M7S0'), ('关TEC', 'M7S1'), ('TEC33.0°C', 'M8S330')): - ctk.CTkButton(row1, text=name, width=68, + ctk.CTkButton(row1, text=name, width=64, command=lambda cc=cmd: self._dlpc_send(cc) - ).pack(side='left', padx=(0, 4)) + ).pack(side='left', fill='x', expand=True, padx=(0, 4)) row2 = ctk.CTkFrame(c, fg_color='transparent') row2.pack(fill='x', pady=(6, 0)) + row2.grid_columnconfigure(0, weight=6) # 左: 预设(约 6 份) + row2.grid_columnconfigure(1, weight=4) # 右: 温度查询+实时显示(约 4 份) lf2 = ctk.CTkFrame(row2, fg_color='transparent') - lf2.pack(side='left', fill='x', expand=True) + lf2.grid(row=0, column=0, sticky='ew') ctk.CTkLabel(lf2, text='预设:', width=52, anchor='w').pack(side='left') self._dlpc_presets = [(n, cmd) for n, cmd, _ in dlpc_rs485.PRESETS] self.cbo_dlpc_cmd = ctk.CTkComboBox( @@ -338,12 +309,20 @@ class App(ctk.CTk): self.cbo_dlpc_cmd.pack(side='left', fill='x', expand=True, padx=6) ctk.CTkButton(lf2, text='发送', width=64, command=self._dlpc_send_preset).pack(side='left') - ctk.CTkButton(row2, text='查DMD温度', width=96, - command=lambda: self._dlpc_send('M201')).pack(side='right') + rf2 = ctk.CTkFrame(row2, fg_color='transparent') + rf2.grid(row=0, column=1, sticky='ew', padx=(8, 0)) + ctk.CTkButton(rf2, text='查DMD温度', width=90, + command=self._query_dmd).pack(side='left') + self.var_dmd = tk.StringVar(value='DMD: —') + ctk.CTkLabel(rf2, textvariable=self.var_dmd, width=110, height=28, + corner_radius=6, fg_color=('#F2F2F2', '#181818'), + anchor='w').pack(side='left', fill='x', expand=True, padx=(6, 0)) row3 = ctk.CTkFrame(c, fg_color='transparent') row3.pack(fill='x', pady=(6, 0)) + row3.grid_columnconfigure(0, weight=6) + row3.grid_columnconfigure(1, weight=4) lf3 = ctk.CTkFrame(row3, fg_color='transparent') - lf3.pack(side='left', fill='x', expand=True) + lf3.grid(row=0, column=0, sticky='ew') ctk.CTkLabel(lf3, text='自定义:', width=52, anchor='w').pack(side='left') self.var_custom_cmd = tk.StringVar() ent = ctk.CTkEntry(lf3, textvariable=self.var_custom_cmd, @@ -352,8 +331,14 @@ class App(ctk.CTk): ent.bind('', lambda _e: self._dlpc_send_custom()) ctk.CTkButton(lf3, text='发送', width=64, command=self._dlpc_send_custom).pack(side='left') - ctk.CTkButton(row3, text='查LAMP温度', width=96, - command=lambda: self._dlpc_send('M202')).pack(side='right') + rf3 = ctk.CTkFrame(row3, fg_color='transparent') + rf3.grid(row=0, column=1, sticky='ew', padx=(8, 0)) + ctk.CTkButton(rf3, text='查LAMP温度', width=90, + command=self._query_lamp).pack(side='left') + self.var_lamp = tk.StringVar(value='LAMP: —') + ctk.CTkLabel(rf3, textvariable=self.var_lamp, width=110, height=28, + corner_radius=6, fg_color=('#F2F2F2', '#181818'), + anchor='w').pack(side='left', fill='x', expand=True, padx=(6, 0)) # -- 快捷滑条 -- card, c = self._card(col,'快捷滑条 — 光强 / 定时投光 / TEC 温度') @@ -375,11 +360,11 @@ class App(ctk.CTk): command=self._dlpc_send_light).pack(side='left', padx=(6, 0)) rowb = ctk.CTkFrame(c, fg_color='transparent') rowb.pack(fill='x', pady=(8, 0)) - ctk.CTkLabel(rowb, text='定时投光(秒):').pack(side='left') - self.var_timer = tk.StringVar(value='2') - sld_t = ctk.CTkSlider(rowb, from_=0, to=32, number_of_steps=32, + ctk.CTkLabel(rowb, text='定时投光(毫秒):').pack(side='left') + self.var_timer = tk.StringVar(value='2000') + sld_t = ctk.CTkSlider(rowb, from_=0, to=32700, number_of_steps=327, command=lambda v: self.var_timer.set(str(int(round(float(v)))))) - sld_t.set(2) + sld_t.set(2000) sld_t.pack(side='left', fill='x', expand=True, padx=8) self.sld_timer = sld_t self.ent_timer = ctk.CTkEntry(rowb, width=56, justify='center', @@ -450,16 +435,15 @@ class App(ctk.CTk): self.cbo_mode.set(d['cur_mode']) elif keys: self.cbo_mode.set(keys[0]) - self.tv_info.delete(*self.tv_info.get_children()) + self._clear_edid_rows() if not d['edid']: - self.tv_info.insert('', 'end', - values=('EDID', '读取失败(注册表无 EDID 数据)')) + self._add_edid_row('EDID', '读取失败(注册表无 EDID 数据)') self._set_text(self.txt_modes, '') self._set_text(self.txt_hex, '') return e = edid_parse.parse_edid(d['edid']) for k, v in edid_parse.edid_info_lines(e): - self.tv_info.insert('', 'end', values=(k, v)) + self._add_edid_row(k, v) lines = [] for k, t in enumerate(e['dtds']): src = 'DTD 首选' if k == 0 else 'DTD' @@ -474,6 +458,18 @@ class App(ctk.CTk): self._set_text(self.txt_modes, '\n'.join(lines)) self._set_text(self.txt_hex, edid_parse.hex_dump(d['edid'])) + def _clear_edid_rows(self): + for w in self.edid_scroll.winfo_children(): + w.destroy() + + def _add_edid_row(self, k, v): + row = ctk.CTkFrame(self.edid_scroll, fg_color='transparent') + row.pack(fill='x', pady=1) + ctk.CTkLabel(row, text=k, width=132, anchor='w', + text_color='gray60').pack(side='left') + ctk.CTkLabel(row, text=v, anchor='w').pack(side='left', + fill='x', expand=True) + def _apply_mode(self): d = self._cur_display() if d is None or self.cbo_mode.get() not in self._modes: @@ -620,8 +616,25 @@ class App(ctk.CTk): # 光机控制(RS485) # ============================================================ def _dlpc_on_event(self, line): + # 温度查询应答: okTL:<0.1°C>, 依发送顺序归入 DMD/LAMP 实时显示框 + m = re.search(r'← okTL:(-?\d+)', line) + if m and self._tl_order: + which = self._tl_order.pop(0) + try: + text = f'{which}: {int(m.group(1)) / 10:.1f}°C' + except ValueError: + text = f'{which}: {m.group(1)}' + self._dlpc_log_q.put(('temp', text)) self._dlpc_log_q.put(('event', line)) + def _query_dmd(self): + self._tl_order.append('DMD') + self._dlpc_send('M201') + + def _query_lamp(self): + self._tl_order.append('LAMP') + self._dlpc_send('M202') + def _dlpc_on_status(self, msg): self._dlpc_log_q.put(('status', msg)) @@ -639,6 +652,11 @@ class App(ctk.CTk): else: color = 'gray60' self.lbl_dlpc_st.configure(text=text, text_color=color) + elif kind == 'temp': + if text.startswith('DMD'): + self.var_dmd.set(text) + else: + self.var_lamp.set(text) self._dlpc_log(text) except queue.Empty: pass @@ -733,13 +751,13 @@ class App(ctk.CTk): self.var_light.set(str(v)) def _timer_entry_apply(self, _evt=None): - """定时滑条数值输入框回车: 校验并限幅 0-32, 同步回滑条""" + """定时滑条数值输入框回车: 校验并限幅 0-32700(ms), 同步回滑条""" try: v = int(self.var_timer.get()) except ValueError: self.var_timer.set(str(int(float(self.sld_timer.get())))) return - v = max(0, min(32, v)) + v = max(0, min(32700, v)) self.sld_timer.set(v) self.var_timer.set(str(v)) @@ -758,9 +776,9 @@ class App(ctk.CTk): try: v = int(self.var_timer.get()) except ValueError: - messagebox.showinfo('定时非法', '定时投光需为 0-32 的整数秒') + messagebox.showinfo('定时非法', '定时投光需为 0-32700 的整数毫秒(0=不限时)') return - self._dlpc_send('M730S0' if v <= 0 else f'M730S0T{v * 1000}') + self._dlpc_send('M730S0' if v <= 0 else f'M730S0T{v}') def _tec_entry_apply(self, _evt=None): """TEC 温度输入框回车: 校验并限幅 200-350(0.1°C), 同步回滑条"""