Source code for hvl_ccb.dev.cube.advanced

#  Copyright (c) ETH Zurich, SIS ID and HVL D-ITET
#
"""
The very special "AdvancedCube" with its two single implementations "SynthCube" and
"FPCSCube".
"""

import logging
from collections.abc import Iterator

from hvl_ccb.configuration import configdataclass
from hvl_ccb.utils.enum import BoolEnum
from hvl_ccb.utils.typing import Number

from .base import BaseCube, BaseCubeConfiguration
from .constants import SafetyStatus, _Door
from .errors import (
    AdvancedCubeModuleError,
    ChargerSwitchError,
    DischargeError,
    ShortCircuitError,
)
from .switches import SwitchOperation, _Switch

logger = logging.getLogger(__name__)


[docs] class ActivationStatus(BoolEnum): """ Boolean activation status of an advanced cube output bit """ ACTIVATED = True DEACTIVATED = False
[docs] class PressurizationStatus(BoolEnum): """ Boolean status of an advanced cube pressure sensor """ PRESSURIZED = True DEPRESSURIZED = False
class _DischargeSwitch(_Switch): """ Discharge switch within a module of the advanced cube. """ _ERROR_CLS = DischargeError _STATES_FOR_SWITCHING = ( SafetyStatus.RED_READY, SafetyStatus.RED_OPERATE, ) def __init__(self, handle, module_number: int) -> None: super().__init__(handle, device_name=f"Discharge Switch {module_number}") self._CMD_STATUS = ( f'"DB_Safety_Circuit"."Module_{module_number}".' '"Discharge_Circuit"."si_HMI_Status"' ) self._CMD_OPERATING_STATUS = ( f'"DB_Safety_Circuit"."Module_{module_number}"."sx_able_to_discharge"' ) self._CMD_MANUAL = ( f'"DB_Safety_Circuit"."Module_{module_number}"."sx_discharge"' ) class _ShortCircuitSwitch(_Switch): """ Short-circuit switch within a module of the advanced cube. """ _ERROR_CLS = ShortCircuitError def __init__(self, handle, module_number: int) -> None: identifier = ( "Central_Earthing" if module_number == 0 else f"Module_{module_number}" ) super().__init__(handle, device_name=f"Short-Circuit Switch {identifier}") identifier_extended = ( identifier if module_number == 0 else identifier + '"."Short_Circuit' ) self._CMD_STATUS = ( f'"DB_Safety_Circuit"."{identifier_extended}"."si_HMI_Status"' ) self._CMD_OPERATING_STATUS = ( f'"DB_Safety_Circuit"."{identifier}"."sx_able_to_short_circuit"' ) self._CMD_MANUAL = None class _ChargerSwitch(_Switch): """ Charger switch within a module of the advanced cube. """ _ERROR_CLS = ChargerSwitchError _STATES_FOR_SWITCHING = (SafetyStatus.RED_OPERATE,) def __init__(self, handle, module_number: int) -> None: identifier = ( "Central_Charger" if module_number == 0 else f"Module_{module_number}" ) super().__init__(handle, device_name=f"Charger Switch {identifier}") identifier_extended = ( identifier if module_number == 0 else identifier + '"."Charger_Circuit' ) self._CMD_STATUS = ( f'"DB_Safety_Circuit"."{identifier_extended}"."si_HMI_Status"' ) self._CMD_OPERATING_STATUS = ( f'"DB_Safety_Circuit"."{identifier}"."sx_able_to_charge"' ) self._CMD_MANUAL = f'"DB_Safety_Circuit"."{identifier}"."sx_charge"' class _Module: """ Module level interface between AdvancedCube and current sources. """ _ACTIVATABLE_STATES = ( SafetyStatus.GREEN_NOT_READY, SafetyStatus.GREEN_READY, ) def __init__(self, handle, index: int) -> None: self._handle: AdvancedCube = handle self._index: int = index self.discharge_switch: _DischargeSwitch = _DischargeSwitch(handle, index) self.short_circuit_switch: _ShortCircuitSwitch = _ShortCircuitSwitch( handle, index ) self.charger_switch: _ChargerSwitch = _ChargerSwitch(handle, index) self._CMD_ACTIVE: str = f'"DB_Safety_Circuit"."Module_{index}"."sx_dial"' self._CMD_VOLTAGE: str = f'"DB_Safety_Circuit"."Module_{index}"."si_voltage"' self._CMD_MAX_SC_SWITCHING_VOLTAGE: str = ( f'"DB_Safety_Circuit"."Module_{index}".' '"Ir_max_short_circuit_switching_voltage"' ) def __repr__(self) -> str: return f"Module {self._index}" @property def active(self) -> ActivationStatus: """ Module activation status :return: boolean module activation status """ activation_status = ActivationStatus(self._handle.read(self._CMD_ACTIVE)) logger.info(f"Module {self._index} is {activation_status.name}") return activation_status @active.setter def active(self, new_activation_status: ActivationStatus): new_activation_status = ActivationStatus(new_activation_status) if self._handle.status in self._ACTIVATABLE_STATES: self._handle.write(self._CMD_ACTIVE, new_activation_status) logger.info(f"Module {self._index} set to {new_activation_status.name}") else: msg = ( f"Tried to set Module {self._index} to {new_activation_status.name}. " "Safety circuit must be in green state to change the activation " f"status of a module, but is in {self._handle.status.name}" ) logger.error(msg) raise AdvancedCubeModuleError(msg) @property def voltage(self) -> int: """ Measured voltage of a module. :return: measured voltage in V """ voltage = int(self._handle.read(self._CMD_VOLTAGE)) logger.info(f"Module {self._index} voltage: {voltage} V") return voltage @property def max_short_circuit_switching_voltage(self) -> int: """ Maximum switching voltage of a module. :return: maximum switching voltage in V """ voltage = int(self._handle.read(self._CMD_MAX_SC_SWITCHING_VOLTAGE)) logger.info( f"Maximum short-circuit switching voltage of Module {self._index}" f" is set to {voltage} V" ) return voltage class _Pneumatics: """ Pneumatic circuit of a current source controlled by an AdvancedCube """ _CMD_STATUS = '"Ix_Allg_pressure_ok"' _CMD_ACTIVE = '"Qx_Allg_Main_Pressure"' def __init__(self, handle) -> None: self._handle: AdvancedCube = handle @property def status(self) -> PressurizationStatus: """ Pneumatic circuit pressure status :return: boolean pressurization status """ status = PressurizationStatus(self._handle.read(self._CMD_STATUS)) logger.info(f"Pneumatic circuit is {status.name}") return status @property def active(self) -> ActivationStatus: """ Pressure valve activation status :return: boolean pressure valve activation status """ activation_status = ActivationStatus(self._handle.read(self._CMD_ACTIVE)) logger.info(f"Main pressure valve is {activation_status.name}") return activation_status @active.setter def active(self, new_activation_status: ActivationStatus): new_activation_status = ActivationStatus(new_activation_status) self._handle.write(self._CMD_ACTIVE, new_activation_status) logger.info(f"Main pressure valve {new_activation_status.name}") class _Siren: """ Acoustic alert device controlled by an AdvancedCube """ def __init__(self, handle, index: int, name: str) -> None: self._handle: AdvancedCube = handle self._name = name self._CMD_ACTIVE: str = f'"Qx_Allg_Acoustic_{index}"' @property def active(self) -> ActivationStatus: """ Siren activation status :return: boolean siren activation status """ active = ActivationStatus(self._handle.read(self._CMD_ACTIVE)) logger.info(f"{self._name} siren is {active.name}") return active @active.setter def active(self, active: ActivationStatus): active = ActivationStatus(active) self._handle.write(self._CMD_ACTIVE, active) logger.info(f"{self._name} siren has been {active.name}")
[docs] @configdataclass class AdvancedCubeConfiguration(BaseCubeConfiguration): """Adaption of base configuration to accept longer time to wait for a discharge""" timeout_status_change: Number = 20
[docs] class AdvancedCube(BaseCube): """ The AdvancedCube is the template for its two special implementations """ _N_MODULES: int | None = None def __init__(self, com, dev_config=None) -> None: super().__init__(com, dev_config) self.modules: dict[int, _Module] = {} self.pneumatics: _Pneumatics | None = None self.charging_siren: _Siren | None = None self.triggering_siren: _Siren | None = None
[docs] @staticmethod def config_cls(): return AdvancedCubeConfiguration
@property def number_of_modules(self) -> int: """ Queries the number of installed modules (active plus inactive) """ if self._N_MODULES is None: msg = "No number of modules was set in implementation." logger.error(msg) raise AdvancedCubeModuleError(msg) val = self._N_MODULES logger.info(f"{self.__class__.__name__} has {val} modules installed.") return val
[docs] def start(self) -> None: """ Start the connection to the AdvancedCube and set up the modules """ super().start() try: _ = self.number_of_modules except AdvancedCubeModuleError: self.stop() raise for ii in range(self.number_of_modules): self.modules[ii + 1] = _Module(self, ii + 1) self.pneumatics = _Pneumatics(self) self.charging_siren = _Siren(self, 1, name="Charging") self.triggering_siren = _Siren(self, 2, name="Triggering")
@property def active_modules(self) -> Iterator[_Module]: """ Iterator for all active modules """ for module in self.modules.values(): if module.active == ActivationStatus.ACTIVATED: yield module @property def operate_discharge_switches(self) -> list[SwitchOperation]: """ Property to query all discharge switches of the installed/active modules :return: `list[SwitchOperation]` of the individual discharge switches """ return [module.discharge_switch.operate for module in self.active_modules] @operate_discharge_switches.setter def operate_discharge_switches(self, operation: SwitchOperation) -> None: """ Operate all discharge switches of the installed/active modules :param operation: a `SwitchOperation` to `OPEN` or `CLOSE` """ for module in self.active_modules: module.discharge_switch.operate = operation
[docs] class SynthCube(AdvancedCube): """ The SynthCube is the special Cube for the Synthetic Circuit """ _N_MODULES = 4 def __init__(self, com, dev_config=None) -> None: super().__init__(com, dev_config) self.hcc_loop_1: _Module | None = None self.hcc_loop_2: _Module | None = None self.hcc_loop_3: _Module | None = None self.cic: _Module | None = None
[docs] def start(self) -> None: super().start() self.hcc_loop_1 = self.modules[1] self.hcc_loop_2 = self.modules[2] self.hcc_loop_3 = self.modules[3] self.cic = self.modules[4]
@property def active_hcc_loops(self) -> Iterator[_Module]: """ Iterator for all active high-current modules """ for module in self.active_modules: if module in (self.hcc_loop_1, self.hcc_loop_2, self.hcc_loop_3): yield module
[docs] class FPCSCube(AdvancedCube): """ The FPCSCube is the special Cube for the FPCS """ _N_MODULES = 8 door_fpcs_status = _Door("FPCS", "door status") def __init__(self, com, dev_config=None) -> None: super().__init__(com, dev_config) self.central_earthing: _ShortCircuitSwitch | None = None self.central_charger: _ChargerSwitch | None = None self.module: dict[str, _Module] = {}
[docs] def start(self) -> None: super().start() for ii in range(self._N_MODULES): self.module[chr(ord("A") + ii)] = self.modules[ii + 1] self.module[chr(ord("a") + ii)] = self.modules[ii + 1] self.central_charger = _ChargerSwitch(self, 0) self.central_earthing = _ShortCircuitSwitch(self, 0)