""" Genesis Laser Worker Thread Manages Genesis laser connection in a separate thread to keep the UI responsive. Provides async querying and status monitoring via Qt signals. """ from PyQt6 import QtCore from hardware.genesis_core import SerialComm, I2CProtocol, I2CDevices, LaserControl import queue import time from typing import Optional class GenesisCommand: """Represents a genesis laser command""" def __init__(self, cmd_type: str, **kwargs): self.cmd_type = cmd_type self.params = kwargs class GenesisWorker(QtCore.QObject): """ Worker object for handling Genesis laser control in a separate thread. Signals: connected: Emitted when laser connects successfully disconnected: Emitted when laser disconnects connection_failed: Emitted when connection fails (error_msg: str) laser_info_updated: Emitted with laser status query_completed: Emitted when a query operation completes (result: dict) error_occurred: Emitted when an error occurs (error_msg: str) """ # Signals connected = QtCore.pyqtSignal() disconnected = QtCore.pyqtSignal() connection_failed = QtCore.pyqtSignal(str) laser_info_updated = QtCore.pyqtSignal(dict) # Status information query_completed = QtCore.pyqtSignal(dict) # Query result error_occurred = QtCore.pyqtSignal(str) # Error message def __init__(self, port: str = "/dev/ttyUSB0", baudrate: int = 9600): super().__init__() self.serial_comm = SerialComm() self.i2c_protocol = I2CProtocol(self.serial_comm) self.i2c_devices = I2CDevices(self.i2c_protocol) self.laser_control = LaserControl(self.i2c_devices) self.port = port self.baudrate = baudrate self.is_connected = False self.command_queue = queue.Queue() self.running = True # Last known laser state self.last_laser_state = {} # Update interval for status polling self.last_status_update_time = 0 self.status_update_interval = 1.0 # seconds @QtCore.pyqtSlot() def run(self): """Main worker loop - processes commands from queue""" print(f"Genesis laser worker thread started - connecting to {self.port}") # Try to connect on startup if self.connect(): self.connected.emit() else: error_msg = f"Failed to connect to Genesis laser on {self.port}" print(error_msg) self.connection_failed.emit(error_msg) while self.running: try: # Check for commands with timeout to allow periodic status updates try: cmd = self.command_queue.get(timeout=0.05) # 50ms timeout self.process_command(cmd) except queue.Empty: pass # Periodically update status if connected if self.is_connected: current_time = time.time() if current_time - self.last_status_update_time >= self.status_update_interval: self.update_laser_status() self.last_status_update_time = current_time except Exception as e: print(f"Error in genesis worker loop: {e}") self.error_occurred.emit(str(e)) # Cleanup on exit self.disconnect() print("Genesis laser worker thread stopped") def connect(self) -> bool: """Establish connection to the laser""" try: if self.serial_comm.connect(self.port, self.baudrate): self.is_connected = True print(f"Connected to Genesis laser on {self.port}") return True else: print(f"Failed to open serial port {self.port}") return False except Exception as e: print(f"Connection error: {e}") return False def disconnect(self): """Disconnect from the laser""" if self.is_connected: self.serial_comm.disconnect() self.is_connected = False self.disconnected.emit() print("Disconnected from Genesis laser") def process_command(self, cmd: GenesisCommand): """Process a command from the queue""" if not self.is_connected: self.error_occurred.emit("Laser not connected") return try: if cmd.cmd_type == "query_all": result = self.query_all_status() self.query_completed.emit(result) elif cmd.cmd_type == "query_current": result = {"current": self.laser_control.get_current_actual()} self.query_completed.emit(result) elif cmd.cmd_type == "query_interlock": result = {"interlock": self.laser_control.get_interlock_status()} self.query_completed.emit(result) elif cmd.cmd_type == "set_current": value = cmd.params.get("value", 0) success = self.laser_control.set_current(int(value)) self.query_completed.emit({"success": success}) elif cmd.cmd_type == "set_shutter": state = cmd.params.get("state", False) success = self.laser_control.set_shutter(state) self.query_completed.emit({"success": success}) else: self.error_occurred.emit(f"Unknown command: {cmd.cmd_type}") except Exception as e: self.error_occurred.emit(f"Command execution error: {e}") def update_laser_status(self): """Query and emit current laser status""" if not self.is_connected: return try: status = { "connected": True, "current_actual": self.laser_control.get_current_actual(), "interlock_status": self.laser_control.get_interlock_status(), "ldd_enable_status": self.laser_control.get_ldd_enable_status(), "psglue_in_status": self.laser_control.get_psglue_in_status(), "psglue_out_status": self.laser_control.get_psglue_out_status(), "head_dio_status": self.laser_control.get_head_dio_status(), } # Only emit if something changed if status != self.last_laser_state: self.last_laser_state = status self.laser_info_updated.emit(status) except Exception as e: print(f"Error updating laser status: {e}") def query_all_status(self) -> dict: """Query all laser status information""" return { "connected": True, "current_actual": self.laser_control.get_current_actual(), "interlock_status": self.laser_control.get_interlock_status(), "ldd_enable_status": self.laser_control.get_ldd_enable_status(), "psglue_in_status": self.laser_control.get_psglue_in_status(), "psglue_out_status": self.laser_control.get_psglue_out_status(), "head_dio_status": self.laser_control.get_head_dio_status(), } def queue_command(self, cmd: GenesisCommand): """Queue a command for execution""" self.command_queue.put(cmd) def stop(self): """Stop the worker thread""" self.running = False