""" Helios Laser System Driver Basic implementation for controlling the Helios pulsed laser. """ import serial import time import logging from typing import Optional, List from enum import Enum logger = logging.getLogger(__name__) class PulseMode(Enum): """Helios pulse mode enumeration""" SINGLE_PULSE = 0 CONTINUOUS_GATING = 1 CONTINUOUS_PULSING = 2 class HeliosLaser: """ Driver for Helios pulsed laser system. Communication: RS-232, 9600 baud, 8N1 Commands are ASCII strings terminated with CR """ def __init__(self, port: str = None, timeout: float = 1.0): """ Initialize Helios laser driver. Args: port: Serial port (e.g., '/dev/ttyUSB0' or 'COM5') timeout: Serial timeout in seconds """ self.port = port self.timeout = timeout self.serial = None self.is_connected = False @staticmethod def list_available_ports() -> List[str]: """List available serial ports""" import serial.tools.list_ports ports = serial.tools.list_ports.comports() return [port.device for port in ports] def connect(self, port: str = None) -> bool: """ Connect to the Helios laser. Args: port: Serial port (uses stored port if None) Returns: True if connection successful """ if port: self.port = port if not self.port: logger.error("No port specified") return False try: self.serial = serial.Serial( port=self.port, baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout ) time.sleep(0.1) # Allow time for connection to stabilize self.is_connected = True logger.info(f"Connected to Helios laser on {self.port}") return True except Exception as e: logger.error(f"Failed to connect to Helios laser: {e}") self.is_connected = False return False def disconnect(self): """Disconnect from the laser""" if self.serial and self.serial.is_open: try: # Disable laser before disconnecting self.set_laser_enable(False) self.serial.close() logger.info("Disconnected from Helios laser") except Exception as e: logger.error(f"Error during disconnect: {e}") self.is_connected = False self.serial = None def _send_command(self, command: str) -> bool: """ Send a command to the laser. Args: command: ASCII command string (without CR) Returns: True if sent successfully """ if not self.is_connected or not self.serial: logger.error("Not connected to laser") return False try: cmd_bytes = (command + '\r').encode('ascii') self.serial.write(cmd_bytes) logger.debug(f"Sent command: {command}") return True except Exception as e: logger.error(f"Failed to send command '{command}': {e}") return False def _query(self, command: str) -> Optional[str]: """ Send a query and read response. Args: command: ASCII query command (without CR) Returns: Response string or None if error """ if not self._send_command(command): return None try: response = self.serial.readline().decode('ascii').strip() logger.debug(f"Query '{command}' response: {response}") return response except Exception as e: logger.error(f"Failed to read response for '{command}': {e}") return None def set_frequency_hz(self, frequency: int) -> bool: """ Set laser pulse frequency in Hz. Args: frequency: Frequency in Hz (16700 - 125000) Returns: True if successful """ if not (16700 <= frequency <= 125000): logger.error(f"Frequency {frequency} Hz out of range (16700-125000)") return False # Convert frequency to period in nanoseconds period_ns = int(1e9 / frequency) command = f"FP={period_ns}" return self._send_command(command) def set_current_ma(self, current: int) -> bool: """ Set pump diode current in mA. Args: current: Current in mA (0 - 7000) Returns: True if successful """ if not (0 <= current <= 7000): logger.error(f"Current {current} mA out of range (0-7000)") return False command = f"PC={current}" return self._send_command(command) def set_pulse_mode(self, mode: PulseMode) -> bool: """ Set pulse mode. Args: mode: PulseMode enumeration value Returns: True if successful """ command = f"PM={mode.value}" return self._send_command(command) def set_laser_enable(self, enable: bool) -> bool: """ Enable or disable laser emission. Args: enable: True to enable, False to disable Returns: True if successful """ command = f"LE={1 if enable else 0}" success = self._send_command(command) if success: state = "enabled" if enable else "disabled" logger.info(f"Laser {state}") return success def is_laser_enabled(self) -> bool: """ Check if laser is currently enabled. Returns: True if laser is enabled """ response = self._query("LE?") if response: try: return int(response) == 1 except ValueError: logger.error(f"Invalid response for LE?: {response}") return False def get_frequency_hz(self) -> Optional[int]: """ Get current laser frequency in Hz. Returns: Frequency in Hz or None if error """ response = self._query("FP?") if response: try: period_ns = int(response) return int(1e9 / period_ns) except (ValueError, ZeroDivisionError): logger.error(f"Invalid response for FP?: {response}") return None def get_current_ma(self) -> Optional[int]: """ Get current pump diode current in mA. Returns: Current in mA or None if error """ response = self._query("PC?") if response: try: return int(response) except ValueError: logger.error(f"Invalid response for PC?: {response}") return None def get_power_mw(self) -> Optional[float]: """ Get laser output power in mW. Returns: Power in mW or None if error """ response = self._query("PO?") if response: try: return float(response) except ValueError: logger.error(f"Invalid response for PO?: {response}") return None def get_controller_serial(self) -> Optional[str]: """ Get controller serial number. Returns: Serial number string or None if error """ return self._query("SN?") def get_head_serial(self) -> Optional[str]: """ Get laser head serial number. Returns: Serial number string or None if error """ return self._query("HSN?") def __del__(self): """Destructor - ensure cleanup""" self.disconnect()