From db76169e3e8e144d37104a575cbe6d6d9a682d35 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:44:03 +0100 Subject: [PATCH 01/57] Copy libphox --- .../QphoX/CryoSwitchController/__init__.py | 0 .../QphoX/CryoSwitchController/libphox.py | 724 ++++++++++++++++++ .../drivers/QphoX/__init__.py | 0 3 files changed, 724 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/__init__.py diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py new file mode 100644 index 000000000..c8c05cef9 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py @@ -0,0 +1,724 @@ +import serial +import serial.tools.list_ports +import time +import json +import socket +import numpy as np +import subprocess +import os +import logging + +class Labphox: + _logger = logging.getLogger("libphox") + + def __init__(self, port=None, debug=False, IP=None, cmd_logging=False, SN=None, HW_val=False): + self.debug = debug + self.time_out = 5 + + + if self.debug or cmd_logging: + self.log = True + self.logging_dir = r'\logging' + self.logger_init(self._logger) + else: + self.log = False + + self.SW_version = 3 + self.board_SN = SN + self.board_FW = None + + self.adc_ref = 3.3 + self.N_channel = 0 + + self.COM_port = None + + self.ETH_HOST = None # The server's IP address + self.ETH_PORT = 7 # The port used by the server + self.ETH_buff_size = 1024 + + self.communication_handler_sleep_time = 0 + self.packet_handler_sleep_time = 0 + if IP: + self.USB_or_ETH = 2 # 1 for USB, 2 for ETH + self.ETH_HOST = IP # The server's IP address + self.ETH_PORT = 7 # The port used by the server + self.ETH_buff_size = 1024 + else: + self.USB_or_ETH = 1 # 1 for USB, 2 for ETH + self.COM_port = port + + self.connect(HW_val=HW_val) + + + def connect(self, HW_val=True): + if self.USB_or_ETH == 1: + if self.COM_port: + # TODO + pass + elif self.board_SN: + for device in serial.tools.list_ports.comports(): + if device.pid == 1812: + try: + self.serial_com = serial.Serial(device.device) + if self.board_SN == self.utility_cmd('sn'): + self.COM_port = device.device + self.PID = device.pid + self.serial_com.close() + break + self.serial_com.close() + except Exception as error: + pass + # print('Port' + str(device.device) + ' is already in use:', error) + + else: + for device in serial.tools.list_ports.comports(): + if device.pid == 1812: + self.PID = device.pid + self.COM_port = device.device + if self.debug: + for i in device: + print(i) + + try: + self.serial_com = serial.Serial(self.COM_port) + + self.board_info = '' + self.name = '' + self.board_SN = None + self.utility_cmd('info') + print('Connected to ' + self.name + ' on COM port ' + self.COM_port + ', PID:', + str(self.PID) + ', SerialN: ' + str(self.board_SN) + ', Channels:' + str(self.N_channel)) + print(self.HW, ', FW_Ver.', self.board_FW) + except: + print('ERROR: Couldn\'t connect via serial') + + elif self.USB_or_ETH == 2: + socket.setdefaulttimeout(self.time_out) + + self.board_info = '' + self.name = '' + self.board_SN = None + self.utility_cmd('info') + print('Connected to ' + self.name + ', IP:', + str(self.ETH_HOST) + ', SerialN: ' + str(self.board_SN) + ', Channels:' + str(self.N_channel)) + print(self.HW, ', FW_Ver.', self.board_FW) + if not self.board_SN: + raise Exception( + "Couldn\'t connect, please check that the device is properly connected or try providing a valid SN, COM port or IP number") + + elif self.board_FW != self.SW_version and HW_val: + print("Board Firmware version and Software version are not up to date, Board FW=" + str( + self.board_FW) + " while SW=" + str(self.SW_version)) + + def disconnect(self): + if self.USB_or_ETH == 1: + self.serial_com.close() + elif self.USB_or_ETH == 2: + pass + + def input_buffer(self): + return self.serial_com.inWaiting() + + def flush_input_buffer(self): + return self.serial_com.flushInput() + + def write(self, cmd): + if self.log: + pass + #self.logging('actions', cmd) + + if self.USB_or_ETH == 1: + self.serial_com.write(cmd) + else: + pass + + def read(self, size): + if self.USB_or_ETH == 1: + data_back = self.serial_com.read(size) + else: + data_back = '' + + return data_back + + def read_buffer(self): + return self.read(self.input_buffer()) + + def decode_buffer(self): + return list(self.read_buffer()) + + def debug_func(self, cmd, reply): + print('Command', cmd) + print('Reply', reply) + print('') + # self._logger.debug(f'Debug: {cmd}') + self._logger.info(f'Debug: {cmd}') + self._logger.debug(f'Command: {cmd}') + self._logger.debug(f'Reply: {reply}') + + def logger_init(self, logger_instance, outfolder=None): + logger_instance.setLevel(logging.DEBUG) + + if outfolder is None: + outfolder = os.path.realpath('.') + self.logging_dir + + os.makedirs(name=outfolder, exist_ok=True) + + date_fmt = "%d/%m/%Y %H:%M:%S" + + # remove all old handlers + for hdlr in logger_instance.handlers[:]: + logger_instance.removeHandler(hdlr) + + # INFO level logger + # file logger + fmt = "[%(asctime)s] [%(levelname)s] %(message)s" + log_format = logging.Formatter(fmt=fmt, datefmt=date_fmt) + + info_handler = logging.FileHandler(os.path.join(outfolder, 'info.log')) + info_handler.setFormatter(log_format) + info_handler.setLevel(logging.INFO) + logger_instance.addHandler(info_handler) + + # DEBUG level logger + fmt = "[%(asctime)s] [%(levelname)s] [%(funcName)s(): line %(lineno)s] %(message)s" + log_format = logging.Formatter(fmt=fmt, datefmt=date_fmt) + + debug_handler = logging.FileHandler(os.path.join(outfolder, 'debug.log')) + debug_handler.setFormatter(log_format) + debug_handler.setLevel(logging.DEBUG) + logger_instance.addHandler(debug_handler) + + # _logger = logging.getLogger("libphox") + + return logger_instance + + def read_line(self): + if self.USB_or_ETH == 1: + return self.serial_com.readline() + else: + return '' + + def query_line(self, cmd): + self.write(cmd) + if self.USB_or_ETH == 1: + return self.serial_com.readline() + else: + return '' + + def compare_cmd(self, cmd1, cmd2): + if cmd1.upper() == cmd2.upper(): + return True + else: + return False + + def encode(self, value): + return str(value).encode() + + def decode_simple_response(self, response): + return response.decode('UTF-8').strip() + + def parse_response(self): + ##time.sleep(1) + + reply = '' + + initial_time = time.time() + end = False + while not end: + time.sleep(self.communication_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer().decode() + if ';' in reply: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.split(';')[0] + response = {'reply': reply, 'command': reply.split(':')[:-2], 'value': reply.split(':')[-1]} + + if self.log: + self.logging('received', reply) + + return response + + def TCP_communication_handler(self, encoded_cmd=None): + reply = '' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as TCP_connection: + TCP_connection.connect((self.ETH_HOST, self.ETH_PORT)) + TCP_connection.sendall(encoded_cmd) + packet = TCP_connection.recv(self.ETH_buff_size) + + try: + reply += packet.decode() + except: + print('Invalid packet character', packet) + + reply = reply.split(';')[0] + return reply + + def UDP_communication_handler(self, encoded_cmd=None): + reply = '' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as UDP_connection: + UDP_connection.sendto(encoded_cmd, (self.ETH_HOST, self.ETH_PORT)) + end = False + while not end: + # time.sleep(self.communication_handler_sleep_time) + packet = UDP_connection.recvfrom(self.ETH_buff_size)[0] + if b';' in packet: + reply += packet.split(b';')[0].decode() + end = True + else: + try: + reply += packet.decode() + except: + print('Invalid packet character', packet) + break + + return reply + + def USB_communication_handler(self, encoded_cmd=None): + reply = '' + self.flush_input_buffer() + self.write(encoded_cmd) + + initial_time = time.time() + end = False + while not end: + time.sleep(self.communication_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer().decode() + if ';' in reply: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.split(';')[0] + return reply + + def standard_reply_parser(self, cmd, reply): + response = {'reply': reply, 'command': reply.split(':')[:-1], 'value': reply.split(':')[-1]} + if not self.validate_reply(cmd, response): + self.raise_value_mismatch(cmd, response) + + return response + + def communication_handler(self, cmd, standard=True, is_encoded=False): + response = '' + if is_encoded: + encoded_cmd = cmd + else: + encoded_cmd = cmd.encode() + + if self.USB_or_ETH == 1: + reply = self.USB_communication_handler(encoded_cmd) + elif self.USB_or_ETH == 2: + reply = self.UDP_communication_handler(encoded_cmd) + elif self.USB_or_ETH == 3: + reply = self.TCP_communication_handler(encoded_cmd) + else: + raise Exception("Invalid communication options USB_or_ETH=", self.USB_or_ETH) + + try: + if standard: + if is_encoded: + response = self.standard_reply_parser(cmd=cmd.decode(), reply=reply) + else: + response = self.standard_reply_parser(cmd=cmd, reply=reply) + else: + response = reply + except: + print('Reply Error', reply) + + if self.debug: + self.debug_func(cmd, response) + + return response + + def validate_reply(self, cmd, response): + stripped = cmd.strip(';').split(':') + command = stripped[:-1] + value = stripped[-1] + + match = True + if command != response['command']: + match = False + + return match + + def USB_packet_handler(self, encoded_cmd, end_sequence): + reply = b'' + self.flush_input_buffer() + self.write(encoded_cmd) + + initial_time = time.time() + end = False + while not end: + time.sleep(self.packet_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer() + if end_sequence in reply[-5:]: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.replace(end_sequence, b'').replace(encoded_cmd, b'') + return reply + + def UDP_packet_handler(self, encoded_cmd, end_sequence): + reply = b'' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as s: + s.sendto(encoded_cmd, (self.ETH_HOST, self.ETH_PORT)) + end = False + while not end: + time.sleep(self.packet_handler_sleep_time) + packet = s.recvfrom(self.ETH_buff_size)[0] + reply += packet + if end_sequence in reply[-5:]: + end = True + + reply = reply.replace(end_sequence, b'').replace(encoded_cmd, b'') + return reply[7:] + + def packet_handler(self, cmd, end_sequence=b'\x00\xff\x00\xff'): + encoded_cmd = cmd.encode() + + if self.USB_or_ETH == 1: + reply = self.USB_packet_handler(encoded_cmd, end_sequence) + return reply + + elif self.USB_or_ETH == 2: + reply = self.UDP_packet_handler(encoded_cmd, end_sequence) + return reply + + def raise_value_mismatch(self, cmd, response): + print('Command mismatch!') + print('Command:', cmd) + print('Reply:', response['command']) + + def utility_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'info'): + self.name = self.utility_cmd('name').upper() + if 'LabP'.upper() in self.name: + self.HW = self.utility_cmd('hw') + self.board_SN = self.utility_cmd('sn') + self.board_FW = int(self.utility_cmd('fw').split('.')[-1]) + self.N_channel = int(self.utility_cmd('channels').split()[1]) + + elif self.compare_cmd(cmd, 'name'): + response = self.communication_handler('W:2:A:;', standard=False) + + elif self.compare_cmd(cmd, 'fw'): + response = self.communication_handler('W:2:B:;', standard=False) + + elif self.compare_cmd(cmd, 'hw'): + response = self.communication_handler('W:2:D:;', standard=False) + + elif self.compare_cmd(cmd, 'sn'): + response = self.communication_handler('W:2:E:;', standard=False) + + elif self.compare_cmd(cmd, 'channels'): + response = self.communication_handler('W:2:F:;', standard=False) + + elif self.compare_cmd(cmd, 'connected'): + response = self.communication_handler('W:2:C:;') + return response['value'] + + elif self.compare_cmd(cmd, 'UID'): + response = self.communication_handler('W:2:G:' + str(value) + ';') + return response['value'] + + elif self.compare_cmd(cmd, 'sleep'): + response = self.communication_handler('W:2:S:' + str(value) + ';') + + return response + + def DAC_cmd(self, cmd, DAC=1, value=0): + response = None + if DAC == 1: + sel_DAC = 5 + elif DAC == 2: + sel_DAC = 8 + else: + return None + + if self.compare_cmd(cmd, 'on'): + response = self.communication_handler('W:' + str(sel_DAC) + ':T:1;') + + elif self.compare_cmd(cmd, 'off'): + response = self.communication_handler('W:' + str(sel_DAC) + ':T:0;') + + elif self.compare_cmd(cmd, 'set'): + response = self.communication_handler('W:' + str(sel_DAC) + ':S:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'buffer'): + response = self.communication_handler('W:' + str(sel_DAC) + ':B:' + str(value) + ';') + + return response + + def application_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'pulse'): + ##self.serial_com.flushInput() + ##response = self.communication_handler('W:3:T:' + str(value) + ';', standard=False) + response = self.packet_handler('W:3:T:' + str(value) + ';') + return np.fromstring(response, dtype=np.uint8) + + elif self.compare_cmd(cmd, 'acquire'): + response = self.communication_handler('W:3:Q:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'voltage'): + response = self.communication_handler('W:3:V:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'test_circuit'): + response = self.communication_handler('W:3:P:' + str(value) + ';') + + return response + + def timer_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'duration'): + response = self.communication_handler('W:0:A:' + str(value) + ';') + if int(response['value']) != int(value): + self.raise_value_mismatch(cmd, response) + + if self.compare_cmd(cmd, 'sampling'): + response = self.communication_handler('W:0:S:' + str(value) + ';') + + return response + + def ADC_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'channel'): + response = self.communication_handler('W:4:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'start'): + response = self.communication_handler('W:4:T:1;') + + elif self.compare_cmd(cmd, 'stop'): + response = self.communication_handler('W:4:T:0;') + + elif self.compare_cmd(cmd, 'select'): ##Select and sample + response = self.communication_handler('W:4:S:' + str(value) + ';') + + + elif self.compare_cmd(cmd, 'get'): + response = self.communication_handler('W:4:G:;') + return int(response['value']) + + elif self.compare_cmd(cmd, 'interrupt'): ##Enable interrupt mode + response = self.communication_handler('W:4:I:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'buffer'): ##Enable interrupt mode + response = self.communication_handler('W:4:B:' + str(value) + ';') + return int(response['value']) + + return response + + + def ADC3_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'channel'): + response = self.communication_handler('W:W:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'start'): + response = self.communication_handler('W:W:T:1;') + + elif self.compare_cmd(cmd, 'stop'): + response = self.communication_handler('W:W:T:0;') + + elif self.compare_cmd(cmd, 'select'): ##Select and sample + response = self.communication_handler('W:W:S:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'get'): + response = self.communication_handler('W:W:G:;') + return int(response['value']) + + return response + + def gpio_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'EN_3V3'): + response = self.communication_handler('W:1:A:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'EN_5V'): + response = self.communication_handler('W:1:B:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'EN_CHGP'): + response = self.communication_handler('W:1:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'FORCE_PWR_EN'): + response = self.communication_handler('W:1:D:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'PWR_EN'): + response = self.communication_handler('W:1:E:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'DCDC_EN'): + response = self.communication_handler('W:1:F:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'CHOPPING_EN'): + response = self.communication_handler('W:1:G:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'PWR_STATUS'): + response = self.communication_handler('W:1:H:0;') + return int(response['value']) + + elif self.compare_cmd(cmd, 'OCP_OUT_STATUS'): + response = self.communication_handler('W:1:I:0;') + return int(response['value']) + + return response + + def IO_expander_cmd(self, cmd, port='A', value=0): + response = None + if self.compare_cmd(cmd, 'connect'): + response = self.communication_handler('W:' + str(port) + ':C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'disconnect'): + response = self.communication_handler('W:' + str(port) + ':D:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'on'): + response = self.communication_handler('W:6:O:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'off'): + response = self.communication_handler('W:6:U:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'type'): + response = self.communication_handler('W:6:S:' + str(value) + ';') + + return response + + def reset_cmd(self, cmd): + response = None + if self.compare_cmd(cmd, 'reset'): + response = self.communication_handler('W:7:R:;') + + elif self.compare_cmd(cmd, 'boot'): + response = self.communication_handler('W:7:B:;') + + elif self.compare_cmd(cmd, 'soft_reset'): + response = self.communication_handler('W:7:S:;') + + return response + + def logging(self, list_name, cmd): + with open('history.json', "r") as history_file: + data = json.load(history_file) + + if type(cmd) == str: + data_to_append = cmd + elif type(cmd) == bytes: + data_to_append = cmd.decode() + + if list_name in data.keys(): + data[list_name].append({'data': data_to_append, 'date': time.time()}) + else: + data[list_name] = [{'data': data_to_append, 'date': time.time()}] + + with open('history.json', "w") as file: + json.dump(data, file) + + def ETHERNET_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'read'): + response = self.communication_handler('W:Q:R:' + str(value) + ';') + elif self.compare_cmd(cmd, 'set_ip'): + response = self.communication_handler('W:Q:I:' + str(value) + ';') + elif self.compare_cmd(cmd, 'get_ip'): + response = self.communication_handler('W:Q:G:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'set_ip_str'): + int_IP = int.from_bytes(socket.inet_aton(value), "little") + response = self.communication_handler('W:Q:I:' + str(int_IP) + ';') + elif self.compare_cmd(cmd, 'get_ip_str'): + response = self.communication_handler('W:Q:G:' + str(value) + ';') + IP = socket.inet_ntoa(int(response['value']).to_bytes(4, 'little')) + print('IP:', IP) + return IP + + elif self.compare_cmd(cmd, 'set_mask_str'): + int_mask = int.from_bytes(socket.inet_aton(value), "little") + response = self.communication_handler('W:Q:K:' + str(int_mask) + ';') + + elif self.compare_cmd(cmd, 'get_mask_str'): + response = self.communication_handler('W:Q:L:' + str(value) + ';') + print(response) + mask = socket.inet_ntoa(int(response['value']).to_bytes(4, 'little')) + print('Subnet mask:', mask) + return mask + + elif self.compare_cmd(cmd, 'get_detection'): + response = self.communication_handler('W:Q:D:;') + + return response + + def UPGRADE_cmd(self, cmd, value): + response = None + + if self.compare_cmd(cmd, 'upgrade'): + response = self.communication_handler('U:A:0' + ':' + str(value) + ';') + if int(response['value']) == value: + print('Update successful,', value, 'channels enabled') + else: + print('Couldn\'t update channel number') + + elif self.compare_cmd(cmd, 'stream_key'): + for idx, element in enumerate(value): + response = self.communication_handler('U:B:' + str(chr(65 + idx)) + ':' + str(element) + ';') + if int(response['value']) != element: + print('Error while sending key!') + + return response + + def FLASH_utils(self, path=None): + DFU_name = '0483:df11' + found = False + process = subprocess.Popen(['.\Firmware\dfu-util', '-l'], shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + + start_time = time.time() + exc_time = 0 + output = '' + while exc_time < self.time_out: + output = process.stdout.readline() + if 'Internal Flash' in output and 'Found DFU: [' + DFU_name + ']' in output: + found = True + break + exc_time = time.time() - start_time + + if not found: + print('Couldn\'t find the the device') + else: + print('Device found in', output.strip().strip('Found DFU: ')) + + if not path: + path = '.' + process = subprocess.Popen('.\Firmware\dfu-util -d ' + DFU_name + ' -a 0 -s 0x08000000:leave -D ' + path + '\Firmware\Labphox.bin', shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + + start_time = time.time() + exc_time = 0 + poll = process.poll() + while poll is None: + output = process.stdout.readline() + if 'Download' in output or 'device'.upper() in output.upper() or 'dfu'.upper() in output.upper() and 'bugs' not in output: + print(output.strip()) + exc_time = time.time() - start_time + + if exc_time > self.time_out: + break + else: + poll = process.poll() + + print('Flashing time', round(exc_time, 2), 's') + print('Flash ended! Please disconnect the device.') + + +if __name__ == "__main__": + labphox = Labphox(debug=True, IP='192.168.1.101') + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/__init__.py b/src/qcodes_contrib_drivers/drivers/QphoX/__init__.py new file mode 100644 index 000000000..e69de29bb From ea6962b5d4f4cd869564e08b29e9d82d36646f40 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:44:59 +0100 Subject: [PATCH 02/57] Copy other QphoxCode --- .../CryoSwitchController.py | 795 ++++++++++++++++++ .../QphoX/CryoSwitchController/constants.json | 130 +++ .../QphoX/CryoSwitchController/states.json | 376 +++++++++ 3 files changed, 1301 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py new file mode 100644 index 000000000..d7a3b3620 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py @@ -0,0 +1,795 @@ +import time +import matplotlib.pyplot as plt +from .libphox import Labphox +import numpy as np +import json +import os + +class Cryoswitch: + + def __init__(self, debug=False, COM_port='', IP=None, SN=None, override_abspath=False): + self.debug = debug + self.port = COM_port + self.IP = IP + self.verbose = True + + self.labphox = Labphox(self.port, debug=self.debug, IP=self.IP, SN=SN) + self.ports_enabled = self.labphox.N_channel + self.SN = self.labphox.board_SN + self.HW_rev = self.get_HW_revision() + self.HW_rev_N = int(self.get_HW_revision()[-1]) + + self.wait_time = 0.5 + self.pulse_duration_ms = 15 + self.converter_voltage = 5 + self.MEASURED_converter_voltage = 0 + self.current_switch_model = '' + self.tolerance = 0.15 + + if override_abspath: + self.abs_path = override_abspath + '\\' + else: + self.abs_path = os.path.dirname(__file__) + '\\' + + self.decimals = 2 + self.plot = False + self.log_wav = True + self.log_wav_dir = self.abs_path + r'data' + self.align_edges = True + self.plot_polarization = True + + self.pulse_logging = True + self.pulse_logging_filename = self.abs_path + r'pulse_logging.txt' + self.log_pulses_to_display = 5 + self.warning_threshold_current = 60 + + self.track_states = True + self.track_states_file = self.abs_path + r'states.json' + + self.constants_file_name = self.abs_path + r'constants.json' + self.__constants() + + if self.track_states: + self.tracking_init() + + if self.pulse_logging: + self.pulse_logging_init() + + if self.log_wav: + self.log_wav_init() + + def tracking_init(self): + file = open(self.track_states_file) + states = json.load(file) + file.close() + if self.SN not in states.keys(): + states[self.SN] = states['SN'] + with open(self.track_states_file, 'w') as outfile: + json.dump(states, outfile, indent=4, sort_keys=True) + + def pulse_logging_init(self): + if not os.path.isfile(self.pulse_logging_filename): + file = open(self.pulse_logging_filename, 'w') + file.close() + + def log_wav_init(self): + if not os.path.isdir(self.log_wav_dir): + os.mkdir(self.log_wav_dir) + + def __constants(self): + file = open(self.constants_file_name) + constants = json.load(file) + file.close() + + if self.HW_rev in constants.keys(): + constants = constants[self.HW_rev] + self.ADC_12B_res = constants['ADC_12B_res'] + self.ADC_8B_res = constants['ADC_8B_res'] + self.ADC_cal_ref = constants['ADC_cal_ref'] + + self.bv_R1 = constants['bv_R1'] + self.bv_R2 = constants['bv_R2'] + self.bv_ADC = constants['bv_ADC'] + + self.converter_divider = constants['converter_divider'] + self.converter_ADC = constants['converter_ADC'] + + self.converter_VREF = constants['converter_VREF'] + self.converter_R1 = constants['converter_R1'] + self.converter_R2 = constants['converter_R2'] + self.converter_Rf = constants['converter_Rf'] + self.converter_DAC_lower_bound = constants['converter_DAC_lower_bound'] + self.converter_DAC_upper_bound = constants['converter_DAC_upper_bound'] + self.converter_correction_codes = constants['converter_correction_codes'] + self.converter_output_voltage_range = constants['converter_output_voltage_range'] + + + self.OCP_gain = constants['OCP_gain'] + self.OCP_range = constants['OCP_range'] + + self.pulse_duration_range = constants['pulse_duration_range'] + self.sampling_frequency_range = constants['sampling_frequency_range'] + + self.current_sense_R = constants['current_sense_R'] + self.current_gain = constants['current_gain'] + self.polarization_params = constants['polarization_params'] + + self.sampling_freq = 28000 + + if constants['calibrate_ADC']: + self.labphox.ADC3_cmd('start') + time.sleep(0.1) + ref_values = [] + for it in range(5): + ref_values.append(self.get_V_ref()) + measured_ref = sum(ref_values) / len(ref_values) + + if 3.1 < measured_ref < 3.5: + self.measured_adc_ref = measured_ref + else: + print(f'Measured ADC ref {measured_ref}V outside of range') + self.measured_adc_ref = self.labphox.adc_ref + else: + self.measured_adc_ref = self.labphox.adc_ref + else: + print(f'Failed to load constants, HW revision {self.HW_rev} not int {constants.keys()}') + + def set_FW_upgrade_mode(self): + self.labphox.reset_cmd('boot') + + def get_UIDs(self): + UID0 = int(self.labphox.utility_cmd('UID', 0)) + UID1 = int(self.labphox.utility_cmd('UID', 1)) + UID2 = int(self.labphox.utility_cmd('UID', 2)) + + return [UID0, UID1, UID2] + + def flash(self, path=None): + reply = input('Are you sure you want to flash the device?') + if 'Y' in reply.upper(): + self.set_FW_upgrade_mode() + time.sleep(5) + self.labphox.FLASH_utils(path) + else: + print('Aborting flash sequence...') + + def reset(self): + self.labphox.reset_cmd('reset') + time.sleep(3) + + def reconnect(self): + self.labphox.connect() + + def enable_5V(self): + self.labphox.gpio_cmd('EN_5V', 1) + + def disable_5V(self): + self.labphox.gpio_cmd('EN_5V', 0) + + def enable_3V3(self): + self.labphox.gpio_cmd('EN_3V3', 1) + + def disable_3V3(self): + self.labphox.gpio_cmd('EN_3V3', 0) + + def standby(self): + self.set_output_voltage(5) + self.disable_converter() + self.disable_negative_supply() + self.disable_3V3() + self.disable_5V() + + def calculate_error(self, measured, set): + error = abs((measured - set) / set) + return error + + def measure_ADC(self, channel): + self.labphox.ADC_cmd('select', channel) + time.sleep(self.wait_time) + return self.labphox.ADC_cmd('get') + + def get_converter_voltage(self): + converter_gain = self.measured_adc_ref * self.converter_divider / self.ADC_12B_res + code = self.measure_ADC(self.converter_ADC) + converter_voltage = round(code * converter_gain, self.decimals) + self.MEASURED_converter_voltage = converter_voltage + return converter_voltage + + def get_bias_voltage(self): + bias_gain = self.measured_adc_ref * ((self.bv_R2 + self.bv_R1) / self.bv_R1) / self.ADC_12B_res + bias_offset = self.measured_adc_ref*self.bv_R2/self.bv_R1 + code = self.measure_ADC(self.bv_ADC) + bias_voltage = code * bias_gain-bias_offset + + return round(bias_voltage, self.decimals) + + def check_voltage(self, measured_voltage, target_voltage, tolerance=0.1, pre_str=''): + error = self.calculate_error(measured_voltage, target_voltage) + if error > tolerance: + print(f'{pre_str} Failed to set voltage: {target_voltage} , measured voltage: {round(measured_voltage, self.decimals)}V') + # print(pre_str, 'failed to set voltage , measured voltage', round(measured_voltage, self.decimals)) + return False + else: + # print(pre_str, 'voltage set to', round(measured_voltage, self.decimals), 'V') + print(f'{pre_str} Voltage set to {round(measured_voltage, self.decimals)}V') + return True + + def get_HW_revision(self): + return self.labphox.HW + + def get_internal_temperature(self): + code = self.measure_ADC(16) + VSENSE = self.measured_adc_ref * code / self.ADC_12B_res + V25 = 0.76 + Avg_Slope = 0.0025 + temp = ((VSENSE - V25) / Avg_Slope) + 25 + return temp + + def get_V_ref(self): + if self.ADC_cal_ref: + self.labphox.ADC3_cmd('select', 8) + time.sleep(self.wait_time) + code = self.labphox.ADC3_cmd('get') + Ref_2V5_code = code + ADC_ref = 2.5 * self.ADC_12B_res / Ref_2V5_code + return round(ADC_ref, 4) + else: + print('Calibration reference is not available in this HW rev') + return None + + def enable_negative_supply(self): + self.labphox.gpio_cmd('EN_CHGP', 1) + time.sleep(1) + bias_voltage = self.get_bias_voltage() + if self.verbose: + self.check_voltage(bias_voltage, -5, tolerance=self.tolerance, pre_str='BIAS STATUS:') + return bias_voltage + + def disable_negative_supply(self): + self.labphox.gpio_cmd('EN_CHGP', 0) + return self.get_bias_voltage() + + def calculate_output_code(self, Vout): + code = ((self.converter_VREF - ( + Vout - self.converter_VREF * (1 + (self.converter_R1 / self.converter_R2))) * ( + self.converter_Rf / self.converter_R1)) * (self.ADC_12B_res / self.measured_adc_ref)) + + code = int((code / self.converter_correction_codes[0]) - self.converter_correction_codes[1]) + if code < self.converter_DAC_lower_bound or code > self.converter_DAC_upper_bound: + print('Wrong DAC value, dont mess with the DAC. DAC angry.') + return False + + return code + + def set_output_voltage(self, Vout): + if self.converter_output_voltage_range[0] <= Vout <= self.converter_output_voltage_range[1]: + if Vout > 10: + self.disable_negative_supply() + else: + self.enable_negative_supply() + self.labphox.DAC_cmd('on', DAC=1) + code = self.calculate_output_code(Vout) + if code: + self.labphox.DAC_cmd('set', DAC=1, value=code) + # if Vout < self.converter_voltage: + # self.discharge() + time.sleep(2) + self.converter_voltage = Vout + measured_voltage = self.get_converter_voltage() + + if self.verbose: + self.check_voltage(measured_voltage, Vout, tolerance=self.tolerance, pre_str='CONVERTER STATUS:') + + return measured_voltage + else: + print(f'Failed to calculate output code') + return False + else: + print('Voltage outside of range (5-30V)') + + return False + + def enable_output_channels(self): + enabled = False + counter = 0 + response = {} + while not enabled: + response = self.labphox.IO_expander_cmd('on') + if int(response['value']) == 0: + enabled = True + elif counter > 3: + break + counter += 1 + + if not int(response['value']) == 0: + print('Failed to enable output channels!', str(response['value'])) + elif self.verbose and counter > 1: + print(counter, 'attempts to enable output channel') + + return int(response['value']) + + def disable_output_channels(self): + self.labphox.IO_expander_cmd('off') + + def enable_converter(self, init_voltage=None): + code = self.calculate_output_code(5) + self.labphox.DAC_cmd('set', DAC=1, value=code) + self.labphox.DAC_cmd('on', DAC=1) + self.labphox.gpio_cmd('PWR_EN', 1) + self.labphox.gpio_cmd('DCDC_EN', 1) + + if init_voltage is None: + init_voltage = self.converter_voltage + + self.set_output_voltage(init_voltage) + + def disable_converter(self): + code = self.calculate_output_code(5) + self.labphox.DAC_cmd('set', DAC=1, value=code) + self.labphox.gpio_cmd('DCDC_EN', 0) + self.labphox.gpio_cmd('PWR_EN', 0) + + def enable_OCP(self): + code = self.calculate_OCP_code(50) + self.labphox.DAC_cmd('set', DAC=2, value=code) + self.labphox.DAC_cmd('on', DAC=2) + self.set_OCP_mA(100) + + def reset_OCP(self): + self.labphox.gpio_cmd('CHOPPING_EN', 1) + time.sleep(0.2) + self.labphox.gpio_cmd('CHOPPING_EN', 0) + + def calculate_OCP_code(self, OCP_value): + code = int(OCP_value*(self.current_sense_R*self.current_gain*self.ADC_12B_res/(self.OCP_gain*1000*self.measured_adc_ref))) + if 0 < code < 4095: + return code + else: + return None + + def set_OCP_mA(self, OCP_value): + if self.OCP_range[0] <= OCP_value <= self.OCP_range[1]: + DAC_reg = self.calculate_OCP_code(OCP_value) + if DAC_reg: + self.labphox.DAC_cmd('set', DAC=2, value=DAC_reg) + return OCP_value + print(f'Over current protection outside of range {self.OCP_range[0]}-{self.OCP_range[1]}mA') + return None + + def get_OCP_status(self): + return self.labphox.gpio_cmd('OCP_OUT_STATUS') + + def enable_chopping(self): + self.labphox.gpio_cmd('CHOPPING_EN', 1) + + def disable_chopping(self): + self.labphox.gpio_cmd('CHOPPING_EN', 0) + + def reset_output_supervisor(self): + self.disable_converter() + self.labphox.gpio_cmd('FORCE_PWR_EN', 1) + time.sleep(0.5) + self.labphox.gpio_cmd('FORCE_PWR_EN', 0) + self.enable_converter() + + def get_output_state(self): + return self.labphox.gpio_cmd('PWR_STATUS') + + def set_pulse_duration_ms(self, ms_duration): + if self.pulse_duration_range[0] <= ms_duration <= self.pulse_duration_range[1]: + self.pulse_duration_ms = ms_duration + pulse_offset = 100 + self.labphox.timer_cmd('duration', round(ms_duration * 100 + pulse_offset)) + if self.verbose: + print(f'Pulse duration set to {ms_duration} ms') + else: + print(f'Pulse duration outside of range ({self.pulse_duration_range[0]}-{self.pulse_duration_range[1]}ms)') + + def set_sampling_frequency_khz(self, f_khz): + if self.sampling_frequency_range[0] <= f_khz <= self.sampling_frequency_range[1]: + self.labphox.timer_cmd('sampling', int(84000/f_khz)) + self.sampling_freq = f_khz * 1000 + else: + print(f'Sampling frequency outside of range ({self.sampling_frequency_range[0]}-{self.sampling_frequency_range[1]}khz)') + + def calculate_polarization_current_mA(self, voltage=None, resistance=None): + if not voltage: + voltage = self.MEASURED_converter_voltage + + if self.converter_voltage <= 10: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 0.2 + 5) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + elif self.converter_voltage < 15: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 0.2) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + else: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 10) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + + if resistance: + th_current += voltage / resistance + + return round(th_current * 1000, 1) + + def send_pulse(self): + if not self.get_power_status(): + print('WARNING: Timing protection triggered, resetting...') + self.reset_output_supervisor() + + current_gain = 1000 * self.measured_adc_ref / (self.current_sense_R * self.current_gain * self.ADC_8B_res) + + current_data = self.labphox.application_cmd('pulse', 1) + + return current_data*current_gain + + def select_switch_model(self, model='R583423141'): + if model.upper() == 'R583423141'.upper(): + self.current_switch_model = 'R583423141' + self.labphox.IO_expander_cmd('type', value=1) + return True + + elif model.upper() == 'R573423600'.upper(): + self.current_switch_model = 'R573423600' + self.labphox.IO_expander_cmd('type', value=2) + return True + else: + return False + + def validate_selected_channel(self, number, polarity, reply): + if polarity and self.current_switch_model == 'R583423141': + shift_byte = 0b0110 + offset = 0 + elif not polarity and self.current_switch_model == 'R583423141': + shift_byte = 0b1001 + offset = 0 + elif polarity and self.current_switch_model == 'R573423600': + shift_byte = 0b10 + offset = 4096 + elif not polarity and self.current_switch_model == 'R573423600': + shift_byte = 0b01 + offset = 8192 + else: + shift_byte = 0 + offset = 0 + + validation_id = (shift_byte << 2 * number) + offset + validation_id1 = validation_id & 255 + validation_id2 = validation_id >> 8 + + if int(reply['value']) != validation_id1|validation_id2: + print('Wrong channel validation ID') + print('Validation ID, Received', reply['value'], '->Expected', validation_id1 | validation_id2) + return False + else: + return True + + def select_output_channel(self, port, number, polarity): + if 0 < number < 7: + number = number - 1 + if polarity: + reply = self.labphox.IO_expander_cmd('connect', port, number) + else: + reply = self.labphox.IO_expander_cmd('disconnect', port, number) + + return self.validate_selected_channel(number, polarity, reply) + else: + print('Contact out of range') + return None + + def plotting_function(self, current_profile, port, contact, polarity): + if polarity: + polarity_str = 'Connect' + else: + polarity_str = 'Disconnect' + + if self.align_edges: + edge = np.argmax(current_profile > 0) + current_data = current_profile[edge:] + else: + current_data = current_profile + + data_points = len(current_data) + sampling_period = 1 / self.sampling_freq + x_axis = np.linspace(0, data_points * sampling_period, data_points) * 1000 + plt.plot(x_axis, current_data) + if self.plot_polarization: + polarization_current = self.calculate_polarization_current_mA() + plt.hlines(polarization_current, x_axis[0], x_axis[-1], colors='red', + linestyles='dashed') + # plt.text(x_axis[-1], polarization_current, 'Pol current') + + plt.xlabel('Time [ms]') + plt.ylabel('Current [mA]') + plt.title(time.strftime("%b-%m %H:%M:%S%p", time.gmtime())) + plt.suptitle('Port ' + port + '-' + str(contact) + ' ' + polarity_str) + + plt.xlim(x_axis[0], x_axis[-1]) + if self.current_switch_model == 'R583423141': + plt.ylim(0, 100) + elif self.current_switch_model == 'R573423600': + plt.ylim(0, 200) + plt.grid() + plt.show() + + def select_and_pulse(self, port, contact, polarity): + if polarity: + polarity = 1 + else: + polarity = 0 + selection_result = self.select_output_channel(port, contact, polarity) + if selection_result: + current_profile = self.send_pulse() + self.disable_output_channels() + if self.plot: + self.plotting_function(current_profile=current_profile, port=port, contact=contact, polarity=polarity) + if self.track_states: + self.save_switch_state(port, contact, polarity) + if self.pulse_logging: + self.log_pulse(port, contact, polarity, current_profile.max()) + if self.log_wav: + self.log_waveform(port, contact, polarity, current_profile) + return current_profile + else: + return [] + + def save_switch_state(self, port, contact, polarity): + file = open(self.track_states_file) + states = json.load(file) + file.close() + + SN = self.SN + port = 'port_' + str(port) + contact = 'contact_' + str(contact) + if SN in states.keys(): + states[SN][port][contact] = polarity + + with open(self.track_states_file, 'w') as outfile: + json.dump(states, outfile, indent=4, sort_keys=True) + + def get_switches_state(self, port=None): + file = open(self.track_states_file) + states = json.load(file) + file.close() + ports = [] + if self.ports_enabled == 1: + ports = ['A'] + elif self.ports_enabled == 2: + ports = ['A', 'B'] + elif self.ports_enabled == 3: + ports = ['A', 'B', 'C'] + elif self.ports_enabled == 4: + ports = ['A', 'B', 'C', 'D'] + + if self.SN in states.keys(): + if port in ports: + current_state = states[self.SN] + print('Port ' + port + ' state') + for switch in range(1, 7): + state = current_state['port_' + port]['contact_' + str(switch)] + if state: + if switch == 1: + print(str(switch) + ' ----' + chr(0x2510)) + else: + print(str(switch) + ' ----' + chr(0x2524)) + else: + print(str(switch) + ' - -' + chr(0x2502)) + print(' ' + chr(0x2514) + '- COM') + print('') + + return states[self.SN] + else: + return None + + def log_waveform(self, port, contact, polarity, current_profile): + name = self.log_wav_dir + '\\' + str(int(time.time())) + '_' + str( + self.MEASURED_converter_voltage) + 'V_' + str(port) + str(contact) + '_' + str(polarity) + '.json' + waveform = {'time':time.time(), 'voltage': self.MEASURED_converter_voltage, 'port': port, 'contact': contact, 'polarity':polarity, 'SF': self.sampling_freq,'data':list(current_profile)} + with open(name, 'w') as outfile: + json.dump(waveform, outfile, indent=4, sort_keys=True) + + def log_pulse(self, port, contact, polarity, max_current): + if polarity: + direction = 'Connect ' + else: + direction = 'Disconnect' + + pulse_string = direction + '-> Port:' + port + '-' + str(contact) + ', CurrentMax:' + str(round(max_current)) + ' Timestamp:' + str(int(time.time())) + + if max_current < self.warning_threshold_current: + warning_string = ' *Warnings: Low current detected!' + else: + warning_string = '' + + with open(self.pulse_logging_filename, 'a') as logging_file: + logging_file.write(pulse_string + warning_string + '\n') + + def get_pulse_history(self, port=None, pulse_number=None): + if not pulse_number: + pulse_number = self.log_pulses_to_display + + with open(self.pulse_logging_filename, 'r') as logging_file: + pulse_info = logging_file.readlines() + + list_for_display = [] + counter = 0 + for idx, pulse in enumerate(pulse_info): + pulse = pulse_info[-idx-1] + if port: + if "Port:" + port + "-" in pulse: + list_for_display.append(pulse) + counter += 1 + else: + list_for_display.append(pulse) + counter += 1 + + if counter >= pulse_number: + break + + for idx, pulse in enumerate(list_for_display): + raw_data = list_for_display[-idx - 1].split(',') + if '*' in raw_data[-1]: + extra_text = raw_data[1].split('*')[-1].strip() + pulse_time = time.localtime(int(raw_data[1].split('*')[0].split(':')[-1].strip())) + else: + extra_text = '' + pulse_time = time.localtime(int(raw_data[1].split(':')[-1].strip())) + + print(raw_data[0] + ', ' + time.strftime("%a %b-%m %H:%M:%S%p", pulse_time) + ' ' + extra_text) + + def validate_port_contact(self, port, contact): + if port == 'A' and self.ports_enabled >= 1: + send_pulse = True + elif port == 'B' and self.ports_enabled >= 2: + send_pulse = True + elif port == 'C' and self.ports_enabled >= 3: + send_pulse = True + elif port == 'D' and self.ports_enabled >= 4: + send_pulse = True + else: + print(f'Port {port} not enabled') + return False + + if 0 < contact < 7: + return send_pulse + else: + return False + + def connect(self, port, contact): + send_pulse = self.validate_port_contact(port, contact) + + if send_pulse: + if self.debug: + print(f'Connecting Port:{port}, Contact {contact}') + + current_profile = self.select_and_pulse(port, contact, 1) + return current_profile + else: + print(f'Port or contact out of range: Port {port}, Contact {contact}') + return None + + def disconnect(self, port, contact): + send_pulse = self.validate_port_contact(port, contact) + + if send_pulse: + if self.debug: + print(f'Connecting Port:{port}, Contact {contact}') + + current_profile = self.select_and_pulse(port, contact, 0) + return current_profile + else: + print(f'Port or contact out of range: Port {port}, Contact {contact}') + return None + + def disconnect_all(self, port): + for contact in range(1, 7): + self.disconnect(port, contact) + if self.plot: + plt.legend([1, 2, 3, 4, 5, 6]) + + def smart_connect(self, port, contact, force=False): + states = self.get_switches_state() + port_state = states['port_' + port] + contacts = [1, 2, 3, 4, 5, 6] + contacts.remove(contact) + for other_contact in contacts: + if port_state['contact_' + str(other_contact)] == 1: + print('Disconnecting', other_contact) + self.disconnect(port, other_contact) + + if port_state['contact_' + str(contact)] == 1: + print('Contact', contact, 'is already connected') + if force: + print('Connecting', contact) + return self.connect(port, contact) + else: + print('Connecting', contact) + return self.connect(port, contact) + + return None + + def discharge(self): + if self.HW_rev_N >= 4: + self.labphox.application_cmd('test_circuit', 1) + test_current = self.send_pulse() + self.labphox.application_cmd('test_circuit', 0) + return test_current + else: + return None + + def test_internals(self, voltage=10): + if self.HW_rev_N >= 4: + last_voltage = self.converter_voltage + self.set_output_voltage(voltage) + voltage = self.MEASURED_converter_voltage + expected_current = ((voltage - 2.2) / 10000 + (voltage - 3) / 4700 + voltage / 480) * 1000 + test_current = self.discharge() + if self.plot: + plt.plot(test_current) + plt.hlines(expected_current, 0, len(test_current), colors='red', linestyles='dashed') + plt.xlabel('Sample') + plt.ylabel('Current [mA]') + self.set_output_voltage(last_voltage) + return test_current + else: + print('Discharge is not possible in this HW revision') + return None + + def get_power_status(self): + return self.labphox.gpio_cmd('PWR_STATUS') + + def set_ip(self, add='192.168.1.101'): + self.labphox.ETHERNET_cmd('set_ip_str', add) + + def get_ip(self): + add = self.labphox.ETHERNET_cmd('get_ip_str') + print(f'IP: {add}') + return add + + def set_sub_net_mask(self, mask='255.255.255.0'): + self.labphox.ETHERNET_cmd('set_mask_str', mask) + + def get_sub_net_mask(self): + mask = self.labphox.ETHERNET_cmd('get_mask_str') + print(f'Subnet Mask: {mask}') + return mask + + def start(self): + if self.verbose: + print('Initialization...') + self.labphox.ADC_cmd('start') + + self.enable_3V3() + self.enable_5V() + self.enable_OCP() + self.set_OCP_mA(80) + self.enable_chopping() + + self.set_pulse_duration_ms(15) + + self.enable_converter() + # self.set_output_voltage(5) + + time.sleep(1) + self.enable_output_channels() + self.select_switch_model('R583423141') + + if not self.get_power_status(): + if self.verbose: + print('POWER STATUS: Output voltage not enabled') + else: + if self.verbose: + print('POWER STATUS: Ready') + + +if __name__ == "__main__": + switch = Cryoswitch(IP='192.168.1.101') ## -> CryoSwitch class declaration and USB connection + + switch.start() ## -> Initialization of the internal hardware + + switch.get_internal_temperature() + switch.get_pulse_history(pulse_number=5, port='A') ##-> Show the last 5 pulses send through on port A + switch.set_output_voltage(5) ## -> Set the output pulse voltage to 5V + + switch.connect(port='A', contact=1) ## Connect contact 1 of port A to the common terminal + switch.disconnect(port='A', contact=1) ## Disconnects contact 1 of port A from the common terminal + switch.smart_connect(port='A', contact=1) ## Connect contact 1 and disconnect wichever port was connected previously (based on the history) + + + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json new file mode 100644 index 000000000..ec34b2ffd --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json @@ -0,0 +1,130 @@ +{ "HW_Ver. 0": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": false, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 550, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 1, + "current_gain": 20, + + "calibrate_ADC": 0, + "polarization_params": [4700, 3000, 4700] + }, + "HW_Ver. 2": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": false, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 500, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 1, + "current_gain": 20, + + "calibrate_ADC": 0, + "polarization_params": [4700, 3000, 4700] + }, + "HW_Ver. 3": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [4700, 3000, 4700] + + }, + "HW_Ver. 4": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.24, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1.03491, -169], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [10000, 3000, 4700] + } +} diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json new file mode 100644 index 000000000..57bcfd65f --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json @@ -0,0 +1,376 @@ +{ + "SN": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN0": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN12": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN22": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN23": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN24": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 1, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 1, + "contact_2": 0, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN25": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN26": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN300": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN6": { + "port_A": { + "contact_1": 1, + "contact_2": 1, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN65535": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + } +} \ No newline at end of file From c2f3134e3a11f2fb515ffc786550c6aadf521369 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:45:38 +0100 Subject: [PATCH 03/57] Main wrapper --- .../CryoSwitchController/qcodes_driver.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py new file mode 100644 index 000000000..ae33ea84e --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -0,0 +1,113 @@ +from qcodes import Instrument, ChannelList, InstrumentChannel +from qcodes.utils.validators import Numbers,Bool,Enum +from qcodes_contrib_drivers.drivers.QPhox.CryoSwitchController.CryoSwitchController import Cryoswitch + +class CryoSwitchChannel(InstrumentChannel): + def __init__(self, parent: Instrument, name: str, channel: str): + super().__init__(parent, name) + + self.add_parameter( + 'active_contact', + get_cmd=self._get_active_contact, + vals=Numbers(0, 6) + ) + + self._channel = channel + self._active_contact = 0 + + def connect(self, contact: int): + trace = self.parent.connect(self._channel, contact) + self._active_contact = contact + return trace + + def disconnect(self, contact: int): + trace = self.parent.disconnect(self._channel, contact) + self._active_contact = 0 + return trace + + def disconnect_all(self): + trace = self.parent.disconnect_all(self._channel) + self._active_contact = 0 + return trace + + def smart_connect(self, contact: int): + trace = self.parent.smart_connect(self._channel, contact) + self._active_contact = contact + return trace + + def _get_active_contact(self): + return self._active_contact + +class CryoSwitchControllerDriver(Instrument): + def __init__(self, name: str, **kwargs): + super().__init__(name, **kwargs) + + self._controller = Cryoswitch() + + self.add_parameter( + 'output_voltage', + set_cmd=self._controller.set_output_voltage, + vals=Numbers(0, 10) + ) + + self.add_parameter( + 'pulse_duration', + set_cmd=self._controller.set_pulse_duration_ms, + vals=Numbers(0, 1000) + ) + + self.add_parameter( + 'OCP_value', + set_cmd=self._controller.set_OCP_mA, + vals=Numbers(0, 1000) + ) + + self.add_parameter( + 'chopping', + set_cmd=self._enable_disable_chopping, + vals=Bool() + ) + + self.add_parameter( + 'switch_model', + set_cmd=self._controller.select_switch_model, + vals=Enum('R583423141', 'R573423600') + ) + + self.add_parameter( + 'power_status', + get_cmd=self._controller.get_power_status, + vals=Enum(0, 1) + ) + + def get_switches_state(self, port: str = None): + return self._controller.get_switches_state(port) + + def disconnect_all(self, port: str): + self._controller.disconnect_all(port) + + def smart_connect(self, port: str, contact: int): + return self._controller.smart_connect(port, contact) + + self.add_function('start', call_cmd=self._controller.start) + self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) + self.add_function('reset_OCP', call_cmd=self._controller.reset_OCP) + + channels = ChannelList(self, "Channels", CryoSwitchChannel, snapshotable=False) + for ch in ['A', 'B', 'C', 'D']: + channel = CryoSwitchChannel(self, f"channel_{ch}", ch) + channels.append(channel) + channels.lock() + self.add_submodule("channels", channels) + + def _enable_disable_chopping(self, enable: bool): + if enable: + self._controller.enable_chopping() + else: + self._controller.disable_chopping() + + def connect(self, port: str, contact: int): + return self._controller.connect(port, contact) + + def disconnect(self, port: str, contact: int): + return self._controller.disconnect(port, contact) From 59494ba5e65885e88a455d8da84da954d547f1c7 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:46:10 +0100 Subject: [PATCH 04/57] Readmes --- .../CryoSwitchController/QphoX_README.md | 77 +++++++++++++++++++ .../QphoX/CryoSwitchController/README.txt | 19 +++++ 2 files changed, 96 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md new file mode 100644 index 000000000..f7068728f --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md @@ -0,0 +1,77 @@ +# CryoSwitchController + +## Project structure + +## Getting started + +This repository holds the QP-CryoSwitchController compatible software. + + +## Installation +- Clone the repo +- Run: ```pip install -r requirements.txt``` +- Browse the CryoSwitchController.py file for library implementation + + +## Library Usage +A basic implementation of the CryoSwitchController class can be done with the following functions: +- start() + + Input: None + Default: None + Enables the voltage rails, voltage converter and output channels + +- set_output_voltage(Vout) + + Input: Desired output voltage (Vout) + Default: 5V + Sets the converter voltage to Vout. The output stage later utilizes the converter voltage to generate the positive/negative pulses. + +- set_pulse_duration_ms(ms_duration) + + Input: Pulse width duration in milliseconds (ms_duration). + Default: 10ms. + Sets the output pulse (positive/negative) duration in milliseconds. + +- connect(port, contact) + + Input: Corresponding port and contact to be connected. Port={A, B, C, D}, contact={1,...,6} + Default: None. + Connects the specified contact of the specified port (switch). + +- disconnect(port, contact) + + Input: Corresponding port and contact to be disconnected. Port={A, B, C, D}, contact={1,...,6} + Default: None. + Disconnects the specified contact of the specified port (switch). + + + +## Advanced functions + +- enable_OCP() + + Input: None + Default: None. + Enables the overcurrent protection. + + +- set_OCP_mA(OCP_value) + + Input: Overcurrent protection trigger value (OCP_value). + Default: 100mA. + Sets the overcurrent protection to the specified value. + +- enable_chopping() + + Input: None. + Default: None. + Enables the chopping function. When an overcurrent condition occurs, the controller will 'chop' the excess current instead of disabling the output. Please refer to the installation guide for further information. + +- disable_chopping() + + Input: None. + Default: None. + Disables the chopping function. When an overcurrent condition occurs, the controller will disable the output voltage. Please refer to the installation guide for further information. + + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt new file mode 100644 index 000000000..16ec753e9 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -0,0 +1,19 @@ +The QCoDeS driver is a wrapper around the QphoX SDK retreived on 2024-02-23 +from https://github.com/QphoX/CryoSwitchController + +The driver is functional (at least) after pulfiling the following requirements: + +cycler=0.11.0 +kiwisolver==1.3.1 +matplotlib~=3.8.3 +numpy~=1.26.4 +Pillow~=10.2.0 +pyparsing==3.0.9 +pyserial==3.5 +python-dateutil==2.8.2 +six==1.16.0 + +These requirements originate from QphoX SDK requirements file, and relaxed to be compatible with python 3.11. As-written, the requirements are likely much more restrictive than neccessary. + +Wrapper written by Filip Malinowski +filip.malinowski@tno.nl. \ No newline at end of file From ae65aba143bde0d258fca8d67f95546ed3a752f0 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:46:56 +0100 Subject: [PATCH 05/57] Qphox license --- .../QphoX/CryoSwitchController/QphoX_license | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license new file mode 100644 index 000000000..29678f62d --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license @@ -0,0 +1,38 @@ +The QCoDeS driver for a CryoSwitchController is a wrapper for the QPhox SDK available at https://github.com/QphoX/CryoSwitchController. +The license below applies to the files: +- CryoSwitchController.py +- libphox.py +- QphoX_README +- states.json +- constants.json +A wrapper: +- qcodes_driver.py is excluded from that + +Copyright (c) 2023, QphoX B.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. All advertising materials mentioning features or use of this software must + display the following acknowledgement: + This product includes software developed by QphoX. +4. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY QphoX B.V. "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL QphoX B.V. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 12d3900219d2018454839f6b8dc619c68558a27f Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Fri, 23 Feb 2024 16:31:53 +0100 Subject: [PATCH 06/57] Fix imports --- .gitignore | 1 + .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 99a84635d..e17f27cb6 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,4 @@ venv.bak/ # System files *.DS_Store desktop.ini +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index ae33ea84e..389b3612b 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -1,6 +1,6 @@ from qcodes import Instrument, ChannelList, InstrumentChannel from qcodes.utils.validators import Numbers,Bool,Enum -from qcodes_contrib_drivers.drivers.QPhox.CryoSwitchController.CryoSwitchController import Cryoswitch +from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch class CryoSwitchChannel(InstrumentChannel): def __init__(self, parent: Instrument, name: str, channel: str): @@ -111,3 +111,6 @@ def connect(self, port: str, contact: int): def disconnect(self, port: str, contact: int): return self._controller.disconnect(port, contact) + + def get_idn(self): + pass From 625828183e562ca4eed783a2d4ab56ce19878aa8 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Mon, 26 Feb 2024 09:40:06 +0100 Subject: [PATCH 07/57] Ignore qphox data. --- .gitignore | 1 + .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 4 ++++ .../drivers/QphoX/CryoSwitchController/states.json | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e17f27cb6..602e1be6c 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,4 @@ venv.bak/ *.DS_Store desktop.ini src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/data diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 389b3612b..10b2d3ef3 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -18,21 +18,25 @@ def __init__(self, parent: Instrument, name: str, channel: str): def connect(self, contact: int): trace = self.parent.connect(self._channel, contact) self._active_contact = contact + self.active_contact() return trace def disconnect(self, contact: int): trace = self.parent.disconnect(self._channel, contact) self._active_contact = 0 + self.active_contact() return trace def disconnect_all(self): trace = self.parent.disconnect_all(self._channel) self._active_contact = 0 + self.active_contact() return trace def smart_connect(self, contact: int): trace = self.parent.smart_connect(self._channel, contact) self._active_contact = contact + self.active_contact() return trace def _get_active_contact(self): diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json index 57bcfd65f..ce4075a7e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json @@ -173,7 +173,7 @@ "port_A": { "contact_1": 0, "contact_2": 0, - "contact_3": 1, + "contact_3": 0, "contact_4": 0, "contact_5": 0, "contact_6": 0 From 8bac49c9c9f19dc056be89b067aa92b0db9bd235 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Mon, 26 Feb 2024 09:58:06 +0100 Subject: [PATCH 08/57] Docstrings --- .../CryoSwitchController/qcodes_driver.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 10b2d3ef3..9af5fc405 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -3,7 +3,27 @@ from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch class CryoSwitchChannel(InstrumentChannel): + """ + CryoSwitchChannel class is used to define the channels for the CryoSwitchControllerDriver. + It is a subclass of the InstrumentChannel class from qcodes. + + Attributes: + parent (Instrument): The parent instrument to which the channel is attached. + name (str): The name of the channel. + channel (str): The channel identifier. + active_contact (Parameter): The active contact for the channel. + It can be a number between 0 and 6. + """ + def __init__(self, parent: Instrument, name: str, channel: str): + """ + Initializes a new instance of the CryoSwitchChannel class. + + Args: + parent (Instrument): The parent instrument to which the channel is attached. + name (str): The name of the channel. + channel (str): The channel identifier. + """ super().__init__(parent, name) self.add_parameter( @@ -16,34 +36,100 @@ def __init__(self, parent: Instrument, name: str, channel: str): self._active_contact = 0 def connect(self, contact: int): + """ + Applies a current pulse to make a specified contact. + + Args: + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the connection. + """ trace = self.parent.connect(self._channel, contact) self._active_contact = contact self.active_contact() return trace def disconnect(self, contact: int): + """ + Applies a current pulse to disconnect a specified contact. + + Args: + contact (int): The contact to be disconnected. + + Returns: + trace: The current waveform after the disconnection. + """ trace = self.parent.disconnect(self._channel, contact) self._active_contact = 0 self.active_contact() return trace def disconnect_all(self): + """ + Applies a disconnecting pulse to all contacts. + + Returns: + trace: The current waveform after all contacts are disconnected. + """ trace = self.parent.disconnect_all(self._channel) self._active_contact = 0 self.active_contact() return trace def smart_connect(self, contact: int): + """ + Connects a contact to the channel smartly, i.e., disconnects the previously connected + contacts and connects the specified switch contact based on the tracking history. + + Args: + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the smart connection. + """ trace = self.parent.smart_connect(self._channel, contact) self._active_contact = contact self.active_contact() return trace def _get_active_contact(self): + """ + Gets the active contact for the channel. + + Returns: + int: The active contact for the channel. + """ return self._active_contact class CryoSwitchControllerDriver(Instrument): + """ + CryoSwitchControllerDriver class is used to control the Cryoswitch. + It is a subclass of the Instrument class from qcodes. + + Attributes: + name (str): The name of the instrument. + output_voltage (Parameter): The output voltage of the controller. + It can be a number between 0 and 10. + pulse_duration (Parameter): The pulse duration of the controller. + It can be a number between 0 and 1000. + OCP_value (Parameter): The overcurrent protection trigger value of the controller. + It can be a number between 0 and 1000. + chopping (Parameter): The chopping function status of the controller. + It can be a boolean value. + switch_model (Parameter): The switch model used by the controller. + It can be either 'R583423141' or 'R573423600'. + power_status (Parameter): The power status of the controller. + It can be either 0 (disabled) or 1 (enabled). + """ + def __init__(self, name: str, **kwargs): + """ + Initializes a new instance of the CryoSwitchControllerDriver class. + + Args: + name (str): The name of the instrument. + """ super().__init__(name, **kwargs) self._controller = Cryoswitch() @@ -105,16 +191,48 @@ def smart_connect(self, port: str, contact: int): self.add_submodule("channels", channels) def _enable_disable_chopping(self, enable: bool): + """ + Enables or disables the chopping function of the controller. + + Args: + enable (bool): True to enable the chopping function, False to disable it. + """ if enable: self._controller.enable_chopping() else: self._controller.disable_chopping() def connect(self, port: str, contact: int): + """ + Applies a current pulse to connect a specific contact of + a switch at a selected port. + + Args: + port (str): The port to which the contact is connected. + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the connection. + """ return self._controller.connect(port, contact) def disconnect(self, port: str, contact: int): + """ + Applies a current pulse to disconnect a specific contact of + a switch at a selected port. + + Args: + port (str): The port from which the contact is disconnected. + contact (int): The contact to be disconnected. + + Returns: + trace: The current waveform after the disconnection. + """ return self._controller.disconnect(port, contact) def get_idn(self): + """ + A dummy getidn function for the instrument initialization + in QCoDeS to work. + """ pass From 167705f42276f43972cdd46f38d84ff506a7ed5e Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 27 Feb 2024 13:24:31 +0100 Subject: [PATCH 09/57] Fix position of several functions --- .../CryoSwitchController/qcodes_driver.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 9af5fc405..87549af61 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -170,14 +170,6 @@ def __init__(self, name: str, **kwargs): vals=Enum(0, 1) ) - def get_switches_state(self, port: str = None): - return self._controller.get_switches_state(port) - - def disconnect_all(self, port: str): - self._controller.disconnect_all(port) - - def smart_connect(self, port: str, contact: int): - return self._controller.smart_connect(port, contact) self.add_function('start', call_cmd=self._controller.start) self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) @@ -185,11 +177,20 @@ def smart_connect(self, port: str, contact: int): channels = ChannelList(self, "Channels", CryoSwitchChannel, snapshotable=False) for ch in ['A', 'B', 'C', 'D']: - channel = CryoSwitchChannel(self, f"channel_{ch}", ch) + channel = CryoSwitchChannel(self, f"{ch}", ch) channels.append(channel) channels.lock() self.add_submodule("channels", channels) + def get_switches_state(self, port: str = None): + return self._controller.get_switches_state(port) + + def disconnect_all(self, port: str): + self._controller.disconnect_all(port) + + def smart_connect(self, port: str, contact: int): + return self._controller.smart_connect(port, contact) + def _enable_disable_chopping(self, enable: bool): """ Enables or disables the chopping function of the controller. From 351830ff4b2385409be28d567f276e90afa62173 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 11:30:36 +0100 Subject: [PATCH 10/57] Use 'RT' and 'CRYO' to select a switch model. --- .../CryoSwitchController/qcodes_driver.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 87549af61..c7dbbc59e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -133,11 +133,12 @@ def __init__(self, name: str, **kwargs): super().__init__(name, **kwargs) self._controller = Cryoswitch() + self._switch_model = None self.add_parameter( 'output_voltage', set_cmd=self._controller.set_output_voltage, - vals=Numbers(0, 10) + vals=Numbers(0, 25) ) self.add_parameter( @@ -160,8 +161,9 @@ def __init__(self, name: str, **kwargs): self.add_parameter( 'switch_model', - set_cmd=self._controller.select_switch_model, - vals=Enum('R583423141', 'R573423600') + set_cmd=self._select_switch_model, + get_cmd=self._get_switch_model, + vals=Enum('R583423141', 'R573423600','CRYO','RT') ) self.add_parameter( @@ -182,14 +184,27 @@ def __init__(self, name: str, **kwargs): channels.lock() self.add_submodule("channels", channels) + def _select_switch_model(self, switch_type: str = None): + if switch_type in ['R583423141', 'CRYO']: + self._controller.select_switch_model('R583423141') + self._switch_model = 'R583423141' + elif switch_type in ['R573423600', 'RT']: + self._controller.select_switch_model('R573423600') + self._switch_model = 'R573423600' + else: + print('Selected switch type does not exist.') + + def _get_switch_model(self): + return self._switch_model + def get_switches_state(self, port: str = None): - return self._controller.get_switches_state(port) + return self._controller.get_switches_state(port) def disconnect_all(self, port: str): - self._controller.disconnect_all(port) + self._controller.disconnect_all(port) def smart_connect(self, port: str, contact: int): - return self._controller.smart_connect(port, contact) + return self._controller.smart_connect(port, contact) def _enable_disable_chopping(self, enable: bool): """ From 00b4c1ea156c269d1313195dce33063efc76358a Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 11:37:35 +0100 Subject: [PATCH 11/57] Update docstring --- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index c7dbbc59e..e5d519c4d 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -110,15 +110,16 @@ class CryoSwitchControllerDriver(Instrument): Attributes: name (str): The name of the instrument. output_voltage (Parameter): The output voltage of the controller. - It can be a number between 0 and 10. + It can be a number between 0 and 25 (V). pulse_duration (Parameter): The pulse duration of the controller. - It can be a number between 0 and 1000. + It can be a number between 0 and 1000 (ms). OCP_value (Parameter): The overcurrent protection trigger value of the controller. - It can be a number between 0 and 1000. + It can be a number between 0 and 1000 (mA). chopping (Parameter): The chopping function status of the controller. It can be a boolean value. switch_model (Parameter): The switch model used by the controller. It can be either 'R583423141' or 'R573423600'. + Equvalently, one may set the model using 'RT' (room temperature) for R573423600, and 'CRYO' instead of 'R583423141'. power_status (Parameter): The power status of the controller. It can be either 0 (disabled) or 1 (enabled). """ @@ -171,7 +172,6 @@ def __init__(self, name: str, **kwargs): get_cmd=self._controller.get_power_status, vals=Enum(0, 1) ) - self.add_function('start', call_cmd=self._controller.start) self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) From 48423f9015a3e6150507a794f3b55444a4a23f4b Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 13:20:17 +0100 Subject: [PATCH 12/57] Do not sync 'states.json'. Create an empty 'states.json' if it doesn't exist. --- .gitignore | 1 + .../QphoX/CryoSwitchController/qcodes_driver.py | 14 ++++++++++++++ .../{states.json => states_empty.json} | 0 3 files changed, 15 insertions(+) rename src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/{states.json => states_empty.json} (100%) diff --git a/.gitignore b/.gitignore index 602e1be6c..1171a8dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,4 @@ venv.bak/ desktop.ini src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/data +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index e5d519c4d..ba1135d62 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -1,6 +1,7 @@ from qcodes import Instrument, ChannelList, InstrumentChannel from qcodes.utils.validators import Numbers,Bool,Enum from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch +import os class CryoSwitchChannel(InstrumentChannel): """ @@ -120,6 +121,7 @@ class CryoSwitchControllerDriver(Instrument): switch_model (Parameter): The switch model used by the controller. It can be either 'R583423141' or 'R573423600'. Equvalently, one may set the model using 'RT' (room temperature) for R573423600, and 'CRYO' instead of 'R583423141'. + The two switch types require different connectivity between the D-Sub on the controller box and the switch. power_status (Parameter): The power status of the controller. It can be either 0 (disabled) or 1 (enabled). """ @@ -133,6 +135,16 @@ def __init__(self, name: str, **kwargs): """ super().__init__(name, **kwargs) + # create an empty states file if it does not exist + current_dir = os.path.dirname(os.path.abspath(__file__)) + target_file = os.path.join(current_dir, 'states.json') + source_file = os.path.join(current_dir, 'states_empty.json') + if not os.path.exists(target_file): + with open(source_file, 'r') as src: + contents = src.read() + with open(target_file, 'w') as tgt: + tgt.write(contents) + self._controller = Cryoswitch() self._switch_model = None @@ -188,9 +200,11 @@ def _select_switch_model(self, switch_type: str = None): if switch_type in ['R583423141', 'CRYO']: self._controller.select_switch_model('R583423141') self._switch_model = 'R583423141' + print('Note that R583423141 and R573423600 models require different connection to the switch box.') elif switch_type in ['R573423600', 'RT']: self._controller.select_switch_model('R573423600') self._switch_model = 'R573423600' + print('Note that R583423141 and R573423600 models require different connection to the switch box.') else: print('Selected switch type does not exist.') diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json similarity index 100% rename from src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json rename to src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json From 50993bbaa6765a871efcbfd85441ae973fbef217 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 14:31:05 +0100 Subject: [PATCH 13/57] Usage example --- .../QphoX/CryoSwitchController/README.txt | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt index 16ec753e9..9bbb71442 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -16,4 +16,45 @@ six==1.16.0 These requirements originate from QphoX SDK requirements file, and relaxed to be compatible with python 3.11. As-written, the requirements are likely much more restrictive than neccessary. Wrapper written by Filip Malinowski -filip.malinowski@tno.nl. \ No newline at end of file +filip.malinowski@tno.nl. + +############### Usage example: ############### + +# load driver +from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.qcodes_driver import CryoSwitchControllerDriver + +# connect to the switch +switchcontroller = CryoSwitchControllerDriver('switchcontroller') +switchcontroller.start() + +# set a switch model +switchcontroller.switch_model('CRYO') + +# set pulse parameters +switchcontroller.output_voltage(15) +switchcontroller.OCP_value(100) +switchcontroller.pulse_duration(8) + +# get all switch states based on the "status.json" +# there is no direct way to see the state of the switch +switchcontroller.get_switches_state() + +# connect contact 3 of port A +switchcontroller.channels.A.connect(3) + +# smart switch to contact 5 (and automatically disconnect from 3) +# there is no software or hardware control to ensure that only contact is connected +# once the pulse parameters are reliable, I recommend only using the "smart_connect" function +switchcontroller.channels.A.smart_connect(5) + +# get an active contact +switchcontroller.channels.A.active_contact() + +# disconnect from contact 5 +data = switchcontroller.channels.A.disconnect(5) + +# plot a current transient for the "disconnect" action +plt.plot(data) + +# get an active contact (should return 0 when nothing is connected) +switchcontroller.channels.A.active_contact() \ No newline at end of file From 6bcefdf6bd15b6d5eebae567ee6b2a30a3fa6041 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Fri, 22 Mar 2024 13:39:09 +0100 Subject: [PATCH 14/57] Enable disconnecting from the switch controller --- .../drivers/QphoX/CryoSwitchController/README.txt | 5 ++++- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt index 9bbb71442..2b88f665e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -57,4 +57,7 @@ data = switchcontroller.channels.A.disconnect(5) plt.plot(data) # get an active contact (should return 0 when nothing is connected) -switchcontroller.channels.A.active_contact() \ No newline at end of file +switchcontroller.channels.A.active_contact() + +# close the switchcontroller instrument +switchcontroller.close() \ No newline at end of file diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index ba1135d62..a08a4d556 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -266,3 +266,10 @@ def get_idn(self): in QCoDeS to work. """ pass + + def close(self): + """ + Disconnect from the switch controller. + """ + self._controller.labphox.disconnect() + super().close() From 7873dcf22a42d4de8f3c0ea1b0a67de1d46818b8 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:44:03 +0100 Subject: [PATCH 15/57] Copy libphox --- .../QphoX/CryoSwitchController/__init__.py | 0 .../QphoX/CryoSwitchController/libphox.py | 724 ++++++++++++++++++ .../drivers/QphoX/__init__.py | 0 3 files changed, 724 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/__init__.py diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py new file mode 100644 index 000000000..c8c05cef9 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py @@ -0,0 +1,724 @@ +import serial +import serial.tools.list_ports +import time +import json +import socket +import numpy as np +import subprocess +import os +import logging + +class Labphox: + _logger = logging.getLogger("libphox") + + def __init__(self, port=None, debug=False, IP=None, cmd_logging=False, SN=None, HW_val=False): + self.debug = debug + self.time_out = 5 + + + if self.debug or cmd_logging: + self.log = True + self.logging_dir = r'\logging' + self.logger_init(self._logger) + else: + self.log = False + + self.SW_version = 3 + self.board_SN = SN + self.board_FW = None + + self.adc_ref = 3.3 + self.N_channel = 0 + + self.COM_port = None + + self.ETH_HOST = None # The server's IP address + self.ETH_PORT = 7 # The port used by the server + self.ETH_buff_size = 1024 + + self.communication_handler_sleep_time = 0 + self.packet_handler_sleep_time = 0 + if IP: + self.USB_or_ETH = 2 # 1 for USB, 2 for ETH + self.ETH_HOST = IP # The server's IP address + self.ETH_PORT = 7 # The port used by the server + self.ETH_buff_size = 1024 + else: + self.USB_or_ETH = 1 # 1 for USB, 2 for ETH + self.COM_port = port + + self.connect(HW_val=HW_val) + + + def connect(self, HW_val=True): + if self.USB_or_ETH == 1: + if self.COM_port: + # TODO + pass + elif self.board_SN: + for device in serial.tools.list_ports.comports(): + if device.pid == 1812: + try: + self.serial_com = serial.Serial(device.device) + if self.board_SN == self.utility_cmd('sn'): + self.COM_port = device.device + self.PID = device.pid + self.serial_com.close() + break + self.serial_com.close() + except Exception as error: + pass + # print('Port' + str(device.device) + ' is already in use:', error) + + else: + for device in serial.tools.list_ports.comports(): + if device.pid == 1812: + self.PID = device.pid + self.COM_port = device.device + if self.debug: + for i in device: + print(i) + + try: + self.serial_com = serial.Serial(self.COM_port) + + self.board_info = '' + self.name = '' + self.board_SN = None + self.utility_cmd('info') + print('Connected to ' + self.name + ' on COM port ' + self.COM_port + ', PID:', + str(self.PID) + ', SerialN: ' + str(self.board_SN) + ', Channels:' + str(self.N_channel)) + print(self.HW, ', FW_Ver.', self.board_FW) + except: + print('ERROR: Couldn\'t connect via serial') + + elif self.USB_or_ETH == 2: + socket.setdefaulttimeout(self.time_out) + + self.board_info = '' + self.name = '' + self.board_SN = None + self.utility_cmd('info') + print('Connected to ' + self.name + ', IP:', + str(self.ETH_HOST) + ', SerialN: ' + str(self.board_SN) + ', Channels:' + str(self.N_channel)) + print(self.HW, ', FW_Ver.', self.board_FW) + if not self.board_SN: + raise Exception( + "Couldn\'t connect, please check that the device is properly connected or try providing a valid SN, COM port or IP number") + + elif self.board_FW != self.SW_version and HW_val: + print("Board Firmware version and Software version are not up to date, Board FW=" + str( + self.board_FW) + " while SW=" + str(self.SW_version)) + + def disconnect(self): + if self.USB_or_ETH == 1: + self.serial_com.close() + elif self.USB_or_ETH == 2: + pass + + def input_buffer(self): + return self.serial_com.inWaiting() + + def flush_input_buffer(self): + return self.serial_com.flushInput() + + def write(self, cmd): + if self.log: + pass + #self.logging('actions', cmd) + + if self.USB_or_ETH == 1: + self.serial_com.write(cmd) + else: + pass + + def read(self, size): + if self.USB_or_ETH == 1: + data_back = self.serial_com.read(size) + else: + data_back = '' + + return data_back + + def read_buffer(self): + return self.read(self.input_buffer()) + + def decode_buffer(self): + return list(self.read_buffer()) + + def debug_func(self, cmd, reply): + print('Command', cmd) + print('Reply', reply) + print('') + # self._logger.debug(f'Debug: {cmd}') + self._logger.info(f'Debug: {cmd}') + self._logger.debug(f'Command: {cmd}') + self._logger.debug(f'Reply: {reply}') + + def logger_init(self, logger_instance, outfolder=None): + logger_instance.setLevel(logging.DEBUG) + + if outfolder is None: + outfolder = os.path.realpath('.') + self.logging_dir + + os.makedirs(name=outfolder, exist_ok=True) + + date_fmt = "%d/%m/%Y %H:%M:%S" + + # remove all old handlers + for hdlr in logger_instance.handlers[:]: + logger_instance.removeHandler(hdlr) + + # INFO level logger + # file logger + fmt = "[%(asctime)s] [%(levelname)s] %(message)s" + log_format = logging.Formatter(fmt=fmt, datefmt=date_fmt) + + info_handler = logging.FileHandler(os.path.join(outfolder, 'info.log')) + info_handler.setFormatter(log_format) + info_handler.setLevel(logging.INFO) + logger_instance.addHandler(info_handler) + + # DEBUG level logger + fmt = "[%(asctime)s] [%(levelname)s] [%(funcName)s(): line %(lineno)s] %(message)s" + log_format = logging.Formatter(fmt=fmt, datefmt=date_fmt) + + debug_handler = logging.FileHandler(os.path.join(outfolder, 'debug.log')) + debug_handler.setFormatter(log_format) + debug_handler.setLevel(logging.DEBUG) + logger_instance.addHandler(debug_handler) + + # _logger = logging.getLogger("libphox") + + return logger_instance + + def read_line(self): + if self.USB_or_ETH == 1: + return self.serial_com.readline() + else: + return '' + + def query_line(self, cmd): + self.write(cmd) + if self.USB_or_ETH == 1: + return self.serial_com.readline() + else: + return '' + + def compare_cmd(self, cmd1, cmd2): + if cmd1.upper() == cmd2.upper(): + return True + else: + return False + + def encode(self, value): + return str(value).encode() + + def decode_simple_response(self, response): + return response.decode('UTF-8').strip() + + def parse_response(self): + ##time.sleep(1) + + reply = '' + + initial_time = time.time() + end = False + while not end: + time.sleep(self.communication_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer().decode() + if ';' in reply: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.split(';')[0] + response = {'reply': reply, 'command': reply.split(':')[:-2], 'value': reply.split(':')[-1]} + + if self.log: + self.logging('received', reply) + + return response + + def TCP_communication_handler(self, encoded_cmd=None): + reply = '' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as TCP_connection: + TCP_connection.connect((self.ETH_HOST, self.ETH_PORT)) + TCP_connection.sendall(encoded_cmd) + packet = TCP_connection.recv(self.ETH_buff_size) + + try: + reply += packet.decode() + except: + print('Invalid packet character', packet) + + reply = reply.split(';')[0] + return reply + + def UDP_communication_handler(self, encoded_cmd=None): + reply = '' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as UDP_connection: + UDP_connection.sendto(encoded_cmd, (self.ETH_HOST, self.ETH_PORT)) + end = False + while not end: + # time.sleep(self.communication_handler_sleep_time) + packet = UDP_connection.recvfrom(self.ETH_buff_size)[0] + if b';' in packet: + reply += packet.split(b';')[0].decode() + end = True + else: + try: + reply += packet.decode() + except: + print('Invalid packet character', packet) + break + + return reply + + def USB_communication_handler(self, encoded_cmd=None): + reply = '' + self.flush_input_buffer() + self.write(encoded_cmd) + + initial_time = time.time() + end = False + while not end: + time.sleep(self.communication_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer().decode() + if ';' in reply: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.split(';')[0] + return reply + + def standard_reply_parser(self, cmd, reply): + response = {'reply': reply, 'command': reply.split(':')[:-1], 'value': reply.split(':')[-1]} + if not self.validate_reply(cmd, response): + self.raise_value_mismatch(cmd, response) + + return response + + def communication_handler(self, cmd, standard=True, is_encoded=False): + response = '' + if is_encoded: + encoded_cmd = cmd + else: + encoded_cmd = cmd.encode() + + if self.USB_or_ETH == 1: + reply = self.USB_communication_handler(encoded_cmd) + elif self.USB_or_ETH == 2: + reply = self.UDP_communication_handler(encoded_cmd) + elif self.USB_or_ETH == 3: + reply = self.TCP_communication_handler(encoded_cmd) + else: + raise Exception("Invalid communication options USB_or_ETH=", self.USB_or_ETH) + + try: + if standard: + if is_encoded: + response = self.standard_reply_parser(cmd=cmd.decode(), reply=reply) + else: + response = self.standard_reply_parser(cmd=cmd, reply=reply) + else: + response = reply + except: + print('Reply Error', reply) + + if self.debug: + self.debug_func(cmd, response) + + return response + + def validate_reply(self, cmd, response): + stripped = cmd.strip(';').split(':') + command = stripped[:-1] + value = stripped[-1] + + match = True + if command != response['command']: + match = False + + return match + + def USB_packet_handler(self, encoded_cmd, end_sequence): + reply = b'' + self.flush_input_buffer() + self.write(encoded_cmd) + + initial_time = time.time() + end = False + while not end: + time.sleep(self.packet_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer() + if end_sequence in reply[-5:]: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.replace(end_sequence, b'').replace(encoded_cmd, b'') + return reply + + def UDP_packet_handler(self, encoded_cmd, end_sequence): + reply = b'' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as s: + s.sendto(encoded_cmd, (self.ETH_HOST, self.ETH_PORT)) + end = False + while not end: + time.sleep(self.packet_handler_sleep_time) + packet = s.recvfrom(self.ETH_buff_size)[0] + reply += packet + if end_sequence in reply[-5:]: + end = True + + reply = reply.replace(end_sequence, b'').replace(encoded_cmd, b'') + return reply[7:] + + def packet_handler(self, cmd, end_sequence=b'\x00\xff\x00\xff'): + encoded_cmd = cmd.encode() + + if self.USB_or_ETH == 1: + reply = self.USB_packet_handler(encoded_cmd, end_sequence) + return reply + + elif self.USB_or_ETH == 2: + reply = self.UDP_packet_handler(encoded_cmd, end_sequence) + return reply + + def raise_value_mismatch(self, cmd, response): + print('Command mismatch!') + print('Command:', cmd) + print('Reply:', response['command']) + + def utility_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'info'): + self.name = self.utility_cmd('name').upper() + if 'LabP'.upper() in self.name: + self.HW = self.utility_cmd('hw') + self.board_SN = self.utility_cmd('sn') + self.board_FW = int(self.utility_cmd('fw').split('.')[-1]) + self.N_channel = int(self.utility_cmd('channels').split()[1]) + + elif self.compare_cmd(cmd, 'name'): + response = self.communication_handler('W:2:A:;', standard=False) + + elif self.compare_cmd(cmd, 'fw'): + response = self.communication_handler('W:2:B:;', standard=False) + + elif self.compare_cmd(cmd, 'hw'): + response = self.communication_handler('W:2:D:;', standard=False) + + elif self.compare_cmd(cmd, 'sn'): + response = self.communication_handler('W:2:E:;', standard=False) + + elif self.compare_cmd(cmd, 'channels'): + response = self.communication_handler('W:2:F:;', standard=False) + + elif self.compare_cmd(cmd, 'connected'): + response = self.communication_handler('W:2:C:;') + return response['value'] + + elif self.compare_cmd(cmd, 'UID'): + response = self.communication_handler('W:2:G:' + str(value) + ';') + return response['value'] + + elif self.compare_cmd(cmd, 'sleep'): + response = self.communication_handler('W:2:S:' + str(value) + ';') + + return response + + def DAC_cmd(self, cmd, DAC=1, value=0): + response = None + if DAC == 1: + sel_DAC = 5 + elif DAC == 2: + sel_DAC = 8 + else: + return None + + if self.compare_cmd(cmd, 'on'): + response = self.communication_handler('W:' + str(sel_DAC) + ':T:1;') + + elif self.compare_cmd(cmd, 'off'): + response = self.communication_handler('W:' + str(sel_DAC) + ':T:0;') + + elif self.compare_cmd(cmd, 'set'): + response = self.communication_handler('W:' + str(sel_DAC) + ':S:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'buffer'): + response = self.communication_handler('W:' + str(sel_DAC) + ':B:' + str(value) + ';') + + return response + + def application_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'pulse'): + ##self.serial_com.flushInput() + ##response = self.communication_handler('W:3:T:' + str(value) + ';', standard=False) + response = self.packet_handler('W:3:T:' + str(value) + ';') + return np.fromstring(response, dtype=np.uint8) + + elif self.compare_cmd(cmd, 'acquire'): + response = self.communication_handler('W:3:Q:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'voltage'): + response = self.communication_handler('W:3:V:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'test_circuit'): + response = self.communication_handler('W:3:P:' + str(value) + ';') + + return response + + def timer_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'duration'): + response = self.communication_handler('W:0:A:' + str(value) + ';') + if int(response['value']) != int(value): + self.raise_value_mismatch(cmd, response) + + if self.compare_cmd(cmd, 'sampling'): + response = self.communication_handler('W:0:S:' + str(value) + ';') + + return response + + def ADC_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'channel'): + response = self.communication_handler('W:4:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'start'): + response = self.communication_handler('W:4:T:1;') + + elif self.compare_cmd(cmd, 'stop'): + response = self.communication_handler('W:4:T:0;') + + elif self.compare_cmd(cmd, 'select'): ##Select and sample + response = self.communication_handler('W:4:S:' + str(value) + ';') + + + elif self.compare_cmd(cmd, 'get'): + response = self.communication_handler('W:4:G:;') + return int(response['value']) + + elif self.compare_cmd(cmd, 'interrupt'): ##Enable interrupt mode + response = self.communication_handler('W:4:I:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'buffer'): ##Enable interrupt mode + response = self.communication_handler('W:4:B:' + str(value) + ';') + return int(response['value']) + + return response + + + def ADC3_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'channel'): + response = self.communication_handler('W:W:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'start'): + response = self.communication_handler('W:W:T:1;') + + elif self.compare_cmd(cmd, 'stop'): + response = self.communication_handler('W:W:T:0;') + + elif self.compare_cmd(cmd, 'select'): ##Select and sample + response = self.communication_handler('W:W:S:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'get'): + response = self.communication_handler('W:W:G:;') + return int(response['value']) + + return response + + def gpio_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'EN_3V3'): + response = self.communication_handler('W:1:A:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'EN_5V'): + response = self.communication_handler('W:1:B:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'EN_CHGP'): + response = self.communication_handler('W:1:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'FORCE_PWR_EN'): + response = self.communication_handler('W:1:D:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'PWR_EN'): + response = self.communication_handler('W:1:E:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'DCDC_EN'): + response = self.communication_handler('W:1:F:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'CHOPPING_EN'): + response = self.communication_handler('W:1:G:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'PWR_STATUS'): + response = self.communication_handler('W:1:H:0;') + return int(response['value']) + + elif self.compare_cmd(cmd, 'OCP_OUT_STATUS'): + response = self.communication_handler('W:1:I:0;') + return int(response['value']) + + return response + + def IO_expander_cmd(self, cmd, port='A', value=0): + response = None + if self.compare_cmd(cmd, 'connect'): + response = self.communication_handler('W:' + str(port) + ':C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'disconnect'): + response = self.communication_handler('W:' + str(port) + ':D:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'on'): + response = self.communication_handler('W:6:O:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'off'): + response = self.communication_handler('W:6:U:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'type'): + response = self.communication_handler('W:6:S:' + str(value) + ';') + + return response + + def reset_cmd(self, cmd): + response = None + if self.compare_cmd(cmd, 'reset'): + response = self.communication_handler('W:7:R:;') + + elif self.compare_cmd(cmd, 'boot'): + response = self.communication_handler('W:7:B:;') + + elif self.compare_cmd(cmd, 'soft_reset'): + response = self.communication_handler('W:7:S:;') + + return response + + def logging(self, list_name, cmd): + with open('history.json', "r") as history_file: + data = json.load(history_file) + + if type(cmd) == str: + data_to_append = cmd + elif type(cmd) == bytes: + data_to_append = cmd.decode() + + if list_name in data.keys(): + data[list_name].append({'data': data_to_append, 'date': time.time()}) + else: + data[list_name] = [{'data': data_to_append, 'date': time.time()}] + + with open('history.json', "w") as file: + json.dump(data, file) + + def ETHERNET_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'read'): + response = self.communication_handler('W:Q:R:' + str(value) + ';') + elif self.compare_cmd(cmd, 'set_ip'): + response = self.communication_handler('W:Q:I:' + str(value) + ';') + elif self.compare_cmd(cmd, 'get_ip'): + response = self.communication_handler('W:Q:G:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'set_ip_str'): + int_IP = int.from_bytes(socket.inet_aton(value), "little") + response = self.communication_handler('W:Q:I:' + str(int_IP) + ';') + elif self.compare_cmd(cmd, 'get_ip_str'): + response = self.communication_handler('W:Q:G:' + str(value) + ';') + IP = socket.inet_ntoa(int(response['value']).to_bytes(4, 'little')) + print('IP:', IP) + return IP + + elif self.compare_cmd(cmd, 'set_mask_str'): + int_mask = int.from_bytes(socket.inet_aton(value), "little") + response = self.communication_handler('W:Q:K:' + str(int_mask) + ';') + + elif self.compare_cmd(cmd, 'get_mask_str'): + response = self.communication_handler('W:Q:L:' + str(value) + ';') + print(response) + mask = socket.inet_ntoa(int(response['value']).to_bytes(4, 'little')) + print('Subnet mask:', mask) + return mask + + elif self.compare_cmd(cmd, 'get_detection'): + response = self.communication_handler('W:Q:D:;') + + return response + + def UPGRADE_cmd(self, cmd, value): + response = None + + if self.compare_cmd(cmd, 'upgrade'): + response = self.communication_handler('U:A:0' + ':' + str(value) + ';') + if int(response['value']) == value: + print('Update successful,', value, 'channels enabled') + else: + print('Couldn\'t update channel number') + + elif self.compare_cmd(cmd, 'stream_key'): + for idx, element in enumerate(value): + response = self.communication_handler('U:B:' + str(chr(65 + idx)) + ':' + str(element) + ';') + if int(response['value']) != element: + print('Error while sending key!') + + return response + + def FLASH_utils(self, path=None): + DFU_name = '0483:df11' + found = False + process = subprocess.Popen(['.\Firmware\dfu-util', '-l'], shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + + start_time = time.time() + exc_time = 0 + output = '' + while exc_time < self.time_out: + output = process.stdout.readline() + if 'Internal Flash' in output and 'Found DFU: [' + DFU_name + ']' in output: + found = True + break + exc_time = time.time() - start_time + + if not found: + print('Couldn\'t find the the device') + else: + print('Device found in', output.strip().strip('Found DFU: ')) + + if not path: + path = '.' + process = subprocess.Popen('.\Firmware\dfu-util -d ' + DFU_name + ' -a 0 -s 0x08000000:leave -D ' + path + '\Firmware\Labphox.bin', shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + + start_time = time.time() + exc_time = 0 + poll = process.poll() + while poll is None: + output = process.stdout.readline() + if 'Download' in output or 'device'.upper() in output.upper() or 'dfu'.upper() in output.upper() and 'bugs' not in output: + print(output.strip()) + exc_time = time.time() - start_time + + if exc_time > self.time_out: + break + else: + poll = process.poll() + + print('Flashing time', round(exc_time, 2), 's') + print('Flash ended! Please disconnect the device.') + + +if __name__ == "__main__": + labphox = Labphox(debug=True, IP='192.168.1.101') + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/__init__.py b/src/qcodes_contrib_drivers/drivers/QphoX/__init__.py new file mode 100644 index 000000000..e69de29bb From fcc33f8649eb345d1a1d1c34e0dadec8322eace7 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:44:59 +0100 Subject: [PATCH 16/57] Copy other QphoxCode --- .../CryoSwitchController.py | 795 ++++++++++++++++++ .../QphoX/CryoSwitchController/constants.json | 130 +++ .../QphoX/CryoSwitchController/states.json | 376 +++++++++ 3 files changed, 1301 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py new file mode 100644 index 000000000..d7a3b3620 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py @@ -0,0 +1,795 @@ +import time +import matplotlib.pyplot as plt +from .libphox import Labphox +import numpy as np +import json +import os + +class Cryoswitch: + + def __init__(self, debug=False, COM_port='', IP=None, SN=None, override_abspath=False): + self.debug = debug + self.port = COM_port + self.IP = IP + self.verbose = True + + self.labphox = Labphox(self.port, debug=self.debug, IP=self.IP, SN=SN) + self.ports_enabled = self.labphox.N_channel + self.SN = self.labphox.board_SN + self.HW_rev = self.get_HW_revision() + self.HW_rev_N = int(self.get_HW_revision()[-1]) + + self.wait_time = 0.5 + self.pulse_duration_ms = 15 + self.converter_voltage = 5 + self.MEASURED_converter_voltage = 0 + self.current_switch_model = '' + self.tolerance = 0.15 + + if override_abspath: + self.abs_path = override_abspath + '\\' + else: + self.abs_path = os.path.dirname(__file__) + '\\' + + self.decimals = 2 + self.plot = False + self.log_wav = True + self.log_wav_dir = self.abs_path + r'data' + self.align_edges = True + self.plot_polarization = True + + self.pulse_logging = True + self.pulse_logging_filename = self.abs_path + r'pulse_logging.txt' + self.log_pulses_to_display = 5 + self.warning_threshold_current = 60 + + self.track_states = True + self.track_states_file = self.abs_path + r'states.json' + + self.constants_file_name = self.abs_path + r'constants.json' + self.__constants() + + if self.track_states: + self.tracking_init() + + if self.pulse_logging: + self.pulse_logging_init() + + if self.log_wav: + self.log_wav_init() + + def tracking_init(self): + file = open(self.track_states_file) + states = json.load(file) + file.close() + if self.SN not in states.keys(): + states[self.SN] = states['SN'] + with open(self.track_states_file, 'w') as outfile: + json.dump(states, outfile, indent=4, sort_keys=True) + + def pulse_logging_init(self): + if not os.path.isfile(self.pulse_logging_filename): + file = open(self.pulse_logging_filename, 'w') + file.close() + + def log_wav_init(self): + if not os.path.isdir(self.log_wav_dir): + os.mkdir(self.log_wav_dir) + + def __constants(self): + file = open(self.constants_file_name) + constants = json.load(file) + file.close() + + if self.HW_rev in constants.keys(): + constants = constants[self.HW_rev] + self.ADC_12B_res = constants['ADC_12B_res'] + self.ADC_8B_res = constants['ADC_8B_res'] + self.ADC_cal_ref = constants['ADC_cal_ref'] + + self.bv_R1 = constants['bv_R1'] + self.bv_R2 = constants['bv_R2'] + self.bv_ADC = constants['bv_ADC'] + + self.converter_divider = constants['converter_divider'] + self.converter_ADC = constants['converter_ADC'] + + self.converter_VREF = constants['converter_VREF'] + self.converter_R1 = constants['converter_R1'] + self.converter_R2 = constants['converter_R2'] + self.converter_Rf = constants['converter_Rf'] + self.converter_DAC_lower_bound = constants['converter_DAC_lower_bound'] + self.converter_DAC_upper_bound = constants['converter_DAC_upper_bound'] + self.converter_correction_codes = constants['converter_correction_codes'] + self.converter_output_voltage_range = constants['converter_output_voltage_range'] + + + self.OCP_gain = constants['OCP_gain'] + self.OCP_range = constants['OCP_range'] + + self.pulse_duration_range = constants['pulse_duration_range'] + self.sampling_frequency_range = constants['sampling_frequency_range'] + + self.current_sense_R = constants['current_sense_R'] + self.current_gain = constants['current_gain'] + self.polarization_params = constants['polarization_params'] + + self.sampling_freq = 28000 + + if constants['calibrate_ADC']: + self.labphox.ADC3_cmd('start') + time.sleep(0.1) + ref_values = [] + for it in range(5): + ref_values.append(self.get_V_ref()) + measured_ref = sum(ref_values) / len(ref_values) + + if 3.1 < measured_ref < 3.5: + self.measured_adc_ref = measured_ref + else: + print(f'Measured ADC ref {measured_ref}V outside of range') + self.measured_adc_ref = self.labphox.adc_ref + else: + self.measured_adc_ref = self.labphox.adc_ref + else: + print(f'Failed to load constants, HW revision {self.HW_rev} not int {constants.keys()}') + + def set_FW_upgrade_mode(self): + self.labphox.reset_cmd('boot') + + def get_UIDs(self): + UID0 = int(self.labphox.utility_cmd('UID', 0)) + UID1 = int(self.labphox.utility_cmd('UID', 1)) + UID2 = int(self.labphox.utility_cmd('UID', 2)) + + return [UID0, UID1, UID2] + + def flash(self, path=None): + reply = input('Are you sure you want to flash the device?') + if 'Y' in reply.upper(): + self.set_FW_upgrade_mode() + time.sleep(5) + self.labphox.FLASH_utils(path) + else: + print('Aborting flash sequence...') + + def reset(self): + self.labphox.reset_cmd('reset') + time.sleep(3) + + def reconnect(self): + self.labphox.connect() + + def enable_5V(self): + self.labphox.gpio_cmd('EN_5V', 1) + + def disable_5V(self): + self.labphox.gpio_cmd('EN_5V', 0) + + def enable_3V3(self): + self.labphox.gpio_cmd('EN_3V3', 1) + + def disable_3V3(self): + self.labphox.gpio_cmd('EN_3V3', 0) + + def standby(self): + self.set_output_voltage(5) + self.disable_converter() + self.disable_negative_supply() + self.disable_3V3() + self.disable_5V() + + def calculate_error(self, measured, set): + error = abs((measured - set) / set) + return error + + def measure_ADC(self, channel): + self.labphox.ADC_cmd('select', channel) + time.sleep(self.wait_time) + return self.labphox.ADC_cmd('get') + + def get_converter_voltage(self): + converter_gain = self.measured_adc_ref * self.converter_divider / self.ADC_12B_res + code = self.measure_ADC(self.converter_ADC) + converter_voltage = round(code * converter_gain, self.decimals) + self.MEASURED_converter_voltage = converter_voltage + return converter_voltage + + def get_bias_voltage(self): + bias_gain = self.measured_adc_ref * ((self.bv_R2 + self.bv_R1) / self.bv_R1) / self.ADC_12B_res + bias_offset = self.measured_adc_ref*self.bv_R2/self.bv_R1 + code = self.measure_ADC(self.bv_ADC) + bias_voltage = code * bias_gain-bias_offset + + return round(bias_voltage, self.decimals) + + def check_voltage(self, measured_voltage, target_voltage, tolerance=0.1, pre_str=''): + error = self.calculate_error(measured_voltage, target_voltage) + if error > tolerance: + print(f'{pre_str} Failed to set voltage: {target_voltage} , measured voltage: {round(measured_voltage, self.decimals)}V') + # print(pre_str, 'failed to set voltage , measured voltage', round(measured_voltage, self.decimals)) + return False + else: + # print(pre_str, 'voltage set to', round(measured_voltage, self.decimals), 'V') + print(f'{pre_str} Voltage set to {round(measured_voltage, self.decimals)}V') + return True + + def get_HW_revision(self): + return self.labphox.HW + + def get_internal_temperature(self): + code = self.measure_ADC(16) + VSENSE = self.measured_adc_ref * code / self.ADC_12B_res + V25 = 0.76 + Avg_Slope = 0.0025 + temp = ((VSENSE - V25) / Avg_Slope) + 25 + return temp + + def get_V_ref(self): + if self.ADC_cal_ref: + self.labphox.ADC3_cmd('select', 8) + time.sleep(self.wait_time) + code = self.labphox.ADC3_cmd('get') + Ref_2V5_code = code + ADC_ref = 2.5 * self.ADC_12B_res / Ref_2V5_code + return round(ADC_ref, 4) + else: + print('Calibration reference is not available in this HW rev') + return None + + def enable_negative_supply(self): + self.labphox.gpio_cmd('EN_CHGP', 1) + time.sleep(1) + bias_voltage = self.get_bias_voltage() + if self.verbose: + self.check_voltage(bias_voltage, -5, tolerance=self.tolerance, pre_str='BIAS STATUS:') + return bias_voltage + + def disable_negative_supply(self): + self.labphox.gpio_cmd('EN_CHGP', 0) + return self.get_bias_voltage() + + def calculate_output_code(self, Vout): + code = ((self.converter_VREF - ( + Vout - self.converter_VREF * (1 + (self.converter_R1 / self.converter_R2))) * ( + self.converter_Rf / self.converter_R1)) * (self.ADC_12B_res / self.measured_adc_ref)) + + code = int((code / self.converter_correction_codes[0]) - self.converter_correction_codes[1]) + if code < self.converter_DAC_lower_bound or code > self.converter_DAC_upper_bound: + print('Wrong DAC value, dont mess with the DAC. DAC angry.') + return False + + return code + + def set_output_voltage(self, Vout): + if self.converter_output_voltage_range[0] <= Vout <= self.converter_output_voltage_range[1]: + if Vout > 10: + self.disable_negative_supply() + else: + self.enable_negative_supply() + self.labphox.DAC_cmd('on', DAC=1) + code = self.calculate_output_code(Vout) + if code: + self.labphox.DAC_cmd('set', DAC=1, value=code) + # if Vout < self.converter_voltage: + # self.discharge() + time.sleep(2) + self.converter_voltage = Vout + measured_voltage = self.get_converter_voltage() + + if self.verbose: + self.check_voltage(measured_voltage, Vout, tolerance=self.tolerance, pre_str='CONVERTER STATUS:') + + return measured_voltage + else: + print(f'Failed to calculate output code') + return False + else: + print('Voltage outside of range (5-30V)') + + return False + + def enable_output_channels(self): + enabled = False + counter = 0 + response = {} + while not enabled: + response = self.labphox.IO_expander_cmd('on') + if int(response['value']) == 0: + enabled = True + elif counter > 3: + break + counter += 1 + + if not int(response['value']) == 0: + print('Failed to enable output channels!', str(response['value'])) + elif self.verbose and counter > 1: + print(counter, 'attempts to enable output channel') + + return int(response['value']) + + def disable_output_channels(self): + self.labphox.IO_expander_cmd('off') + + def enable_converter(self, init_voltage=None): + code = self.calculate_output_code(5) + self.labphox.DAC_cmd('set', DAC=1, value=code) + self.labphox.DAC_cmd('on', DAC=1) + self.labphox.gpio_cmd('PWR_EN', 1) + self.labphox.gpio_cmd('DCDC_EN', 1) + + if init_voltage is None: + init_voltage = self.converter_voltage + + self.set_output_voltage(init_voltage) + + def disable_converter(self): + code = self.calculate_output_code(5) + self.labphox.DAC_cmd('set', DAC=1, value=code) + self.labphox.gpio_cmd('DCDC_EN', 0) + self.labphox.gpio_cmd('PWR_EN', 0) + + def enable_OCP(self): + code = self.calculate_OCP_code(50) + self.labphox.DAC_cmd('set', DAC=2, value=code) + self.labphox.DAC_cmd('on', DAC=2) + self.set_OCP_mA(100) + + def reset_OCP(self): + self.labphox.gpio_cmd('CHOPPING_EN', 1) + time.sleep(0.2) + self.labphox.gpio_cmd('CHOPPING_EN', 0) + + def calculate_OCP_code(self, OCP_value): + code = int(OCP_value*(self.current_sense_R*self.current_gain*self.ADC_12B_res/(self.OCP_gain*1000*self.measured_adc_ref))) + if 0 < code < 4095: + return code + else: + return None + + def set_OCP_mA(self, OCP_value): + if self.OCP_range[0] <= OCP_value <= self.OCP_range[1]: + DAC_reg = self.calculate_OCP_code(OCP_value) + if DAC_reg: + self.labphox.DAC_cmd('set', DAC=2, value=DAC_reg) + return OCP_value + print(f'Over current protection outside of range {self.OCP_range[0]}-{self.OCP_range[1]}mA') + return None + + def get_OCP_status(self): + return self.labphox.gpio_cmd('OCP_OUT_STATUS') + + def enable_chopping(self): + self.labphox.gpio_cmd('CHOPPING_EN', 1) + + def disable_chopping(self): + self.labphox.gpio_cmd('CHOPPING_EN', 0) + + def reset_output_supervisor(self): + self.disable_converter() + self.labphox.gpio_cmd('FORCE_PWR_EN', 1) + time.sleep(0.5) + self.labphox.gpio_cmd('FORCE_PWR_EN', 0) + self.enable_converter() + + def get_output_state(self): + return self.labphox.gpio_cmd('PWR_STATUS') + + def set_pulse_duration_ms(self, ms_duration): + if self.pulse_duration_range[0] <= ms_duration <= self.pulse_duration_range[1]: + self.pulse_duration_ms = ms_duration + pulse_offset = 100 + self.labphox.timer_cmd('duration', round(ms_duration * 100 + pulse_offset)) + if self.verbose: + print(f'Pulse duration set to {ms_duration} ms') + else: + print(f'Pulse duration outside of range ({self.pulse_duration_range[0]}-{self.pulse_duration_range[1]}ms)') + + def set_sampling_frequency_khz(self, f_khz): + if self.sampling_frequency_range[0] <= f_khz <= self.sampling_frequency_range[1]: + self.labphox.timer_cmd('sampling', int(84000/f_khz)) + self.sampling_freq = f_khz * 1000 + else: + print(f'Sampling frequency outside of range ({self.sampling_frequency_range[0]}-{self.sampling_frequency_range[1]}khz)') + + def calculate_polarization_current_mA(self, voltage=None, resistance=None): + if not voltage: + voltage = self.MEASURED_converter_voltage + + if self.converter_voltage <= 10: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 0.2 + 5) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + elif self.converter_voltage < 15: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 0.2) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + else: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 10) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + + if resistance: + th_current += voltage / resistance + + return round(th_current * 1000, 1) + + def send_pulse(self): + if not self.get_power_status(): + print('WARNING: Timing protection triggered, resetting...') + self.reset_output_supervisor() + + current_gain = 1000 * self.measured_adc_ref / (self.current_sense_R * self.current_gain * self.ADC_8B_res) + + current_data = self.labphox.application_cmd('pulse', 1) + + return current_data*current_gain + + def select_switch_model(self, model='R583423141'): + if model.upper() == 'R583423141'.upper(): + self.current_switch_model = 'R583423141' + self.labphox.IO_expander_cmd('type', value=1) + return True + + elif model.upper() == 'R573423600'.upper(): + self.current_switch_model = 'R573423600' + self.labphox.IO_expander_cmd('type', value=2) + return True + else: + return False + + def validate_selected_channel(self, number, polarity, reply): + if polarity and self.current_switch_model == 'R583423141': + shift_byte = 0b0110 + offset = 0 + elif not polarity and self.current_switch_model == 'R583423141': + shift_byte = 0b1001 + offset = 0 + elif polarity and self.current_switch_model == 'R573423600': + shift_byte = 0b10 + offset = 4096 + elif not polarity and self.current_switch_model == 'R573423600': + shift_byte = 0b01 + offset = 8192 + else: + shift_byte = 0 + offset = 0 + + validation_id = (shift_byte << 2 * number) + offset + validation_id1 = validation_id & 255 + validation_id2 = validation_id >> 8 + + if int(reply['value']) != validation_id1|validation_id2: + print('Wrong channel validation ID') + print('Validation ID, Received', reply['value'], '->Expected', validation_id1 | validation_id2) + return False + else: + return True + + def select_output_channel(self, port, number, polarity): + if 0 < number < 7: + number = number - 1 + if polarity: + reply = self.labphox.IO_expander_cmd('connect', port, number) + else: + reply = self.labphox.IO_expander_cmd('disconnect', port, number) + + return self.validate_selected_channel(number, polarity, reply) + else: + print('Contact out of range') + return None + + def plotting_function(self, current_profile, port, contact, polarity): + if polarity: + polarity_str = 'Connect' + else: + polarity_str = 'Disconnect' + + if self.align_edges: + edge = np.argmax(current_profile > 0) + current_data = current_profile[edge:] + else: + current_data = current_profile + + data_points = len(current_data) + sampling_period = 1 / self.sampling_freq + x_axis = np.linspace(0, data_points * sampling_period, data_points) * 1000 + plt.plot(x_axis, current_data) + if self.plot_polarization: + polarization_current = self.calculate_polarization_current_mA() + plt.hlines(polarization_current, x_axis[0], x_axis[-1], colors='red', + linestyles='dashed') + # plt.text(x_axis[-1], polarization_current, 'Pol current') + + plt.xlabel('Time [ms]') + plt.ylabel('Current [mA]') + plt.title(time.strftime("%b-%m %H:%M:%S%p", time.gmtime())) + plt.suptitle('Port ' + port + '-' + str(contact) + ' ' + polarity_str) + + plt.xlim(x_axis[0], x_axis[-1]) + if self.current_switch_model == 'R583423141': + plt.ylim(0, 100) + elif self.current_switch_model == 'R573423600': + plt.ylim(0, 200) + plt.grid() + plt.show() + + def select_and_pulse(self, port, contact, polarity): + if polarity: + polarity = 1 + else: + polarity = 0 + selection_result = self.select_output_channel(port, contact, polarity) + if selection_result: + current_profile = self.send_pulse() + self.disable_output_channels() + if self.plot: + self.plotting_function(current_profile=current_profile, port=port, contact=contact, polarity=polarity) + if self.track_states: + self.save_switch_state(port, contact, polarity) + if self.pulse_logging: + self.log_pulse(port, contact, polarity, current_profile.max()) + if self.log_wav: + self.log_waveform(port, contact, polarity, current_profile) + return current_profile + else: + return [] + + def save_switch_state(self, port, contact, polarity): + file = open(self.track_states_file) + states = json.load(file) + file.close() + + SN = self.SN + port = 'port_' + str(port) + contact = 'contact_' + str(contact) + if SN in states.keys(): + states[SN][port][contact] = polarity + + with open(self.track_states_file, 'w') as outfile: + json.dump(states, outfile, indent=4, sort_keys=True) + + def get_switches_state(self, port=None): + file = open(self.track_states_file) + states = json.load(file) + file.close() + ports = [] + if self.ports_enabled == 1: + ports = ['A'] + elif self.ports_enabled == 2: + ports = ['A', 'B'] + elif self.ports_enabled == 3: + ports = ['A', 'B', 'C'] + elif self.ports_enabled == 4: + ports = ['A', 'B', 'C', 'D'] + + if self.SN in states.keys(): + if port in ports: + current_state = states[self.SN] + print('Port ' + port + ' state') + for switch in range(1, 7): + state = current_state['port_' + port]['contact_' + str(switch)] + if state: + if switch == 1: + print(str(switch) + ' ----' + chr(0x2510)) + else: + print(str(switch) + ' ----' + chr(0x2524)) + else: + print(str(switch) + ' - -' + chr(0x2502)) + print(' ' + chr(0x2514) + '- COM') + print('') + + return states[self.SN] + else: + return None + + def log_waveform(self, port, contact, polarity, current_profile): + name = self.log_wav_dir + '\\' + str(int(time.time())) + '_' + str( + self.MEASURED_converter_voltage) + 'V_' + str(port) + str(contact) + '_' + str(polarity) + '.json' + waveform = {'time':time.time(), 'voltage': self.MEASURED_converter_voltage, 'port': port, 'contact': contact, 'polarity':polarity, 'SF': self.sampling_freq,'data':list(current_profile)} + with open(name, 'w') as outfile: + json.dump(waveform, outfile, indent=4, sort_keys=True) + + def log_pulse(self, port, contact, polarity, max_current): + if polarity: + direction = 'Connect ' + else: + direction = 'Disconnect' + + pulse_string = direction + '-> Port:' + port + '-' + str(contact) + ', CurrentMax:' + str(round(max_current)) + ' Timestamp:' + str(int(time.time())) + + if max_current < self.warning_threshold_current: + warning_string = ' *Warnings: Low current detected!' + else: + warning_string = '' + + with open(self.pulse_logging_filename, 'a') as logging_file: + logging_file.write(pulse_string + warning_string + '\n') + + def get_pulse_history(self, port=None, pulse_number=None): + if not pulse_number: + pulse_number = self.log_pulses_to_display + + with open(self.pulse_logging_filename, 'r') as logging_file: + pulse_info = logging_file.readlines() + + list_for_display = [] + counter = 0 + for idx, pulse in enumerate(pulse_info): + pulse = pulse_info[-idx-1] + if port: + if "Port:" + port + "-" in pulse: + list_for_display.append(pulse) + counter += 1 + else: + list_for_display.append(pulse) + counter += 1 + + if counter >= pulse_number: + break + + for idx, pulse in enumerate(list_for_display): + raw_data = list_for_display[-idx - 1].split(',') + if '*' in raw_data[-1]: + extra_text = raw_data[1].split('*')[-1].strip() + pulse_time = time.localtime(int(raw_data[1].split('*')[0].split(':')[-1].strip())) + else: + extra_text = '' + pulse_time = time.localtime(int(raw_data[1].split(':')[-1].strip())) + + print(raw_data[0] + ', ' + time.strftime("%a %b-%m %H:%M:%S%p", pulse_time) + ' ' + extra_text) + + def validate_port_contact(self, port, contact): + if port == 'A' and self.ports_enabled >= 1: + send_pulse = True + elif port == 'B' and self.ports_enabled >= 2: + send_pulse = True + elif port == 'C' and self.ports_enabled >= 3: + send_pulse = True + elif port == 'D' and self.ports_enabled >= 4: + send_pulse = True + else: + print(f'Port {port} not enabled') + return False + + if 0 < contact < 7: + return send_pulse + else: + return False + + def connect(self, port, contact): + send_pulse = self.validate_port_contact(port, contact) + + if send_pulse: + if self.debug: + print(f'Connecting Port:{port}, Contact {contact}') + + current_profile = self.select_and_pulse(port, contact, 1) + return current_profile + else: + print(f'Port or contact out of range: Port {port}, Contact {contact}') + return None + + def disconnect(self, port, contact): + send_pulse = self.validate_port_contact(port, contact) + + if send_pulse: + if self.debug: + print(f'Connecting Port:{port}, Contact {contact}') + + current_profile = self.select_and_pulse(port, contact, 0) + return current_profile + else: + print(f'Port or contact out of range: Port {port}, Contact {contact}') + return None + + def disconnect_all(self, port): + for contact in range(1, 7): + self.disconnect(port, contact) + if self.plot: + plt.legend([1, 2, 3, 4, 5, 6]) + + def smart_connect(self, port, contact, force=False): + states = self.get_switches_state() + port_state = states['port_' + port] + contacts = [1, 2, 3, 4, 5, 6] + contacts.remove(contact) + for other_contact in contacts: + if port_state['contact_' + str(other_contact)] == 1: + print('Disconnecting', other_contact) + self.disconnect(port, other_contact) + + if port_state['contact_' + str(contact)] == 1: + print('Contact', contact, 'is already connected') + if force: + print('Connecting', contact) + return self.connect(port, contact) + else: + print('Connecting', contact) + return self.connect(port, contact) + + return None + + def discharge(self): + if self.HW_rev_N >= 4: + self.labphox.application_cmd('test_circuit', 1) + test_current = self.send_pulse() + self.labphox.application_cmd('test_circuit', 0) + return test_current + else: + return None + + def test_internals(self, voltage=10): + if self.HW_rev_N >= 4: + last_voltage = self.converter_voltage + self.set_output_voltage(voltage) + voltage = self.MEASURED_converter_voltage + expected_current = ((voltage - 2.2) / 10000 + (voltage - 3) / 4700 + voltage / 480) * 1000 + test_current = self.discharge() + if self.plot: + plt.plot(test_current) + plt.hlines(expected_current, 0, len(test_current), colors='red', linestyles='dashed') + plt.xlabel('Sample') + plt.ylabel('Current [mA]') + self.set_output_voltage(last_voltage) + return test_current + else: + print('Discharge is not possible in this HW revision') + return None + + def get_power_status(self): + return self.labphox.gpio_cmd('PWR_STATUS') + + def set_ip(self, add='192.168.1.101'): + self.labphox.ETHERNET_cmd('set_ip_str', add) + + def get_ip(self): + add = self.labphox.ETHERNET_cmd('get_ip_str') + print(f'IP: {add}') + return add + + def set_sub_net_mask(self, mask='255.255.255.0'): + self.labphox.ETHERNET_cmd('set_mask_str', mask) + + def get_sub_net_mask(self): + mask = self.labphox.ETHERNET_cmd('get_mask_str') + print(f'Subnet Mask: {mask}') + return mask + + def start(self): + if self.verbose: + print('Initialization...') + self.labphox.ADC_cmd('start') + + self.enable_3V3() + self.enable_5V() + self.enable_OCP() + self.set_OCP_mA(80) + self.enable_chopping() + + self.set_pulse_duration_ms(15) + + self.enable_converter() + # self.set_output_voltage(5) + + time.sleep(1) + self.enable_output_channels() + self.select_switch_model('R583423141') + + if not self.get_power_status(): + if self.verbose: + print('POWER STATUS: Output voltage not enabled') + else: + if self.verbose: + print('POWER STATUS: Ready') + + +if __name__ == "__main__": + switch = Cryoswitch(IP='192.168.1.101') ## -> CryoSwitch class declaration and USB connection + + switch.start() ## -> Initialization of the internal hardware + + switch.get_internal_temperature() + switch.get_pulse_history(pulse_number=5, port='A') ##-> Show the last 5 pulses send through on port A + switch.set_output_voltage(5) ## -> Set the output pulse voltage to 5V + + switch.connect(port='A', contact=1) ## Connect contact 1 of port A to the common terminal + switch.disconnect(port='A', contact=1) ## Disconnects contact 1 of port A from the common terminal + switch.smart_connect(port='A', contact=1) ## Connect contact 1 and disconnect wichever port was connected previously (based on the history) + + + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json new file mode 100644 index 000000000..ec34b2ffd --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json @@ -0,0 +1,130 @@ +{ "HW_Ver. 0": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": false, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 550, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 1, + "current_gain": 20, + + "calibrate_ADC": 0, + "polarization_params": [4700, 3000, 4700] + }, + "HW_Ver. 2": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": false, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 500, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 1, + "current_gain": 20, + + "calibrate_ADC": 0, + "polarization_params": [4700, 3000, 4700] + }, + "HW_Ver. 3": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [4700, 3000, 4700] + + }, + "HW_Ver. 4": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.24, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1.03491, -169], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [10000, 3000, 4700] + } +} diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json new file mode 100644 index 000000000..57bcfd65f --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json @@ -0,0 +1,376 @@ +{ + "SN": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN0": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN12": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN22": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN23": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN24": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 1, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 1, + "contact_2": 0, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN25": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN26": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN300": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN6": { + "port_A": { + "contact_1": 1, + "contact_2": 1, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN65535": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + } +} \ No newline at end of file From eb9435e20076bd290bfb2794c07ae3454840d39e Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:45:38 +0100 Subject: [PATCH 17/57] Main wrapper --- .../CryoSwitchController/qcodes_driver.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py new file mode 100644 index 000000000..ae33ea84e --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -0,0 +1,113 @@ +from qcodes import Instrument, ChannelList, InstrumentChannel +from qcodes.utils.validators import Numbers,Bool,Enum +from qcodes_contrib_drivers.drivers.QPhox.CryoSwitchController.CryoSwitchController import Cryoswitch + +class CryoSwitchChannel(InstrumentChannel): + def __init__(self, parent: Instrument, name: str, channel: str): + super().__init__(parent, name) + + self.add_parameter( + 'active_contact', + get_cmd=self._get_active_contact, + vals=Numbers(0, 6) + ) + + self._channel = channel + self._active_contact = 0 + + def connect(self, contact: int): + trace = self.parent.connect(self._channel, contact) + self._active_contact = contact + return trace + + def disconnect(self, contact: int): + trace = self.parent.disconnect(self._channel, contact) + self._active_contact = 0 + return trace + + def disconnect_all(self): + trace = self.parent.disconnect_all(self._channel) + self._active_contact = 0 + return trace + + def smart_connect(self, contact: int): + trace = self.parent.smart_connect(self._channel, contact) + self._active_contact = contact + return trace + + def _get_active_contact(self): + return self._active_contact + +class CryoSwitchControllerDriver(Instrument): + def __init__(self, name: str, **kwargs): + super().__init__(name, **kwargs) + + self._controller = Cryoswitch() + + self.add_parameter( + 'output_voltage', + set_cmd=self._controller.set_output_voltage, + vals=Numbers(0, 10) + ) + + self.add_parameter( + 'pulse_duration', + set_cmd=self._controller.set_pulse_duration_ms, + vals=Numbers(0, 1000) + ) + + self.add_parameter( + 'OCP_value', + set_cmd=self._controller.set_OCP_mA, + vals=Numbers(0, 1000) + ) + + self.add_parameter( + 'chopping', + set_cmd=self._enable_disable_chopping, + vals=Bool() + ) + + self.add_parameter( + 'switch_model', + set_cmd=self._controller.select_switch_model, + vals=Enum('R583423141', 'R573423600') + ) + + self.add_parameter( + 'power_status', + get_cmd=self._controller.get_power_status, + vals=Enum(0, 1) + ) + + def get_switches_state(self, port: str = None): + return self._controller.get_switches_state(port) + + def disconnect_all(self, port: str): + self._controller.disconnect_all(port) + + def smart_connect(self, port: str, contact: int): + return self._controller.smart_connect(port, contact) + + self.add_function('start', call_cmd=self._controller.start) + self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) + self.add_function('reset_OCP', call_cmd=self._controller.reset_OCP) + + channels = ChannelList(self, "Channels", CryoSwitchChannel, snapshotable=False) + for ch in ['A', 'B', 'C', 'D']: + channel = CryoSwitchChannel(self, f"channel_{ch}", ch) + channels.append(channel) + channels.lock() + self.add_submodule("channels", channels) + + def _enable_disable_chopping(self, enable: bool): + if enable: + self._controller.enable_chopping() + else: + self._controller.disable_chopping() + + def connect(self, port: str, contact: int): + return self._controller.connect(port, contact) + + def disconnect(self, port: str, contact: int): + return self._controller.disconnect(port, contact) From 69cacda3c7ba19ed60dc9ceda4d3c1680c39fabf Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:46:10 +0100 Subject: [PATCH 18/57] Readmes --- .../CryoSwitchController/QphoX_README.md | 77 +++++++++++++++++++ .../QphoX/CryoSwitchController/README.txt | 19 +++++ 2 files changed, 96 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md new file mode 100644 index 000000000..f7068728f --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md @@ -0,0 +1,77 @@ +# CryoSwitchController + +## Project structure + +## Getting started + +This repository holds the QP-CryoSwitchController compatible software. + + +## Installation +- Clone the repo +- Run: ```pip install -r requirements.txt``` +- Browse the CryoSwitchController.py file for library implementation + + +## Library Usage +A basic implementation of the CryoSwitchController class can be done with the following functions: +- start() + + Input: None + Default: None + Enables the voltage rails, voltage converter and output channels + +- set_output_voltage(Vout) + + Input: Desired output voltage (Vout) + Default: 5V + Sets the converter voltage to Vout. The output stage later utilizes the converter voltage to generate the positive/negative pulses. + +- set_pulse_duration_ms(ms_duration) + + Input: Pulse width duration in milliseconds (ms_duration). + Default: 10ms. + Sets the output pulse (positive/negative) duration in milliseconds. + +- connect(port, contact) + + Input: Corresponding port and contact to be connected. Port={A, B, C, D}, contact={1,...,6} + Default: None. + Connects the specified contact of the specified port (switch). + +- disconnect(port, contact) + + Input: Corresponding port and contact to be disconnected. Port={A, B, C, D}, contact={1,...,6} + Default: None. + Disconnects the specified contact of the specified port (switch). + + + +## Advanced functions + +- enable_OCP() + + Input: None + Default: None. + Enables the overcurrent protection. + + +- set_OCP_mA(OCP_value) + + Input: Overcurrent protection trigger value (OCP_value). + Default: 100mA. + Sets the overcurrent protection to the specified value. + +- enable_chopping() + + Input: None. + Default: None. + Enables the chopping function. When an overcurrent condition occurs, the controller will 'chop' the excess current instead of disabling the output. Please refer to the installation guide for further information. + +- disable_chopping() + + Input: None. + Default: None. + Disables the chopping function. When an overcurrent condition occurs, the controller will disable the output voltage. Please refer to the installation guide for further information. + + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt new file mode 100644 index 000000000..16ec753e9 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -0,0 +1,19 @@ +The QCoDeS driver is a wrapper around the QphoX SDK retreived on 2024-02-23 +from https://github.com/QphoX/CryoSwitchController + +The driver is functional (at least) after pulfiling the following requirements: + +cycler=0.11.0 +kiwisolver==1.3.1 +matplotlib~=3.8.3 +numpy~=1.26.4 +Pillow~=10.2.0 +pyparsing==3.0.9 +pyserial==3.5 +python-dateutil==2.8.2 +six==1.16.0 + +These requirements originate from QphoX SDK requirements file, and relaxed to be compatible with python 3.11. As-written, the requirements are likely much more restrictive than neccessary. + +Wrapper written by Filip Malinowski +filip.malinowski@tno.nl. \ No newline at end of file From 4f44b9982f9d33251dfe7eea092fdb0cd94b38d9 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:46:56 +0100 Subject: [PATCH 19/57] Qphox license --- .../QphoX/CryoSwitchController/QphoX_license | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license new file mode 100644 index 000000000..29678f62d --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license @@ -0,0 +1,38 @@ +The QCoDeS driver for a CryoSwitchController is a wrapper for the QPhox SDK available at https://github.com/QphoX/CryoSwitchController. +The license below applies to the files: +- CryoSwitchController.py +- libphox.py +- QphoX_README +- states.json +- constants.json +A wrapper: +- qcodes_driver.py is excluded from that + +Copyright (c) 2023, QphoX B.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. All advertising materials mentioning features or use of this software must + display the following acknowledgement: + This product includes software developed by QphoX. +4. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY QphoX B.V. "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL QphoX B.V. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 73ebe08bc81bd33f39b511bc6932e37032c3dcaa Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Fri, 23 Feb 2024 16:31:53 +0100 Subject: [PATCH 20/57] Fix imports --- .gitignore | 1 + .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 99a84635d..e17f27cb6 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,4 @@ venv.bak/ # System files *.DS_Store desktop.ini +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index ae33ea84e..389b3612b 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -1,6 +1,6 @@ from qcodes import Instrument, ChannelList, InstrumentChannel from qcodes.utils.validators import Numbers,Bool,Enum -from qcodes_contrib_drivers.drivers.QPhox.CryoSwitchController.CryoSwitchController import Cryoswitch +from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch class CryoSwitchChannel(InstrumentChannel): def __init__(self, parent: Instrument, name: str, channel: str): @@ -111,3 +111,6 @@ def connect(self, port: str, contact: int): def disconnect(self, port: str, contact: int): return self._controller.disconnect(port, contact) + + def get_idn(self): + pass From 2f987e073c3616505480754ca0cc83ae4d21c6ed Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Mon, 26 Feb 2024 09:40:06 +0100 Subject: [PATCH 21/57] Ignore qphox data. --- .gitignore | 1 + .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 4 ++++ .../drivers/QphoX/CryoSwitchController/states.json | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e17f27cb6..602e1be6c 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,4 @@ venv.bak/ *.DS_Store desktop.ini src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/data diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 389b3612b..10b2d3ef3 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -18,21 +18,25 @@ def __init__(self, parent: Instrument, name: str, channel: str): def connect(self, contact: int): trace = self.parent.connect(self._channel, contact) self._active_contact = contact + self.active_contact() return trace def disconnect(self, contact: int): trace = self.parent.disconnect(self._channel, contact) self._active_contact = 0 + self.active_contact() return trace def disconnect_all(self): trace = self.parent.disconnect_all(self._channel) self._active_contact = 0 + self.active_contact() return trace def smart_connect(self, contact: int): trace = self.parent.smart_connect(self._channel, contact) self._active_contact = contact + self.active_contact() return trace def _get_active_contact(self): diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json index 57bcfd65f..ce4075a7e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json @@ -173,7 +173,7 @@ "port_A": { "contact_1": 0, "contact_2": 0, - "contact_3": 1, + "contact_3": 0, "contact_4": 0, "contact_5": 0, "contact_6": 0 From 9aa486eff2bd54a30301248c7ae0056a2699581b Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Mon, 26 Feb 2024 09:58:06 +0100 Subject: [PATCH 22/57] Docstrings --- .../CryoSwitchController/qcodes_driver.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 10b2d3ef3..9af5fc405 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -3,7 +3,27 @@ from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch class CryoSwitchChannel(InstrumentChannel): + """ + CryoSwitchChannel class is used to define the channels for the CryoSwitchControllerDriver. + It is a subclass of the InstrumentChannel class from qcodes. + + Attributes: + parent (Instrument): The parent instrument to which the channel is attached. + name (str): The name of the channel. + channel (str): The channel identifier. + active_contact (Parameter): The active contact for the channel. + It can be a number between 0 and 6. + """ + def __init__(self, parent: Instrument, name: str, channel: str): + """ + Initializes a new instance of the CryoSwitchChannel class. + + Args: + parent (Instrument): The parent instrument to which the channel is attached. + name (str): The name of the channel. + channel (str): The channel identifier. + """ super().__init__(parent, name) self.add_parameter( @@ -16,34 +36,100 @@ def __init__(self, parent: Instrument, name: str, channel: str): self._active_contact = 0 def connect(self, contact: int): + """ + Applies a current pulse to make a specified contact. + + Args: + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the connection. + """ trace = self.parent.connect(self._channel, contact) self._active_contact = contact self.active_contact() return trace def disconnect(self, contact: int): + """ + Applies a current pulse to disconnect a specified contact. + + Args: + contact (int): The contact to be disconnected. + + Returns: + trace: The current waveform after the disconnection. + """ trace = self.parent.disconnect(self._channel, contact) self._active_contact = 0 self.active_contact() return trace def disconnect_all(self): + """ + Applies a disconnecting pulse to all contacts. + + Returns: + trace: The current waveform after all contacts are disconnected. + """ trace = self.parent.disconnect_all(self._channel) self._active_contact = 0 self.active_contact() return trace def smart_connect(self, contact: int): + """ + Connects a contact to the channel smartly, i.e., disconnects the previously connected + contacts and connects the specified switch contact based on the tracking history. + + Args: + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the smart connection. + """ trace = self.parent.smart_connect(self._channel, contact) self._active_contact = contact self.active_contact() return trace def _get_active_contact(self): + """ + Gets the active contact for the channel. + + Returns: + int: The active contact for the channel. + """ return self._active_contact class CryoSwitchControllerDriver(Instrument): + """ + CryoSwitchControllerDriver class is used to control the Cryoswitch. + It is a subclass of the Instrument class from qcodes. + + Attributes: + name (str): The name of the instrument. + output_voltage (Parameter): The output voltage of the controller. + It can be a number between 0 and 10. + pulse_duration (Parameter): The pulse duration of the controller. + It can be a number between 0 and 1000. + OCP_value (Parameter): The overcurrent protection trigger value of the controller. + It can be a number between 0 and 1000. + chopping (Parameter): The chopping function status of the controller. + It can be a boolean value. + switch_model (Parameter): The switch model used by the controller. + It can be either 'R583423141' or 'R573423600'. + power_status (Parameter): The power status of the controller. + It can be either 0 (disabled) or 1 (enabled). + """ + def __init__(self, name: str, **kwargs): + """ + Initializes a new instance of the CryoSwitchControllerDriver class. + + Args: + name (str): The name of the instrument. + """ super().__init__(name, **kwargs) self._controller = Cryoswitch() @@ -105,16 +191,48 @@ def smart_connect(self, port: str, contact: int): self.add_submodule("channels", channels) def _enable_disable_chopping(self, enable: bool): + """ + Enables or disables the chopping function of the controller. + + Args: + enable (bool): True to enable the chopping function, False to disable it. + """ if enable: self._controller.enable_chopping() else: self._controller.disable_chopping() def connect(self, port: str, contact: int): + """ + Applies a current pulse to connect a specific contact of + a switch at a selected port. + + Args: + port (str): The port to which the contact is connected. + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the connection. + """ return self._controller.connect(port, contact) def disconnect(self, port: str, contact: int): + """ + Applies a current pulse to disconnect a specific contact of + a switch at a selected port. + + Args: + port (str): The port from which the contact is disconnected. + contact (int): The contact to be disconnected. + + Returns: + trace: The current waveform after the disconnection. + """ return self._controller.disconnect(port, contact) def get_idn(self): + """ + A dummy getidn function for the instrument initialization + in QCoDeS to work. + """ pass From e1a87b3be3ac38908c221e77eb11bab820ae4f36 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 27 Feb 2024 13:24:31 +0100 Subject: [PATCH 23/57] Fix position of several functions --- .../CryoSwitchController/qcodes_driver.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 9af5fc405..87549af61 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -170,14 +170,6 @@ def __init__(self, name: str, **kwargs): vals=Enum(0, 1) ) - def get_switches_state(self, port: str = None): - return self._controller.get_switches_state(port) - - def disconnect_all(self, port: str): - self._controller.disconnect_all(port) - - def smart_connect(self, port: str, contact: int): - return self._controller.smart_connect(port, contact) self.add_function('start', call_cmd=self._controller.start) self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) @@ -185,11 +177,20 @@ def smart_connect(self, port: str, contact: int): channels = ChannelList(self, "Channels", CryoSwitchChannel, snapshotable=False) for ch in ['A', 'B', 'C', 'D']: - channel = CryoSwitchChannel(self, f"channel_{ch}", ch) + channel = CryoSwitchChannel(self, f"{ch}", ch) channels.append(channel) channels.lock() self.add_submodule("channels", channels) + def get_switches_state(self, port: str = None): + return self._controller.get_switches_state(port) + + def disconnect_all(self, port: str): + self._controller.disconnect_all(port) + + def smart_connect(self, port: str, contact: int): + return self._controller.smart_connect(port, contact) + def _enable_disable_chopping(self, enable: bool): """ Enables or disables the chopping function of the controller. From 86c9e446ce6a035c4eacc9289859e6d6f9636264 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 11:30:36 +0100 Subject: [PATCH 24/57] Use 'RT' and 'CRYO' to select a switch model. --- .../CryoSwitchController/qcodes_driver.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 87549af61..c7dbbc59e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -133,11 +133,12 @@ def __init__(self, name: str, **kwargs): super().__init__(name, **kwargs) self._controller = Cryoswitch() + self._switch_model = None self.add_parameter( 'output_voltage', set_cmd=self._controller.set_output_voltage, - vals=Numbers(0, 10) + vals=Numbers(0, 25) ) self.add_parameter( @@ -160,8 +161,9 @@ def __init__(self, name: str, **kwargs): self.add_parameter( 'switch_model', - set_cmd=self._controller.select_switch_model, - vals=Enum('R583423141', 'R573423600') + set_cmd=self._select_switch_model, + get_cmd=self._get_switch_model, + vals=Enum('R583423141', 'R573423600','CRYO','RT') ) self.add_parameter( @@ -182,14 +184,27 @@ def __init__(self, name: str, **kwargs): channels.lock() self.add_submodule("channels", channels) + def _select_switch_model(self, switch_type: str = None): + if switch_type in ['R583423141', 'CRYO']: + self._controller.select_switch_model('R583423141') + self._switch_model = 'R583423141' + elif switch_type in ['R573423600', 'RT']: + self._controller.select_switch_model('R573423600') + self._switch_model = 'R573423600' + else: + print('Selected switch type does not exist.') + + def _get_switch_model(self): + return self._switch_model + def get_switches_state(self, port: str = None): - return self._controller.get_switches_state(port) + return self._controller.get_switches_state(port) def disconnect_all(self, port: str): - self._controller.disconnect_all(port) + self._controller.disconnect_all(port) def smart_connect(self, port: str, contact: int): - return self._controller.smart_connect(port, contact) + return self._controller.smart_connect(port, contact) def _enable_disable_chopping(self, enable: bool): """ From 9631ee9d2a00ff9f5b0642c1a9427462499dd918 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 11:37:35 +0100 Subject: [PATCH 25/57] Update docstring --- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index c7dbbc59e..e5d519c4d 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -110,15 +110,16 @@ class CryoSwitchControllerDriver(Instrument): Attributes: name (str): The name of the instrument. output_voltage (Parameter): The output voltage of the controller. - It can be a number between 0 and 10. + It can be a number between 0 and 25 (V). pulse_duration (Parameter): The pulse duration of the controller. - It can be a number between 0 and 1000. + It can be a number between 0 and 1000 (ms). OCP_value (Parameter): The overcurrent protection trigger value of the controller. - It can be a number between 0 and 1000. + It can be a number between 0 and 1000 (mA). chopping (Parameter): The chopping function status of the controller. It can be a boolean value. switch_model (Parameter): The switch model used by the controller. It can be either 'R583423141' or 'R573423600'. + Equvalently, one may set the model using 'RT' (room temperature) for R573423600, and 'CRYO' instead of 'R583423141'. power_status (Parameter): The power status of the controller. It can be either 0 (disabled) or 1 (enabled). """ @@ -171,7 +172,6 @@ def __init__(self, name: str, **kwargs): get_cmd=self._controller.get_power_status, vals=Enum(0, 1) ) - self.add_function('start', call_cmd=self._controller.start) self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) From a5481311fa0f0aa08d99f885a9f948f2d5cd993a Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 13:20:17 +0100 Subject: [PATCH 26/57] Do not sync 'states.json'. Create an empty 'states.json' if it doesn't exist. --- .gitignore | 1 + .../QphoX/CryoSwitchController/qcodes_driver.py | 14 ++++++++++++++ .../{states.json => states_empty.json} | 0 3 files changed, 15 insertions(+) rename src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/{states.json => states_empty.json} (100%) diff --git a/.gitignore b/.gitignore index 602e1be6c..1171a8dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,4 @@ venv.bak/ desktop.ini src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/data +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index e5d519c4d..ba1135d62 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -1,6 +1,7 @@ from qcodes import Instrument, ChannelList, InstrumentChannel from qcodes.utils.validators import Numbers,Bool,Enum from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch +import os class CryoSwitchChannel(InstrumentChannel): """ @@ -120,6 +121,7 @@ class CryoSwitchControllerDriver(Instrument): switch_model (Parameter): The switch model used by the controller. It can be either 'R583423141' or 'R573423600'. Equvalently, one may set the model using 'RT' (room temperature) for R573423600, and 'CRYO' instead of 'R583423141'. + The two switch types require different connectivity between the D-Sub on the controller box and the switch. power_status (Parameter): The power status of the controller. It can be either 0 (disabled) or 1 (enabled). """ @@ -133,6 +135,16 @@ def __init__(self, name: str, **kwargs): """ super().__init__(name, **kwargs) + # create an empty states file if it does not exist + current_dir = os.path.dirname(os.path.abspath(__file__)) + target_file = os.path.join(current_dir, 'states.json') + source_file = os.path.join(current_dir, 'states_empty.json') + if not os.path.exists(target_file): + with open(source_file, 'r') as src: + contents = src.read() + with open(target_file, 'w') as tgt: + tgt.write(contents) + self._controller = Cryoswitch() self._switch_model = None @@ -188,9 +200,11 @@ def _select_switch_model(self, switch_type: str = None): if switch_type in ['R583423141', 'CRYO']: self._controller.select_switch_model('R583423141') self._switch_model = 'R583423141' + print('Note that R583423141 and R573423600 models require different connection to the switch box.') elif switch_type in ['R573423600', 'RT']: self._controller.select_switch_model('R573423600') self._switch_model = 'R573423600' + print('Note that R583423141 and R573423600 models require different connection to the switch box.') else: print('Selected switch type does not exist.') diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json similarity index 100% rename from src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json rename to src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json From 4bd0cdaadc37fd29c81a12e069630a34462e7178 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 14:31:05 +0100 Subject: [PATCH 27/57] Usage example --- .../QphoX/CryoSwitchController/README.txt | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt index 16ec753e9..9bbb71442 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -16,4 +16,45 @@ six==1.16.0 These requirements originate from QphoX SDK requirements file, and relaxed to be compatible with python 3.11. As-written, the requirements are likely much more restrictive than neccessary. Wrapper written by Filip Malinowski -filip.malinowski@tno.nl. \ No newline at end of file +filip.malinowski@tno.nl. + +############### Usage example: ############### + +# load driver +from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.qcodes_driver import CryoSwitchControllerDriver + +# connect to the switch +switchcontroller = CryoSwitchControllerDriver('switchcontroller') +switchcontroller.start() + +# set a switch model +switchcontroller.switch_model('CRYO') + +# set pulse parameters +switchcontroller.output_voltage(15) +switchcontroller.OCP_value(100) +switchcontroller.pulse_duration(8) + +# get all switch states based on the "status.json" +# there is no direct way to see the state of the switch +switchcontroller.get_switches_state() + +# connect contact 3 of port A +switchcontroller.channels.A.connect(3) + +# smart switch to contact 5 (and automatically disconnect from 3) +# there is no software or hardware control to ensure that only contact is connected +# once the pulse parameters are reliable, I recommend only using the "smart_connect" function +switchcontroller.channels.A.smart_connect(5) + +# get an active contact +switchcontroller.channels.A.active_contact() + +# disconnect from contact 5 +data = switchcontroller.channels.A.disconnect(5) + +# plot a current transient for the "disconnect" action +plt.plot(data) + +# get an active contact (should return 0 when nothing is connected) +switchcontroller.channels.A.active_contact() \ No newline at end of file From df940c111d4e058231bfcaa75a6f48c8de34e6a9 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Fri, 22 Mar 2024 13:39:09 +0100 Subject: [PATCH 28/57] Enable disconnecting from the switch controller --- .../drivers/QphoX/CryoSwitchController/README.txt | 5 ++++- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt index 9bbb71442..2b88f665e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -57,4 +57,7 @@ data = switchcontroller.channels.A.disconnect(5) plt.plot(data) # get an active contact (should return 0 when nothing is connected) -switchcontroller.channels.A.active_contact() \ No newline at end of file +switchcontroller.channels.A.active_contact() + +# close the switchcontroller instrument +switchcontroller.close() \ No newline at end of file diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index ba1135d62..a08a4d556 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -266,3 +266,10 @@ def get_idn(self): in QCoDeS to work. """ pass + + def close(self): + """ + Disconnect from the switch controller. + """ + self._controller.labphox.disconnect() + super().close() From 6a85ac0c45861195e340354d45a3d0fcfcb994be Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Fri, 22 Mar 2024 13:59:29 +0100 Subject: [PATCH 29/57] Remaining docstrings --- .../CryoSwitchController/qcodes_driver.py | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index a08a4d556..a30b2a984 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -44,7 +44,7 @@ def connect(self, contact: int): contact (int): The contact to be connected. Returns: - trace: The current waveform after the connection. + np.ndarray: The current waveform after the connection. """ trace = self.parent.connect(self._channel, contact) self._active_contact = contact @@ -59,7 +59,7 @@ def disconnect(self, contact: int): contact (int): The contact to be disconnected. Returns: - trace: The current waveform after the disconnection. + np.ndarray: The current waveform after the disconnection. """ trace = self.parent.disconnect(self._channel, contact) self._active_contact = 0 @@ -71,7 +71,7 @@ def disconnect_all(self): Applies a disconnecting pulse to all contacts. Returns: - trace: The current waveform after all contacts are disconnected. + np.ndarray: The current waveform after all contacts are disconnected. """ trace = self.parent.disconnect_all(self._channel) self._active_contact = 0 @@ -87,7 +87,7 @@ def smart_connect(self, contact: int): contact (int): The contact to be connected. Returns: - trace: The current waveform after the smart connection. + np.ndarray: The current waveform after the smart connection. """ trace = self.parent.smart_connect(self._channel, contact) self._active_contact = contact @@ -212,12 +212,37 @@ def _get_switch_model(self): return self._switch_model def get_switches_state(self, port: str = None): + """ + Read and return the state of the contacts as recorded in the states.json + + Args: + port (str|None): A port which states are to be returned. None will return + the state of all contacts. (default None) + + Returns: + dict: dictionary listing a state of every contact. + """ return self._controller.get_switches_state(port) def disconnect_all(self, port: str): + """ + Applies a disconnecting current pulse to all contacts of the + specified port + + Args: + port (str): A letter A-D indicating the port to be controlled. + """ self._controller.disconnect_all(port) def smart_connect(self, port: str, contact: int): + """ + Disconnects from all the connected contacts for a specified channel + and connects only to the indicated one. + + Args: + port (str): A letter A-D indicating the port to be controlled. + contact (int): Number of the contact that is to be connected + """ return self._controller.smart_connect(port, contact) def _enable_disable_chopping(self, enable: bool): From b32ddd7030b484b5fe1c1777945fe9a6e669c5eb Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:44:03 +0100 Subject: [PATCH 30/57] Copy libphox --- .../QphoX/CryoSwitchController/__init__.py | 0 .../QphoX/CryoSwitchController/libphox.py | 724 ++++++++++++++++++ .../drivers/QphoX/__init__.py | 0 3 files changed, 724 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/__init__.py diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py new file mode 100644 index 000000000..c8c05cef9 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/libphox.py @@ -0,0 +1,724 @@ +import serial +import serial.tools.list_ports +import time +import json +import socket +import numpy as np +import subprocess +import os +import logging + +class Labphox: + _logger = logging.getLogger("libphox") + + def __init__(self, port=None, debug=False, IP=None, cmd_logging=False, SN=None, HW_val=False): + self.debug = debug + self.time_out = 5 + + + if self.debug or cmd_logging: + self.log = True + self.logging_dir = r'\logging' + self.logger_init(self._logger) + else: + self.log = False + + self.SW_version = 3 + self.board_SN = SN + self.board_FW = None + + self.adc_ref = 3.3 + self.N_channel = 0 + + self.COM_port = None + + self.ETH_HOST = None # The server's IP address + self.ETH_PORT = 7 # The port used by the server + self.ETH_buff_size = 1024 + + self.communication_handler_sleep_time = 0 + self.packet_handler_sleep_time = 0 + if IP: + self.USB_or_ETH = 2 # 1 for USB, 2 for ETH + self.ETH_HOST = IP # The server's IP address + self.ETH_PORT = 7 # The port used by the server + self.ETH_buff_size = 1024 + else: + self.USB_or_ETH = 1 # 1 for USB, 2 for ETH + self.COM_port = port + + self.connect(HW_val=HW_val) + + + def connect(self, HW_val=True): + if self.USB_or_ETH == 1: + if self.COM_port: + # TODO + pass + elif self.board_SN: + for device in serial.tools.list_ports.comports(): + if device.pid == 1812: + try: + self.serial_com = serial.Serial(device.device) + if self.board_SN == self.utility_cmd('sn'): + self.COM_port = device.device + self.PID = device.pid + self.serial_com.close() + break + self.serial_com.close() + except Exception as error: + pass + # print('Port' + str(device.device) + ' is already in use:', error) + + else: + for device in serial.tools.list_ports.comports(): + if device.pid == 1812: + self.PID = device.pid + self.COM_port = device.device + if self.debug: + for i in device: + print(i) + + try: + self.serial_com = serial.Serial(self.COM_port) + + self.board_info = '' + self.name = '' + self.board_SN = None + self.utility_cmd('info') + print('Connected to ' + self.name + ' on COM port ' + self.COM_port + ', PID:', + str(self.PID) + ', SerialN: ' + str(self.board_SN) + ', Channels:' + str(self.N_channel)) + print(self.HW, ', FW_Ver.', self.board_FW) + except: + print('ERROR: Couldn\'t connect via serial') + + elif self.USB_or_ETH == 2: + socket.setdefaulttimeout(self.time_out) + + self.board_info = '' + self.name = '' + self.board_SN = None + self.utility_cmd('info') + print('Connected to ' + self.name + ', IP:', + str(self.ETH_HOST) + ', SerialN: ' + str(self.board_SN) + ', Channels:' + str(self.N_channel)) + print(self.HW, ', FW_Ver.', self.board_FW) + if not self.board_SN: + raise Exception( + "Couldn\'t connect, please check that the device is properly connected or try providing a valid SN, COM port or IP number") + + elif self.board_FW != self.SW_version and HW_val: + print("Board Firmware version and Software version are not up to date, Board FW=" + str( + self.board_FW) + " while SW=" + str(self.SW_version)) + + def disconnect(self): + if self.USB_or_ETH == 1: + self.serial_com.close() + elif self.USB_or_ETH == 2: + pass + + def input_buffer(self): + return self.serial_com.inWaiting() + + def flush_input_buffer(self): + return self.serial_com.flushInput() + + def write(self, cmd): + if self.log: + pass + #self.logging('actions', cmd) + + if self.USB_or_ETH == 1: + self.serial_com.write(cmd) + else: + pass + + def read(self, size): + if self.USB_or_ETH == 1: + data_back = self.serial_com.read(size) + else: + data_back = '' + + return data_back + + def read_buffer(self): + return self.read(self.input_buffer()) + + def decode_buffer(self): + return list(self.read_buffer()) + + def debug_func(self, cmd, reply): + print('Command', cmd) + print('Reply', reply) + print('') + # self._logger.debug(f'Debug: {cmd}') + self._logger.info(f'Debug: {cmd}') + self._logger.debug(f'Command: {cmd}') + self._logger.debug(f'Reply: {reply}') + + def logger_init(self, logger_instance, outfolder=None): + logger_instance.setLevel(logging.DEBUG) + + if outfolder is None: + outfolder = os.path.realpath('.') + self.logging_dir + + os.makedirs(name=outfolder, exist_ok=True) + + date_fmt = "%d/%m/%Y %H:%M:%S" + + # remove all old handlers + for hdlr in logger_instance.handlers[:]: + logger_instance.removeHandler(hdlr) + + # INFO level logger + # file logger + fmt = "[%(asctime)s] [%(levelname)s] %(message)s" + log_format = logging.Formatter(fmt=fmt, datefmt=date_fmt) + + info_handler = logging.FileHandler(os.path.join(outfolder, 'info.log')) + info_handler.setFormatter(log_format) + info_handler.setLevel(logging.INFO) + logger_instance.addHandler(info_handler) + + # DEBUG level logger + fmt = "[%(asctime)s] [%(levelname)s] [%(funcName)s(): line %(lineno)s] %(message)s" + log_format = logging.Formatter(fmt=fmt, datefmt=date_fmt) + + debug_handler = logging.FileHandler(os.path.join(outfolder, 'debug.log')) + debug_handler.setFormatter(log_format) + debug_handler.setLevel(logging.DEBUG) + logger_instance.addHandler(debug_handler) + + # _logger = logging.getLogger("libphox") + + return logger_instance + + def read_line(self): + if self.USB_or_ETH == 1: + return self.serial_com.readline() + else: + return '' + + def query_line(self, cmd): + self.write(cmd) + if self.USB_or_ETH == 1: + return self.serial_com.readline() + else: + return '' + + def compare_cmd(self, cmd1, cmd2): + if cmd1.upper() == cmd2.upper(): + return True + else: + return False + + def encode(self, value): + return str(value).encode() + + def decode_simple_response(self, response): + return response.decode('UTF-8').strip() + + def parse_response(self): + ##time.sleep(1) + + reply = '' + + initial_time = time.time() + end = False + while not end: + time.sleep(self.communication_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer().decode() + if ';' in reply: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.split(';')[0] + response = {'reply': reply, 'command': reply.split(':')[:-2], 'value': reply.split(':')[-1]} + + if self.log: + self.logging('received', reply) + + return response + + def TCP_communication_handler(self, encoded_cmd=None): + reply = '' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as TCP_connection: + TCP_connection.connect((self.ETH_HOST, self.ETH_PORT)) + TCP_connection.sendall(encoded_cmd) + packet = TCP_connection.recv(self.ETH_buff_size) + + try: + reply += packet.decode() + except: + print('Invalid packet character', packet) + + reply = reply.split(';')[0] + return reply + + def UDP_communication_handler(self, encoded_cmd=None): + reply = '' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as UDP_connection: + UDP_connection.sendto(encoded_cmd, (self.ETH_HOST, self.ETH_PORT)) + end = False + while not end: + # time.sleep(self.communication_handler_sleep_time) + packet = UDP_connection.recvfrom(self.ETH_buff_size)[0] + if b';' in packet: + reply += packet.split(b';')[0].decode() + end = True + else: + try: + reply += packet.decode() + except: + print('Invalid packet character', packet) + break + + return reply + + def USB_communication_handler(self, encoded_cmd=None): + reply = '' + self.flush_input_buffer() + self.write(encoded_cmd) + + initial_time = time.time() + end = False + while not end: + time.sleep(self.communication_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer().decode() + if ';' in reply: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.split(';')[0] + return reply + + def standard_reply_parser(self, cmd, reply): + response = {'reply': reply, 'command': reply.split(':')[:-1], 'value': reply.split(':')[-1]} + if not self.validate_reply(cmd, response): + self.raise_value_mismatch(cmd, response) + + return response + + def communication_handler(self, cmd, standard=True, is_encoded=False): + response = '' + if is_encoded: + encoded_cmd = cmd + else: + encoded_cmd = cmd.encode() + + if self.USB_or_ETH == 1: + reply = self.USB_communication_handler(encoded_cmd) + elif self.USB_or_ETH == 2: + reply = self.UDP_communication_handler(encoded_cmd) + elif self.USB_or_ETH == 3: + reply = self.TCP_communication_handler(encoded_cmd) + else: + raise Exception("Invalid communication options USB_or_ETH=", self.USB_or_ETH) + + try: + if standard: + if is_encoded: + response = self.standard_reply_parser(cmd=cmd.decode(), reply=reply) + else: + response = self.standard_reply_parser(cmd=cmd, reply=reply) + else: + response = reply + except: + print('Reply Error', reply) + + if self.debug: + self.debug_func(cmd, response) + + return response + + def validate_reply(self, cmd, response): + stripped = cmd.strip(';').split(':') + command = stripped[:-1] + value = stripped[-1] + + match = True + if command != response['command']: + match = False + + return match + + def USB_packet_handler(self, encoded_cmd, end_sequence): + reply = b'' + self.flush_input_buffer() + self.write(encoded_cmd) + + initial_time = time.time() + end = False + while not end: + time.sleep(self.packet_handler_sleep_time) + if self.input_buffer(): + reply += self.read_buffer() + if end_sequence in reply[-5:]: + end = True + + elif (time.time() - initial_time) > self.time_out: + raise Exception("LABPHOX time out exceeded", self.time_out, 's') + + reply = reply.replace(end_sequence, b'').replace(encoded_cmd, b'') + return reply + + def UDP_packet_handler(self, encoded_cmd, end_sequence): + reply = b'' + with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as s: + s.sendto(encoded_cmd, (self.ETH_HOST, self.ETH_PORT)) + end = False + while not end: + time.sleep(self.packet_handler_sleep_time) + packet = s.recvfrom(self.ETH_buff_size)[0] + reply += packet + if end_sequence in reply[-5:]: + end = True + + reply = reply.replace(end_sequence, b'').replace(encoded_cmd, b'') + return reply[7:] + + def packet_handler(self, cmd, end_sequence=b'\x00\xff\x00\xff'): + encoded_cmd = cmd.encode() + + if self.USB_or_ETH == 1: + reply = self.USB_packet_handler(encoded_cmd, end_sequence) + return reply + + elif self.USB_or_ETH == 2: + reply = self.UDP_packet_handler(encoded_cmd, end_sequence) + return reply + + def raise_value_mismatch(self, cmd, response): + print('Command mismatch!') + print('Command:', cmd) + print('Reply:', response['command']) + + def utility_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'info'): + self.name = self.utility_cmd('name').upper() + if 'LabP'.upper() in self.name: + self.HW = self.utility_cmd('hw') + self.board_SN = self.utility_cmd('sn') + self.board_FW = int(self.utility_cmd('fw').split('.')[-1]) + self.N_channel = int(self.utility_cmd('channels').split()[1]) + + elif self.compare_cmd(cmd, 'name'): + response = self.communication_handler('W:2:A:;', standard=False) + + elif self.compare_cmd(cmd, 'fw'): + response = self.communication_handler('W:2:B:;', standard=False) + + elif self.compare_cmd(cmd, 'hw'): + response = self.communication_handler('W:2:D:;', standard=False) + + elif self.compare_cmd(cmd, 'sn'): + response = self.communication_handler('W:2:E:;', standard=False) + + elif self.compare_cmd(cmd, 'channels'): + response = self.communication_handler('W:2:F:;', standard=False) + + elif self.compare_cmd(cmd, 'connected'): + response = self.communication_handler('W:2:C:;') + return response['value'] + + elif self.compare_cmd(cmd, 'UID'): + response = self.communication_handler('W:2:G:' + str(value) + ';') + return response['value'] + + elif self.compare_cmd(cmd, 'sleep'): + response = self.communication_handler('W:2:S:' + str(value) + ';') + + return response + + def DAC_cmd(self, cmd, DAC=1, value=0): + response = None + if DAC == 1: + sel_DAC = 5 + elif DAC == 2: + sel_DAC = 8 + else: + return None + + if self.compare_cmd(cmd, 'on'): + response = self.communication_handler('W:' + str(sel_DAC) + ':T:1;') + + elif self.compare_cmd(cmd, 'off'): + response = self.communication_handler('W:' + str(sel_DAC) + ':T:0;') + + elif self.compare_cmd(cmd, 'set'): + response = self.communication_handler('W:' + str(sel_DAC) + ':S:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'buffer'): + response = self.communication_handler('W:' + str(sel_DAC) + ':B:' + str(value) + ';') + + return response + + def application_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'pulse'): + ##self.serial_com.flushInput() + ##response = self.communication_handler('W:3:T:' + str(value) + ';', standard=False) + response = self.packet_handler('W:3:T:' + str(value) + ';') + return np.fromstring(response, dtype=np.uint8) + + elif self.compare_cmd(cmd, 'acquire'): + response = self.communication_handler('W:3:Q:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'voltage'): + response = self.communication_handler('W:3:V:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'test_circuit'): + response = self.communication_handler('W:3:P:' + str(value) + ';') + + return response + + def timer_cmd(self, cmd, value=0): + response = False + if self.compare_cmd(cmd, 'duration'): + response = self.communication_handler('W:0:A:' + str(value) + ';') + if int(response['value']) != int(value): + self.raise_value_mismatch(cmd, response) + + if self.compare_cmd(cmd, 'sampling'): + response = self.communication_handler('W:0:S:' + str(value) + ';') + + return response + + def ADC_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'channel'): + response = self.communication_handler('W:4:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'start'): + response = self.communication_handler('W:4:T:1;') + + elif self.compare_cmd(cmd, 'stop'): + response = self.communication_handler('W:4:T:0;') + + elif self.compare_cmd(cmd, 'select'): ##Select and sample + response = self.communication_handler('W:4:S:' + str(value) + ';') + + + elif self.compare_cmd(cmd, 'get'): + response = self.communication_handler('W:4:G:;') + return int(response['value']) + + elif self.compare_cmd(cmd, 'interrupt'): ##Enable interrupt mode + response = self.communication_handler('W:4:I:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'buffer'): ##Enable interrupt mode + response = self.communication_handler('W:4:B:' + str(value) + ';') + return int(response['value']) + + return response + + + def ADC3_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'channel'): + response = self.communication_handler('W:W:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'start'): + response = self.communication_handler('W:W:T:1;') + + elif self.compare_cmd(cmd, 'stop'): + response = self.communication_handler('W:W:T:0;') + + elif self.compare_cmd(cmd, 'select'): ##Select and sample + response = self.communication_handler('W:W:S:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'get'): + response = self.communication_handler('W:W:G:;') + return int(response['value']) + + return response + + def gpio_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'EN_3V3'): + response = self.communication_handler('W:1:A:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'EN_5V'): + response = self.communication_handler('W:1:B:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'EN_CHGP'): + response = self.communication_handler('W:1:C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'FORCE_PWR_EN'): + response = self.communication_handler('W:1:D:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'PWR_EN'): + response = self.communication_handler('W:1:E:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'DCDC_EN'): + response = self.communication_handler('W:1:F:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'CHOPPING_EN'): + response = self.communication_handler('W:1:G:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'PWR_STATUS'): + response = self.communication_handler('W:1:H:0;') + return int(response['value']) + + elif self.compare_cmd(cmd, 'OCP_OUT_STATUS'): + response = self.communication_handler('W:1:I:0;') + return int(response['value']) + + return response + + def IO_expander_cmd(self, cmd, port='A', value=0): + response = None + if self.compare_cmd(cmd, 'connect'): + response = self.communication_handler('W:' + str(port) + ':C:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'disconnect'): + response = self.communication_handler('W:' + str(port) + ':D:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'on'): + response = self.communication_handler('W:6:O:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'off'): + response = self.communication_handler('W:6:U:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'type'): + response = self.communication_handler('W:6:S:' + str(value) + ';') + + return response + + def reset_cmd(self, cmd): + response = None + if self.compare_cmd(cmd, 'reset'): + response = self.communication_handler('W:7:R:;') + + elif self.compare_cmd(cmd, 'boot'): + response = self.communication_handler('W:7:B:;') + + elif self.compare_cmd(cmd, 'soft_reset'): + response = self.communication_handler('W:7:S:;') + + return response + + def logging(self, list_name, cmd): + with open('history.json', "r") as history_file: + data = json.load(history_file) + + if type(cmd) == str: + data_to_append = cmd + elif type(cmd) == bytes: + data_to_append = cmd.decode() + + if list_name in data.keys(): + data[list_name].append({'data': data_to_append, 'date': time.time()}) + else: + data[list_name] = [{'data': data_to_append, 'date': time.time()}] + + with open('history.json', "w") as file: + json.dump(data, file) + + def ETHERNET_cmd(self, cmd, value=0): + response = None + if self.compare_cmd(cmd, 'read'): + response = self.communication_handler('W:Q:R:' + str(value) + ';') + elif self.compare_cmd(cmd, 'set_ip'): + response = self.communication_handler('W:Q:I:' + str(value) + ';') + elif self.compare_cmd(cmd, 'get_ip'): + response = self.communication_handler('W:Q:G:' + str(value) + ';') + + elif self.compare_cmd(cmd, 'set_ip_str'): + int_IP = int.from_bytes(socket.inet_aton(value), "little") + response = self.communication_handler('W:Q:I:' + str(int_IP) + ';') + elif self.compare_cmd(cmd, 'get_ip_str'): + response = self.communication_handler('W:Q:G:' + str(value) + ';') + IP = socket.inet_ntoa(int(response['value']).to_bytes(4, 'little')) + print('IP:', IP) + return IP + + elif self.compare_cmd(cmd, 'set_mask_str'): + int_mask = int.from_bytes(socket.inet_aton(value), "little") + response = self.communication_handler('W:Q:K:' + str(int_mask) + ';') + + elif self.compare_cmd(cmd, 'get_mask_str'): + response = self.communication_handler('W:Q:L:' + str(value) + ';') + print(response) + mask = socket.inet_ntoa(int(response['value']).to_bytes(4, 'little')) + print('Subnet mask:', mask) + return mask + + elif self.compare_cmd(cmd, 'get_detection'): + response = self.communication_handler('W:Q:D:;') + + return response + + def UPGRADE_cmd(self, cmd, value): + response = None + + if self.compare_cmd(cmd, 'upgrade'): + response = self.communication_handler('U:A:0' + ':' + str(value) + ';') + if int(response['value']) == value: + print('Update successful,', value, 'channels enabled') + else: + print('Couldn\'t update channel number') + + elif self.compare_cmd(cmd, 'stream_key'): + for idx, element in enumerate(value): + response = self.communication_handler('U:B:' + str(chr(65 + idx)) + ':' + str(element) + ';') + if int(response['value']) != element: + print('Error while sending key!') + + return response + + def FLASH_utils(self, path=None): + DFU_name = '0483:df11' + found = False + process = subprocess.Popen(['.\Firmware\dfu-util', '-l'], shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + + start_time = time.time() + exc_time = 0 + output = '' + while exc_time < self.time_out: + output = process.stdout.readline() + if 'Internal Flash' in output and 'Found DFU: [' + DFU_name + ']' in output: + found = True + break + exc_time = time.time() - start_time + + if not found: + print('Couldn\'t find the the device') + else: + print('Device found in', output.strip().strip('Found DFU: ')) + + if not path: + path = '.' + process = subprocess.Popen('.\Firmware\dfu-util -d ' + DFU_name + ' -a 0 -s 0x08000000:leave -D ' + path + '\Firmware\Labphox.bin', shell=True, + stdout=subprocess.PIPE, + universal_newlines=True) + + start_time = time.time() + exc_time = 0 + poll = process.poll() + while poll is None: + output = process.stdout.readline() + if 'Download' in output or 'device'.upper() in output.upper() or 'dfu'.upper() in output.upper() and 'bugs' not in output: + print(output.strip()) + exc_time = time.time() - start_time + + if exc_time > self.time_out: + break + else: + poll = process.poll() + + print('Flashing time', round(exc_time, 2), 's') + print('Flash ended! Please disconnect the device.') + + +if __name__ == "__main__": + labphox = Labphox(debug=True, IP='192.168.1.101') + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/__init__.py b/src/qcodes_contrib_drivers/drivers/QphoX/__init__.py new file mode 100644 index 000000000..e69de29bb From 28e8fd472ef33f16f2947c2fa35028ab79004ca1 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:44:59 +0100 Subject: [PATCH 31/57] Copy other QphoxCode --- .../CryoSwitchController.py | 795 ++++++++++++++++++ .../QphoX/CryoSwitchController/constants.json | 130 +++ .../QphoX/CryoSwitchController/states.json | 376 +++++++++ 3 files changed, 1301 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py new file mode 100644 index 000000000..d7a3b3620 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py @@ -0,0 +1,795 @@ +import time +import matplotlib.pyplot as plt +from .libphox import Labphox +import numpy as np +import json +import os + +class Cryoswitch: + + def __init__(self, debug=False, COM_port='', IP=None, SN=None, override_abspath=False): + self.debug = debug + self.port = COM_port + self.IP = IP + self.verbose = True + + self.labphox = Labphox(self.port, debug=self.debug, IP=self.IP, SN=SN) + self.ports_enabled = self.labphox.N_channel + self.SN = self.labphox.board_SN + self.HW_rev = self.get_HW_revision() + self.HW_rev_N = int(self.get_HW_revision()[-1]) + + self.wait_time = 0.5 + self.pulse_duration_ms = 15 + self.converter_voltage = 5 + self.MEASURED_converter_voltage = 0 + self.current_switch_model = '' + self.tolerance = 0.15 + + if override_abspath: + self.abs_path = override_abspath + '\\' + else: + self.abs_path = os.path.dirname(__file__) + '\\' + + self.decimals = 2 + self.plot = False + self.log_wav = True + self.log_wav_dir = self.abs_path + r'data' + self.align_edges = True + self.plot_polarization = True + + self.pulse_logging = True + self.pulse_logging_filename = self.abs_path + r'pulse_logging.txt' + self.log_pulses_to_display = 5 + self.warning_threshold_current = 60 + + self.track_states = True + self.track_states_file = self.abs_path + r'states.json' + + self.constants_file_name = self.abs_path + r'constants.json' + self.__constants() + + if self.track_states: + self.tracking_init() + + if self.pulse_logging: + self.pulse_logging_init() + + if self.log_wav: + self.log_wav_init() + + def tracking_init(self): + file = open(self.track_states_file) + states = json.load(file) + file.close() + if self.SN not in states.keys(): + states[self.SN] = states['SN'] + with open(self.track_states_file, 'w') as outfile: + json.dump(states, outfile, indent=4, sort_keys=True) + + def pulse_logging_init(self): + if not os.path.isfile(self.pulse_logging_filename): + file = open(self.pulse_logging_filename, 'w') + file.close() + + def log_wav_init(self): + if not os.path.isdir(self.log_wav_dir): + os.mkdir(self.log_wav_dir) + + def __constants(self): + file = open(self.constants_file_name) + constants = json.load(file) + file.close() + + if self.HW_rev in constants.keys(): + constants = constants[self.HW_rev] + self.ADC_12B_res = constants['ADC_12B_res'] + self.ADC_8B_res = constants['ADC_8B_res'] + self.ADC_cal_ref = constants['ADC_cal_ref'] + + self.bv_R1 = constants['bv_R1'] + self.bv_R2 = constants['bv_R2'] + self.bv_ADC = constants['bv_ADC'] + + self.converter_divider = constants['converter_divider'] + self.converter_ADC = constants['converter_ADC'] + + self.converter_VREF = constants['converter_VREF'] + self.converter_R1 = constants['converter_R1'] + self.converter_R2 = constants['converter_R2'] + self.converter_Rf = constants['converter_Rf'] + self.converter_DAC_lower_bound = constants['converter_DAC_lower_bound'] + self.converter_DAC_upper_bound = constants['converter_DAC_upper_bound'] + self.converter_correction_codes = constants['converter_correction_codes'] + self.converter_output_voltage_range = constants['converter_output_voltage_range'] + + + self.OCP_gain = constants['OCP_gain'] + self.OCP_range = constants['OCP_range'] + + self.pulse_duration_range = constants['pulse_duration_range'] + self.sampling_frequency_range = constants['sampling_frequency_range'] + + self.current_sense_R = constants['current_sense_R'] + self.current_gain = constants['current_gain'] + self.polarization_params = constants['polarization_params'] + + self.sampling_freq = 28000 + + if constants['calibrate_ADC']: + self.labphox.ADC3_cmd('start') + time.sleep(0.1) + ref_values = [] + for it in range(5): + ref_values.append(self.get_V_ref()) + measured_ref = sum(ref_values) / len(ref_values) + + if 3.1 < measured_ref < 3.5: + self.measured_adc_ref = measured_ref + else: + print(f'Measured ADC ref {measured_ref}V outside of range') + self.measured_adc_ref = self.labphox.adc_ref + else: + self.measured_adc_ref = self.labphox.adc_ref + else: + print(f'Failed to load constants, HW revision {self.HW_rev} not int {constants.keys()}') + + def set_FW_upgrade_mode(self): + self.labphox.reset_cmd('boot') + + def get_UIDs(self): + UID0 = int(self.labphox.utility_cmd('UID', 0)) + UID1 = int(self.labphox.utility_cmd('UID', 1)) + UID2 = int(self.labphox.utility_cmd('UID', 2)) + + return [UID0, UID1, UID2] + + def flash(self, path=None): + reply = input('Are you sure you want to flash the device?') + if 'Y' in reply.upper(): + self.set_FW_upgrade_mode() + time.sleep(5) + self.labphox.FLASH_utils(path) + else: + print('Aborting flash sequence...') + + def reset(self): + self.labphox.reset_cmd('reset') + time.sleep(3) + + def reconnect(self): + self.labphox.connect() + + def enable_5V(self): + self.labphox.gpio_cmd('EN_5V', 1) + + def disable_5V(self): + self.labphox.gpio_cmd('EN_5V', 0) + + def enable_3V3(self): + self.labphox.gpio_cmd('EN_3V3', 1) + + def disable_3V3(self): + self.labphox.gpio_cmd('EN_3V3', 0) + + def standby(self): + self.set_output_voltage(5) + self.disable_converter() + self.disable_negative_supply() + self.disable_3V3() + self.disable_5V() + + def calculate_error(self, measured, set): + error = abs((measured - set) / set) + return error + + def measure_ADC(self, channel): + self.labphox.ADC_cmd('select', channel) + time.sleep(self.wait_time) + return self.labphox.ADC_cmd('get') + + def get_converter_voltage(self): + converter_gain = self.measured_adc_ref * self.converter_divider / self.ADC_12B_res + code = self.measure_ADC(self.converter_ADC) + converter_voltage = round(code * converter_gain, self.decimals) + self.MEASURED_converter_voltage = converter_voltage + return converter_voltage + + def get_bias_voltage(self): + bias_gain = self.measured_adc_ref * ((self.bv_R2 + self.bv_R1) / self.bv_R1) / self.ADC_12B_res + bias_offset = self.measured_adc_ref*self.bv_R2/self.bv_R1 + code = self.measure_ADC(self.bv_ADC) + bias_voltage = code * bias_gain-bias_offset + + return round(bias_voltage, self.decimals) + + def check_voltage(self, measured_voltage, target_voltage, tolerance=0.1, pre_str=''): + error = self.calculate_error(measured_voltage, target_voltage) + if error > tolerance: + print(f'{pre_str} Failed to set voltage: {target_voltage} , measured voltage: {round(measured_voltage, self.decimals)}V') + # print(pre_str, 'failed to set voltage , measured voltage', round(measured_voltage, self.decimals)) + return False + else: + # print(pre_str, 'voltage set to', round(measured_voltage, self.decimals), 'V') + print(f'{pre_str} Voltage set to {round(measured_voltage, self.decimals)}V') + return True + + def get_HW_revision(self): + return self.labphox.HW + + def get_internal_temperature(self): + code = self.measure_ADC(16) + VSENSE = self.measured_adc_ref * code / self.ADC_12B_res + V25 = 0.76 + Avg_Slope = 0.0025 + temp = ((VSENSE - V25) / Avg_Slope) + 25 + return temp + + def get_V_ref(self): + if self.ADC_cal_ref: + self.labphox.ADC3_cmd('select', 8) + time.sleep(self.wait_time) + code = self.labphox.ADC3_cmd('get') + Ref_2V5_code = code + ADC_ref = 2.5 * self.ADC_12B_res / Ref_2V5_code + return round(ADC_ref, 4) + else: + print('Calibration reference is not available in this HW rev') + return None + + def enable_negative_supply(self): + self.labphox.gpio_cmd('EN_CHGP', 1) + time.sleep(1) + bias_voltage = self.get_bias_voltage() + if self.verbose: + self.check_voltage(bias_voltage, -5, tolerance=self.tolerance, pre_str='BIAS STATUS:') + return bias_voltage + + def disable_negative_supply(self): + self.labphox.gpio_cmd('EN_CHGP', 0) + return self.get_bias_voltage() + + def calculate_output_code(self, Vout): + code = ((self.converter_VREF - ( + Vout - self.converter_VREF * (1 + (self.converter_R1 / self.converter_R2))) * ( + self.converter_Rf / self.converter_R1)) * (self.ADC_12B_res / self.measured_adc_ref)) + + code = int((code / self.converter_correction_codes[0]) - self.converter_correction_codes[1]) + if code < self.converter_DAC_lower_bound or code > self.converter_DAC_upper_bound: + print('Wrong DAC value, dont mess with the DAC. DAC angry.') + return False + + return code + + def set_output_voltage(self, Vout): + if self.converter_output_voltage_range[0] <= Vout <= self.converter_output_voltage_range[1]: + if Vout > 10: + self.disable_negative_supply() + else: + self.enable_negative_supply() + self.labphox.DAC_cmd('on', DAC=1) + code = self.calculate_output_code(Vout) + if code: + self.labphox.DAC_cmd('set', DAC=1, value=code) + # if Vout < self.converter_voltage: + # self.discharge() + time.sleep(2) + self.converter_voltage = Vout + measured_voltage = self.get_converter_voltage() + + if self.verbose: + self.check_voltage(measured_voltage, Vout, tolerance=self.tolerance, pre_str='CONVERTER STATUS:') + + return measured_voltage + else: + print(f'Failed to calculate output code') + return False + else: + print('Voltage outside of range (5-30V)') + + return False + + def enable_output_channels(self): + enabled = False + counter = 0 + response = {} + while not enabled: + response = self.labphox.IO_expander_cmd('on') + if int(response['value']) == 0: + enabled = True + elif counter > 3: + break + counter += 1 + + if not int(response['value']) == 0: + print('Failed to enable output channels!', str(response['value'])) + elif self.verbose and counter > 1: + print(counter, 'attempts to enable output channel') + + return int(response['value']) + + def disable_output_channels(self): + self.labphox.IO_expander_cmd('off') + + def enable_converter(self, init_voltage=None): + code = self.calculate_output_code(5) + self.labphox.DAC_cmd('set', DAC=1, value=code) + self.labphox.DAC_cmd('on', DAC=1) + self.labphox.gpio_cmd('PWR_EN', 1) + self.labphox.gpio_cmd('DCDC_EN', 1) + + if init_voltage is None: + init_voltage = self.converter_voltage + + self.set_output_voltage(init_voltage) + + def disable_converter(self): + code = self.calculate_output_code(5) + self.labphox.DAC_cmd('set', DAC=1, value=code) + self.labphox.gpio_cmd('DCDC_EN', 0) + self.labphox.gpio_cmd('PWR_EN', 0) + + def enable_OCP(self): + code = self.calculate_OCP_code(50) + self.labphox.DAC_cmd('set', DAC=2, value=code) + self.labphox.DAC_cmd('on', DAC=2) + self.set_OCP_mA(100) + + def reset_OCP(self): + self.labphox.gpio_cmd('CHOPPING_EN', 1) + time.sleep(0.2) + self.labphox.gpio_cmd('CHOPPING_EN', 0) + + def calculate_OCP_code(self, OCP_value): + code = int(OCP_value*(self.current_sense_R*self.current_gain*self.ADC_12B_res/(self.OCP_gain*1000*self.measured_adc_ref))) + if 0 < code < 4095: + return code + else: + return None + + def set_OCP_mA(self, OCP_value): + if self.OCP_range[0] <= OCP_value <= self.OCP_range[1]: + DAC_reg = self.calculate_OCP_code(OCP_value) + if DAC_reg: + self.labphox.DAC_cmd('set', DAC=2, value=DAC_reg) + return OCP_value + print(f'Over current protection outside of range {self.OCP_range[0]}-{self.OCP_range[1]}mA') + return None + + def get_OCP_status(self): + return self.labphox.gpio_cmd('OCP_OUT_STATUS') + + def enable_chopping(self): + self.labphox.gpio_cmd('CHOPPING_EN', 1) + + def disable_chopping(self): + self.labphox.gpio_cmd('CHOPPING_EN', 0) + + def reset_output_supervisor(self): + self.disable_converter() + self.labphox.gpio_cmd('FORCE_PWR_EN', 1) + time.sleep(0.5) + self.labphox.gpio_cmd('FORCE_PWR_EN', 0) + self.enable_converter() + + def get_output_state(self): + return self.labphox.gpio_cmd('PWR_STATUS') + + def set_pulse_duration_ms(self, ms_duration): + if self.pulse_duration_range[0] <= ms_duration <= self.pulse_duration_range[1]: + self.pulse_duration_ms = ms_duration + pulse_offset = 100 + self.labphox.timer_cmd('duration', round(ms_duration * 100 + pulse_offset)) + if self.verbose: + print(f'Pulse duration set to {ms_duration} ms') + else: + print(f'Pulse duration outside of range ({self.pulse_duration_range[0]}-{self.pulse_duration_range[1]}ms)') + + def set_sampling_frequency_khz(self, f_khz): + if self.sampling_frequency_range[0] <= f_khz <= self.sampling_frequency_range[1]: + self.labphox.timer_cmd('sampling', int(84000/f_khz)) + self.sampling_freq = f_khz * 1000 + else: + print(f'Sampling frequency outside of range ({self.sampling_frequency_range[0]}-{self.sampling_frequency_range[1]}khz)') + + def calculate_polarization_current_mA(self, voltage=None, resistance=None): + if not voltage: + voltage = self.MEASURED_converter_voltage + + if self.converter_voltage <= 10: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 0.2 + 5) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + elif self.converter_voltage < 15: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 0.2) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + else: + th_current = (voltage - 2.2) / self.polarization_params[0] + (voltage - 10) / self.polarization_params[1] + (voltage - 3) / self.polarization_params[2] + + if resistance: + th_current += voltage / resistance + + return round(th_current * 1000, 1) + + def send_pulse(self): + if not self.get_power_status(): + print('WARNING: Timing protection triggered, resetting...') + self.reset_output_supervisor() + + current_gain = 1000 * self.measured_adc_ref / (self.current_sense_R * self.current_gain * self.ADC_8B_res) + + current_data = self.labphox.application_cmd('pulse', 1) + + return current_data*current_gain + + def select_switch_model(self, model='R583423141'): + if model.upper() == 'R583423141'.upper(): + self.current_switch_model = 'R583423141' + self.labphox.IO_expander_cmd('type', value=1) + return True + + elif model.upper() == 'R573423600'.upper(): + self.current_switch_model = 'R573423600' + self.labphox.IO_expander_cmd('type', value=2) + return True + else: + return False + + def validate_selected_channel(self, number, polarity, reply): + if polarity and self.current_switch_model == 'R583423141': + shift_byte = 0b0110 + offset = 0 + elif not polarity and self.current_switch_model == 'R583423141': + shift_byte = 0b1001 + offset = 0 + elif polarity and self.current_switch_model == 'R573423600': + shift_byte = 0b10 + offset = 4096 + elif not polarity and self.current_switch_model == 'R573423600': + shift_byte = 0b01 + offset = 8192 + else: + shift_byte = 0 + offset = 0 + + validation_id = (shift_byte << 2 * number) + offset + validation_id1 = validation_id & 255 + validation_id2 = validation_id >> 8 + + if int(reply['value']) != validation_id1|validation_id2: + print('Wrong channel validation ID') + print('Validation ID, Received', reply['value'], '->Expected', validation_id1 | validation_id2) + return False + else: + return True + + def select_output_channel(self, port, number, polarity): + if 0 < number < 7: + number = number - 1 + if polarity: + reply = self.labphox.IO_expander_cmd('connect', port, number) + else: + reply = self.labphox.IO_expander_cmd('disconnect', port, number) + + return self.validate_selected_channel(number, polarity, reply) + else: + print('Contact out of range') + return None + + def plotting_function(self, current_profile, port, contact, polarity): + if polarity: + polarity_str = 'Connect' + else: + polarity_str = 'Disconnect' + + if self.align_edges: + edge = np.argmax(current_profile > 0) + current_data = current_profile[edge:] + else: + current_data = current_profile + + data_points = len(current_data) + sampling_period = 1 / self.sampling_freq + x_axis = np.linspace(0, data_points * sampling_period, data_points) * 1000 + plt.plot(x_axis, current_data) + if self.plot_polarization: + polarization_current = self.calculate_polarization_current_mA() + plt.hlines(polarization_current, x_axis[0], x_axis[-1], colors='red', + linestyles='dashed') + # plt.text(x_axis[-1], polarization_current, 'Pol current') + + plt.xlabel('Time [ms]') + plt.ylabel('Current [mA]') + plt.title(time.strftime("%b-%m %H:%M:%S%p", time.gmtime())) + plt.suptitle('Port ' + port + '-' + str(contact) + ' ' + polarity_str) + + plt.xlim(x_axis[0], x_axis[-1]) + if self.current_switch_model == 'R583423141': + plt.ylim(0, 100) + elif self.current_switch_model == 'R573423600': + plt.ylim(0, 200) + plt.grid() + plt.show() + + def select_and_pulse(self, port, contact, polarity): + if polarity: + polarity = 1 + else: + polarity = 0 + selection_result = self.select_output_channel(port, contact, polarity) + if selection_result: + current_profile = self.send_pulse() + self.disable_output_channels() + if self.plot: + self.plotting_function(current_profile=current_profile, port=port, contact=contact, polarity=polarity) + if self.track_states: + self.save_switch_state(port, contact, polarity) + if self.pulse_logging: + self.log_pulse(port, contact, polarity, current_profile.max()) + if self.log_wav: + self.log_waveform(port, contact, polarity, current_profile) + return current_profile + else: + return [] + + def save_switch_state(self, port, contact, polarity): + file = open(self.track_states_file) + states = json.load(file) + file.close() + + SN = self.SN + port = 'port_' + str(port) + contact = 'contact_' + str(contact) + if SN in states.keys(): + states[SN][port][contact] = polarity + + with open(self.track_states_file, 'w') as outfile: + json.dump(states, outfile, indent=4, sort_keys=True) + + def get_switches_state(self, port=None): + file = open(self.track_states_file) + states = json.load(file) + file.close() + ports = [] + if self.ports_enabled == 1: + ports = ['A'] + elif self.ports_enabled == 2: + ports = ['A', 'B'] + elif self.ports_enabled == 3: + ports = ['A', 'B', 'C'] + elif self.ports_enabled == 4: + ports = ['A', 'B', 'C', 'D'] + + if self.SN in states.keys(): + if port in ports: + current_state = states[self.SN] + print('Port ' + port + ' state') + for switch in range(1, 7): + state = current_state['port_' + port]['contact_' + str(switch)] + if state: + if switch == 1: + print(str(switch) + ' ----' + chr(0x2510)) + else: + print(str(switch) + ' ----' + chr(0x2524)) + else: + print(str(switch) + ' - -' + chr(0x2502)) + print(' ' + chr(0x2514) + '- COM') + print('') + + return states[self.SN] + else: + return None + + def log_waveform(self, port, contact, polarity, current_profile): + name = self.log_wav_dir + '\\' + str(int(time.time())) + '_' + str( + self.MEASURED_converter_voltage) + 'V_' + str(port) + str(contact) + '_' + str(polarity) + '.json' + waveform = {'time':time.time(), 'voltage': self.MEASURED_converter_voltage, 'port': port, 'contact': contact, 'polarity':polarity, 'SF': self.sampling_freq,'data':list(current_profile)} + with open(name, 'w') as outfile: + json.dump(waveform, outfile, indent=4, sort_keys=True) + + def log_pulse(self, port, contact, polarity, max_current): + if polarity: + direction = 'Connect ' + else: + direction = 'Disconnect' + + pulse_string = direction + '-> Port:' + port + '-' + str(contact) + ', CurrentMax:' + str(round(max_current)) + ' Timestamp:' + str(int(time.time())) + + if max_current < self.warning_threshold_current: + warning_string = ' *Warnings: Low current detected!' + else: + warning_string = '' + + with open(self.pulse_logging_filename, 'a') as logging_file: + logging_file.write(pulse_string + warning_string + '\n') + + def get_pulse_history(self, port=None, pulse_number=None): + if not pulse_number: + pulse_number = self.log_pulses_to_display + + with open(self.pulse_logging_filename, 'r') as logging_file: + pulse_info = logging_file.readlines() + + list_for_display = [] + counter = 0 + for idx, pulse in enumerate(pulse_info): + pulse = pulse_info[-idx-1] + if port: + if "Port:" + port + "-" in pulse: + list_for_display.append(pulse) + counter += 1 + else: + list_for_display.append(pulse) + counter += 1 + + if counter >= pulse_number: + break + + for idx, pulse in enumerate(list_for_display): + raw_data = list_for_display[-idx - 1].split(',') + if '*' in raw_data[-1]: + extra_text = raw_data[1].split('*')[-1].strip() + pulse_time = time.localtime(int(raw_data[1].split('*')[0].split(':')[-1].strip())) + else: + extra_text = '' + pulse_time = time.localtime(int(raw_data[1].split(':')[-1].strip())) + + print(raw_data[0] + ', ' + time.strftime("%a %b-%m %H:%M:%S%p", pulse_time) + ' ' + extra_text) + + def validate_port_contact(self, port, contact): + if port == 'A' and self.ports_enabled >= 1: + send_pulse = True + elif port == 'B' and self.ports_enabled >= 2: + send_pulse = True + elif port == 'C' and self.ports_enabled >= 3: + send_pulse = True + elif port == 'D' and self.ports_enabled >= 4: + send_pulse = True + else: + print(f'Port {port} not enabled') + return False + + if 0 < contact < 7: + return send_pulse + else: + return False + + def connect(self, port, contact): + send_pulse = self.validate_port_contact(port, contact) + + if send_pulse: + if self.debug: + print(f'Connecting Port:{port}, Contact {contact}') + + current_profile = self.select_and_pulse(port, contact, 1) + return current_profile + else: + print(f'Port or contact out of range: Port {port}, Contact {contact}') + return None + + def disconnect(self, port, contact): + send_pulse = self.validate_port_contact(port, contact) + + if send_pulse: + if self.debug: + print(f'Connecting Port:{port}, Contact {contact}') + + current_profile = self.select_and_pulse(port, contact, 0) + return current_profile + else: + print(f'Port or contact out of range: Port {port}, Contact {contact}') + return None + + def disconnect_all(self, port): + for contact in range(1, 7): + self.disconnect(port, contact) + if self.plot: + plt.legend([1, 2, 3, 4, 5, 6]) + + def smart_connect(self, port, contact, force=False): + states = self.get_switches_state() + port_state = states['port_' + port] + contacts = [1, 2, 3, 4, 5, 6] + contacts.remove(contact) + for other_contact in contacts: + if port_state['contact_' + str(other_contact)] == 1: + print('Disconnecting', other_contact) + self.disconnect(port, other_contact) + + if port_state['contact_' + str(contact)] == 1: + print('Contact', contact, 'is already connected') + if force: + print('Connecting', contact) + return self.connect(port, contact) + else: + print('Connecting', contact) + return self.connect(port, contact) + + return None + + def discharge(self): + if self.HW_rev_N >= 4: + self.labphox.application_cmd('test_circuit', 1) + test_current = self.send_pulse() + self.labphox.application_cmd('test_circuit', 0) + return test_current + else: + return None + + def test_internals(self, voltage=10): + if self.HW_rev_N >= 4: + last_voltage = self.converter_voltage + self.set_output_voltage(voltage) + voltage = self.MEASURED_converter_voltage + expected_current = ((voltage - 2.2) / 10000 + (voltage - 3) / 4700 + voltage / 480) * 1000 + test_current = self.discharge() + if self.plot: + plt.plot(test_current) + plt.hlines(expected_current, 0, len(test_current), colors='red', linestyles='dashed') + plt.xlabel('Sample') + plt.ylabel('Current [mA]') + self.set_output_voltage(last_voltage) + return test_current + else: + print('Discharge is not possible in this HW revision') + return None + + def get_power_status(self): + return self.labphox.gpio_cmd('PWR_STATUS') + + def set_ip(self, add='192.168.1.101'): + self.labphox.ETHERNET_cmd('set_ip_str', add) + + def get_ip(self): + add = self.labphox.ETHERNET_cmd('get_ip_str') + print(f'IP: {add}') + return add + + def set_sub_net_mask(self, mask='255.255.255.0'): + self.labphox.ETHERNET_cmd('set_mask_str', mask) + + def get_sub_net_mask(self): + mask = self.labphox.ETHERNET_cmd('get_mask_str') + print(f'Subnet Mask: {mask}') + return mask + + def start(self): + if self.verbose: + print('Initialization...') + self.labphox.ADC_cmd('start') + + self.enable_3V3() + self.enable_5V() + self.enable_OCP() + self.set_OCP_mA(80) + self.enable_chopping() + + self.set_pulse_duration_ms(15) + + self.enable_converter() + # self.set_output_voltage(5) + + time.sleep(1) + self.enable_output_channels() + self.select_switch_model('R583423141') + + if not self.get_power_status(): + if self.verbose: + print('POWER STATUS: Output voltage not enabled') + else: + if self.verbose: + print('POWER STATUS: Ready') + + +if __name__ == "__main__": + switch = Cryoswitch(IP='192.168.1.101') ## -> CryoSwitch class declaration and USB connection + + switch.start() ## -> Initialization of the internal hardware + + switch.get_internal_temperature() + switch.get_pulse_history(pulse_number=5, port='A') ##-> Show the last 5 pulses send through on port A + switch.set_output_voltage(5) ## -> Set the output pulse voltage to 5V + + switch.connect(port='A', contact=1) ## Connect contact 1 of port A to the common terminal + switch.disconnect(port='A', contact=1) ## Disconnects contact 1 of port A from the common terminal + switch.smart_connect(port='A', contact=1) ## Connect contact 1 and disconnect wichever port was connected previously (based on the history) + + + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json new file mode 100644 index 000000000..ec34b2ffd --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json @@ -0,0 +1,130 @@ +{ "HW_Ver. 0": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": false, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 550, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 1, + "current_gain": 20, + + "calibrate_ADC": 0, + "polarization_params": [4700, 3000, 4700] + }, + "HW_Ver. 2": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": false, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 500, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 1, + "current_gain": 20, + + "calibrate_ADC": 0, + "polarization_params": [4700, 3000, 4700] + }, + "HW_Ver. 3": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [4700, 3000, 4700] + + }, + "HW_Ver. 4": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.24, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1.03491, -169], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [10000, 3000, 4700] + } +} diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json new file mode 100644 index 000000000..57bcfd65f --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json @@ -0,0 +1,376 @@ +{ + "SN": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN0": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN12": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN22": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN23": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN24": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 1, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 1, + "contact_2": 0, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN25": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN26": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN300": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN6": { + "port_A": { + "contact_1": 1, + "contact_2": 1, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN65535": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + } +} \ No newline at end of file From e2775c3e9a9071c71fe1ecbdea72e44ff1681ca0 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:45:38 +0100 Subject: [PATCH 32/57] Main wrapper --- .../CryoSwitchController/qcodes_driver.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py new file mode 100644 index 000000000..ae33ea84e --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -0,0 +1,113 @@ +from qcodes import Instrument, ChannelList, InstrumentChannel +from qcodes.utils.validators import Numbers,Bool,Enum +from qcodes_contrib_drivers.drivers.QPhox.CryoSwitchController.CryoSwitchController import Cryoswitch + +class CryoSwitchChannel(InstrumentChannel): + def __init__(self, parent: Instrument, name: str, channel: str): + super().__init__(parent, name) + + self.add_parameter( + 'active_contact', + get_cmd=self._get_active_contact, + vals=Numbers(0, 6) + ) + + self._channel = channel + self._active_contact = 0 + + def connect(self, contact: int): + trace = self.parent.connect(self._channel, contact) + self._active_contact = contact + return trace + + def disconnect(self, contact: int): + trace = self.parent.disconnect(self._channel, contact) + self._active_contact = 0 + return trace + + def disconnect_all(self): + trace = self.parent.disconnect_all(self._channel) + self._active_contact = 0 + return trace + + def smart_connect(self, contact: int): + trace = self.parent.smart_connect(self._channel, contact) + self._active_contact = contact + return trace + + def _get_active_contact(self): + return self._active_contact + +class CryoSwitchControllerDriver(Instrument): + def __init__(self, name: str, **kwargs): + super().__init__(name, **kwargs) + + self._controller = Cryoswitch() + + self.add_parameter( + 'output_voltage', + set_cmd=self._controller.set_output_voltage, + vals=Numbers(0, 10) + ) + + self.add_parameter( + 'pulse_duration', + set_cmd=self._controller.set_pulse_duration_ms, + vals=Numbers(0, 1000) + ) + + self.add_parameter( + 'OCP_value', + set_cmd=self._controller.set_OCP_mA, + vals=Numbers(0, 1000) + ) + + self.add_parameter( + 'chopping', + set_cmd=self._enable_disable_chopping, + vals=Bool() + ) + + self.add_parameter( + 'switch_model', + set_cmd=self._controller.select_switch_model, + vals=Enum('R583423141', 'R573423600') + ) + + self.add_parameter( + 'power_status', + get_cmd=self._controller.get_power_status, + vals=Enum(0, 1) + ) + + def get_switches_state(self, port: str = None): + return self._controller.get_switches_state(port) + + def disconnect_all(self, port: str): + self._controller.disconnect_all(port) + + def smart_connect(self, port: str, contact: int): + return self._controller.smart_connect(port, contact) + + self.add_function('start', call_cmd=self._controller.start) + self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) + self.add_function('reset_OCP', call_cmd=self._controller.reset_OCP) + + channels = ChannelList(self, "Channels", CryoSwitchChannel, snapshotable=False) + for ch in ['A', 'B', 'C', 'D']: + channel = CryoSwitchChannel(self, f"channel_{ch}", ch) + channels.append(channel) + channels.lock() + self.add_submodule("channels", channels) + + def _enable_disable_chopping(self, enable: bool): + if enable: + self._controller.enable_chopping() + else: + self._controller.disable_chopping() + + def connect(self, port: str, contact: int): + return self._controller.connect(port, contact) + + def disconnect(self, port: str, contact: int): + return self._controller.disconnect(port, contact) From 1ba0aaea4517da835d45dca4f9d9e7ff85b44f33 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:46:10 +0100 Subject: [PATCH 33/57] Readmes --- .../CryoSwitchController/QphoX_README.md | 77 +++++++++++++++++++ .../QphoX/CryoSwitchController/README.txt | 19 +++++ 2 files changed, 96 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md new file mode 100644 index 000000000..f7068728f --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_README.md @@ -0,0 +1,77 @@ +# CryoSwitchController + +## Project structure + +## Getting started + +This repository holds the QP-CryoSwitchController compatible software. + + +## Installation +- Clone the repo +- Run: ```pip install -r requirements.txt``` +- Browse the CryoSwitchController.py file for library implementation + + +## Library Usage +A basic implementation of the CryoSwitchController class can be done with the following functions: +- start() + + Input: None + Default: None + Enables the voltage rails, voltage converter and output channels + +- set_output_voltage(Vout) + + Input: Desired output voltage (Vout) + Default: 5V + Sets the converter voltage to Vout. The output stage later utilizes the converter voltage to generate the positive/negative pulses. + +- set_pulse_duration_ms(ms_duration) + + Input: Pulse width duration in milliseconds (ms_duration). + Default: 10ms. + Sets the output pulse (positive/negative) duration in milliseconds. + +- connect(port, contact) + + Input: Corresponding port and contact to be connected. Port={A, B, C, D}, contact={1,...,6} + Default: None. + Connects the specified contact of the specified port (switch). + +- disconnect(port, contact) + + Input: Corresponding port and contact to be disconnected. Port={A, B, C, D}, contact={1,...,6} + Default: None. + Disconnects the specified contact of the specified port (switch). + + + +## Advanced functions + +- enable_OCP() + + Input: None + Default: None. + Enables the overcurrent protection. + + +- set_OCP_mA(OCP_value) + + Input: Overcurrent protection trigger value (OCP_value). + Default: 100mA. + Sets the overcurrent protection to the specified value. + +- enable_chopping() + + Input: None. + Default: None. + Enables the chopping function. When an overcurrent condition occurs, the controller will 'chop' the excess current instead of disabling the output. Please refer to the installation guide for further information. + +- disable_chopping() + + Input: None. + Default: None. + Disables the chopping function. When an overcurrent condition occurs, the controller will disable the output voltage. Please refer to the installation guide for further information. + + diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt new file mode 100644 index 000000000..16ec753e9 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -0,0 +1,19 @@ +The QCoDeS driver is a wrapper around the QphoX SDK retreived on 2024-02-23 +from https://github.com/QphoX/CryoSwitchController + +The driver is functional (at least) after pulfiling the following requirements: + +cycler=0.11.0 +kiwisolver==1.3.1 +matplotlib~=3.8.3 +numpy~=1.26.4 +Pillow~=10.2.0 +pyparsing==3.0.9 +pyserial==3.5 +python-dateutil==2.8.2 +six==1.16.0 + +These requirements originate from QphoX SDK requirements file, and relaxed to be compatible with python 3.11. As-written, the requirements are likely much more restrictive than neccessary. + +Wrapper written by Filip Malinowski +filip.malinowski@tno.nl. \ No newline at end of file From a3d7efb2645aa0905be38ae9beec2f02eccfe3ef Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:46:56 +0100 Subject: [PATCH 34/57] Qphox license --- .../QphoX/CryoSwitchController/QphoX_license | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license new file mode 100644 index 000000000..29678f62d --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/QphoX_license @@ -0,0 +1,38 @@ +The QCoDeS driver for a CryoSwitchController is a wrapper for the QPhox SDK available at https://github.com/QphoX/CryoSwitchController. +The license below applies to the files: +- CryoSwitchController.py +- libphox.py +- QphoX_README +- states.json +- constants.json +A wrapper: +- qcodes_driver.py is excluded from that + +Copyright (c) 2023, QphoX B.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. All advertising materials mentioning features or use of this software must + display the following acknowledgement: + This product includes software developed by QphoX. +4. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY QphoX B.V. "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL QphoX B.V. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 43fc128133f4824f3eaf8fa562d0b1a170e6a4d2 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Fri, 23 Feb 2024 16:31:53 +0100 Subject: [PATCH 35/57] Fix imports --- .gitignore | 1 + .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 99a84635d..e17f27cb6 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,4 @@ venv.bak/ # System files *.DS_Store desktop.ini +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index ae33ea84e..389b3612b 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -1,6 +1,6 @@ from qcodes import Instrument, ChannelList, InstrumentChannel from qcodes.utils.validators import Numbers,Bool,Enum -from qcodes_contrib_drivers.drivers.QPhox.CryoSwitchController.CryoSwitchController import Cryoswitch +from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch class CryoSwitchChannel(InstrumentChannel): def __init__(self, parent: Instrument, name: str, channel: str): @@ -111,3 +111,6 @@ def connect(self, port: str, contact: int): def disconnect(self, port: str, contact: int): return self._controller.disconnect(port, contact) + + def get_idn(self): + pass From 0559d52d0f02ff0eeb8d562f42bba01a1a582807 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Mon, 26 Feb 2024 09:40:06 +0100 Subject: [PATCH 36/57] Ignore qphox data. --- .gitignore | 1 + .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 4 ++++ .../drivers/QphoX/CryoSwitchController/states.json | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e17f27cb6..602e1be6c 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,4 @@ venv.bak/ *.DS_Store desktop.ini src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/data diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 389b3612b..10b2d3ef3 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -18,21 +18,25 @@ def __init__(self, parent: Instrument, name: str, channel: str): def connect(self, contact: int): trace = self.parent.connect(self._channel, contact) self._active_contact = contact + self.active_contact() return trace def disconnect(self, contact: int): trace = self.parent.disconnect(self._channel, contact) self._active_contact = 0 + self.active_contact() return trace def disconnect_all(self): trace = self.parent.disconnect_all(self._channel) self._active_contact = 0 + self.active_contact() return trace def smart_connect(self, contact: int): trace = self.parent.smart_connect(self._channel, contact) self._active_contact = contact + self.active_contact() return trace def _get_active_contact(self): diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json index 57bcfd65f..ce4075a7e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json @@ -173,7 +173,7 @@ "port_A": { "contact_1": 0, "contact_2": 0, - "contact_3": 1, + "contact_3": 0, "contact_4": 0, "contact_5": 0, "contact_6": 0 From 662eda2e989ad3cb1da08a1345fcbb031c016895 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Mon, 26 Feb 2024 09:58:06 +0100 Subject: [PATCH 37/57] Docstrings --- .../CryoSwitchController/qcodes_driver.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 10b2d3ef3..9af5fc405 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -3,7 +3,27 @@ from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch class CryoSwitchChannel(InstrumentChannel): + """ + CryoSwitchChannel class is used to define the channels for the CryoSwitchControllerDriver. + It is a subclass of the InstrumentChannel class from qcodes. + + Attributes: + parent (Instrument): The parent instrument to which the channel is attached. + name (str): The name of the channel. + channel (str): The channel identifier. + active_contact (Parameter): The active contact for the channel. + It can be a number between 0 and 6. + """ + def __init__(self, parent: Instrument, name: str, channel: str): + """ + Initializes a new instance of the CryoSwitchChannel class. + + Args: + parent (Instrument): The parent instrument to which the channel is attached. + name (str): The name of the channel. + channel (str): The channel identifier. + """ super().__init__(parent, name) self.add_parameter( @@ -16,34 +36,100 @@ def __init__(self, parent: Instrument, name: str, channel: str): self._active_contact = 0 def connect(self, contact: int): + """ + Applies a current pulse to make a specified contact. + + Args: + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the connection. + """ trace = self.parent.connect(self._channel, contact) self._active_contact = contact self.active_contact() return trace def disconnect(self, contact: int): + """ + Applies a current pulse to disconnect a specified contact. + + Args: + contact (int): The contact to be disconnected. + + Returns: + trace: The current waveform after the disconnection. + """ trace = self.parent.disconnect(self._channel, contact) self._active_contact = 0 self.active_contact() return trace def disconnect_all(self): + """ + Applies a disconnecting pulse to all contacts. + + Returns: + trace: The current waveform after all contacts are disconnected. + """ trace = self.parent.disconnect_all(self._channel) self._active_contact = 0 self.active_contact() return trace def smart_connect(self, contact: int): + """ + Connects a contact to the channel smartly, i.e., disconnects the previously connected + contacts and connects the specified switch contact based on the tracking history. + + Args: + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the smart connection. + """ trace = self.parent.smart_connect(self._channel, contact) self._active_contact = contact self.active_contact() return trace def _get_active_contact(self): + """ + Gets the active contact for the channel. + + Returns: + int: The active contact for the channel. + """ return self._active_contact class CryoSwitchControllerDriver(Instrument): + """ + CryoSwitchControllerDriver class is used to control the Cryoswitch. + It is a subclass of the Instrument class from qcodes. + + Attributes: + name (str): The name of the instrument. + output_voltage (Parameter): The output voltage of the controller. + It can be a number between 0 and 10. + pulse_duration (Parameter): The pulse duration of the controller. + It can be a number between 0 and 1000. + OCP_value (Parameter): The overcurrent protection trigger value of the controller. + It can be a number between 0 and 1000. + chopping (Parameter): The chopping function status of the controller. + It can be a boolean value. + switch_model (Parameter): The switch model used by the controller. + It can be either 'R583423141' or 'R573423600'. + power_status (Parameter): The power status of the controller. + It can be either 0 (disabled) or 1 (enabled). + """ + def __init__(self, name: str, **kwargs): + """ + Initializes a new instance of the CryoSwitchControllerDriver class. + + Args: + name (str): The name of the instrument. + """ super().__init__(name, **kwargs) self._controller = Cryoswitch() @@ -105,16 +191,48 @@ def smart_connect(self, port: str, contact: int): self.add_submodule("channels", channels) def _enable_disable_chopping(self, enable: bool): + """ + Enables or disables the chopping function of the controller. + + Args: + enable (bool): True to enable the chopping function, False to disable it. + """ if enable: self._controller.enable_chopping() else: self._controller.disable_chopping() def connect(self, port: str, contact: int): + """ + Applies a current pulse to connect a specific contact of + a switch at a selected port. + + Args: + port (str): The port to which the contact is connected. + contact (int): The contact to be connected. + + Returns: + trace: The current waveform after the connection. + """ return self._controller.connect(port, contact) def disconnect(self, port: str, contact: int): + """ + Applies a current pulse to disconnect a specific contact of + a switch at a selected port. + + Args: + port (str): The port from which the contact is disconnected. + contact (int): The contact to be disconnected. + + Returns: + trace: The current waveform after the disconnection. + """ return self._controller.disconnect(port, contact) def get_idn(self): + """ + A dummy getidn function for the instrument initialization + in QCoDeS to work. + """ pass From 984b283e2574aa1194699849ed77bd8b3827d586 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 27 Feb 2024 13:24:31 +0100 Subject: [PATCH 38/57] Fix position of several functions --- .../CryoSwitchController/qcodes_driver.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 9af5fc405..87549af61 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -170,14 +170,6 @@ def __init__(self, name: str, **kwargs): vals=Enum(0, 1) ) - def get_switches_state(self, port: str = None): - return self._controller.get_switches_state(port) - - def disconnect_all(self, port: str): - self._controller.disconnect_all(port) - - def smart_connect(self, port: str, contact: int): - return self._controller.smart_connect(port, contact) self.add_function('start', call_cmd=self._controller.start) self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) @@ -185,11 +177,20 @@ def smart_connect(self, port: str, contact: int): channels = ChannelList(self, "Channels", CryoSwitchChannel, snapshotable=False) for ch in ['A', 'B', 'C', 'D']: - channel = CryoSwitchChannel(self, f"channel_{ch}", ch) + channel = CryoSwitchChannel(self, f"{ch}", ch) channels.append(channel) channels.lock() self.add_submodule("channels", channels) + def get_switches_state(self, port: str = None): + return self._controller.get_switches_state(port) + + def disconnect_all(self, port: str): + self._controller.disconnect_all(port) + + def smart_connect(self, port: str, contact: int): + return self._controller.smart_connect(port, contact) + def _enable_disable_chopping(self, enable: bool): """ Enables or disables the chopping function of the controller. From a0bf1e3b22233b87df55b55b9566a5879a31ad4c Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 11:30:36 +0100 Subject: [PATCH 39/57] Use 'RT' and 'CRYO' to select a switch model. --- .../CryoSwitchController/qcodes_driver.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 87549af61..c7dbbc59e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -133,11 +133,12 @@ def __init__(self, name: str, **kwargs): super().__init__(name, **kwargs) self._controller = Cryoswitch() + self._switch_model = None self.add_parameter( 'output_voltage', set_cmd=self._controller.set_output_voltage, - vals=Numbers(0, 10) + vals=Numbers(0, 25) ) self.add_parameter( @@ -160,8 +161,9 @@ def __init__(self, name: str, **kwargs): self.add_parameter( 'switch_model', - set_cmd=self._controller.select_switch_model, - vals=Enum('R583423141', 'R573423600') + set_cmd=self._select_switch_model, + get_cmd=self._get_switch_model, + vals=Enum('R583423141', 'R573423600','CRYO','RT') ) self.add_parameter( @@ -182,14 +184,27 @@ def __init__(self, name: str, **kwargs): channels.lock() self.add_submodule("channels", channels) + def _select_switch_model(self, switch_type: str = None): + if switch_type in ['R583423141', 'CRYO']: + self._controller.select_switch_model('R583423141') + self._switch_model = 'R583423141' + elif switch_type in ['R573423600', 'RT']: + self._controller.select_switch_model('R573423600') + self._switch_model = 'R573423600' + else: + print('Selected switch type does not exist.') + + def _get_switch_model(self): + return self._switch_model + def get_switches_state(self, port: str = None): - return self._controller.get_switches_state(port) + return self._controller.get_switches_state(port) def disconnect_all(self, port: str): - self._controller.disconnect_all(port) + self._controller.disconnect_all(port) def smart_connect(self, port: str, contact: int): - return self._controller.smart_connect(port, contact) + return self._controller.smart_connect(port, contact) def _enable_disable_chopping(self, enable: bool): """ From e631fd2747e2b3a76fd521a5af61ae4eef87b4ce Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 11:37:35 +0100 Subject: [PATCH 40/57] Update docstring --- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index c7dbbc59e..e5d519c4d 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -110,15 +110,16 @@ class CryoSwitchControllerDriver(Instrument): Attributes: name (str): The name of the instrument. output_voltage (Parameter): The output voltage of the controller. - It can be a number between 0 and 10. + It can be a number between 0 and 25 (V). pulse_duration (Parameter): The pulse duration of the controller. - It can be a number between 0 and 1000. + It can be a number between 0 and 1000 (ms). OCP_value (Parameter): The overcurrent protection trigger value of the controller. - It can be a number between 0 and 1000. + It can be a number between 0 and 1000 (mA). chopping (Parameter): The chopping function status of the controller. It can be a boolean value. switch_model (Parameter): The switch model used by the controller. It can be either 'R583423141' or 'R573423600'. + Equvalently, one may set the model using 'RT' (room temperature) for R573423600, and 'CRYO' instead of 'R583423141'. power_status (Parameter): The power status of the controller. It can be either 0 (disabled) or 1 (enabled). """ @@ -171,7 +172,6 @@ def __init__(self, name: str, **kwargs): get_cmd=self._controller.get_power_status, vals=Enum(0, 1) ) - self.add_function('start', call_cmd=self._controller.start) self.add_function('enable_OCP', call_cmd=self._controller.enable_OCP) From 4d3ed1800f63666559642413a4ed8eb72e72d438 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 13:20:17 +0100 Subject: [PATCH 41/57] Do not sync 'states.json'. Create an empty 'states.json' if it doesn't exist. --- .gitignore | 1 + .../QphoX/CryoSwitchController/qcodes_driver.py | 14 ++++++++++++++ .../{states.json => states_empty.json} | 0 3 files changed, 15 insertions(+) rename src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/{states.json => states_empty.json} (100%) diff --git a/.gitignore b/.gitignore index 602e1be6c..1171a8dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,4 @@ venv.bak/ desktop.ini src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/pulse_logging.txt src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/data +src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index e5d519c4d..ba1135d62 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -1,6 +1,7 @@ from qcodes import Instrument, ChannelList, InstrumentChannel from qcodes.utils.validators import Numbers,Bool,Enum from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch +import os class CryoSwitchChannel(InstrumentChannel): """ @@ -120,6 +121,7 @@ class CryoSwitchControllerDriver(Instrument): switch_model (Parameter): The switch model used by the controller. It can be either 'R583423141' or 'R573423600'. Equvalently, one may set the model using 'RT' (room temperature) for R573423600, and 'CRYO' instead of 'R583423141'. + The two switch types require different connectivity between the D-Sub on the controller box and the switch. power_status (Parameter): The power status of the controller. It can be either 0 (disabled) or 1 (enabled). """ @@ -133,6 +135,16 @@ def __init__(self, name: str, **kwargs): """ super().__init__(name, **kwargs) + # create an empty states file if it does not exist + current_dir = os.path.dirname(os.path.abspath(__file__)) + target_file = os.path.join(current_dir, 'states.json') + source_file = os.path.join(current_dir, 'states_empty.json') + if not os.path.exists(target_file): + with open(source_file, 'r') as src: + contents = src.read() + with open(target_file, 'w') as tgt: + tgt.write(contents) + self._controller = Cryoswitch() self._switch_model = None @@ -188,9 +200,11 @@ def _select_switch_model(self, switch_type: str = None): if switch_type in ['R583423141', 'CRYO']: self._controller.select_switch_model('R583423141') self._switch_model = 'R583423141' + print('Note that R583423141 and R573423600 models require different connection to the switch box.') elif switch_type in ['R573423600', 'RT']: self._controller.select_switch_model('R573423600') self._switch_model = 'R573423600' + print('Note that R583423141 and R573423600 models require different connection to the switch box.') else: print('Selected switch type does not exist.') diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json similarity index 100% rename from src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json rename to src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json From e9a0815877d7aec1bd54fbc03c9c3aab6fc3fe6a Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 14:31:05 +0100 Subject: [PATCH 42/57] Usage example --- .../QphoX/CryoSwitchController/README.txt | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt index 16ec753e9..9bbb71442 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -16,4 +16,45 @@ six==1.16.0 These requirements originate from QphoX SDK requirements file, and relaxed to be compatible with python 3.11. As-written, the requirements are likely much more restrictive than neccessary. Wrapper written by Filip Malinowski -filip.malinowski@tno.nl. \ No newline at end of file +filip.malinowski@tno.nl. + +############### Usage example: ############### + +# load driver +from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.qcodes_driver import CryoSwitchControllerDriver + +# connect to the switch +switchcontroller = CryoSwitchControllerDriver('switchcontroller') +switchcontroller.start() + +# set a switch model +switchcontroller.switch_model('CRYO') + +# set pulse parameters +switchcontroller.output_voltage(15) +switchcontroller.OCP_value(100) +switchcontroller.pulse_duration(8) + +# get all switch states based on the "status.json" +# there is no direct way to see the state of the switch +switchcontroller.get_switches_state() + +# connect contact 3 of port A +switchcontroller.channels.A.connect(3) + +# smart switch to contact 5 (and automatically disconnect from 3) +# there is no software or hardware control to ensure that only contact is connected +# once the pulse parameters are reliable, I recommend only using the "smart_connect" function +switchcontroller.channels.A.smart_connect(5) + +# get an active contact +switchcontroller.channels.A.active_contact() + +# disconnect from contact 5 +data = switchcontroller.channels.A.disconnect(5) + +# plot a current transient for the "disconnect" action +plt.plot(data) + +# get an active contact (should return 0 when nothing is connected) +switchcontroller.channels.A.active_contact() \ No newline at end of file From 171bed9a167aea4d51833c6b5ace84d923833e4d Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Fri, 22 Mar 2024 13:39:09 +0100 Subject: [PATCH 43/57] Enable disconnecting from the switch controller --- .../drivers/QphoX/CryoSwitchController/README.txt | 5 ++++- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt index 9bbb71442..2b88f665e 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/README.txt @@ -57,4 +57,7 @@ data = switchcontroller.channels.A.disconnect(5) plt.plot(data) # get an active contact (should return 0 when nothing is connected) -switchcontroller.channels.A.active_contact() \ No newline at end of file +switchcontroller.channels.A.active_contact() + +# close the switchcontroller instrument +switchcontroller.close() \ No newline at end of file diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index ba1135d62..a08a4d556 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -266,3 +266,10 @@ def get_idn(self): in QCoDeS to work. """ pass + + def close(self): + """ + Disconnect from the switch controller. + """ + self._controller.labphox.disconnect() + super().close() From 620aa09efe1dc4b54e031e0e2410c9a80f33e469 Mon Sep 17 00:00:00 2001 From: Starlight Date: Fri, 23 Feb 2024 15:44:59 +0100 Subject: [PATCH 44/57] Copy other QphoxCode --- .../QphoX/CryoSwitchController/states.json | 376 ++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json new file mode 100644 index 000000000..57bcfd65f --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json @@ -0,0 +1,376 @@ +{ + "SN": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN0": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN12": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN22": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN23": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN24": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 1, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 1, + "contact_2": 0, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN25": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN26": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN300": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN6": { + "port_A": { + "contact_1": 1, + "contact_2": 1, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN65535": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + } +} \ No newline at end of file From 68e0eebcbd1dc5b60e613b5be41fbd38b5a4d924 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Tue, 5 Mar 2024 13:20:17 +0100 Subject: [PATCH 45/57] Do not sync 'states.json'. Create an empty 'states.json' if it doesn't exist. --- .../QphoX/CryoSwitchController/states.json | 376 ------------------ 1 file changed, 376 deletions(-) delete mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json deleted file mode 100644 index 57bcfd65f..000000000 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "SN": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN0": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN12": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN22": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN23": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN24": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 1, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 1, - "contact_2": 0, - "contact_3": 1, - "contact_4": 1, - "contact_5": 1, - "contact_6": 1 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN25": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN26": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN300": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN6": { - "port_A": { - "contact_1": 1, - "contact_2": 1, - "contact_3": 1, - "contact_4": 1, - "contact_5": 1, - "contact_6": 1 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN65535": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - } -} \ No newline at end of file From 3db2749242229af93d0fede7e761589fc78ebf9c Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Fri, 22 Mar 2024 13:59:29 +0100 Subject: [PATCH 46/57] Remaining docstrings --- .../CryoSwitchController/qcodes_driver.py | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index a08a4d556..a30b2a984 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -44,7 +44,7 @@ def connect(self, contact: int): contact (int): The contact to be connected. Returns: - trace: The current waveform after the connection. + np.ndarray: The current waveform after the connection. """ trace = self.parent.connect(self._channel, contact) self._active_contact = contact @@ -59,7 +59,7 @@ def disconnect(self, contact: int): contact (int): The contact to be disconnected. Returns: - trace: The current waveform after the disconnection. + np.ndarray: The current waveform after the disconnection. """ trace = self.parent.disconnect(self._channel, contact) self._active_contact = 0 @@ -71,7 +71,7 @@ def disconnect_all(self): Applies a disconnecting pulse to all contacts. Returns: - trace: The current waveform after all contacts are disconnected. + np.ndarray: The current waveform after all contacts are disconnected. """ trace = self.parent.disconnect_all(self._channel) self._active_contact = 0 @@ -87,7 +87,7 @@ def smart_connect(self, contact: int): contact (int): The contact to be connected. Returns: - trace: The current waveform after the smart connection. + np.ndarray: The current waveform after the smart connection. """ trace = self.parent.smart_connect(self._channel, contact) self._active_contact = contact @@ -212,12 +212,37 @@ def _get_switch_model(self): return self._switch_model def get_switches_state(self, port: str = None): + """ + Read and return the state of the contacts as recorded in the states.json + + Args: + port (str|None): A port which states are to be returned. None will return + the state of all contacts. (default None) + + Returns: + dict: dictionary listing a state of every contact. + """ return self._controller.get_switches_state(port) def disconnect_all(self, port: str): + """ + Applies a disconnecting current pulse to all contacts of the + specified port + + Args: + port (str): A letter A-D indicating the port to be controlled. + """ self._controller.disconnect_all(port) def smart_connect(self, port: str, contact: int): + """ + Disconnects from all the connected contacts for a specified channel + and connects only to the indicated one. + + Args: + port (str): A letter A-D indicating the port to be controlled. + contact (int): Number of the contact that is to be connected + """ return self._controller.smart_connect(port, contact) def _enable_disable_chopping(self, enable: bool): From 6724d8bfce64038afc06f32e6e179e5fae97996a Mon Sep 17 00:00:00 2001 From: TNO-Helios Date: Mon, 29 Apr 2024 13:15:05 +0200 Subject: [PATCH 47/57] Fix the voltage limits. --- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index a30b2a984..5a2f38ce3 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -151,7 +151,7 @@ def __init__(self, name: str, **kwargs): self.add_parameter( 'output_voltage', set_cmd=self._controller.set_output_voltage, - vals=Numbers(0, 25) + vals=Numbers(5, 30) ) self.add_parameter( From aefba17032016c6e10dfea0f6d35cc922f1b6fcf Mon Sep 17 00:00:00 2001 From: cferrer-Q Date: Tue, 21 May 2024 11:00:05 +0200 Subject: [PATCH 48/57] Changed range of output_voltage, pulse_duration, OCP_value to match datasheet --- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index 5a2f38ce3..be1b42951 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -151,19 +151,19 @@ def __init__(self, name: str, **kwargs): self.add_parameter( 'output_voltage', set_cmd=self._controller.set_output_voltage, - vals=Numbers(5, 30) + vals=Numbers(5, 28) ) self.add_parameter( 'pulse_duration', set_cmd=self._controller.set_pulse_duration_ms, - vals=Numbers(0, 1000) + vals=Numbers(1, 100) ) self.add_parameter( 'OCP_value', set_cmd=self._controller.set_OCP_mA, - vals=Numbers(0, 1000) + vals=Numbers(1, 150) ) self.add_parameter( @@ -176,7 +176,7 @@ def __init__(self, name: str, **kwargs): 'switch_model', set_cmd=self._select_switch_model, get_cmd=self._get_switch_model, - vals=Enum('R583423141', 'R573423600','CRYO','RT') + vals=Enum('R583423141', 'R573423600', 'CRYO', 'RT') ) self.add_parameter( From c14456c4da5d23eccbd0c2e089daf5527b90d3fc Mon Sep 17 00:00:00 2001 From: Daan Waardenburg Date: Thu, 29 Aug 2024 13:17:30 +0200 Subject: [PATCH 49/57] Create states_empty.json file if not existant --- .../QphoX/CryoSwitchController/__init__.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py index e69de29bb..466ba3024 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/__init__.py @@ -0,0 +1,48 @@ +import os +import json + + +# Function to create a port template +def create_port_template(): + return {"contact_1": 0, "contact_2": 0, "contact_3": 0, "contact_4": 0, "contact_5": 0, "contact_6": 0} + + +# Function to create the main JSON structure with all values initially set to 0 +def create_json_structure(): + ports = ["port_A", "port_B", "port_C", "port_D"] + json_data = {} + + for sn in ["SN", "SN0", "SN12", "SN22", "SN23", "SN24", "SN25", "SN26", "SN300", "SN6", "SN65535"]: + json_data[sn] = {port: create_port_template() for port in ports} + + # Set exceptions where values should be 1 + json_data["SN24"]["port_B"]["contact_1"] = 1 + json_data["SN24"]["port_B"]["contact_3"] = 1 + json_data["SN24"]["port_B"]["contact_4"] = 1 + json_data["SN24"]["port_B"]["contact_5"] = 1 + json_data["SN24"]["port_B"]["contact_6"] = 1 + + json_data["SN6"]["port_A"]["contact_1"] = 1 + json_data["SN6"]["port_A"]["contact_2"] = 1 + json_data["SN6"]["port_A"]["contact_3"] = 1 + json_data["SN6"]["port_A"]["contact_4"] = 1 + json_data["SN6"]["port_A"]["contact_5"] = 1 + json_data["SN6"]["port_A"]["contact_6"] = 1 + + return json_data + + +# Specify the file path +file_path = os.path.dirname(__file__) + "/states_empty.json" + +# Check if the file exists +if not os.path.exists(file_path): + # Create the directory if it doesn't exist + os.makedirs(os.path.dirname(file_path), exist_ok=True) + + # Create the JSON structure + json_data = create_json_structure() + + # Write the JSON data to the file + with open(file_path, 'w') as json_file: + json.dump(json_data, json_file, indent=4) From c28c732c5f500a0525826976e3708e7b2e7225ac Mon Sep 17 00:00:00 2001 From: Daan Waardenburg Date: Thu, 29 Aug 2024 15:32:51 +0200 Subject: [PATCH 50/57] Create FSV_3013.py --- .../drivers/RohdeSchwarz/FSV_3013.py | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/RohdeSchwarz/FSV_3013.py diff --git a/src/qcodes_contrib_drivers/drivers/RohdeSchwarz/FSV_3013.py b/src/qcodes_contrib_drivers/drivers/RohdeSchwarz/FSV_3013.py new file mode 100644 index 000000000..13d59f985 --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/RohdeSchwarz/FSV_3013.py @@ -0,0 +1,182 @@ +import time +import time +from qcodes import VisaInstrument +from qcodes.utils.validators import Numbers, Enum +import numpy as np +from scipy.signal import find_peaks + +class RFSpectrumAnalyzer(VisaInstrument): + def __init__(self, name, address, **kwargs): + super().__init__(name, address, **kwargs) + + # Frequency parameters + self.add_parameter('frequency', label='Center Frequency', unit='Hz', + get_cmd=self._get_numeric_value(':FREQ:CENT?'), + set_cmd=':FREQ:CENT {}', + vals=Numbers(1e6, 44e9)) + self.add_parameter('span', label='Frequency Span', unit='Hz', + get_cmd=self._get_numeric_value(':FREQ:SPAN?'), + set_cmd=':FREQ:SPAN {}', + vals=Numbers(1e3, 3.2e9)) + + # Amplitude parameters + self.add_parameter('amplitude', label='Amplitude', unit='dBm', + get_cmd=self._get_numeric_value(':DISP:WIND:TRAC:Y:SCAL:RLEV?'), + set_cmd=':DISP:WIND:TRAC:Y:SCAL:RLEV {}', + vals=Numbers(-120, 30)) + + # Reference Level Control + self.add_parameter('reference_level', label='Reference Level', unit='dBm', + get_cmd=self._get_numeric_value(':DISP:WIND:TRAC:Y:RLEV?'), + set_cmd=':DISP:WIND:TRAC:Y:RLEV {}', + vals=Numbers(-120, 30)) + + # Bandwidth settings + self.add_parameter('resolution_bandwidth', label='Resolution Bandwidth', unit='Hz', + get_cmd=self._get_numeric_value(':BAND:RES?'), + set_cmd=':BAND:RES {}', + vals=Numbers(1, 10e6)) + self.add_parameter('video_bandwidth', label='Video Bandwidth', unit='Hz', + get_cmd=self._get_numeric_value(':BAND:VID?'), + set_cmd=':BAND:VID {}', + vals=Numbers(1, 10e6)) + + # Sweep settings + self.add_parameter('sweep_time', label='Sweep Time', unit='s', + get_cmd=self._get_numeric_value(':SWE:TIME?'), + set_cmd=':SWE:TIME {}', + vals=Numbers(1e-6, 10000)) + self.add_parameter('sweep_mode', label='Sweep Mode', + get_cmd=self._get_enum_value(':INIT:CONT?'), + set_cmd=':INIT:CONT {}', + vals=Enum('ON', 'OFF')) + + # Trigger settings + self.add_parameter('trigger_source', label='Trigger Source', + get_cmd=self._get_enum_value(':TRIG:SOUR?'), + set_cmd=':TRIG:SOUR {}', + vals=Enum('IMM', 'EXT', 'VID')) + self.add_parameter('trigger_level', label='Trigger Level', unit='V', + get_cmd=self._get_numeric_value(':TRIG:LEV?'), + set_cmd=':TRIG:LEV {}', + vals=Numbers(-5, 5)) + + # Correction settings + self.add_parameter('correction_state', label='Correction State', + get_cmd=self._get_enum_value(':CORR:STAT?'), + set_cmd=':CORR:STAT {}', + vals=Enum('ON', 'OFF')) + + # Measurement settings + self.add_parameter('measurement_state', label='Measurement State', + get_cmd=self._get_enum_value(':INIT:IMM?'), + set_cmd=':INIT:IMM') + + # Miscellaneous settings + self.add_parameter('input_impedance', label='Input Impedance', unit='Ohm', + get_cmd=self._get_numeric_value(':INP:IMP?'), + set_cmd=':INP:IMP {}', + vals=Enum(50, 75)) + + # Noise and Peak measurement settings + self.add_parameter('noise_level_without_peak', label='Noise Level', unit='dBm', + get_cmd=lambda:self._get_noise_lvl_and_peaks()[0]) + self.add_parameter('peak_center', label='Peak Center', unit='Hz', + get_cmd=lambda:self._get_noise_lvl_and_peaks()[1]) + self.add_parameter('peak_height', label='Peak Height', unit='dBm', + get_cmd=lambda:self._get_noise_lvl_and_peaks()[2]) + self.add_parameter('peak_width', label='Peak Width', unit='Hz', + get_cmd=lambda:self._get_noise_lvl_and_peaks()[3]) + + # Trace data parameters + self.add_parameter('trace_frequencies', + label='Trace Frequencies', + unit='Hz', + get_cmd=self._get_trace_frequencies) + + self.add_parameter('trace_amplitudes', + label='Trace Amplitudes', + unit='dBm', + get_cmd=self._get_trace_amplitudes) + + def _get_numeric_value(self, cmd): + """Fetch a value from the instrument and return it as a float.""" + return lambda: float(self.ask(cmd)) + + def _get_enum_value(self, cmd): + """Fetch a value from the instrument and return it as a string.""" + return lambda: self.ask(cmd).strip() + + def reset(self): + """Resets the instrument to its default state.""" + self.write('*RST') + + def measure_power(self): + """Performs a basic power measurement.""" + self.write(':INIT:IMM') + return float(self.ask(':FETCH:POW:ACP?')) + + def get_trace(self): + """Fetches the trace data from the instrument and calculates the frequency axis.""" + self.write(':FORM:TRAC ASC') + center_frequency = float(self.frequency()) + span = float(self.span()) + + # Start the sweep + self.write(':INIT') + + # Add a waiting time of 1 second + time.sleep(1) + + # Query operation complete to ensure sweep is done + self.ask('*OPC?') + + # Fetch the trace data + trace_data = self.ask(':TRAC:DATA? TRACE1') + + # Convert the acquired trace data into a list of floating-point numbers + amplitudes = np.array([float(val) for val in trace_data.split(',')]) + + # Calculate the corresponding frequency values + num_points = len(amplitudes) + frequencies = np.linspace(center_frequency - span/2, center_frequency + span/2, num_points) + + return frequencies, amplitudes + + def _get_noise_lvl_and_peaks(self): + """Return mean noise level in the absence of a signal peak and peak parameters.""" + f, amplitudes = self.get_trace() + try: + pks = find_peaks(amplitudes, prominence=20, width=2) + highest_peak_index = np.argmax(pks[1]["prominences"]) + peak_center = f[pks[0][highest_peak_index]] + peak_height = amplitudes[pks[0][highest_peak_index]] + peak_width = int(pks[1]["widths"][highest_peak_index]) + + # Remove the peak region from noise calculation + noise_data = np.delete(amplitudes, range(pks[0][highest_peak_index] - int(peak_width / 2 + 1), pks[0][highest_peak_index] + int(peak_width / 2 + 1))) + noise_lvl = np.mean(noise_data) + + except Exception as e: + print(f"Couldn't get peak for the data: {e}") + noise_lvl = np.mean(amplitudes) + peak_center = 0 + peak_width = 0 + peak_height = 0 + + return noise_lvl, peak_center, peak_height, peak_width + + def _get_trace_frequencies(self): + """Return the frequency axis for the current trace.""" + frequencies, _ = self.get_trace() + return frequencies + + def _get_trace_amplitudes(self): + """Return the amplitude data for the current trace.""" + _, amplitudes = self.get_trace() + return amplitudes + + def close(self): + """Override close to ensure proper disconnection.""" + self.write('SYST:LOC') + super().close() \ No newline at end of file From a1a3f5991f8aa47cc980f4a78cf4e7c95e799192 Mon Sep 17 00:00:00 2001 From: TNO-Helios Date: Thu, 5 Dec 2024 11:43:29 +0100 Subject: [PATCH 51/57] Add Rigol and Keithley driver classes --- .../drivers/Rigol/Rigol_DP932.py | 75 ++++++++ .../drivers/Tektronix/Keithley_2401.py | 173 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 src/qcodes_contrib_drivers/drivers/Rigol/Rigol_DP932.py create mode 100644 src/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2401.py diff --git a/src/qcodes_contrib_drivers/drivers/Rigol/Rigol_DP932.py b/src/qcodes_contrib_drivers/drivers/Rigol/Rigol_DP932.py new file mode 100644 index 000000000..06f18999e --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/Rigol/Rigol_DP932.py @@ -0,0 +1,75 @@ +from qcodes import VisaInstrument +from qcodes.utils.validators import Numbers + +class RigolDP932E(VisaInstrument): + """ + QCoDeS driver for the Rigol DP932E Programmable DC Power Supply. + """ + + def __init__(self, name: str, address: str, **kwargs): + super().__init__(name, address, **kwargs) + + # Add parameters for each channel + for channel in range(1, 4): + self.add_parameter(f'voltage_{channel}', + label=f'Channel {channel} Voltage', + unit='V', + get_cmd=f':MEASure:VOLTage? CH{channel}', + set_cmd=f':APPLy CH{channel},{{}}', + get_parser=float, + vals=Numbers(0, 31.5)) # According to manual, max voltage is 31.5V for DP932E + + self.add_parameter(f'current_{channel}', + label=f'Channel {channel} Current', + unit='A', + get_cmd=f':MEASure:CURRent? CH{channel}', + set_cmd=f':APPLy CH{channel},,,{{}}', + get_parser=float, + vals=Numbers(0, 3.15)) # According to manual, max current is 3.15A for DP932E + + self.add_parameter(f'output_{channel}', + label=f'Channel {channel} Output', + get_cmd=f':OUTPut:STATe? CH{channel}', + set_cmd=f':OUTPut:STATe {{}} CH{channel}', + val_mapping={'ON': 1, 'OFF': 0}) + + # Reset the instrument + self.write('*RST') + self.connect_message() + + def reset(self): + """Resets the instrument to its default settings.""" + self.write('*RST') + + def enable_output(self, channel: int): + """Enables the output for the specified channel.""" + self.write(f':OUTPut CH{channel},ON') + + def disable_output(self, channel: int): + """Disables the output for the specified channel.""" + self.write(f':OUTPut CH{channel},OFF') + + def measure_voltage(self, channel: int) -> float: + """Measures the voltage at the output terminal of the specified channel.""" + return float(self.ask(f':MEASure:VOLTage? CH{channel}')) + + def measure_current(self, channel: int) -> float: + """Measures the current at the output terminal of the specified channel.""" + return float(self.ask(f':MEASure:CURRent? CH{channel}')) + + def set_voltage(self, channel: int, voltage: float): + """Sets the output voltage for the specified channel.""" + self.write(f':APPLy CH{channel},{voltage}') + + def set_current(self, channel: int, current: float): + """Sets the output current for the specified channel.""" + self.write(f':APPLy CH{channel},,,{current}') + + def set_output_state(self, channel: int, state: str): + """Sets the output state (ON/OFF) for the specified channel.""" + self.write(f':OUTPut:STATe {state},CH{channel}') + + def measure_power(self, channel: int) -> float: + """Measures the power at the output terminal of the specified channel.""" + return float(self.ask(f':MEASure:POWEr? CH{channel}')) + \ No newline at end of file diff --git a/src/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2401.py b/src/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2401.py new file mode 100644 index 000000000..a23a2186d --- /dev/null +++ b/src/qcodes_contrib_drivers/drivers/Tektronix/Keithley_2401.py @@ -0,0 +1,173 @@ +from qcodes import VisaInstrument +from qcodes.utils.validators import Numbers +import numpy as np +import time + + +class Keithley2400(VisaInstrument): + """ + QCoDeS driver for Keithley 2400 SourceMeter. + Provides functionality for sourcing and measuring voltage, current, and resistance. + Includes support for voltage sweeps and detailed parameter configuration. + """ + + def __init__(self, name: str, address: str, **kwargs): + """ + Initialize the Keithley 2400 SourceMeter. + Args: + name (str): Name of the instrument. + address (str): Visa address of the instrument. + """ + super().__init__(name, address, terminator='\n', **kwargs) + + # Parameters + self.add_parameter( + 'voltage', + label='Set Voltage', + unit='V', + get_cmd=':SOUR:VOLT:LEV:IMM:AMPL?', + set_cmd=':SOUR:VOLT:LEV:IMM:AMPL {:.4f}', + get_parser=float, + vals=Numbers(-210, 210) + ) + + self.add_parameter( + 'current', + label='Measured Current', + unit='A', + get_cmd=self._measure_current + ) + + self.add_parameter( + 'set_current', + label='Set Current', + unit='A', + get_cmd=':SOUR:CURR:LEV:IMM:AMPL?', + set_cmd=':SOUR:CURR:LEV:IMM:AMPL {:.4f}', + get_parser=float, + vals=Numbers(-1.05, 1.05) # Adjust the range based on the Keithley 2400 specs + ) + + self.add_parameter( + 'measured_voltage', + label='Measured Voltage', + unit='V', + get_cmd=self._measure_voltage + ) + + self.add_parameter( + 'resistance', + label='Measured Resistance', + unit='Ohm', + get_cmd=self._measure_resistance + ) + + self.add_parameter( + 'output', + label='Output State', + get_cmd=':OUTP?', + set_cmd=':OUTP {}', + val_mapping={'on': 1, 'off': 0} + ) + + self.add_parameter( + 'current_range', + label='Current Range', + get_cmd=':SENS:CURR:RANG?', + set_cmd=':SENS:CURR:RANG {:.4f}', + get_parser=float, + vals=Numbers(1e-9, 1e1) + ) + + self.add_parameter( + 'auto_range', + label='Auto Range', + get_cmd=':SENS:CURR:RANG:AUTO?', + set_cmd=':SENS:CURR:RANG:AUTO {}', + val_mapping={True: 'ON', False: 'OFF'} + ) + + self.add_parameter( + 'sweep_voltage', + label='Sweep Voltage', + unit='V', + set_cmd=self.set_voltage + ) + + self.connect_message() + + def _measure_voltage(self): + """ + Measure the voltage in the selected range or auto-range. + Returns: + float: Measured voltage in volts. + """ + self.write(':FUNC "VOLT"') + self.write(':FORM:ELEM VOLT') + response = self.ask(':READ?') + return float(response) + + def _measure_current(self): + """ + Measure the current in the selected range or auto-range. + Returns: + float: Measured current in amperes. + """ + self.write(':FUNC "CURR"') + self.write(':FORM:ELEM CURR') + response = self.ask(':READ?') + return float(response) + + def _measure_resistance(self): + """ + Measure the resistance in the selected range. + Returns: + float: Measured resistance in ohms. + """ + self.write(':FUNC "RES"') + self.write(':FORM:ELEM RES') + response = self.ask(':READ?') + return float(response) + + def enable_output(self, state: bool = True): + """ + Enable or disable the output. + Args: + state (bool): Set True to enable, False to disable. + """ + self.output('on' if state else 'off') + + def set_voltage(self, voltage: float): + """ + Set the output voltage. + Args: + voltage (float): The voltage level to set in volts. + """ + self.voltage(voltage) + + def sweep_voltage_measure(self, voltage_start: float, voltage_stop: float, steps: int): + """ + Sweep voltage from start to stop in specified steps and measure voltage and current. + Args: + voltage_start (float): Starting voltage. + voltage_stop (float): Ending voltage. + steps (int): Number of steps. + Returns: + list of dict: Contains 'voltage_set', 'voltage_measured', and 'current_measured'. + """ + voltage_values = np.linspace(voltage_start, voltage_stop, steps) + measurements = [] + self.write(':FUNC "VOLT","CURR"') + self.write(':FORM:ELEM VOLT,CURR') + + for voltage in voltage_values: + self.voltage(voltage) + time.sleep(0.1) # Allow settling + response = self.ask(':READ?') + voltage_measured, current_measured = map(float, response.split(',')) + measurements.append({ + 'voltage_set': voltage, + 'voltage_measured': voltage_measured, + 'current_measured': current_measured + }) + return measurements \ No newline at end of file From 521c39f5a9217872d58aa9e53a66db0451e719e6 Mon Sep 17 00:00:00 2001 From: TNO-Selene Date: Wed, 8 Jan 2025 13:38:43 +0100 Subject: [PATCH 52/57] Fix issue and automatically create constants and states json files --- .../CryoSwitchController.py | 546 ++++++++++++++++++ .../QphoX/CryoSwitchController/constants.json | 353 +++++++---- .../CryoSwitchController/qcodes_driver.py | 12 +- .../CryoSwitchController/states_empty.json | 376 ------------ 4 files changed, 772 insertions(+), 515 deletions(-) delete mode 100644 src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py index d7a3b3620..9535ae674 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/CryoSwitchController.py @@ -59,6 +59,9 @@ def __init__(self, debug=False, COM_port='', IP=None, SN=None, override_abspath= self.log_wav_init() def tracking_init(self): + if not os.path.isfile(self.track_states_file): + with open(self.track_states_file, 'w') as fp: + json.dump(STATES_DEFAULT, fp, indent=4) file = open(self.track_states_file) states = json.load(file) file.close() @@ -77,6 +80,9 @@ def log_wav_init(self): os.mkdir(self.log_wav_dir) def __constants(self): + if not os.path.isfile(self.constants_file_name): + with open(self.constants_file_name, 'w') as fp: + json.dump(CONSTANTS, fp, indent=4) file = open(self.constants_file_name) constants = json.load(file) file.close() @@ -793,3 +799,543 @@ def start(self): + +CONSTANTS = { "HW_Ver. 0": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": False, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 550, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 1, + "current_gain": 20, + + "calibrate_ADC": 0, + "polarization_params": [4700, 3000, 4700] + }, + "HW_Ver. 2": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": False, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 500, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [1, 0], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 1, + "current_gain": 20, + + "calibrate_ADC": 0, + "polarization_params": [4700, 3000, 4700] + }, + "HW_Ver. 3": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1.01722519, 0.01721413], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [4700, 3000, 4700] + + }, + "HW_Ver. 4": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.24, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1.03418631, 0.11739705], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [10000, 3000, 4700] + }, + "HW_Ver. 5": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.24, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [1.03418631, 0.11739705], + "converter_output_voltage_range": [5, 29], + + "OCP_gain": 2, + "OCP_range": [1, 300], + + "pulse_duration_range": [1, 100], + "sampling_frequency_range": [10, 100], + + "current_sense_R": 0.5, + "current_gain": 20, + + "calibrate_ADC": 1, + "polarization_params": [10000, 3000, 4700] + } +} + +STATES_DEFAULT = { + "SN": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN0": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN12": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN22": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN23": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN24": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 1, + "contact_2": 0, + "contact_3": 1, + "contact_4": 1, + "contact_5": 1, + "contact_6": 1 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN25": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN26": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN300": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN6": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + }, + "SN65535": { + "port_A": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_B": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_C": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + }, + "port_D": { + "contact_1": 0, + "contact_2": 0, + "contact_3": 0, + "contact_4": 0, + "contact_5": 0, + "contact_6": 0 + } + } +} \ No newline at end of file diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json index ec34b2ffd..272f88036 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/constants.json @@ -1,130 +1,227 @@ -{ "HW_Ver. 0": { - "ADC_12B_res": 4095, - "ADC_8B_res": 255, - "ADC_cal_ref": false, - - "bv_R1": 68000, - "bv_R2": 100000, - "bv_ADC": 3, - - "converter_divider": 11, - "converter_ADC": 10, - "converter_VREF": 1.23, - "converter_R1": 500000, - "converter_R2": 500000, - "converter_Rf": 15000, - "converter_DAC_lower_bound": 550, - "converter_DAC_upper_bound": 1500, - "converter_correction_codes": [1, 0], - "converter_output_voltage_range": [5, 29], - - "OCP_gain": 2, - "OCP_range": [1, 300], - - "pulse_duration_range": [1, 100], - "sampling_frequency_range": [10, 100], - - "current_sense_R": 1, - "current_gain": 20, - - "calibrate_ADC": 0, - "polarization_params": [4700, 3000, 4700] - }, - "HW_Ver. 2": { - "ADC_12B_res": 4095, - "ADC_8B_res": 255, - "ADC_cal_ref": false, - - "bv_R1": 68000, - "bv_R2": 100000, - "bv_ADC": 3, - - "converter_divider": 11, - "converter_ADC": 10, - "converter_VREF": 1.23, - "converter_R1": 500000, - "converter_R2": 500000, - "converter_Rf": 15000, - "converter_DAC_lower_bound": 500, - "converter_DAC_upper_bound": 1500, - "converter_correction_codes": [1, 0], - "converter_output_voltage_range": [5, 29], - - "OCP_gain": 2, - "OCP_range": [1, 300], - - "pulse_duration_range": [1, 100], - "sampling_frequency_range": [10, 100], - - "current_sense_R": 1, - "current_gain": 20, - - "calibrate_ADC": 0, - "polarization_params": [4700, 3000, 4700] - }, +{ + "HW_Ver. 0": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": false, + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 550, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [ + 1, + 0 + ], + "converter_output_voltage_range": [ + 5, + 29 + ], + "OCP_gain": 2, + "OCP_range": [ + 1, + 300 + ], + "pulse_duration_range": [ + 1, + 100 + ], + "sampling_frequency_range": [ + 10, + 100 + ], + "current_sense_R": 1, + "current_gain": 20, + "calibrate_ADC": 0, + "polarization_params": [ + 4700, + 3000, + 4700 + ] + }, + "HW_Ver. 2": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": false, + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 500000, + "converter_Rf": 15000, + "converter_DAC_lower_bound": 500, + "converter_DAC_upper_bound": 1500, + "converter_correction_codes": [ + 1, + 0 + ], + "converter_output_voltage_range": [ + 5, + 29 + ], + "OCP_gain": 2, + "OCP_range": [ + 1, + 300 + ], + "pulse_duration_range": [ + 1, + 100 + ], + "sampling_frequency_range": [ + 10, + 100 + ], + "current_sense_R": 1, + "current_gain": 20, + "calibrate_ADC": 0, + "polarization_params": [ + 4700, + 3000, + 4700 + ] + }, "HW_Ver. 3": { - "ADC_12B_res": 4095, - "ADC_8B_res": 255, - "ADC_cal_ref": 2.5, - - "bv_R1": 68000, - "bv_R2": 100000, - "bv_ADC": 3, - - "converter_divider": 11, - "converter_ADC": 10, - "converter_VREF": 1.23, - "converter_R1": 500000, - "converter_R2": 33000, - "converter_Rf": 56000, - "converter_DAC_lower_bound": 0, - "converter_DAC_upper_bound": 4095, - "converter_correction_codes": [1, 0], - "converter_output_voltage_range": [5, 29], - - "OCP_gain": 2, - "OCP_range": [1, 300], - - "pulse_duration_range": [1, 100], - "sampling_frequency_range": [10, 100], - - "current_sense_R": 0.5, - "current_gain": 20, - - "calibrate_ADC": 1, - "polarization_params": [4700, 3000, 4700] - - }, + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.23, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [ + 1.01722519, + 0.01721413 + ], + "converter_output_voltage_range": [ + 5, + 29 + ], + "OCP_gain": 2, + "OCP_range": [ + 1, + 300 + ], + "pulse_duration_range": [ + 1, + 100 + ], + "sampling_frequency_range": [ + 10, + 100 + ], + "current_sense_R": 0.5, + "current_gain": 20, + "calibrate_ADC": 1, + "polarization_params": [ + 4700, + 3000, + 4700 + ] + }, "HW_Ver. 4": { - "ADC_12B_res": 4095, - "ADC_8B_res": 255, - "ADC_cal_ref": 2.5, - - "bv_R1": 68000, - "bv_R2": 100000, - "bv_ADC": 3, - - "converter_divider": 11, - "converter_ADC": 10, - "converter_VREF": 1.24, - "converter_R1": 500000, - "converter_R2": 33000, - "converter_Rf": 56000, - "converter_DAC_lower_bound": 0, - "converter_DAC_upper_bound": 4095, - "converter_correction_codes": [1.03491, -169], - "converter_output_voltage_range": [5, 29], - - "OCP_gain": 2, - "OCP_range": [1, 300], - - "pulse_duration_range": [1, 100], - "sampling_frequency_range": [10, 100], - - "current_sense_R": 0.5, - "current_gain": 20, - - "calibrate_ADC": 1, - "polarization_params": [10000, 3000, 4700] - } -} + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.24, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [ + 1.03418631, + 0.11739705 + ], + "converter_output_voltage_range": [ + 5, + 29 + ], + "OCP_gain": 2, + "OCP_range": [ + 1, + 300 + ], + "pulse_duration_range": [ + 1, + 100 + ], + "sampling_frequency_range": [ + 10, + 100 + ], + "current_sense_R": 0.5, + "current_gain": 20, + "calibrate_ADC": 1, + "polarization_params": [ + 10000, + 3000, + 4700 + ] + }, + "HW_Ver. 5": { + "ADC_12B_res": 4095, + "ADC_8B_res": 255, + "ADC_cal_ref": 2.5, + "bv_R1": 68000, + "bv_R2": 100000, + "bv_ADC": 3, + "converter_divider": 11, + "converter_ADC": 10, + "converter_VREF": 1.24, + "converter_R1": 500000, + "converter_R2": 33000, + "converter_Rf": 56000, + "converter_DAC_lower_bound": 0, + "converter_DAC_upper_bound": 4095, + "converter_correction_codes": [ + 1.03418631, + 0.11739705 + ], + "converter_output_voltage_range": [ + 5, + 29 + ], + "OCP_gain": 2, + "OCP_range": [ + 1, + 300 + ], + "pulse_duration_range": [ + 1, + 100 + ], + "sampling_frequency_range": [ + 10, + 100 + ], + "current_sense_R": 0.5, + "current_gain": 20, + "calibrate_ADC": 1, + "polarization_params": [ + 10000, + 3000, + 4700 + ] + } +} \ No newline at end of file diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index be1b42951..c4cfa56f5 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -135,16 +135,6 @@ def __init__(self, name: str, **kwargs): """ super().__init__(name, **kwargs) - # create an empty states file if it does not exist - current_dir = os.path.dirname(os.path.abspath(__file__)) - target_file = os.path.join(current_dir, 'states.json') - source_file = os.path.join(current_dir, 'states_empty.json') - if not os.path.exists(target_file): - with open(source_file, 'r') as src: - contents = src.read() - with open(target_file, 'w') as tgt: - tgt.write(contents) - self._controller = Cryoswitch() self._switch_model = None @@ -297,4 +287,4 @@ def close(self): Disconnect from the switch controller. """ self._controller.labphox.disconnect() - super().close() + super().close() \ No newline at end of file diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json deleted file mode 100644 index ce4075a7e..000000000 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/states_empty.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "SN": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN0": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN12": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN22": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN23": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN24": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 1, - "contact_2": 0, - "contact_3": 1, - "contact_4": 1, - "contact_5": 1, - "contact_6": 1 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN25": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN26": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN300": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN6": { - "port_A": { - "contact_1": 1, - "contact_2": 1, - "contact_3": 1, - "contact_4": 1, - "contact_5": 1, - "contact_6": 1 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - }, - "SN65535": { - "port_A": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_B": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_C": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - }, - "port_D": { - "contact_1": 0, - "contact_2": 0, - "contact_3": 0, - "contact_4": 0, - "contact_5": 0, - "contact_6": 0 - } - } -} \ No newline at end of file From 39fb9c8eb7c8119f999f262b1f12e7805b157460 Mon Sep 17 00:00:00 2001 From: FKMalina Date: Tue, 14 Jan 2025 10:47:18 +0100 Subject: [PATCH 53/57] Fix: Correct type annotations for optional parameters --- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index c4cfa56f5..a016a2c77 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -136,7 +136,7 @@ def __init__(self, name: str, **kwargs): super().__init__(name, **kwargs) self._controller = Cryoswitch() - self._switch_model = None + self._switch_model: Optional[str] = None self.add_parameter( 'output_voltage', @@ -186,7 +186,7 @@ def __init__(self, name: str, **kwargs): channels.lock() self.add_submodule("channels", channels) - def _select_switch_model(self, switch_type: str = None): + def _select_switch_model(self, switch_type: Optional[str] = None): if switch_type in ['R583423141', 'CRYO']: self._controller.select_switch_model('R583423141') self._switch_model = 'R583423141' @@ -201,7 +201,7 @@ def _select_switch_model(self, switch_type: str = None): def _get_switch_model(self): return self._switch_model - def get_switches_state(self, port: str = None): + def get_switches_state(self, port: Optional[str] = None): """ Read and return the state of the contacts as recorded in the states.json From 02fbf72a7b2d635b7cb29d8f5d3a24fdf178c81f Mon Sep 17 00:00:00 2001 From: FKMalina Date: Tue, 14 Jan 2025 10:47:18 +0100 Subject: [PATCH 54/57] Fix: Correct type annotations for optional parameters --- .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index c4cfa56f5..a016a2c77 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -136,7 +136,7 @@ def __init__(self, name: str, **kwargs): super().__init__(name, **kwargs) self._controller = Cryoswitch() - self._switch_model = None + self._switch_model: Optional[str] = None self.add_parameter( 'output_voltage', @@ -186,7 +186,7 @@ def __init__(self, name: str, **kwargs): channels.lock() self.add_submodule("channels", channels) - def _select_switch_model(self, switch_type: str = None): + def _select_switch_model(self, switch_type: Optional[str] = None): if switch_type in ['R583423141', 'CRYO']: self._controller.select_switch_model('R583423141') self._switch_model = 'R583423141' @@ -201,7 +201,7 @@ def _select_switch_model(self, switch_type: str = None): def _get_switch_model(self): return self._switch_model - def get_switches_state(self, port: str = None): + def get_switches_state(self, port: Optional[str] = None): """ Read and return the state of the contacts as recorded in the states.json From 87c80962ab5d5b41b1570d4390bf11a6fb3da324 Mon Sep 17 00:00:00 2001 From: FKMalina Date: Tue, 14 Jan 2025 10:51:21 +0100 Subject: [PATCH 55/57] Add pyserial and imports --- pyproject.toml | 1 + .../drivers/QphoX/CryoSwitchController/qcodes_driver.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7b8cf4b34..822f50961 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ test = [ "pyvisa-sim", "types-tqdm>=4.64.6", "pandas-stubs", + "pyserial", ] docs = [ "sphinx", "furo", "nbsphinx", ] diff --git a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py index a016a2c77..7954e5093 100644 --- a/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py +++ b/src/qcodes_contrib_drivers/drivers/QphoX/CryoSwitchController/qcodes_driver.py @@ -2,6 +2,7 @@ from qcodes.utils.validators import Numbers,Bool,Enum from qcodes_contrib_drivers.drivers.QphoX.CryoSwitchController.CryoSwitchController import Cryoswitch import os +from typing import Optional class CryoSwitchChannel(InstrumentChannel): """ From 91130dc89580f3bde33f5e5aaed1576df8f6e1c0 Mon Sep 17 00:00:00 2001 From: FKMalina Date: Fri, 17 Jan 2025 11:30:34 +0100 Subject: [PATCH 56/57] Ignore missing pyserial stubs --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 822f50961..983361971 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,8 @@ module = [ "AMC", "ACS", "autobahn", - "python-dotenv" + "python-dotenv", + "serial" ] ignore_missing_imports = true From fdea907df4efeb2a82d5438ca64900a70b54623f Mon Sep 17 00:00:00 2001 From: FKMalina Date: Fri, 17 Jan 2025 11:35:26 +0100 Subject: [PATCH 57/57] Figuring out this stubs thing... --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 983361971..15b076d3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,8 @@ module = [ "ACS", "autobahn", "python-dotenv", - "serial" + "serial.*", + "scipy.*" ] ignore_missing_imports = true