diff --git a/src/qudi/hardware/switches/power_switch_keysight.py b/src/qudi/hardware/switches/power_switch_keysight.py new file mode 100644 index 000000000..b96bd2e52 --- /dev/null +++ b/src/qudi/hardware/switches/power_switch_keysight.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- + +""" +Control the Keysight power supply through the USB interface. + +Copyright (c) 2021, 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 . +""" + +try: + import visa +except ImportError: + import pyvisa as visa +from qudi.core.configoption import ConfigOption +from qudi.core.statusvariable import StatusVar +from qudi.util.mutex import RecursiveMutex +from qudi.interface.switch_interface import SwitchInterface + + +class PowerSwitch(SwitchInterface): + """ This class is implements communication with the Keysight E36234A power supply using pyVISA + + Example config for copy-paste: + + power_switch3: + module.Class: 'switches.power_switch_keysight.PowerSwitch' + options: + interface: 'USB0::0x2A8D::0x3402::MY59001224::INSTR' + hardware_name: 'Keysight_Power2' # optional + switch_time: 1 # optional + remember_states: False # optional + voltages: [5, 28] + currents: [1.3, 4] + switches: + APD: ['OFF', 'ON'] # optional + Amplifier: ['OFF', 'ON'] # optional + """ + + # customize available switches in config. Each switch needs a tuple of at least 2 state names. + _switches = ConfigOption(name='switches', missing='error') + # optional name of the hardware + _hardware_name = ConfigOption(name='hardware_name', default='power_supply', missing='nothing') + # if remember_states is True the last state will be restored at reloading of the module + _remember_states = ConfigOption(name='remember_states', default=False, missing='nothing') + # desired output voltage and currents + _voltages = ConfigOption(name='voltages', default=[5, 28], missing='warn') + _currents = ConfigOption(name='currents', default=[1.3, 4], missing='warn') + # switch_time to wait after setting the states for the solenoids to react + _switch_time = ConfigOption(name='switch_time', default=0.5, missing='nothing') + # name of the serial interface where the hardware is connected. + # Use e.g. the Keysight IO connections expert to find the device. + serial_interface = ConfigOption('interface', 'USB0::0x2A8D::0x3402::MY59001224::INSTR') + + # StatusVariable for remembering the last state of the hardware + _states = StatusVar(name='states', default=None) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.lock = RecursiveMutex() + self._resource_manager = None + self._instrument = None + self._switches = self._chk_refine_available_switches(self._switches) + + def on_activate(self): + """ Prepare module, connect to hardware. + """ + + self._resource_manager = visa.ResourceManager() + self._instrument = self._resource_manager.open_resource(self.serial_interface) + # set the maximum/protection output Voltage/current of the power supply + # Excelitas APD: 5V,1.3A + self._instrument.write('INST CH1') + self._instrument.write('OUTP 0') + self._instrument.write('VOLT {}'.format(float(self._voltages[0]))) + self._instrument.write('CURR {}'.format(float(self._currents[0]))) + # Minicircuits amplifier: 28V, 4A + self._instrument.write('INST CH2') + self._instrument.write('OUTP 0') + self._instrument.write('VOLT {}'.format(float(self._voltages[1]))) + self._instrument.write('CURR {}'.format(float(self._currents[1]))) + + # To be safe, power off during activation + self._states = dict() + self._states = {switch: states[0] for switch, states in self._switches.items()} + + + def on_deactivate(self): + """ Disconnect from hardware on deactivation. + """ + # switch off outputs of the power supply + for switch in (0, len(self._switches)): + self._instrument.write('INST CH{}'.format(switch+1)) + self._instrument.write('OUTP 0') + self._instrument.close() + self._resource_manager.close() + + @property + def name(self): + """ Name of the hardware as string. + + @return str: The name of the hardware + """ + return self._hardware_name + + @property + def available_states(self): + """ 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 + representing the ordered names of available states for each switch. + + @return dict: Available states per switch in the form {"switch": ("state1", "state2")} + """ + return self._switches.copy() + + @property + def states(self): + """ The current states the hardware is in. + + The states of the system as a dict consisting of switch names as keys and state names as values. + + @return dict: All the current states of the switches in a state dict of the form {"switch": "state"} + """ + return self._states.copy() + + @states.setter + def states(self, state_dict): + """ 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 + and the names of the states as values. + + @param dict state_dict: state dict of the form {"switch": "state"} + """ + assert isinstance(state_dict, dict), 'Parameter "state_dict" must be dict type' + for switch, state in state_dict.items(): + self.set_state(switch, state) + + def get_state(self, switch): + """ 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 + """ + # find the correlated output channel for the switch + switch_index = list(self._switches).index(switch) + # Select the switch + self._instrument.write('INST CH{}'.format(switch_index+1)) + response = self._instrument.query('OUTP?').strip() + assert int(response) in [0, 1], 'Device not giving proper response' + self._states[switch] = self._switches[switch][int(response)] + + return self._states[switch] + + def set_state(self, switch, state): + """ Set state of single switch by name + + @param str switch: name of the switch to change + @param str state: name of the state to set + """ + # find the correlated output channel for the switch + switch_index = list(self._switches).index(switch) + # get the state index, 0==off, 1==on + state_index = self._switches[switch].index(state) + # do the job + if self._states[switch] != state: + # Select the output channel + self._instrument.write('INST CH{}'.format(switch_index+1)) + # Switch the switch + self._instrument.write('OUTP {}'.format(int(state_index))) + self._states[switch] = state + +