# Copyright (c) ETH Zurich, SIS ID and HVL D-ITET
#
"""
Switches of the different "Cubes".
"""
import logging
from abc import ABC
from typing import TYPE_CHECKING, Optional
from aenum import IntEnum
from hvl_ccb.utils.enum import BoolEnum
from .constants import SafetyStatus
from .errors import SwitchOperationError
if TYPE_CHECKING:
from . import BaseCube # pragma: no cover
logger = logging.getLogger(__name__)
[docs]
class SwitchStatus(IntEnum):
"""
Status of a switch. These are the possible values in the status integer
e.g. in :attr:`_Switch.status`.
"""
# Switch is deselected and not enabled in safety circuit. To get out of
# this state, the earthing has to be enabled in the BaseCube HMI setup.
INACTIVE = 0
# Earthing is closed (safe).
CLOSED = 1
# Earthing is open (not safe).
OPEN = 2
# Earthing is in error, e.g. when the stick did not close correctly or could not
# open.
ERROR = 3
[docs]
class SwitchOperatingStatus(IntEnum):
"""
Operating Status of a switch. Switch can be in auto or manual mode.
"""
AUTO = 0
MANUAL = 1
[docs]
class SwitchOperation(BoolEnum):
"""
Operation of a switch in manual operating mode. Can be closed or opened.
"""
OPEN = False
CLOSE = True
class _Switch(ABC):
"""
Switch with status, operating status (manual and auto) and manual operate.
"""
_CLS_ERROR = SwitchOperationError
_SWITCHABLE_AT_STATES: Optional[tuple[SafetyStatus]] = None
def __init__(self, handle, device_name: str):
self._handle: BaseCube = handle
self._device_name: str = device_name
self._CMD_STATUS: Optional[str] = None
self._CMD_OPERATING_STATUS: Optional[str] = None
self._CMD_MANUAL: Optional[str] = None
@property
def status(self) -> SwitchStatus:
"""
Position status of a switch.
:return: Status of the switch.
"""
if self._CMD_STATUS is None:
msg = (
f"Tried to query position status of {self._device_name}, "
f"but {self._device_name} does not have a position status feedback."
)
logger.error(msg)
raise self._CLS_ERROR(msg)
value = SwitchStatus(self._handle.read(self._CMD_STATUS))
logger.info(f"Status of {self._device_name} is {value.name}")
return value
@property
def operating_status(self) -> SwitchOperatingStatus:
"""
Switch operating status, if 'manual' the stick can be controlled by the
user.
:return: Switch operating status, can be either auto or manual
"""
if self._CMD_OPERATING_STATUS is None:
msg = (
f"Tried to query operating status of {self._device_name}, "
f"but {self._device_name} does not have an operating status."
)
logger.error(msg)
raise self._CLS_ERROR(msg)
value = SwitchOperatingStatus(self._handle.read(self._CMD_OPERATING_STATUS))
logger.info(f"Operating Status of {self._device_name} is {value.name}")
return value
def operation_conditions_fulfilled(self) -> None:
"""
Method to be called before an operation is performed. It will raise an
exception if not
"""
if (
self._SWITCHABLE_AT_STATES
and self._handle.status not in self._SWITCHABLE_AT_STATES
):
switchable_states = " or ".join(
[f'"{_}"' for _ in self._SWITCHABLE_AT_STATES]
)
msg = (
f"Cube needs to be in state {switchable_states} "
f"to operate {self._device_name} manually, "
f'but is in "{self._handle.status.name}".'
)
logger.error(msg)
raise self._CLS_ERROR(msg)
if not self.operating_status == SwitchOperatingStatus.MANUAL:
msg = (
f"Operation of the {self._device_name} is not possible, "
"as the feature is not activated in the Cube Setup."
)
logger.error(msg)
raise self._CLS_ERROR(msg)
@property
def operate(self) -> SwitchOperation:
"""
Operation of a switch which has a manual operating status.
:return: Switch operation setting, can be open or close
"""
if self._CMD_MANUAL is None:
msg = (
f"Tried to query manual operation setting of {self._device_name}, "
f"but {self._device_name} cannot be operated manually."
)
logger.error(msg)
raise self._CLS_ERROR(msg)
value = SwitchOperation(self._handle.read(self._CMD_MANUAL))
logger.info(f"Manual operation status of {self._device_name} is {value}")
return value
@operate.setter
def operate(self, operation: SwitchOperation) -> None:
"""
Operation of a switch which has a manual operating status.
:param operation: switch operation setting (close or open)
:raises SwitchOperationError: when the operation conditions of the switch
are not fulfilled, or if the switch is not allowed to be manually operated
"""
self.operation_conditions_fulfilled()
if self._CMD_MANUAL is None:
msg = ( # pragma: no cover
f"Tried to operate {self._device_name}, "
f"but {self._device_name} cannot"
"be operated manually."
)
logger.error(msg) # pragma: no cover
raise self._CLS_ERROR(msg) # pragma: no cover
operation = SwitchOperation(operation)
self._handle.write(self._CMD_MANUAL, operation)
logger.info(f"{self._device_name} is set to {operation}")