when'd i last commit this pos?
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user