diff --git a/HDMI-Tool/README.md b/HDMI-Tool/README.md index a2aa0aa..d56f61b 100644 --- a/HDMI-Tool/README.md +++ b/HDMI-Tool/README.md @@ -86,8 +86,10 @@ HDMI-Tool/ `protocols.md` 为准:命令帧 `D5 01 LEN CRC`、响应帧 `B5 TYPE LEN CRC`, CRC 为第 2 字节起累加和取低 8 位。 -GUI"光机控制"区:选串口 → 连接 → 点快捷按钮或命令下拉/自定义输入发送;日志区实时显示 -`→ 命令` / `← 译码结果 (原始 hex)`,错误自动附释义(如 `err:3(I2C NACK)`)。 +GUI"光机控制"区:选串口 → 连接 → 点快捷按钮或命令下拉/自定义输入发送;串口下拉显示 +"COMx + 设备描述",CH344 等多路适配器每一路描述不同(如 serial-A/B/C/D),据此选口; +多口描述相同时自动附物理位置消歧。日志区实时显示 `→ 命令` / `← 译码结果 (原始 hex)`, +错误自动附释义(如 `err:3(I2C NACK)`)。 | 命令 | 功能 | 响应示例 | |---|---|---| diff --git a/HDMI-Tool/release/HDMI-Tool.exe b/HDMI-Tool/release/HDMI-Tool.exe index b868d49..b0819e6 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 c16a791..a2aa0aa 100644 --- a/HDMI-Tool/release/README.md +++ b/HDMI-Tool/release/README.md @@ -192,6 +192,7 @@ HDMI-Tool/ ├── release/ # 构建与发布产物(机器生成区, 不手改) │ ├── HDMI-Tool.exe # 打包单文件版(改动跟随源码提交入库) │ ├── pattern_slots.json # exe 运行配置(栏位预置 + 串口记忆) +│ ├── *.png / README.md # 附属文件平铺副本(由 package_release.py 从 assets/ 同步) │ └── *.zip # 发布包(不入库, 上传 Gitea Release) ├── README.md # 本说明 └── AGENTS.md # 面向 AI/Agent 的工程档案(结构/约定/构建规程) diff --git a/HDMI-Tool/src/dlpc_rs485.py b/HDMI-Tool/src/dlpc_rs485.py index d78bd15..dbae74b 100644 --- a/HDMI-Tool/src/dlpc_rs485.py +++ b/HDMI-Tool/src/dlpc_rs485.py @@ -168,11 +168,31 @@ class DLPC485: threading.Thread(target=self._run, daemon=True).start() @staticmethod - def list_ports(): - """枚举本机串口名(未装 pyserial 返回空表)""" + def list_ports_info(): + """枚举串口, 返回 [(设备名, 显示标签), ...]。 + + 标签 = "COMx 设备描述"(描述里的 "(COMx)" 尾巴去掉, COM 号已在最前), + CH344 这类多路适配器的每一路描述不同(如 serial-A/B/C/D), 依此区分; + 多口描述完全相同(同型号多口)时追加物理位置/序列号消歧。 + """ if not HAS_SERIAL: return [] - return [p.device for p in serial.tools.list_ports.comports()] + items = [] + for p in serial.tools.list_ports.comports(): + desc = re.sub(r'\s*\((COM\d+)\)\s*$', '', (p.description or '').strip(), + flags=re.I) + if desc.upper() == p.device.upper(): + desc = '' + label = f'{p.device} {desc}'.rstrip() + tail = p.location or p.serial_number or '' + items.append((p.device, label, tail)) + counts = collections.Counter(label for _d, label, _t in items) + out = [] + for dev, label, tail in items: + if counts[label] > 1 and tail: # 同标签多口: 附物理位置消歧 + label = f'{label} [{tail}]' + out.append((dev, label)) + return out def open(self, port, baud=DEFAULT_BAUD): if not HAS_SERIAL: diff --git a/HDMI-Tool/src/hdmi_gui.py b/HDMI-Tool/src/hdmi_gui.py index 9faf2c0..c4906df 100644 --- a/HDMI-Tool/src/hdmi_gui.py +++ b/HDMI-Tool/src/hdmi_gui.py @@ -1179,10 +1179,12 @@ def run_gui(auto_close=0): r1 = ttk.Frame(grp2) r1.pack(fill='x') ttk.Label(r1, text='串口:').pack(side='left') - self.cbo_dlpc = ttk.Combobox(r1, width=12, state='readonly', - values=dlpc_rs485.DLPC485.list_ports()) + # 下拉显示 "COMx + 设备描述"(CH344 四路按 serial-A/B/C/D 等描述区分) + self._dlpc_ports = dlpc_rs485.DLPC485.list_ports_info() + self.cbo_dlpc = ttk.Combobox(r1, width=22, state='readonly', + values=[lb for _d, lb in self._dlpc_ports]) if SERIAL_CFG['port']: - self.cbo_dlpc.set(SERIAL_CFG['port']) + self._dlpc_pick(SERIAL_CFG['port']) self.cbo_dlpc.pack(side='left', padx=(4, 2)) ttk.Button(r1, text='刷新', width=6, command=self._dlpc_refresh).pack(side='left') @@ -1341,7 +1343,7 @@ def run_gui(auto_close=0): if connected != self._dlpc_connected: self._dlpc_connected = connected self.btn_dlpc.config(text='断开' if connected else '连接') - self.after(80, self._poll_dlpc) + self._dlpc_after_id = self.after(80, self._poll_dlpc) def _dlpc_log(self, line): self.txt_dlpc.configure(state='normal') @@ -1352,18 +1354,33 @@ def run_gui(auto_close=0): self.txt_dlpc.configure(state='disabled') def _dlpc_refresh(self): - ports = dlpc_rs485.DLPC485.list_ports() - self.cbo_dlpc['values'] = ports - if SERIAL_CFG['port'] in ports: - self.cbo_dlpc.set(SERIAL_CFG['port']) - elif ports: - self.cbo_dlpc.current(0) + self._dlpc_ports = dlpc_rs485.DLPC485.list_ports_info() + self.cbo_dlpc['values'] = [lb for _d, lb in self._dlpc_ports] + devices = [d for d, _l in self._dlpc_ports] + if SERIAL_CFG['port'] in devices: + self._dlpc_pick(SERIAL_CFG['port']) + elif devices: + self._dlpc_pick(devices[0]) + + def _dlpc_pick(self, device): + """按 COM 口名选中下拉项(下拉显示的是带描述的标签)""" + for i, (d, _label) in enumerate(self._dlpc_ports): + if d == device: + self.cbo_dlpc.current(i) + return + + def _dlpc_selected_port(self): + """当前下拉选中的 COM 口名""" + i = self.cbo_dlpc.current() + if 0 <= i < len(self._dlpc_ports): + return self._dlpc_ports[i][0] + return self.cbo_dlpc.get().strip() def _dlpc_toggle(self): if self.dlpc.is_connected(): self.dlpc.close() return - port = self.cbo_dlpc.get().strip() + port = self._dlpc_selected_port() if not port: self.lbl_dlpc_st.config(text='未选择串口', foreground='#B05000') return @@ -1405,6 +1422,10 @@ def run_gui(auto_close=0): self._dlpc_send(f'M731S{v}') def destroy(self): + try: # 取消待触发的轮询回调, 避免销毁后 Tcl 报错 + self.after_cancel(self._dlpc_after_id) + except (KeyError, AttributeError, Exception): + pass try: # 两种退出路径(关窗/--seconds 自动关)都收掉串口线程 self.dlpc.close() except Exception: