diff --git a/docs/changelog.md b/docs/changelog.md index 07e07f209..3e25edcd5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -9,6 +9,9 @@ None ### New Features - support for Zaber (linear) motorized stages (in hardware/motor/zaber_motion) +- Added mixin `qudi.interface.mixins.process_control_switch.ProcessControlSwitchMixin` to provide +optional default implementation satisfying the `SwitchInterface` for process control hardware +modules implementing any of the interfaces contained in `qudi.interface.process_control_interface` ### Other None \ No newline at end of file diff --git a/src/qudi/default.cfg b/src/qudi/default.cfg index b6eafb2e4..9c9ffee7c 100644 --- a/src/qudi/default.cfg +++ b/src/qudi/default.cfg @@ -292,7 +292,7 @@ hardware: extend_hardware_name: True connect: switch1: 'switch1_dummy' - switch2: 'switch2_dummy' + switch2: 'process_control_dummy' switch1_dummy: @@ -305,16 +305,6 @@ hardware: two: ['down', 'up'] three: ['low', 'middle', 'high'] - switch2_dummy: - module.Class: 'dummy.switch_dummy.SwitchDummy' - options: - name: 'Second' # optional - remember_states: True # optional - switches: - 'An even longer name of the switch itself': - - 'Very long name of a random state' - - 'Another very long name of a random state' - fast_counter_dummy: module.Class: 'dummy.fast_counter_dummy.FastCounterDummy' options: @@ -327,3 +317,25 @@ hardware: spectrometer_dummy: module.Class: 'dummy.spectrometer_dummy.SpectrometerDummy' + + process_control_dummy: + module.Class: 'dummy.process_control_dummy.ProcessControlDummy' + options: + process_value_channels: + Temperature: + unit: 'K' + limits: [0, .inf] + dtype: float + Voltage: + unit: 'V' + limits: [-10.0, 10.0] + dtype: float + setpoint_channels: + Current: + unit: 'A' + limits: [-5, 5] + dtype: float + Frequency: + unit: 'Hz' + limits: [100.0e3, 20.0e9] + dtype: float diff --git a/src/qudi/hardware/dummy/process_control_dummy.py b/src/qudi/hardware/dummy/process_control_dummy.py index 13e149656..6e9b764ec 100644 --- a/src/qudi/hardware/dummy/process_control_dummy.py +++ b/src/qudi/hardware/dummy/process_control_dummy.py @@ -26,11 +26,11 @@ from qudi.util.mutex import RecursiveMutex from qudi.core.configoption import ConfigOption from qudi.interface.process_control_interface import ProcessControlConstraints -from qudi.interface.process_control_interface import ProcessSetpointInterface -from qudi.interface.process_control_interface import ProcessValueInterface +from qudi.interface.process_control_interface import ProcessControlInterface +from qudi.interface.mixins.process_control_switch import ProcessControlSwitchMixin -class ProcessControlDummy(ProcessSetpointInterface, ProcessValueInterface): +class ProcessControlDummy(ProcessControlSwitchMixin, ProcessControlInterface): """ A dummy class to emulate a process control device (setpoints and process values) Example config for copy-paste: @@ -89,8 +89,8 @@ def on_activate(self): units.update({ch: d['unit'] for ch, d in self._process_value_channels.items() if 'unit' in d}) limits = {ch: d['limits'] for ch, d in self._setpoint_channels.items() if 'limits' in d} limits.update({ch: d['limits'] for ch, d in self._process_value_channels.items() if 'limits' in d}) - dtypes = {ch: d['dtype'] for ch, d in self._setpoint_channels.items() if 'dtype' in d} - dtypes.update({ch: d['dtype'] for ch, d in self._process_value_channels.items() if 'dtype' in d}) + dtypes = {ch: eval(d['dtype']) for ch, d in self._setpoint_channels.items() if 'dtype' in d} + dtypes.update({ch: eval(d['dtype']) for ch, d in self._process_value_channels.items() if 'dtype' in d}) self.__constraints = ProcessControlConstraints( setpoint_channels=tuple(self._setpoint_channels), process_channels=tuple(self._process_value_channels), diff --git a/src/qudi/interface/mixins/process_control_switch.py b/src/qudi/interface/mixins/process_control_switch.py new file mode 100644 index 000000000..3b576d5be --- /dev/null +++ b/src/qudi/interface/mixins/process_control_switch.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +""" +Copyright (c) 2022, the qudi developers. See the AUTHORS.md file at the top-level directory of this +distribution and on + +This file is part of qudi. + +Qudi is free software: you can redistribute it and/or modify it under the terms of +the GNU Lesser General Public License as published by the Free Software Foundation, +either version 3 of the License, or (at your option) any later version. + +Qudi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along with qudi. +If not, see . +""" + +__all__ = ['ProcessControlSwitchMixin'] + +from typing import Dict, Tuple + +from qudi.interface.switch_interface import SwitchInterface + + +class ProcessControlSwitchMixin(SwitchInterface): + """ Mixin to inherit alongside interfaces contained in qudi.interface.process_control_interface + to automatically provide a SwitchInterface for enabling/disabling process control hardware. + + Use like this: + + class MyHardwareModule(ProcessControlSwitchMixin, ProcessControlInterface): + ... + """ + + @property + def name(self) -> str: + return self.module_name + + @property + def available_states(self) -> Dict[str, Tuple[str, ...]]: + return {'state': ('disabled', 'enabled')} + + def get_state(self, switch: str) -> str: + """ Query state of single switch by name + + @param str switch: name of the switch to query the state for + @return str: The current switch state + """ + return self.available_states['state'][int(self.is_active)] + + def set_state(self, switch: str, state: str) -> None: + """ Query state of single switch by name + + @param str switch: name of the switch to change + @param str state: name of the state to set + """ + try: + active = bool(self.available_states[switch].index(state)) + except (KeyError, ValueError) as err: + raise ValueError('Invalid switch name or state descriptor') from err + self.set_activity_state(active) diff --git a/src/qudi/interface/switch_interface.py b/src/qudi/interface/switch_interface.py index 26d21fe5d..70b8d5fd0 100644 --- a/src/qudi/interface/switch_interface.py +++ b/src/qudi/interface/switch_interface.py @@ -18,7 +18,11 @@ If not, see . """ +__all__ = ['SwitchInterface'] + +from typing import Dict, Mapping, Sequence, Tuple from abc import abstractmethod + from qudi.core.module import Base @@ -33,7 +37,7 @@ class SwitchInterface(Base): @property @abstractmethod - def name(self): + def name(self) -> str: """ Name of the hardware as string. @return str: The name of the hardware @@ -42,7 +46,7 @@ def name(self): @property @abstractmethod - def available_states(self): + def available_states(self) -> Dict[str, Tuple[str, ...]]: """ Names of the states as a dict of tuples. The keys contain the names for each of the switches. The values are tuples of strings @@ -53,7 +57,7 @@ def available_states(self): pass @abstractmethod - def get_state(self, switch): + def get_state(self, switch: str) -> str: """ Query state of single switch by name @param str switch: name of the switch to query the state for @@ -62,7 +66,7 @@ def get_state(self, switch): pass @abstractmethod - def set_state(self, switch, state): + def set_state(self, switch: str, state: str) -> None: """ Query state of single switch by name @param str switch: name of the switch to change @@ -73,7 +77,7 @@ def set_state(self, switch, state): # Non-abstract default implementations below @property - def number_of_switches(self): + def number_of_switches(self) -> int: """ Number of switches provided by the hardware. @return int: number of switches @@ -81,7 +85,7 @@ def number_of_switches(self): return len(self.available_states) @property - def switch_names(self): + def switch_names(self) -> Tuple[str, ...]: """ Names of all available switches as tuple. @return str[]: Tuple of strings of available switch names. @@ -89,7 +93,7 @@ def switch_names(self): return tuple(self.available_states) @property - def states(self): + def states(self) -> Dict[str, str]: """ The current states the hardware is in as state dictionary with switch names as keys and state names as values. @@ -98,7 +102,7 @@ def states(self): return {switch: self.get_state(switch) for switch in self.available_states} @states.setter - def states(self, state_dict): + def states(self, state_dict: Mapping[str, str]) -> None: """ The setter for the states of the hardware. The states of the system can be set by specifying a dict that has the switch names as keys @@ -111,7 +115,8 @@ def states(self, state_dict): self.set_state(switch, state) @staticmethod - def _chk_refine_available_switches(switch_dict): + def _chk_refine_available_switches(switch_dict: Dict[str, Sequence[str]] + ) -> Dict[str, Tuple[str, ...]]: """ Perform some general checking of the configured available switches and their possible states. When implementing a hardware module, you can overwrite this method to include custom checks, but make sure to call this implementation first via super().