From 7d32fb4b65144909c6876147f3fe2db8d03df2f6 Mon Sep 17 00:00:00 2001 From: jack Date: Mon, 15 Jun 2026 12:39:16 -0700 Subject: [PATCH] refactor: normalize data directory naming, and consolidate serial connection handling --- .../backend/controllers/session_controller.py | 12 ++--- src/pygui/backend/data/constants.py | 3 ++ src/pygui/backend/data/file_browser.py | 3 +- .../backend/data/local_settings_model.py | 3 +- src/pygui/serialcomm/serial_port.py | 53 ++++++++++--------- 5 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 src/pygui/backend/data/constants.py diff --git a/src/pygui/backend/controllers/session_controller.py b/src/pygui/backend/controllers/session_controller.py index 8f190ca..3463fea 100644 --- a/src/pygui/backend/controllers/session_controller.py +++ b/src/pygui/backend/controllers/session_controller.py @@ -309,9 +309,11 @@ class SessionController(QObject): # The new binary protocol sends payload of (sensorCount * 2) bytes # Each sensor is a big-endian 16-bit value (Sign-Magnitude). + expected_sensors = 80 if (family_code or "") == "X" else 244 + def parse_binary_frame(payload: bytes, seq: int) -> Frame: values = [] - num_sensors = min(80, len(payload) // 2) + num_sensors = min(expected_sensors, len(payload) // 2) for i in range(num_sensors): high_byte = payload[i * 2] low_byte = payload[i * 2 + 1] @@ -333,13 +335,9 @@ class SessionController(QObject): self._last = None self._last_raw_frame = None - try: - transport = pyserial.Serial(port, SerialPort.BAUDRATE, timeout=1) - except Exception as exc: - log.warning("Baud rate %d failed on %s, falling back to 115200: %s", SerialPort.BAUDRATE, port, exc) - transport = pyserial.Serial(port, 115200, timeout=1) + transport = SerialPort.open_port(port, timeout=1) # Send 'D2' command padded to 512 bytes to start the stream - cmd = b"D2" + (b"F" * 510) + cmd = b"D2" + SerialPort.COMMAND_PAD.encode() transport.write(cmd) def on_error(exc: Exception): diff --git a/src/pygui/backend/data/constants.py b/src/pygui/backend/data/constants.py new file mode 100644 index 0000000..3d28cfd --- /dev/null +++ b/src/pygui/backend/data/constants.py @@ -0,0 +1,3 @@ +# ===== Backend Data Constants ===== + +DEFAULT_DATA_DIR_NAME = "isc_data" diff --git a/src/pygui/backend/data/file_browser.py b/src/pygui/backend/data/file_browser.py index 817e425..6885e45 100644 --- a/src/pygui/backend/data/file_browser.py +++ b/src/pygui/backend/data/file_browser.py @@ -10,6 +10,7 @@ from PySide6.QtWidgets import QFileDialog, QMessageBox from pygui.backend.data.csv_file_metadata import CSVFileMetadata from pygui.backend.wafer.zwafer_parser import ZWaferParser +from pygui.backend.data.constants import DEFAULT_DATA_DIR_NAME # ===== File Browser Model ===== @@ -232,7 +233,7 @@ class FileBrowser(QObject): QStandardPaths.DocumentsLocation ) base_dir = Path(documents_dir) if documents_dir else (Path.home() / "Documents") - return base_dir / "ISC_DATA" + return base_dir / DEFAULT_DATA_DIR_NAME def _set_current_directory(self, directory: Path) -> None: normalized = Path(directory) diff --git a/src/pygui/backend/data/local_settings_model.py b/src/pygui/backend/data/local_settings_model.py index 689bc17..a7f04e4 100644 --- a/src/pygui/backend/data/local_settings_model.py +++ b/src/pygui/backend/data/local_settings_model.py @@ -7,6 +7,7 @@ from typing import Any from PySide6.QtCore import QObject, Property, QDateTime, QStandardPaths, Signal, Slot from pygui.backend.data.local_settings import LocalSettings +from pygui.backend.data.constants import DEFAULT_DATA_DIR_NAME MASTER_FAMILIES = ("A", "B", "C", "D", "E", "F", "P", "X", "Z") @@ -52,7 +53,7 @@ class LocalSettingsModel(QObject): QStandardPaths.DocumentsLocation ) base_dir = Path(documents_dir) if documents_dir else (Path.home() / "Documents") - return base_dir / "ISC_DATA" + return base_dir / DEFAULT_DATA_DIR_NAME def _new_defaults(self) -> dict[str, Any]: return { diff --git a/src/pygui/serialcomm/serial_port.py b/src/pygui/serialcomm/serial_port.py index b8aceef..b506c7d 100644 --- a/src/pygui/serialcomm/serial_port.py +++ b/src/pygui/serialcomm/serial_port.py @@ -45,6 +45,33 @@ class SerialPort: def __init__(self, port_name: str) -> None: self._port_name = port_name + # ------------------------------------------------------------------ + # Port opening (shared between scoped and long-lived use) + # ------------------------------------------------------------------ + + @staticmethod + def open_port(port_name: str, timeout: float | None = None) -> pyserial.Serial: + """Open a serial port with baud-rate fallback. + + Tries SerialPort.BAUDRATE first, falls back to 115200 on failure. + The caller owns the lifecycle of the returned Serial object.""" + kwargs = dict( + parity=pyserial.PARITY_NONE, + bytesize=8, + stopbits=pyserial.STOPBITS_ONE, + xonxoff=False, + rtscts=False, + dsrdtr=False, + ) + if timeout is not None: + kwargs["timeout"] = timeout + + try: + return pyserial.Serial(port_name, SerialPort.BAUDRATE, **kwargs) + except Exception as exc: + log.warning("Baud rate %d failed on %s, falling back to 115200: %s", SerialPort.BAUDRATE, port_name, exc) + return pyserial.Serial(port_name, 115200, **kwargs) + # ------------------------------------------------------------------ # Context manager for scoped port access # ------------------------------------------------------------------ @@ -52,31 +79,7 @@ class SerialPort: @contextmanager def _open(self, timeout: float | None = None) -> Iterator[pyserial.Serial]: """Open the serial port, yield it, then close on exit.""" - try: - port = pyserial.Serial( - self._port_name, - self.BAUDRATE, - parity=pyserial.PARITY_NONE, - bytesize=8, - stopbits=pyserial.STOPBITS_ONE, - xonxoff=False, - rtscts=False, - dsrdtr=False, - ) - except Exception as exc: - log.warning("Baud rate %d failed on %s, falling back to 115200: %s", self.BAUDRATE, self._port_name, exc) - port = pyserial.Serial( - self._port_name, - 115200, - parity=pyserial.PARITY_NONE, - bytesize=8, - stopbits=pyserial.STOPBITS_ONE, - xonxoff=False, - rtscts=False, - dsrdtr=False, - ) - if timeout is not None: - port.timeout = timeout + port = self.open_port(self._port_name, timeout=timeout) try: yield port finally: