"""Qt adapter over the (Qt-free) T3RDriver. The driver fires its callbacks on the reader thread. This adapter turns each one into a Qt signal emitted from that thread; because the adapter lives on the GUI thread, Qt queues the delivery and slots run on the GUI thread — which is what widget code requires. Command methods are forwarded to the driver, so panels can hold the adapter alone and use it exactly like the old QObject driver. """ from __future__ import annotations from PyQt6.QtCore import QObject, pyqtSignal from hardware.t3r_driver import T3RDriver class QtT3RAdapter(QObject): port_opened = pyqtSignal() handshake_ok = pyqtSignal(int, int, int) # proto_ver, fw_ver, num_channels disconnected = pyqtSignal(str) # reason ("" = user-initiated) info_updated = pyqtSignal(int, object) # ch, proto.Info drv_status_updated = pyqtSignal(int, object) position_updated = pyqtSignal(int, int) motion_done = pyqtSignal(int, int) stopped = pyqtSignal(int, int) fault_occurred = pyqtSignal(int, int) ack_received = pyqtSignal(int, int) frame_received = pyqtSignal(int, bytes) _EVENTS = ("port_opened", "handshake_ok", "disconnected", "info_updated", "drv_status_updated", "position_updated", "motion_done", "stopped", "fault_occurred", "ack_received", "frame_received") def __init__(self, driver: T3RDriver | None = None, parent=None): super().__init__(parent) self.driver = driver if driver is not None else T3RDriver() for name in self._EVENTS: getattr(self.driver, name).connect(getattr(self, name).emit) # Class attributes (constants) the panels read off the driver CHANNEL_NAMES = T3RDriver.CHANNEL_NAMES GR_AXIS_CH = T3RDriver.GR_AXIS_CH @property def is_open(self) -> bool: return self.driver.is_open def __getattr__(self, name): # Only reached for attributes this QObject doesn't define, i.e. the # driver's command API (open/close/move/jog/enable/...). if name.startswith("_"): raise AttributeError(name) return getattr(object.__getattribute__(self, "driver"), name)