Source code for hvl_ccb.dev.keysightb298xx.modules.submodules.sense

#  Copyright (c) ETH Zurich, SIS ID and HVL D-ITET
#
import logging
from abc import ABC, abstractmethod

from aenum import StrEnum

from hvl_ccb.dev.keysightb298xx.comm import KeysightB2985AVisaCommunication
from hvl_ccb.dev.keysightb298xx.modules.submodules.base import _BaseModule
from hvl_ccb.dev.keysightb298xx.modules.submodules.format import FormatElements
from hvl_ccb.utils.enum import RangeEnum
from hvl_ccb.utils.validation import validate_bool, validate_number

logger = logging.getLogger(__name__)


[docs] class ApertureMode(StrEnum): """ Enum for the different aperture auto modes. """ OFF = "OFF" SHORT = "SHOR" MEDIUM = "MED" LONG = "LONG"
[docs] class CurrentRange(RangeEnum): """ Enum for the different current measurement ranges. """ TWO_pA = 2e-12 TWENTY_pA = 2e-11 TWO_HUNDRED_pA = 2e-10 TWO_nA = 2e-9 TWENTY_nA = 2e-8 TWO_HUNDRED_nA = 2e-7 TWO_uA = 2e-6 TWENTY_uA = 2e-5 TWO_HUNDRED_uA = 2e-4 TWO_mA = 2e-3 TWENTY_mA = 2e-2
[docs] class ChargeRange(RangeEnum): """ Enum for the different charge measurement ranges. """ TWO_nC = 2e-9 TWENTY_nC = 2e-8 TWO_HUNDRED_nC = 2e-7 TWO_uC = 2e-6
[docs] class VoltageRange(RangeEnum): """ Enum for the different voltage measurement ranges. """ TWO_VOLT = 2 TWENTY_VOLT = 20
[docs] class ResistanceRange(RangeEnum): """ Enum for the different resistance measurement ranges. """ ONE_M_OHM = 1e6 TEN_M_OHM = 1e7 ONE_HUNDRED_M_OHM = 1e8 ONE_G_OHM = 1e9 TEN_G_OHM = 1e10 ONE_HUNDRED_G_OHM = 1e11 ONE_T_OHM = 1e12 TEN_T_OHM = 1e13 ONE_HUNDRED_T_OHM = 1e14 ONE_P_OHM = 1e15
class _SenseBase(_BaseModule): """ Base class for the sense module. """ @property def aperture(self) -> float: """ Get the measurement aperture. :return: float with the aperture in s """ return float(self._com.query(f"{self._base_command}:APER?")) @aperture.setter def aperture(self, value: float) -> None: """ Set the measurement aperture in s :param value: aperture duration in s. """ validate_number("Aperture Time", value, (1e-5, 2), (int, float), logger) logger.info(f"Aperture time: {value} s") self._com.write(f"{self._base_command}:APER {value}") @property def aperture_mode(self) -> ApertureMode: """ Returns the aperture mode :return: aperture mode as StrEnum value """ status, mode = self._com.query_multiple( f"{self._base_command}:APER:AUTO?", f"{self._base_command}:APER:AUTO:MODE?" ) if not int(status): return ApertureMode.OFF # type: ignore[return-value] return ApertureMode(mode) @aperture_mode.setter def aperture_mode(self, value: ApertureMode) -> None: """ Set the aperture mode :param value: Aperture mode in ApertureMode StrEnum """ value = ApertureMode(value) logger.info(f"Aperture mode: {value}") if value == ApertureMode.OFF: self._com.write(f"{self._base_command}:APER:AUTO {value}") else: self._com.write(f"{self._base_command}:APER:AUTO ON") self._com.write(f"{self._base_command}:APER:AUTO:MODE {value}") class _SensValue(_BaseModule, ABC): """ Base class for all sense modules. """ def __init__( self, com: KeysightB2985AVisaCommunication, base_command: str, name: str, unit: str, ) -> None: super().__init__(com, base_command, name) self._unit = unit @property def auto_range(self) -> bool: """ Get the current set range mode of the module :return: bool indicating the auto range mode """ return bool(int(self._com.query(f"{self._base_command}:RANG:AUTO?"))) @auto_range.setter def auto_range(self, value: bool) -> None: """ Enable or disable the auto range mode :param value: bool of the desired state """ validate_bool("auto range", value, logger) logger.info(f"Ruto range active: {value}") self._com.write(f"{self._base_command}:RANG:AUTO {int(value)}") @property def range(self) -> float: """ Get the current range of a measurement system. :return: float with the range of a measurement system """ return float(self._com.query(f"{self._base_command}:RANG?")) @range.setter def range(self, value: float) -> None: """ Set the range of a measurement system. :param value: float with the desired value. Will be checked with a RangeEnum. """ value = self._validate_range(value) logger.info(f"{self._name} range: {value} {self._unit}") self._com.write(f"{self._base_command}:RANG {value}") @abstractmethod def _validate_range(self, value) -> float: ...
[docs] class SensCurrent(_SensValue): """ Current measurement class. """ def __init__(self, com: KeysightB2985AVisaCommunication) -> None: super().__init__(com, ":SENS:CURR", "current", "A") def _validate_range(self, value) -> float: return CurrentRange(abs(value))
[docs] class SensVoltage(_SensValue): """ Voltage measurement class. """ def __init__(self, com: KeysightB2985AVisaCommunication) -> None: super().__init__(com, ":SENS:VOLT", "voltage", "V") def _validate_range(self, value) -> float: return VoltageRange(value)
[docs] class SensResistance(_SensValue): """ Resistance measurement class. """ def __init__(self, com: KeysightB2985AVisaCommunication) -> None: super().__init__(com, ":SENS:RES", "resistance", "Ω") def _validate_range(self, value) -> float: return ResistanceRange(value)
[docs] class SensCharge(_SensValue): """ Charge measurement class. """ def __init__(self, com: KeysightB2985AVisaCommunication) -> None: super().__init__(com, ":SENS:CHAR", "charge", "C") def _validate_range(self, value) -> float: return ChargeRange(value)
class _SensDataBase(_BaseModule): """ Base class for data module. """ def __init__(self, com: KeysightB2985AVisaCommunication) -> None: super().__init__(com, ":SENS:DATA", "sense_data") def clear(self) -> None: """ Clear the data in the output buffer. """ self._com.write(f"{self._base_command}:CLE") @staticmethod def _clean_data( data: str, form: list[FormatElements] ) -> dict[FormatElements, list]: """ Method to clean the data string from the following placeholders and transform it in a usable dict: - +9.91e37 --> NaN - +9.90e37 --> +inf - -9.90e37 --> -inf The keys are FormatElements and the values are lists containing the actual data e.g. timestamps and voltages/currents. :return: Dict with transformed data """ data_list = list(map(float, data.split(","))) data_list = [float(" NaN") if x == +9.91e37 else x for x in data_list] data_list = [float("+inf") if x == +9.90e37 else x for x in data_list] data_list = [float("-inf") if x == -9.90e37 else x for x in data_list] data_dict = {} for row in range(len(form)): data_dict[form[row]] = data_list[row :: len(form)] return data_dict