""" Data models for laser control module. Provides dataclasses and enums for structured data representation throughout the laser control system. """ from dataclasses import dataclass from enum import IntEnum from typing import Optional, Dict, Any from datetime import datetime class VariationType(IntEnum): """Types of parameter variation modes.""" MANUAL = 0x00 CHANGE_CURRENT_LD1 = 0x01 CHANGE_CURRENT_LD2 = 0x02 CHANGE_TEMPERATURE_LD1 = 0x03 CHANGE_TEMPERATURE_LD2 = 0x04 class DeviceState(IntEnum): """Device operational states.""" IDLE = 0x0000 RUNNING = 0x0001 BUSY = 0x0002 ERROR = 0x00FF ERROR_OVERHEAT = 0x0100 ERROR_POWER = 0x0200 ERROR_COMMUNICATION = 0x0400 ERROR_INVALID_COMMAND = 0x0800 @dataclass class ManualModeParams: """Parameters for manual control mode.""" temp1: float # Temperature for laser 1 (°C) temp2: float # Temperature for laser 2 (°C) current1: float # Current for laser 1 (mA) current2: float # Current for laser 2 (mA) pi_coeff1_p: float = 1.0 # PI controller proportional coefficient for laser 1 pi_coeff1_i: float = 0.5 # PI controller integral coefficient for laser 1 pi_coeff2_p: float = 1.0 # PI controller proportional coefficient for laser 2 pi_coeff2_i: float = 0.5 # PI controller integral coefficient for laser 2 def to_dict(self) -> Dict[str, float]: """Convert to dictionary representation.""" return { 'temp1': self.temp1, 'temp2': self.temp2, 'current1': self.current1, 'current2': self.current2, 'pi_coeff1_p': self.pi_coeff1_p, 'pi_coeff1_i': self.pi_coeff1_i, 'pi_coeff2_p': self.pi_coeff2_p, 'pi_coeff2_i': self.pi_coeff2_i } @dataclass class VariationParams: """Parameters for variation mode.""" variation_type: VariationType # Static parameters (fixed during variation) static_temp1: float static_temp2: float static_current1: float static_current2: float # Variation range min_value: float # Minimum value for varied parameter max_value: float # Maximum value for varied parameter step: float # Step size for variation # Time parameters time_step: int # Time step in microseconds (20-100) delay_time: int # Delay between measurements in milliseconds (3-10) def to_dict(self) -> Dict[str, Any]: """Convert to dictionary representation.""" return { 'variation_type': self.variation_type.value, 'static_temp1': self.static_temp1, 'static_temp2': self.static_temp2, 'static_current1': self.static_current1, 'static_current2': self.static_current2, 'min_value': self.min_value, 'max_value': self.max_value, 'step': self.step, 'time_step': self.time_step, 'delay_time': self.delay_time } @dataclass class Measurements: """Real-time measurements from the device.""" # Photodiode currents current1: float # Photodiode current for laser 1 (mA) current2: float # Photodiode current for laser 2 (mA) # Temperatures temp1: float # Temperature of laser 1 (°C) temp2: float # Temperature of laser 2 (°C) temp_ext1: Optional[float] = None # External thermistor 1 temperature (°C) temp_ext2: Optional[float] = None # External thermistor 2 temperature (°C) # Power supply voltages voltage_3v3: float = 0.0 # 3.3V rail voltage voltage_5v1: float = 0.0 # 5V rail 1 voltage voltage_5v2: float = 0.0 # 5V rail 2 voltage voltage_7v0: float = 0.0 # 7V rail voltage # Metadata timestamp: Optional[datetime] = None message_id: Optional[int] = None to6_counter_lsb: Optional[int] = None to6_counter_msb: Optional[int] = None def __post_init__(self): """Set timestamp if not provided.""" if self.timestamp is None: self.timestamp = datetime.now() def to_dict(self) -> Dict[str, Any]: """Convert to dictionary representation.""" return { 'current1': self.current1, 'current2': self.current2, 'temp1': self.temp1, 'temp2': self.temp2, 'temp_ext1': self.temp_ext1, 'temp_ext2': self.temp_ext2, 'voltage_3v3': self.voltage_3v3, 'voltage_5v1': self.voltage_5v1, 'voltage_5v2': self.voltage_5v2, 'voltage_7v0': self.voltage_7v0, 'timestamp': self.timestamp.isoformat() if self.timestamp else None, 'message_id': self.message_id } def check_power_rails(self) -> Dict[str, bool]: """Check if power supply voltages are within acceptable range.""" return { '3v3': 3.1 <= self.voltage_3v3 <= 3.5, '5v1': 4.8 <= self.voltage_5v1 <= 5.3, '5v2': 4.8 <= self.voltage_5v2 <= 5.3, '7v0': 6.5 <= self.voltage_7v0 <= 7.5 } @dataclass class DeviceStatus: """Complete device status information.""" state: DeviceState measurements: Optional[Measurements] = None is_connected: bool = False last_command_id: Optional[int] = None error_message: Optional[str] = None @property def is_idle(self) -> bool: """Check if device is idle.""" return self.state == DeviceState.IDLE @property def is_running(self) -> bool: """Check if device is running a task.""" return self.state == DeviceState.RUNNING @property def has_error(self) -> bool: """Check if device has any error.""" return self.state >= DeviceState.ERROR @property def error_type(self) -> Optional[str]: """Get human-readable error type.""" if not self.has_error: return None error_map = { DeviceState.ERROR_OVERHEAT: "Overheating", DeviceState.ERROR_POWER: "Power supply issue", DeviceState.ERROR_COMMUNICATION: "Communication error", DeviceState.ERROR_INVALID_COMMAND: "Invalid command" } return error_map.get(self.state, "Unknown error") def to_dict(self) -> Dict[str, Any]: """Convert to dictionary representation.""" return { 'state': self.state.value, 'state_name': self.state.name, 'measurements': self.measurements.to_dict() if self.measurements else None, 'is_connected': self.is_connected, 'last_command_id': self.last_command_id, 'error_message': self.error_message, 'is_idle': self.is_idle, 'is_running': self.is_running, 'has_error': self.has_error, 'error_type': self.error_type } @dataclass class CalibrationData: """Calibration data for device sensors.""" # Temperature calibration coefficients temp1_offset: float = 0.0 temp1_scale: float = 1.0 temp2_offset: float = 0.0 temp2_scale: float = 1.0 # Current calibration coefficients current1_offset: float = 0.0 current1_scale: float = 1.0 current2_offset: float = 0.0 current2_scale: float = 1.0 # Voltage calibration voltage_3v3_scale: float = 1.0 voltage_5v1_scale: float = 1.0 voltage_5v2_scale: float = 1.0 voltage_7v0_scale: float = 1.0