# Copyright (c) ETH Zurich, SIS ID and HVL D-ITET
#
""" """
import logging
from typing import Literal
import libtiepie as ltp
from aenum import Enum, MultiValue
from libtiepie import oscilloscopechannel as ltp_osc_ch
from hvl_ccb.utils.enum import NameEnum, RangeEnum
from hvl_ccb.utils.typing import Number
from hvl_ccb.utils.validation import validate_bool, validate_number
from .base import TiePieError, wrap_libtiepie_exception
from .utils import PublicPropertiesReprMixin
logger = logging.getLogger(__name__)
[docs]
class TiePieOscilloscopeChannelCoupling(NameEnum, init="value description"): # type: ignore[call-arg]
DCV = ltp.CK_DCV, "DC volt"
ACV = ltp.CK_ACV, "AC volt"
DCA = ltp.CK_DCA, "DC current"
ACA = ltp.CK_ACA, "AC current"
[docs]
class TiePieOscilloscopeRange(RangeEnum):
TWO_HUNDRED_MILLI_VOLT = 0.2
FOUR_HUNDRED_MILLI_VOLT = 0.4
EIGHT_HUNDRED_MILLI_VOLT = 0.8
TWO_VOLT = 2
FOUR_VOLT = 4
EIGHT_VOLT = 8
TWENTY_VOLT = 20
FORTY_VOLT = 40
EIGHTY_VOLT = 80
[docs]
@classmethod
def unit(cls) -> Literal["V"]:
return "V"
[docs]
class TiePieOscilloscopeTriggerKind(Enum, settings=MultiValue): # type: ignore[call-arg]
RISING = ltp.TK_RISINGEDGE, "Rising", "RISING"
FALLING = ltp.TK_FALLINGEDGE, "Falling", "FALLING"
ANY = ltp.TK_ANYEDGE, "Any", "ANY"
[docs]
class TiePieOscilloscopeTriggerLevelMode(NameEnum, init="value description"): # type: ignore[call-arg]
UNKNOWN = ltp.TLM_UNKNOWN, "Unknown"
RELATIVE = ltp.TLM_RELATIVE, "Relative"
ABSOLUTE = ltp.TLM_ABSOLUTE, "Absolute"
[docs]
class TiePieOscilloscopeChannelConfig(PublicPropertiesReprMixin):
"""
Oscilloscope's channel configuration, with cleaning of
values in properties setters as well as setting and reading them on and
from the device's channel.
"""
def __init__(self, ch_number: int, channel: ltp_osc_ch.OscilloscopeChannel) -> None:
self.ch_number: int = ch_number
self._channel: ltp_osc_ch.OscilloscopeChannel = channel
self.param_lim: TiePieOscilloscopeChannelConfigLimits = (
TiePieOscilloscopeChannelConfigLimits(osc_channel=channel)
)
[docs]
@staticmethod
def clean_coupling(
coupling: str | TiePieOscilloscopeChannelCoupling,
) -> TiePieOscilloscopeChannelCoupling:
return TiePieOscilloscopeChannelCoupling(coupling)
@property
@wrap_libtiepie_exception
def coupling(self) -> TiePieOscilloscopeChannelCoupling:
return TiePieOscilloscopeChannelCoupling(self._channel.coupling)
@coupling.setter
def coupling(self, coupling: str | TiePieOscilloscopeChannelCoupling) -> None:
self._channel.coupling = self.clean_coupling(coupling).value
logger.info(f"Coupling is set to {coupling}.")
[docs]
@staticmethod
def clean_enabled(enabled: bool) -> bool:
validate_bool("channel enabled", enabled, logger=logger)
return enabled
@property
def enabled(self) -> bool:
return self._channel.enabled
@enabled.setter
def enabled(self, enabled: bool) -> None:
self._channel.enabled = self.clean_enabled(enabled)
msg = "enabled" if enabled else "disabled"
logger.info(f"Channel {self.ch_number} is set to {msg}.")
@property
def input_range(self) -> TiePieOscilloscopeRange:
return TiePieOscilloscopeRange(self._channel.range)
@input_range.setter
def input_range(self, input_range: float | TiePieOscilloscopeRange) -> None:
input_range = self.clean_input_range(input_range).value
self._channel.range = input_range
self.param_lim.trigger_level_abs = (-input_range, input_range)
logger.info(f"input range is set to {self._channel.range} V.")
@property
def probe_offset(self) -> float:
"""The measured value of the channel will be shifted by an offset.
*This feature is currently not implemented*
"""
msg = (
"The 'probe_offset' is deprecated and cannot be used anymore. Please,"
" support the 'hvl_ccb' with an own implementation."
)
logger.error(msg)
raise NotImplementedError(msg)
@probe_offset.setter
def probe_offset(self, _probe_offset: float) -> None:
"""The measured value of the channel will be shifted by an offset.
*This feature is currently not implemented*
"""
msg = (
"The 'probe_offset' is deprecated and cannot be used anymore. Please,"
" support the 'hvl_ccb' with an own implementation."
)
logger.error(msg)
raise NotImplementedError(msg)
@property
def probe_gain(self) -> float:
"""The measured value of the channel will be scaled by a gain.
*This feature is currently not implemented*
"""
msg = (
"The 'probe_gain' is deprecated and cannot be used anymore. Please,"
" support the 'hvl_ccb' with an own implementation."
)
logger.error(msg)
raise NotImplementedError(msg)
@probe_gain.setter
def probe_gain(self, _probe_gain: float) -> None:
"""The measured value of the channel will be scaled by a gain.
*This feature is currently not implemented*
"""
msg = (
"The 'probe_gain' is deprecated and cannot be used anymore. Please,"
" support the 'hvl_ccb' with an own implementation."
)
logger.error(msg)
raise NotImplementedError(msg)
@property
@wrap_libtiepie_exception
def has_safeground(self) -> bool:
"""
Check whether bound oscilloscope device has "safeground" option
:return: bool: 1=safeground available
"""
return self._channel.has_safeground
@property
@wrap_libtiepie_exception
def safeground_enabled(self) -> bool:
"""safeground
:return: _description_
"""
if not self.has_safeground:
msg = "The oscilloscope has no safe ground option."
logger.error(msg)
raise TiePieError(msg)
return self._channel.safeground_enabled
@safeground_enabled.setter
@wrap_libtiepie_exception
def safeground_enabled(self, value: bool) -> None:
if not self.has_safeground:
msg = "The oscilloscope has no safeground option."
raise TiePieError(msg)
validate_bool("safeground enabled", value, logger=logger)
self._channel.safeground_enabled = value
msg = "enabled" if value else "disabled"
logger.info(f"Safeground is {msg}.")
[docs]
def clean_trigger_hysteresis(self, trigger_hysteresis: float) -> float:
validate_number(
"trigger hysteresis",
trigger_hysteresis,
self.param_lim.trigger_hysteresis,
logger=logger,
)
return float(trigger_hysteresis)
@property
def trigger_hysteresis(self) -> float:
return self._channel.trigger.hystereses[0]
@trigger_hysteresis.setter
def trigger_hysteresis(self, trigger_hysteresis: float) -> None:
self._channel.trigger.hystereses[0] = self.clean_trigger_hysteresis(
trigger_hysteresis
)
logger.info(f"Trigger hysteresis is set to {trigger_hysteresis}.")
[docs]
@staticmethod
def clean_trigger_kind(
trigger_kind: str | TiePieOscilloscopeTriggerKind,
) -> TiePieOscilloscopeTriggerKind:
return TiePieOscilloscopeTriggerKind(trigger_kind)
@property
def trigger_kind(self) -> TiePieOscilloscopeTriggerKind:
return TiePieOscilloscopeTriggerKind(self._channel.trigger.kind)
@trigger_kind.setter
def trigger_kind(self, trigger_kind: str | TiePieOscilloscopeTriggerKind) -> None:
self._channel.trigger.kind = self.clean_trigger_kind(trigger_kind).value
logger.info(f"Trigger kind is set to {trigger_kind}.")
[docs]
@staticmethod
def clean_trigger_level_mode(
level_mode: str | TiePieOscilloscopeTriggerLevelMode,
) -> TiePieOscilloscopeTriggerLevelMode:
return TiePieOscilloscopeTriggerLevelMode(level_mode)
@property
def trigger_level_mode(self) -> TiePieOscilloscopeTriggerLevelMode:
return TiePieOscilloscopeTriggerLevelMode(self._channel.trigger.level_mode)
@trigger_level_mode.setter
def trigger_level_mode(
self, level_mode: str | TiePieOscilloscopeTriggerLevelMode
) -> None:
level_mode_ = self.clean_trigger_level_mode(level_mode)
self._channel.trigger.level_mode = level_mode_.value
logger.info(f"Trigger level mode is set to {level_mode_.name}.")
[docs]
def clean_trigger_level(self, trigger_level: Number) -> float:
if self.trigger_level_mode == TiePieOscilloscopeTriggerLevelMode.RELATIVE:
validate_number(
"trigger level",
trigger_level,
self.param_lim.trigger_level_rel,
float,
logger=logger,
)
if self.trigger_level_mode == TiePieOscilloscopeTriggerLevelMode.ABSOLUTE:
validate_number(
"trigger level",
trigger_level,
self.param_lim.trigger_level_abs,
(int, float),
logger=logger,
)
return float(trigger_level)
@property
def trigger_level(self) -> float:
return self._channel.trigger.levels[0]
@trigger_level.setter
def trigger_level(self, trigger_level: Number) -> None:
self._channel.trigger.levels[0] = self.clean_trigger_level(trigger_level)
if self.trigger_level_mode == TiePieOscilloscopeTriggerLevelMode.RELATIVE:
logger.info(f"Trigger level is set to {trigger_level}.")
if self.trigger_level_mode == TiePieOscilloscopeTriggerLevelMode.ABSOLUTE:
logger.info(f"Trigger level is set to {trigger_level} V.")
[docs]
@staticmethod
def clean_trigger_enabled(trigger_enabled: bool) -> bool:
validate_bool("Trigger enabled", trigger_enabled, logger=logger)
return trigger_enabled
@property
def trigger_enabled(self) -> bool:
return self._channel.trigger.enabled
@trigger_enabled.setter
def trigger_enabled(self, trigger_enabled: bool) -> None:
self._channel.trigger.enabled = self.clean_trigger_enabled(trigger_enabled)
msg = "enabled" if trigger_enabled else "disabled"
logger.info(f"Trigger is set to {msg}.")
[docs]
class TiePieOscilloscopeChannelConfigLimits:
"""
Default limits for oscilloscope channel parameters.
"""
def __init__(self, osc_channel: ltp_osc_ch.OscilloscopeChannel) -> None:
self.input_range = (osc_channel.ranges[0], osc_channel.ranges[-1]) # [V]
self.trigger_hysteresis = (0, 1)
self.trigger_level_rel = (0, 1)
self.trigger_level_abs = (-osc_channel.ranges[-1], osc_channel.ranges[-1])