""" Custom exceptions for laser control module. Provides a hierarchy of exceptions for different error conditions that may occur during laser control operations. """ class LaserControlError(Exception): """Base exception for all laser control errors.""" pass class ValidationError(LaserControlError): """Base exception for validation errors.""" pass class TemperatureOutOfRangeError(ValidationError): """Exception raised when temperature is outside valid range.""" def __init__(self, param_name: str, value: float, min_val: float, max_val: float): self.param_name = param_name self.value = value self.min_val = min_val self.max_val = max_val super().__init__( f"{param_name}: Temperature {value}°C is out of range " f"[{min_val}°C - {max_val}°C]" ) class CurrentOutOfRangeError(ValidationError): """Exception raised when current is outside valid range.""" def __init__(self, param_name: str, value: float, min_val: float, max_val: float): self.param_name = param_name self.value = value self.min_val = min_val self.max_val = max_val super().__init__( f"{param_name}: Current {value}mA is out of range " f"[{min_val}mA - {max_val}mA]" ) class InvalidParameterError(ValidationError): """Exception raised for invalid parameter types or values.""" def __init__(self, param_name: str, message: str): self.param_name = param_name super().__init__(f"{param_name}: {message}") class CommunicationError(LaserControlError): """Base exception for communication errors.""" pass class PortNotFoundError(CommunicationError): """Exception raised when serial port cannot be found.""" def __init__(self, port: str | None = None): if port: message = f"Serial port '{port}' not found" else: message = "No suitable serial port found for device connection" super().__init__(message) class PortBusyError(CommunicationError): """Raised when the serial port is already held by another program. A laser controller is a single half-duplex request/response bus: only one process may own it at a time. When the port is opened exclusively (``exclusive=True``), a second exclusive open fails with this error instead of silently sharing the bus and corrupting both sides' traffic. """ def __init__(self, port: str | None = None): target = f"'{port}'" if port else "the laser controller port" super().__init__( f"Serial port {target} is busy — another program is already using " f"the laser controller. Close it first (only one program may own the " f"port at a time)." ) class DeviceNotRespondingError(CommunicationError): """Exception raised when device doesn't respond to commands.""" def __init__(self, timeout: float | None = None): if timeout: message = f"Device did not respond within {timeout} seconds" else: message = "Device is not responding to commands" super().__init__(message) class CRCError(CommunicationError): """Exception raised when CRC check fails.""" def __init__(self, expected: int | None = None, received: int | None = None): if expected is not None and received is not None: message = f"CRC check failed. Expected: 0x{expected:04X}, Received: 0x{received:04X}" else: message = "CRC check failed on received data" super().__init__(message) class ProtocolError(CommunicationError): """Exception raised for protocol-level errors.""" def __init__(self, message: str): super().__init__(f"Protocol error: {message}")