Source code for herosdevices.hardware.windfreak

"""Drivers for Windfreak Technologies devices."""

from collections.abc import Callable
from math import isnan
from typing import Never

from herosdevices.core import DeviceCommandQuantity
from herosdevices.core.templates import SerialDeviceTemplate as SerialDevice
from herosdevices.helper import explicit, limits, limits_int, transform_unit
from herosdevices.interfaces.atomiq import RFSource


[docs] class SynthUSB(RFSource, SerialDevice): """Base class for Windfreak Synth USB RF generators. Not for standalone use, use :py:class:`herosdevice.device.windfreak.SynthUSB2` or :py:class:`herosdevice.device.windfreak.SynthUSB3` instead. """ amplitude: float = 0 freq_max: float = 6.4e9 freq_min: float = 12.5e6 status = DeviceCommandQuantity(command_get="?", dtype=str, read_line=False) # Status overview def __init__(self, address: str, timeout: float = 1.0) -> None: SerialDevice.__init__( self, address, baudrate=19200, timeout=timeout, delays={"read_echo": 0.05}, line_termination=b"\n" ) def _set_frequency(self, frequency: float) -> None: self.frequency = frequency def _get_frequency(self) -> float: return self.frequency def _set_amplitude(self, amplitude: int) -> None: self.power = amplitude def _get_amplitude(self) -> float: return self.power def _set_phase(self, phase: float) -> None: raise NotImplementedError("The Windfreak USBSynth does not support setting a phase") def _get_phase(self) -> float: raise NotImplementedError("The Windfreak USBSynth does not support setting a phase")
[docs] class SynthUSBII(SynthUSB): """Windfreak SynthUSB2 RF generator driver.""" amp_max: float = 3 amp_min: float = 0 on: int = DeviceCommandQuantity( command_set="o{}", command_get="o?", dtype=int, value_check_fun=explicit([0, 1]) ) # on/off (0/1) high_rf: int = DeviceCommandQuantity( command_set="h{}", command_get="h?", dtype=int, value_check_fun=explicit([0, 1]), read_line=True ) # High RF on/off (0/1) amplitude: int = DeviceCommandQuantity( command_set="a{}", command_get="a?", dtype=int, value_check_fun=limits_int(0, 3) ) # RF Power int between 0 and 3 frequency = DeviceCommandQuantity( command_set="f{:.3f}", command_get="f?", dtype=float, unit="base", value_check_fun=limits(34.4e6, 4.4e9), transform_fun=transform_unit("base", "MHz"), format_fun=lambda x: float(x.rstrip()) * 1e-3, # Device returns the value without decimal point... ) # Frequency in Hz
[docs] class SynthUSBIII(SynthUSB): """Windfreak SynthUSB3 RF generator driver.""" amp_max: float = 10 amp_min: float = -50 frequency = DeviceCommandQuantity( command_set="f{:.3f}", command_get="f?", dtype=float, unit="base", value_check_fun=limits(12.5e6, 5.4e9), transform_fun=transform_unit("base", "MHz"), format_fun=lambda x: float(x.rstrip()), ) # Frequency in Hz amplitude = DeviceCommandQuantity( command_set="W{}", command_get="W?", dtype=float, unit="dBm", value_check_fun=limits(-50, 10), ) # RF Power doubler = DeviceCommandQuantity( command_set="D{}", command_get="D?", dtype=int, unit="dBm", value_check_fun=explicit([0, 1]), ) # Reference Doubler frequency_ramp_lower = DeviceCommandQuantity( command_set="l{}", command_get="l?", dtype=float, unit="base", value_check_fun=limits(SynthUSB.freq_min, SynthUSB.freq_max), transform_fun=transform_unit("base", "MHz"), ) # Lower Ramp Limit frequency_ramp_upper = DeviceCommandQuantity( command_set="u{}", command_get="u?", dtype=float, unit="base", value_check_fun=limits(SynthUSB.freq_min, SynthUSB.freq_max), transform_fun=transform_unit("base", "MHz"), ) # Upper Ramp Limit ramp_step_size = DeviceCommandQuantity( command_set="s{}", command_get="s?", dtype=float, unit="base", transform_fun=transform_unit("base", "MHz") ) # Ramp Step Size ramp_step_time = DeviceCommandQuantity( command_set="t{}", command_get="t?", dtype=float, unit="ms", ) # Dwell time per step amplitude_ramp_lower = DeviceCommandQuantity( command_set="[{}", command_get="[?", dtype=float, unit="dBm", value_check_fun=limits(-50, 10), ) # Lower Power Ramp Limit amplitude_ramp_upper = DeviceCommandQuantity( command_set="]{}", command_get="]?", dtype=float, unit="dBm", value_check_fun=limits(-50, 10), ) # Upper Power Ramp Limit ramp_direction = DeviceCommandQuantity( command_set="^{}", command_get="^?", dtype=int, value_check_fun=explicit([0, 1]) ) # ramp direction 0: upper->lower, 1: lower->upper run_sweep = DeviceCommandQuantity( command_set="g{}", command_get="g?", dtype=int, value_check_fun=explicit([0, 1]) ) # controls running a sweep 1:start, restart or continue and 0:pause sweep_cont = DeviceCommandQuantity( command_set="c{}", command_get="c?", dtype=int, value_check_fun=explicit([0, 1]) ) # 1:sweep continuously, 0:single sweep
[docs] def ramp( self, duration: float, frequency_start: float = float("nan"), frequency_end: float = float("nan"), amplitude_start: float = float("nan"), amplitude_end: float = float("nan"), ramp_timestep: float = float("nan"), ramp_steps: int = -1, ) -> None: """Ramp frequency and amplitude over a given duration. Parameters default to ``-1`` or ``nan`` to indicate no change. If the start frequency/amplitude is set to ``nan``, the ramp starts from the last frequency/amplitude which was set. This method advances the timeline by `duration` Args: duration: ramp duration [s] frequency_start: initial frequency [Hz] frequency_end: end frequency [Hz] amplitude_start: initial amplitude [0..1] amplitude_end: end amplitude [0..1] ramp_timesteps: time between steps in the ramp [s] ramp_steps: number of steps the whole ramp should have. This takes precedence over `ramp_timesteps` """ self.run_sweep = 0 if not (isnan(frequency_start) and isnan(frequency_end)) and not ( isnan(amplitude_start) and isnan(amplitude_end) ): msg = f"The Windfreak SynthUSB3 {self.address} is not capable parallel frequency/amplitude ramps" raise NotImplementedError(msg) elif not isnan(frequency_start) or not isnan(frequency_end): frequency_start = self.frequency if isnan(frequency_start) else frequency_start frequency_end = self.frequency if isnan(frequency_end) else frequency_end if frequency_end > frequency_start: self.ramp_direction = 1 self.frequency_ramp_upper = frequency_end self.frequency_ramp_lower = frequency_start else: self.ramp_direction = 0 self.frequency_ramp_upper = frequency_start self.frequency_ramp_lower = frequency_end elif not isnan(amplitude_start) or not isnan(amplitude_end): amplitude_start = self.amplitude if isnan(amplitude_start) else amplitude_start amplitude_end = self.amplitude if isnan(amplitude_end) else amplitude_end if amplitude_end > amplitude_start: self.ramp_direction = 1 self.amplitude_ramp_upper = amplitude_end self.amplitude_ramp_lower = amplitude_start else: self.ramp_direction = 0 self.amplitude_ramp_upper = amplitude_start self.amplitude_ramp_lower = amplitude_end if isnan(ramp_steps): ramp_steps = self.default_ramp_steps if not isnan(ramp_timestep): pass elif ramp_steps > 0: ramp_timestep = duration / ramp_steps self.ramp_step_time = ramp_timestep self.ramp_step_size = abs(amplitude_start - amplitude_end) / ramp_steps self.run_sweep = 1 self.frequency = frequency_end self.amplitude = amplitude_end
[docs] def arb( self, duration: float, samples_amp: list[float], samples_freq: list[float], samples_phase: list[float], repetitions: int = 1, prepare_only: bool = False, run_prepared: bool = False, transform_amp: Callable[[float], float] = lambda x: x, transform_freq: Callable[[float], float] = lambda x: x, transform_phase: Callable[[float], float] = lambda x: x, ) -> Never: """Play Arbitrary Samples from a List. This method is currently not implemented on the Windfreak SynthUSB3. The device would be capable of this and drivers can be extended. Args: samples_amp: List of amplitude samples. If this list is empty (default), the amplitude is not modified. samples_freq: List of frequency samples. If this list is empty (default), the frequency is not modified. samples_phase: List of phase samples. If this list is empty (default), the phase is not modified. duration: The time in which the whole sequence of samples should be played back [s]. repetitions: Number of times the sequence of all samples should be played. (default 1) prepare_only: Only write the sequence to RAM, don't play it. run_prepared: Play arb sequence previously prepared with :code:`prepare_only`. transform_amp: Function to transform amplitude samples, must take a single argument of type :type:`float` and return a single :type:`float`. transform_freq: Function to transform frequency samples (see :code:`transform_amp`). transform_phase: Function to transform phase samples (see :code:`transform_amp`). """ raise NotImplementedError( "Our current driver does not support the arb register functionality of the " "SynthUSB3. If you need this, please get in contact with us." )