when'd i last commit this pos?
This commit is contained in:
@@ -0,0 +1,669 @@
|
||||
"""
|
||||
Genesis SLM MX 532 Laser Core Hardware Control Module
|
||||
======================================================
|
||||
|
||||
This module provides low-level hardware control for the Genesis SLM MX 532 laser
|
||||
using NXP I2C-over-serial protocol. It contains reusable classes for serial
|
||||
communication, I2C protocol handling, device control, and laser operations.
|
||||
|
||||
This module is GUI-independent and can be used by both command-line and GUI applications.
|
||||
|
||||
Protocol Overview:
|
||||
-----------------
|
||||
The laser uses NXP I2C-over-serial protocol with packet format:
|
||||
[0x53] [I2C_ADDR] [LENGTH] [COMMAND_BYTES] [DATA_BYTES] [0x50]
|
||||
|
||||
For reads:
|
||||
[0x53] [ADDR_WRITE] [CMD_LEN] [CMD] [0x53] [ADDR_READ] [DATA_LEN] [0x50]
|
||||
|
||||
I2C Devices:
|
||||
-----------
|
||||
- X9119 digital potentiometer at 0x52 - laser current control
|
||||
- PCA9555 I/O expander at 0x4a - digital I/O
|
||||
- AD5254 digital potentiometer at 0x58 - limits control
|
||||
- ADS7828 ADC at 0x90 - sensor readings
|
||||
- M24C64 EEPROM at 0xa4 - configuration storage
|
||||
|
||||
Author: Claude
|
||||
Date: 2026-01-24
|
||||
"""
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from enum import IntEnum
|
||||
|
||||
import serial
|
||||
from serial.tools import list_ports
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Constants - I2C Addresses
|
||||
# ============================================================================
|
||||
|
||||
class I2CAddress(IntEnum):
|
||||
"""I2C device addresses (7-bit addresses shifted left by 1)"""
|
||||
# X9119 Digital Potentiometer (current control)
|
||||
X9119_CURRENT_WRITE = 0x52
|
||||
X9119_CURRENT_READ = 0x53
|
||||
|
||||
# PCA9555 I/O Expanders
|
||||
PCA9555_DIO_WRITE = 0x4a # Main control I/O at 0x14a
|
||||
PCA9555_DIO_READ = 0x4b
|
||||
PCA9555_PSGLUE_WRITE = 0x48 # Power supply glue at 0x148
|
||||
PCA9555_PSGLUE_READ = 0x49
|
||||
PCA9555_HEAD_WRITE = 0x44 # Head DIO at 0x144
|
||||
PCA9555_HEAD_READ = 0x45
|
||||
PCA9555_LDD_WRITE = 0x40 # LDD control at 0x140
|
||||
PCA9555_LDD_READ = 0x41
|
||||
|
||||
# AD5254 Digital Potentiometer (limits)
|
||||
AD5254_WRITE = 0x58
|
||||
|
||||
# ADS7828 ADC (sensor readings)
|
||||
ADS7828_WRITE = 0x90
|
||||
ADS7828_READ = 0x91
|
||||
|
||||
# M24C64 EEPROM (configuration)
|
||||
EEPROM_WRITE = 0xa4
|
||||
EEPROM_READ = 0xa5
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Constants - PCA9555 Register Addresses
|
||||
# ============================================================================
|
||||
|
||||
class PCA9555Register(IntEnum):
|
||||
"""PCA9555 I/O expander register addresses"""
|
||||
INPUT_PORT_0 = 0x00
|
||||
INPUT_PORT_1 = 0x01
|
||||
OUTPUT_PORT_0 = 0x02
|
||||
OUTPUT_PORT_1 = 0x03
|
||||
POLARITY_INV_0 = 0x04
|
||||
POLARITY_INV_1 = 0x05
|
||||
CONFIG_PORT_0 = 0x06
|
||||
CONFIG_PORT_1 = 0x07
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Constants - Control Bitmasks
|
||||
# ============================================================================
|
||||
|
||||
class ControlBitmask(IntEnum):
|
||||
"""Bitmasks for PCA9555 port 0 control bits"""
|
||||
SHUTTER = 0x01 # Bit 0
|
||||
CURRENT_MODE = 0x04 # Bit 2
|
||||
REMOTE_ENABLE = 0x08 # Bit 3
|
||||
ANALOG_ENABLE = 0x10 # Bit 4
|
||||
KEYSWITCH = 0x20 # Bit 5
|
||||
INTERLOCK = 0x01 # Bit 0 (on different PCA9555)
|
||||
LDD_ENABLE = 0x01 # Bit 0 (on LDD PCA9555)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Serial Communication Layer
|
||||
# ============================================================================
|
||||
|
||||
class SerialComm:
|
||||
"""Handles low-level serial port communication"""
|
||||
|
||||
def __init__(self):
|
||||
self.port: Optional[serial.Serial] = None
|
||||
self.port_name: str = "/dev/ttyUSB0"
|
||||
self.baudrate: int = 9600
|
||||
self.timeout: float = 1.0
|
||||
self.packet_log: List[str] = []
|
||||
|
||||
def connect(self, port_name: str, baudrate: int = 9600) -> bool:
|
||||
"""
|
||||
Connect to serial port
|
||||
|
||||
Args:
|
||||
port_name: Serial port device name
|
||||
baudrate: Baud rate (default 9600)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
self.port_name = port_name
|
||||
self.baudrate = baudrate
|
||||
self.port = serial.Serial(
|
||||
port=port_name,
|
||||
baudrate=baudrate,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
parity=serial.PARITY_NONE,
|
||||
stopbits=serial.STOPBITS_ONE,
|
||||
timeout=self.timeout
|
||||
)
|
||||
time.sleep(0.1) # Allow port to stabilize
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Serial connection error: {e}")
|
||||
return False
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect from serial port"""
|
||||
if self.port and self.port.is_open:
|
||||
self.port.close()
|
||||
self.port = None
|
||||
|
||||
def is_connected(self) -> bool:
|
||||
"""Check if serial port is connected"""
|
||||
return self.port is not None and self.port.is_open
|
||||
|
||||
def write(self, data: bytes) -> bool:
|
||||
"""
|
||||
Write data to serial port
|
||||
|
||||
Args:
|
||||
data: Bytes to write
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
if not self.is_connected():
|
||||
return False
|
||||
|
||||
try:
|
||||
self.port.write(data)
|
||||
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||
hex_str = " ".join(f"{b:02x}" for b in data)
|
||||
self.packet_log.append(f"[{timestamp}] TX: {hex_str}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Serial write error: {e}")
|
||||
return False
|
||||
|
||||
def read(self, size: int) -> Optional[bytes]:
|
||||
"""
|
||||
Read data from serial port
|
||||
|
||||
Args:
|
||||
size: Number of bytes to read
|
||||
|
||||
Returns:
|
||||
Bytes read or None on error
|
||||
"""
|
||||
if not self.is_connected():
|
||||
return None
|
||||
|
||||
try:
|
||||
data = self.port.read(size)
|
||||
if data:
|
||||
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||
hex_str = " ".join(f"{b:02x}" for b in data)
|
||||
self.packet_log.append(f"[{timestamp}] RX: {hex_str}")
|
||||
return data
|
||||
except Exception as e:
|
||||
print(f"Serial read error: {e}")
|
||||
return None
|
||||
|
||||
def flush(self):
|
||||
"""Flush serial port buffers"""
|
||||
if self.is_connected():
|
||||
self.port.reset_input_buffer()
|
||||
self.port.reset_output_buffer()
|
||||
|
||||
def get_packet_log(self, last_n: int = 100) -> List[str]:
|
||||
"""Get last N entries from packet log"""
|
||||
return self.packet_log[-last_n:]
|
||||
|
||||
def clear_packet_log(self):
|
||||
"""Clear packet log"""
|
||||
self.packet_log.clear()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# I2C Protocol Layer
|
||||
# ============================================================================
|
||||
|
||||
class I2CProtocol:
|
||||
"""Handles NXP I2C-over-serial protocol packet construction"""
|
||||
|
||||
NXP_START = 0x53
|
||||
NXP_STOP = 0x50
|
||||
|
||||
def __init__(self, serial_comm: SerialComm):
|
||||
self.serial = serial_comm
|
||||
|
||||
def write(self, i2c_addr_write: int, cmd: bytes, data: bytes = b'') -> bool:
|
||||
"""
|
||||
Build and send NXP I2C write packet
|
||||
|
||||
Packet format: [0x53] [I2C_ADDR] [LENGTH] [COMMAND] [DATA] [0x50]
|
||||
|
||||
Args:
|
||||
i2c_addr_write: I2C write address
|
||||
cmd: Command bytes
|
||||
data: Data bytes (optional)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
if not isinstance(cmd, bytes):
|
||||
cmd = bytes([cmd])
|
||||
if not isinstance(data, bytes):
|
||||
data = bytes(data)
|
||||
|
||||
length = len(cmd) + len(data)
|
||||
packet = bytes([self.NXP_START, i2c_addr_write, length]) + cmd + data + bytes([self.NXP_STOP])
|
||||
|
||||
return self.serial.write(packet)
|
||||
|
||||
def read(self, i2c_addr_write: int, cmd: bytes, cmd_len: int, data_len: int) -> Optional[bytes]:
|
||||
"""
|
||||
Build and send NXP I2C read packet, return data
|
||||
|
||||
Packet format for read:
|
||||
[0x53] [ADDR_WRITE] [CMD_LEN] [CMD] [0x53] [ADDR_READ] [DATA_LEN] [0x50]
|
||||
|
||||
Args:
|
||||
i2c_addr_write: I2C write address
|
||||
cmd: Command bytes
|
||||
cmd_len: Length of command
|
||||
data_len: Expected data length to read
|
||||
|
||||
Returns:
|
||||
Data bytes read or None on error
|
||||
"""
|
||||
if not isinstance(cmd, bytes):
|
||||
cmd = bytes([cmd])
|
||||
|
||||
i2c_addr_read = i2c_addr_write | 0x01 # Set read bit
|
||||
|
||||
# First part: write command
|
||||
packet_write = bytes([self.NXP_START, i2c_addr_write, cmd_len]) + cmd
|
||||
|
||||
# Second part: read data
|
||||
packet_read = bytes([self.NXP_START, i2c_addr_read, data_len, self.NXP_STOP])
|
||||
|
||||
packet = packet_write + packet_read
|
||||
|
||||
if not self.serial.write(packet):
|
||||
return None
|
||||
|
||||
# Read response
|
||||
# Expected response: [data_bytes]
|
||||
time.sleep(0.05) # Give device time to respond
|
||||
response = self.serial.read(data_len)
|
||||
|
||||
return response if response and len(response) == data_len else None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# I2C Device Functions
|
||||
# ============================================================================
|
||||
|
||||
class I2CDevices:
|
||||
"""High-level I2C device control functions"""
|
||||
|
||||
def __init__(self, protocol: I2CProtocol):
|
||||
self.protocol = protocol
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# PCA9555 I/O Expander Functions
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
def pca9555_read_port(self, addr_write: int, port: int) -> Optional[int]:
|
||||
"""
|
||||
Read PCA9555 port register
|
||||
|
||||
Args:
|
||||
addr_write: I2C write address
|
||||
port: Register address (0-7)
|
||||
|
||||
Returns:
|
||||
Port value (0-255) or None on error
|
||||
"""
|
||||
data = self.protocol.read(addr_write, bytes([port]), 1, 1)
|
||||
return data[0] if data else None
|
||||
|
||||
def pca9555_write_port(self, addr_write: int, port: int, value: int) -> bool:
|
||||
"""
|
||||
Write PCA9555 port register
|
||||
|
||||
Args:
|
||||
addr_write: I2C write address
|
||||
port: Register address (0-7)
|
||||
value: Value to write (0-255)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return self.protocol.write(addr_write, bytes([port]), bytes([value]))
|
||||
|
||||
def pca9555_set_bit(self, addr_write: int, port: int, bitmask: int, state: bool) -> bool:
|
||||
"""
|
||||
Set or clear specific bit on PCA9555
|
||||
|
||||
Args:
|
||||
addr_write: I2C write address
|
||||
port: Register address
|
||||
bitmask: Bit mask (e.g., 0x01 for bit 0)
|
||||
state: True to set bit, False to clear
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
# Read current value
|
||||
current = self.pca9555_read_port(addr_write, port)
|
||||
if current is None:
|
||||
return False
|
||||
|
||||
# Modify bit
|
||||
if state:
|
||||
new_value = current | bitmask
|
||||
else:
|
||||
new_value = current & ~bitmask
|
||||
|
||||
# Write back
|
||||
return self.pca9555_write_port(addr_write, port, new_value)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# X9119 Digital Potentiometer Functions
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
def x9119_write_wiper(self, addr_write: int, value: int) -> bool:
|
||||
"""
|
||||
Set X9119 wiper position
|
||||
|
||||
Args:
|
||||
addr_write: I2C write address
|
||||
value: Wiper position (0-1023, 10-bit)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
# X9119 write wiper command: 0xa0 followed by 2 bytes (10-bit value)
|
||||
# Value is split into two bytes: MSB contains upper 2 bits, LSB contains lower 8 bits
|
||||
value = max(0, min(1023, value)) # Clamp to valid range
|
||||
|
||||
msb = (value >> 8) & 0x03 # Upper 2 bits
|
||||
lsb = value & 0xFF # Lower 8 bits
|
||||
|
||||
cmd = bytes([0xa0])
|
||||
data = bytes([msb, lsb])
|
||||
|
||||
return self.protocol.write(addr_write, cmd, data)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# ADS7828 ADC Functions
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
def ads7828_read(self, addr_write: int, channel: int) -> Optional[int]:
|
||||
"""
|
||||
Read ADS7828 ADC channel
|
||||
|
||||
Args:
|
||||
addr_write: I2C write address
|
||||
channel: Channel to read (0-7)
|
||||
|
||||
Returns:
|
||||
12-bit ADC value (0-4095) or None on error
|
||||
"""
|
||||
# ADS7828 command byte format:
|
||||
# Bit 7: SD (single-ended=1)
|
||||
# Bits 6-4: Channel select
|
||||
# Bit 3: PD1 (power-down mode)
|
||||
# Bit 2: PD0 (power-down mode)
|
||||
# Bits 1-0: Don't care
|
||||
|
||||
# For single-ended read with internal reference on
|
||||
cmd_byte = 0x80 | ((channel & 0x07) << 4) | 0x0c
|
||||
|
||||
data = self.protocol.read(addr_write, bytes([cmd_byte]), 1, 2)
|
||||
if not data or len(data) != 2:
|
||||
return None
|
||||
|
||||
# Combine two bytes into 12-bit value
|
||||
value = (data[0] << 8) | data[1]
|
||||
value = (value >> 4) & 0x0FFF # Extract 12-bit value
|
||||
|
||||
return value
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Laser Control Layer
|
||||
# ============================================================================
|
||||
|
||||
class LaserControl:
|
||||
"""High-level laser control functions"""
|
||||
|
||||
def __init__(self, devices: I2CDevices):
|
||||
self.devices = devices
|
||||
self._safe_state_active = False
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# SET Commands
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
def set_current(self, value: int) -> bool:
|
||||
"""
|
||||
Set laser current command (CCMD=)
|
||||
|
||||
Args:
|
||||
value: Current value (0-1023)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return self.devices.x9119_write_wiper(I2CAddress.X9119_CURRENT_WRITE, value)
|
||||
|
||||
def set_power_cmd(self, value: int) -> bool:
|
||||
"""
|
||||
Set power command (PCMD=)
|
||||
|
||||
Note: Uses same device as current control, different channel
|
||||
|
||||
Args:
|
||||
value: Power value (0-1023)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return self.devices.x9119_write_wiper(I2CAddress.X9119_CURRENT_WRITE, value)
|
||||
|
||||
def set_shutter(self, state: bool) -> bool:
|
||||
"""
|
||||
Set shutter state (SHCMD=)
|
||||
|
||||
Args:
|
||||
state: True for open, False for closed
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return self.devices.pca9555_set_bit(
|
||||
I2CAddress.PCA9555_DIO_WRITE,
|
||||
PCA9555Register.OUTPUT_PORT_0,
|
||||
ControlBitmask.SHUTTER,
|
||||
state
|
||||
)
|
||||
|
||||
def set_keyswitch(self, state: bool) -> bool:
|
||||
"""
|
||||
Set keyswitch state (KSWCMD=)
|
||||
|
||||
Args:
|
||||
state: True for on, False for off
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return self.devices.pca9555_set_bit(
|
||||
I2CAddress.PCA9555_DIO_WRITE,
|
||||
PCA9555Register.OUTPUT_PORT_0,
|
||||
ControlBitmask.KEYSWITCH,
|
||||
state
|
||||
)
|
||||
|
||||
def set_current_mode(self, state: bool) -> bool:
|
||||
"""
|
||||
Set current mode (CMODECMD=)
|
||||
|
||||
Args:
|
||||
state: True for on, False for off
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return self.devices.pca9555_set_bit(
|
||||
I2CAddress.PCA9555_DIO_WRITE,
|
||||
PCA9555Register.OUTPUT_PORT_0,
|
||||
ControlBitmask.CURRENT_MODE,
|
||||
state
|
||||
)
|
||||
|
||||
def set_analog_enable(self, state: bool) -> bool:
|
||||
"""
|
||||
Set analog input enable (ANACMD=)
|
||||
|
||||
Args:
|
||||
state: True for enabled, False for disabled
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return self.devices.pca9555_set_bit(
|
||||
I2CAddress.PCA9555_DIO_WRITE,
|
||||
PCA9555Register.OUTPUT_PORT_0,
|
||||
ControlBitmask.ANALOG_ENABLE,
|
||||
state
|
||||
)
|
||||
|
||||
def set_remote_enable(self, state: bool) -> bool:
|
||||
"""
|
||||
Set remote enable (REM=)
|
||||
|
||||
Args:
|
||||
state: True for enabled, False for disabled
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return self.devices.pca9555_set_bit(
|
||||
I2CAddress.PCA9555_DIO_WRITE,
|
||||
PCA9555Register.OUTPUT_PORT_0,
|
||||
ControlBitmask.REMOTE_ENABLE,
|
||||
state
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# GET/Query Commands
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
def get_current_actual(self) -> Optional[float]:
|
||||
"""
|
||||
Get actual current reading
|
||||
|
||||
Returns:
|
||||
Current in arbitrary units (0-4095) or None on error
|
||||
"""
|
||||
# Read from ADS7828, channel based on command 0x84
|
||||
# Command 0x84 suggests channel 0
|
||||
value = self.devices.ads7828_read(I2CAddress.ADS7828_WRITE, 0)
|
||||
return value if value is not None else None
|
||||
|
||||
def get_interlock_status(self) -> Optional[bool]:
|
||||
"""
|
||||
Get interlock status
|
||||
|
||||
Returns:
|
||||
True if interlock OK, False if fault, None on error
|
||||
"""
|
||||
value = self.devices.pca9555_read_port(
|
||||
I2CAddress.PCA9555_PSGLUE_WRITE,
|
||||
PCA9555Register.INPUT_PORT_0
|
||||
)
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Bit 0 indicates interlock status
|
||||
return bool(value & ControlBitmask.INTERLOCK)
|
||||
|
||||
def get_ldd_enable_status(self) -> Optional[bool]:
|
||||
"""
|
||||
Get laser diode driver enable status
|
||||
|
||||
Returns:
|
||||
True if enabled, False if disabled, None on error
|
||||
"""
|
||||
value = self.devices.pca9555_read_port(
|
||||
I2CAddress.PCA9555_LDD_WRITE,
|
||||
PCA9555Register.INPUT_PORT_0
|
||||
)
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
return bool(value & ControlBitmask.LDD_ENABLE)
|
||||
|
||||
def get_psglue_in_status(self) -> Optional[int]:
|
||||
"""
|
||||
Get power supply glue input status
|
||||
|
||||
Returns:
|
||||
Port value or None on error
|
||||
"""
|
||||
return self.devices.pca9555_read_port(
|
||||
I2CAddress.PCA9555_PSGLUE_WRITE,
|
||||
PCA9555Register.INPUT_PORT_0
|
||||
)
|
||||
|
||||
def get_psglue_out_status(self) -> Optional[int]:
|
||||
"""
|
||||
Get power supply glue output status
|
||||
|
||||
Returns:
|
||||
Port value or None on error
|
||||
"""
|
||||
return self.devices.pca9555_read_port(
|
||||
I2CAddress.PCA9555_DIO_WRITE,
|
||||
PCA9555Register.OUTPUT_PORT_0
|
||||
)
|
||||
|
||||
def get_head_dio_status(self) -> Optional[int]:
|
||||
"""
|
||||
Get head DIO status
|
||||
|
||||
Returns:
|
||||
Port value or None on error
|
||||
"""
|
||||
return self.devices.pca9555_read_port(
|
||||
I2CAddress.PCA9555_HEAD_WRITE,
|
||||
PCA9555Register.INPUT_PORT_0
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# Safety Functions
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
def emergency_stop(self) -> bool:
|
||||
"""
|
||||
Emergency stop: close shutter, set current to 0, disable keyswitch
|
||||
|
||||
Returns:
|
||||
True if all operations successful, False otherwise
|
||||
"""
|
||||
success = True
|
||||
success &= self.set_shutter(False)
|
||||
success &= self.set_current(0)
|
||||
success &= self.set_keyswitch(False)
|
||||
self._safe_state_active = True
|
||||
return success
|
||||
|
||||
def enter_safe_state(self) -> bool:
|
||||
"""
|
||||
Enter safe state (similar to emergency stop but also disables remote)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
success = True
|
||||
success &= self.set_shutter(False)
|
||||
success &= self.set_current(0)
|
||||
success &= self.set_power_cmd(0)
|
||||
success &= self.set_keyswitch(False)
|
||||
success &= self.set_remote_enable(False)
|
||||
self._safe_state_active = True
|
||||
return success
|
||||
Reference in New Issue
Block a user