from __future__ import annotations from pathlib import Path from typing import Optional from tkinter import messagebox from .path_utils import get_base_dir from .tooling import run_command def ensure_pyserial(python_cmd: Optional[Path], show_dialog: bool) -> bool: try: import serial # noqa: F401 return True except Exception: if not show_dialog: return False if python_cmd is None: messagebox.showerror("缺少依赖", "内置环境缺少 pyserial,请重新打包。") return False if not messagebox.askyesno("缺少依赖", "未检测到 pyserial,是否自动安装?"): return False cmd = [str(python_cmd), "-m", "pip", "install", "pyserial"] code, output = run_command(cmd, cwd=get_base_dir(), stream=False) if code != 0: messagebox.showerror( "安装失败", "pyserial 安装失败,请手动执行:\n" f"{python_cmd} -m pip install pyserial\n\n{output}", ) return False messagebox.showinfo("安装完成", "pyserial 已安装,请重试检测。") return True def list_serial_ports(python_cmd: Optional[Path], show_dialog: bool) -> list[str]: if not ensure_pyserial(python_cmd, show_dialog): return [] from serial.tools import list_ports return [p.device for p in list_ports.comports()] def pick_preferred_port(ports: list[str]) -> str: if not ports: return "" hints = ("wchusbserial", "usbserial", "ttyusb", "ttyacm", "com") for hint in hints: for port in ports: if hint in port.lower(): return port return ports[0]