pre uc480 integration

This commit is contained in:
Thomas Ales [M S E]
2026-05-22 09:38:39 -05:00
parent 43e7b99512
commit a0e0151b5d
30 changed files with 12557 additions and 41 deletions
+142 -23
View File
@@ -126,17 +126,34 @@ class HeliosLaser:
Send a query and read response.
Args:
command: ASCII query command (without CR)
command: ASCII query command (without CR or ?)
Returns:
Response string or None if error
Response value string or None if error
"""
if not self._send_command(command):
return None
try:
response = self.serial.readline().decode('ascii').strip()
# Clear any pending data in the buffer
self.serial.reset_input_buffer()
time.sleep(0.05)
if not self._send_command(command):
return None
time.sleep(0.2) # Give device time to respond
response = self.serial.read_until(b'\r').decode('ascii', errors='replace').strip()
logger.debug(f"Query '{command}' response: {response}")
# Helios format: "COMMAND = VALUE UNIT"
# Extract just the value part
if '=' in response:
parts = response.split('=')
if len(parts) >= 2:
value_part = parts[1].strip()
# Remove unit suffix if present (e.g., "ns", "mA", "mW")
value = value_part.split()[0]
return value
return response
except Exception as e:
@@ -160,7 +177,12 @@ class HeliosLaser:
# Convert frequency to period in nanoseconds
period_ns = int(1e9 / frequency)
command = f"FP={period_ns}"
# Clamp to valid range (8000-60000 ns)
if not (8000 <= period_ns <= 60000):
logger.error(f"Period {period_ns} ns out of range (8000-60000)")
return False
command = f"LDF {period_ns}"
return self._send_command(command)
def set_current_ma(self, current: int) -> bool:
@@ -168,16 +190,16 @@ class HeliosLaser:
Set pump diode current in mA.
Args:
current: Current in mA (0 - 7000)
current: Current in mA (0 - 2000 for this model)
Returns:
True if successful
"""
if not (0 <= current <= 7000):
logger.error(f"Current {current} mA out of range (0-7000)")
if not (0 <= current <= 2000):
logger.error(f"Current {current} mA out of range (0-2000)")
return False
command = f"PC={current}"
command = f"LDS {current}"
return self._send_command(command)
def set_pulse_mode(self, mode: PulseMode) -> bool:
@@ -190,7 +212,7 @@ class HeliosLaser:
Returns:
True if successful
"""
command = f"PM={mode.value}"
command = f"LDG {mode.value}"
return self._send_command(command)
def set_laser_enable(self, enable: bool) -> bool:
@@ -203,7 +225,7 @@ class HeliosLaser:
Returns:
True if successful
"""
command = f"LE={1 if enable else 0}"
command = f"LDO {1 if enable else 0}"
success = self._send_command(command)
if success:
@@ -219,12 +241,12 @@ class HeliosLaser:
Returns:
True if laser is enabled
"""
response = self._query("LE?")
response = self._query("LDO")
if response:
try:
return int(response) == 1
except ValueError:
logger.error(f"Invalid response for LE?: {response}")
logger.error(f"Invalid response for LDO: {response}")
return False
def get_frequency_hz(self) -> Optional[int]:
@@ -234,13 +256,13 @@ class HeliosLaser:
Returns:
Frequency in Hz or None if error
"""
response = self._query("FP?")
response = self._query("LDF")
if response:
try:
period_ns = int(response)
return int(1e9 / period_ns)
except (ValueError, ZeroDivisionError):
logger.error(f"Invalid response for FP?: {response}")
logger.error(f"Invalid response for LDF: {response}")
return None
def get_current_ma(self) -> Optional[int]:
@@ -250,12 +272,12 @@ class HeliosLaser:
Returns:
Current in mA or None if error
"""
response = self._query("PC?")
response = self._query("LDS")
if response:
try:
return int(response)
except ValueError:
logger.error(f"Invalid response for PC?: {response}")
logger.error(f"Invalid response for LDS: {response}")
return None
def get_power_mw(self) -> Optional[float]:
@@ -265,12 +287,12 @@ class HeliosLaser:
Returns:
Power in mW or None if error
"""
response = self._query("PO?")
response = self._query("HMP")
if response:
try:
return float(response)
except ValueError:
logger.error(f"Invalid response for PO?: {response}")
logger.error(f"Invalid response for HMP: {response}")
return None
def get_controller_serial(self) -> Optional[str]:
@@ -280,7 +302,7 @@ class HeliosLaser:
Returns:
Serial number string or None if error
"""
return self._query("SN?")
return self._query("CSR")
def get_head_serial(self) -> Optional[str]:
"""
@@ -289,7 +311,104 @@ class HeliosLaser:
Returns:
Serial number string or None if error
"""
return self._query("HSN?")
return self._query("HSR")
def get_status_registers(self) -> tuple:
"""
Query LER, LCE, and CCE status registers.
Each register is a bitmask (sum of flags). Non-zero values indicate
active faults. Reset with reset_faults().
Returns:
Tuple of (ler, lce, cce) as ints, or None for each on error.
"""
def _read_reg(cmd):
resp = self._query(cmd)
if resp is not None:
try:
return int(resp)
except ValueError:
logger.error(f"Invalid response for {cmd}: {resp}")
return None
ler = _read_reg("LER")
lce = _read_reg("LCE")
cce = _read_reg("CCE")
return (ler, lce, cce)
def reset_faults(self) -> bool:
"""
Execute the controller reset sequence to clear status registers.
Protocol-specified sequence: CCE 0 -> LCE 0 -> LER 0
Returns:
True if all three commands sent successfully
"""
ok = True
ok = self._send_command("CCE 0") and ok
time.sleep(0.1)
ok = self._send_command("LCE 0") and ok
time.sleep(0.1)
ok = self._send_command("LER 0") and ok
if ok:
logger.info("Fault reset sequence sent")
return ok
def get_remote_enable(self) -> Optional[bool]:
"""
Query the remote enable state (LRE - activates utility connector pin 8).
Returns:
True if remote enable is active, False if not, None on error
"""
response = self._query("LRE")
if response is not None:
try:
return int(response) == 1
except ValueError:
logger.error(f"Invalid response for LRE: {response}")
return None
def send_raw_command(self, command: str) -> Optional[str]:
"""
Send a raw command string and return the raw response.
Useful for diagnostics. Sends *command* + CR, waits briefly,
then reads whatever the device returns (up to the first CR or timeout).
Returns:
Raw response string (decoded, stripped) or None on error.
"""
if not self.is_connected or not self.serial:
logger.error("Not connected to laser")
return None
try:
self.serial.reset_input_buffer()
time.sleep(0.05)
self.serial.write((command + '\r').encode('ascii'))
time.sleep(0.3)
raw = self.serial.read_until(b'\r')
if not raw:
raw = self.serial.read(self.serial.in_waiting)
return raw.decode('ascii', errors='replace').strip()
except Exception as e:
logger.error(f"send_raw_command error: {e}")
return None
def set_remote_enable(self, enable: bool) -> bool:
"""
Set the remote enable state (LRE - utility connector pin 8).
Args:
enable: True to activate remote enable, False to deactivate
Returns:
True if successful
"""
command = f"LRE {1 if enable else 0}"
return self._send_command(command)
def __del__(self):
"""Destructor - ensure cleanup"""