Port serial layer to pyserial; add Windows COM detection and PyInstaller spec

- serial_port.py: replace termios/fcntl/select with pyserial wrapper,
  same SerialPort interface preserved for all other modules
- workflow_serial.py: detect_serial_device() uses serial.tools.list_ports
  to enumerate COM ports; removes _fix_permissions() (not needed on Windows);
  multi-device table now shows port description
- es24n_conf.py: enable VT/ANSI colour mode on Windows 10+ via ctypes;
  update docstring for Windows edition
- requirements.txt: pyserial >= 3.5, pyinstaller >= 6.0
- es24n_conf.spec: PyInstaller single-file .exe spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 15:50:17 -04:00
parent a6a0f1b246
commit 75873dd4e0
5 changed files with 103 additions and 97 deletions

View File

@@ -9,14 +9,13 @@ All Redfish operations are therefore performed by issuing curl commands over
the serial connection and parsing the JSON responses.
"""
import glob
import json
import os
import re
import subprocess
import time
from typing import Optional
import serial.tools.list_ports
from models import IOMConfig, ShelfConfig
from serial_port import SerialPort
from ui import (
@@ -235,7 +234,7 @@ def detect_serial_device() -> Optional[str]:
rule("Step 1 of 5 -- Serial Cable & Device Detection")
print(" Connect the serial cable from the ES24N IOM port")
print(" to the active F-Series controller USB port.")
print(" to a USB port on this Windows machine.")
print()
prompt("Press Enter when the cable is connected")
@@ -243,9 +242,9 @@ def detect_serial_device() -> Optional[str]:
info(f"Scanning for USB serial devices (attempt {attempt}/3)...")
time.sleep(1)
# FreeBSD: /dev/ttyU* Linux: /dev/ttyUSB*, /dev/ttyACM*
patterns = ["/dev/ttyUSB*", "/dev/ttyACM*", "/dev/ttyU*"]
ports = sorted({p for pat in patterns for p in glob.glob(pat)})
all_ports = serial.tools.list_ports.comports()
usb_ports = [p for p in all_ports if p.hwid and "USB" in p.hwid.upper()]
ports = sorted(p.device for p in usb_ports)
if ports:
break
@@ -259,21 +258,22 @@ def detect_serial_device() -> Optional[str]:
print()
print(" Troubleshooting:")
print(" - Ensure the serial cable is fully seated at both ends.")
print(" - Try a different USB port on the controller.")
print(" - Try a different USB port on this machine.")
print(" - Confirm the ES24N is powered on.")
print(" - Check Device Manager for the COM port assignment.")
return None
if len(ports) == 1:
ok(f"Device found: {_c(C.BOLD, ports[0])}")
_fix_permissions(ports[0])
return ports[0]
# Multiple devices — let the user choose
port_objs = {p.device: p for p in usb_ports}
print()
draw_table(
["#", "Device"],
[[str(i), p] for i, p in enumerate(ports, 1)],
[4, 24],
["#", "Port", "Description"],
[[str(i), p, port_objs[p].description or ""] for i, p in enumerate(ports, 1)],
[4, 8, 40],
)
print()
@@ -282,30 +282,10 @@ def detect_serial_device() -> Optional[str]:
if val.isdigit() and 1 <= int(val) <= len(ports):
selected = ports[int(val) - 1]
ok(f"Selected: {_c(C.BOLD, selected)}")
_fix_permissions(selected)
return selected
warn(f"Please enter a number between 1 and {len(ports)}.")
def _fix_permissions(device: str):
try:
result = subprocess.run(
["sudo", "chown", ":wheel", device],
capture_output=True, timeout=5,
)
if result.returncode == 0:
ok(f"Permissions updated on {device}")
return
except Exception:
pass
try:
os.chmod(device, 0o666)
ok(f"Permissions updated on {device}")
except PermissionError:
warn("Could not update device permissions automatically.")
warn("If the connection fails, re-run this script with sudo.")
# ── Step 2: Open serial connection & wake IOM console ─────────────────────────
def open_serial_connection(device: str) -> Optional[SerialPort]:
rule("Step 2 of 5 -- Opening Serial Connection")