Source code for hvl_ccb.dev.tiepie.generator

#  Copyright (c) ETH Zurich, SIS ID and HVL D-ITET
#
"""

"""

import logging
from array import array
from typing import Optional, Union, cast

import libtiepie as ltp
import numpy.typing as npt
from libtiepie import generator as ltp_gen

from hvl_ccb.utils.enum import NameEnum
from hvl_ccb.utils.validation import validate_bool, validate_number

from .base import (
    TiePieDeviceType,
    _require_dev_handle,
    _verify_via_libtiepie,
    wrap_libtiepie_exception,
)
from .oscilloscope import TiePieOscilloscope
from .utils import PublicPropertiesReprMixin

logger = logging.getLogger(__name__)


[docs] class TiePieGeneratorSignalType(NameEnum, init="value description"): # type:ignore UNKNOWN = ltp.ST_UNKNOWN, "Unknown" SINE = ltp.ST_SINE, "Sine" TRIANGLE = ltp.ST_TRIANGLE, "Triangle" SQUARE = ltp.ST_SQUARE, "Square" DC = ltp.ST_DC, "DC" NOISE = ltp.ST_NOISE, "Noise" ARBITRARY = ltp.ST_ARBITRARY, "Arbitrary" PULSE = ltp.ST_PULSE, "Pulse"
[docs] class TiePieGeneratorConfigLimits: """ Default limits for generator parameters. """ def __init__(self, dev_gen: ltp_gen.Generator) -> None: self.frequency = (0, dev_gen.frequency_max) self.amplitude = (0, dev_gen.amplitude_max) self.offset = (None, dev_gen.offset_max)
[docs] class TiePieGeneratorConfig(PublicPropertiesReprMixin): """ Generator's configuration with cleaning of values in properties setters. """ def __init__(self, dev_gen: ltp_gen.Generator): self.dev_gen: ltp_gen.Generator = dev_gen self._waveform: Optional[npt.NDArray] = None self.param_lim: TiePieGeneratorConfigLimits = TiePieGeneratorConfigLimits( dev_gen=dev_gen )
[docs] def clean_frequency(self, frequency: float) -> float: validate_number( "Frequency", frequency, limits=self.param_lim.frequency, logger=logger ) frequency = _verify_via_libtiepie(self.dev_gen, "frequency", frequency) return float(frequency)
@property def frequency(self) -> float: return self.dev_gen.frequency @frequency.setter def frequency(self, frequency: float) -> None: """ Set signal generator frequency. :param frequency: Desired signal frequency in Hertz """ frequency = self.clean_frequency(frequency) self.dev_gen.frequency = frequency logger.info(f"Generator frequency is set to {frequency} Hz.")
[docs] def clean_amplitude(self, amplitude: float) -> float: validate_number( "Generator amplitude", amplitude, limits=self.param_lim.amplitude, logger=logger, ) amplitude = _verify_via_libtiepie(self.dev_gen, "amplitude", amplitude) return float(amplitude)
@property def amplitude(self) -> float: return self.dev_gen.amplitude @amplitude.setter def amplitude(self, amplitude: float) -> None: """ Set signal generator amplitude (peak value). :param amplitude: in Volts """ amplitude = self.clean_amplitude(amplitude) self.dev_gen.amplitude = amplitude logger.info(f"Generator amplitude is set to {amplitude} V.")
[docs] def clean_offset(self, offset: float) -> float: validate_number( "Generator offset", offset, limits=self.param_lim.offset, logger=logger ) offset = _verify_via_libtiepie(self.dev_gen, "offset", offset) return float(offset)
@property def offset(self) -> float: return self.dev_gen.offset @offset.setter def offset(self, offset: float) -> None: """ Set signal offset voltage. :param offset: in Volts. """ offset = self.clean_offset(offset) self.dev_gen.offset = offset logger.info(f"Generator offset is set to {offset} V.")
[docs] @staticmethod def clean_signal_type( signal_type: Union[int, TiePieGeneratorSignalType] ) -> TiePieGeneratorSignalType: return TiePieGeneratorSignalType(signal_type)
@property def signal_type(self) -> TiePieGeneratorSignalType: return TiePieGeneratorSignalType(self.dev_gen.signal_type) @signal_type.setter def signal_type(self, signal_type: Union[int, TiePieGeneratorSignalType]) -> None: """ Set signal type. :param signal_type: use TiePieGeneratorSignalType.SIGNAL_KEYWORD """ self.dev_gen.signal_type = self.clean_signal_type(signal_type).value logger.info(f"Signal type is set to {signal_type}.")
[docs] @staticmethod def clean_enabled(enabled: bool) -> bool: validate_bool("channel enabled", enabled, logger=logger) return enabled
@property def enabled(self) -> bool: return self.dev_gen.output_on @enabled.setter def enabled(self, enabled: bool) -> None: """ Enable generator (note: this will not yet start the signal generation) :param enabled: if 'True', generator is enabled """ self.dev_gen.output_on = self.clean_enabled(enabled) if enabled: msg = "enabled" else: msg = "disabled" logger.info(f"Generator is set to {msg}.")
[docs] def clean_waveform(self, waveform: npt.NDArray) -> npt.NDArray: validate_number("Waveform", waveform, (-1, 1), logger=logger) waveform = waveform.squeeze() set_data_length = _verify_via_libtiepie( self.dev_gen, "data_length", waveform.size ) if waveform.ndim != 1: msg = "Waveform array must be 1-dimensional." logger.error(msg) raise ValueError(msg) if set_data_length != waveform.size: msg = ( f"Waveform with {waveform.size} samples not possible " f"(max. is {set_data_length})." ) logger.error(msg) raise ValueError(msg) return waveform
@property def waveform(self) -> Optional[npt.NDArray]: return self._waveform @waveform.setter def waveform(self, waveform: npt.NDArray): """ Set arbitrary waveform for signal generation (choose TiePieGeneratorSignalType.ARBITRARY) :param waveform: desired waveform as a numpy array. The array will be scaled by a multiplicative factor such that its absolute maximum value is 1. The chosen 'amplitude' will thus be the absolute peak value of the generated waveform. """ waveform = self.clean_waveform(waveform) self._waveform = waveform waveform_array = array("f") waveform_array.fromlist(waveform.tolist()) self.dev_gen.set_data(waveform_array) logger.info("Arbitrary waveform data loaded")
[docs] class TiePieGeneratorMixin: """ TiePie Generator sub-device. A wrapper for the `libtiepie.generator.Generator` class. To be mixed in with `TiePieOscilloscope` base class. """ def __init__(self, com, dev_config) -> None: super().__init__(com, dev_config) # type: ignore[call-arg] self._gen: Optional[ltp_gen.Generator] = None self.config_gen: Optional[TiePieGeneratorConfig] = None """ Generator's dynamical configuration. """ @_require_dev_handle(TiePieDeviceType.GENERATOR) def _gen_config_setup(self) -> None: """ Setup dynamical configuration for the connected generator. """ self.config_gen = TiePieGeneratorConfig( dev_gen=self._gen, ) def _gen_config_teardown(self) -> None: self.config_gen = None def _gen_close(self) -> None: if self._gen is not None: del self._gen self._gen = None
[docs] def start(self) -> None: """ Start the Generator. """ super().start() # type: ignore logger.info("Starting generator") self._gen = cast(TiePieOscilloscope, self)._get_device_by_serial_number( TiePieDeviceType.GENERATOR ) self._gen_config_setup()
[docs] @wrap_libtiepie_exception def stop(self) -> None: """ Stop the generator. """ logger.info("Stopping generator") self._gen_config_teardown() self._gen_close() super().stop() # type: ignore
[docs] @wrap_libtiepie_exception @_require_dev_handle(TiePieDeviceType.GENERATOR) def generator_start(self): """ Start signal generation. """ if self.generator_is_running: logger.info("Generator was not started as it is already running") return self._gen.start() logger.info("Starting signal generation")
@property @wrap_libtiepie_exception @_require_dev_handle(TiePieDeviceType.GENERATOR) def generator_is_running(self) -> bool: """ Property that is `true` when generator is running and has a signal output """ assert self._gen is not None is_running: bool = self._gen.is_running logger.debug(f"Generator is {'' if is_running else 'not '}running") return is_running
[docs] @wrap_libtiepie_exception @_require_dev_handle(TiePieDeviceType.GENERATOR) def generator_stop(self): """ Stop signal generation. """ if not self.generator_is_running: logger.info("Generator was not stopped as it is not running") return self._gen.stop() logger.info("Stopping signal generation")