From 19f4fe627ba12e136dfd6ba1aa409056d2d1c235 Mon Sep 17 00:00:00 2001 From: Mario Gely Date: Tue, 6 Dec 2022 16:14:40 +0000 Subject: [PATCH 1/8] current_stabilizer - adding biquad feedback (written by Marius/Ryan) --- .../devices/stabilizer/current_stabilizer.py | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/oxart/devices/stabilizer/current_stabilizer.py b/oxart/devices/stabilizer/current_stabilizer.py index 1ba7b82..ca73ac6 100755 --- a/oxart/devices/stabilizer/current_stabilizer.py +++ b/oxart/devices/stabilizer/current_stabilizer.py @@ -56,6 +56,68 @@ def configure_pi(self, kp, ki, g=0.): self.ba[3] = a1 self.ba[4] = 0. + def configure_biquad(self, zeros, poles, gain=1.): + """Calulate biquad iir filter coeficents + The function constructs the iir coeficents for a transfer function with + desired zeros and poles. + :param zeros: list of upto two zero locations in Hz, must be real or + complex conjugate pairs. + :param poles: list of upto two pole locations in Hz, must be real or + complex conjugate pairs. + :param gain: gain scaling factor of transfer function. + """ + def get_polynomial_coefs(factors): + "convert factors to coeficents" + if len(factors) == 0: + return [1., 0., 0.] + elif len(factors) == 1: + if factors[0] != 0.: + return [1., 1. / factors[0], 0.] + else: + return [0., 1., 0.] + elif len(factors) == 2: + div = factors[0] * factors[1] + if div == 0.: + return [0., factors[0] + factors[1], 1.] + else: + return [1., (factors[0] + factors[1]) / div, 1. / div] + else: + raise ValueError("Invalid number of factors") + + def z_transform(s_coefs, t_update): + """ + z-transformation of second order s polynomial in coefficients + + This uses Tustin’s transformation + see https://arxiv.org/pdf/1508.06319.pdf + + We drop a factor of 1/(1 + z^-1)^2 which is common to both + polynomials + """ + c = 2 / t_update + + z_coefs = [ + s_coefs[0] + c * s_coefs[1] + c * c * s_coefs[2], + 2 * s_coefs[0] - 2 * c * c * s_coefs[2], + s_coefs[0] - c * s_coefs[1] + c * c * s_coefs[2], + ] + return z_coefs + + num_coefs = z_transform(get_polynomial_coefs( + [2 * np.pi * x for x in zeros]), self.t_update) + denom_coefs = z_transform(get_polynomial_coefs( + [2 * np.pi * x for x in poles]), self.t_update) + + # normalise to a0 = 1 & apply gain factor + num_coefs = [np.real(x / denom_coefs[0]) * gain for x in num_coefs] + denom_coefs = [np.real(x / denom_coefs[0]) for x in denom_coefs] + + self.ba[0] = num_coefs[0] + self.ba[1] = num_coefs[1] + self.ba[2] = num_coefs[2] + self.ba[3] = -denom_coefs[1] + self.ba[4] = -denom_coefs[2] + def set_x_offset(self, o): b = self.ba[:3].sum() * self.full_scale self.y_offset = b * o / self.I_set_range @@ -190,6 +252,25 @@ async def set_feedback(self, assert self.channel in range(2) await set_feedback(self.fb_connection, self.channel, i, d, g) + async def set_feedback_biquad(self, + frontend_offset=250, + zeros=[], + poles=[], + gain = 1., + feedback_offset=0, + channel_offset=0): + d = CPU_DAC() + d.set_out(feedback_offset) + d.set_en(True) + i = IIR() + i.configure_biquad(zeros, poles, gain) + i.set_x_offset(channel_offset) + g = GPIO_HDR_SPI() + g.set_gpio_hdr(frontend_offset) + + assert self.channel in range(2) + await set_feedback(self.fb_connection, self.channel, i, d, g) + async def set_feedforward(self, coefficients=None, offset=0): if coefficients is None: coefficients = np.zeros(self.num_harmonics) @@ -199,5 +280,4 @@ async def set_feedforward(self, coefficients=None, offset=0): ff.set_cosine_amplitudes(cos_amps) ff.set_sine_amplitudes(sin_amps) ff.set_offset(offset) - await set_feedforward(self.ff_connection, ff) From 0a7d17b7d0354e470a85bca01239de7fa13df732 Mon Sep 17 00:00:00 2001 From: Mario Gely Date: Tue, 6 Dec 2022 16:22:45 +0000 Subject: [PATCH 2/8] aqctl_thorlabs_tcube - (DPN) simple_server_loop settings --- oxart/frontend/aqctl_thorlabs_tcube.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oxart/frontend/aqctl_thorlabs_tcube.py b/oxart/frontend/aqctl_thorlabs_tcube.py index 812c14c..f5b738d 100644 --- a/oxart/frontend/aqctl_thorlabs_tcube.py +++ b/oxart/frontend/aqctl_thorlabs_tcube.py @@ -67,7 +67,7 @@ def main(): try: simple_server_loop({product: dev}, common_args.bind_address_from_args(args), - args.port) + args.port,loop=asyncio.get_event_loop()) finally: dev.close() From d35733c5085076081ab2a26ae529d04322238657 Mon Sep 17 00:00:00 2001 From: Mario Gely Date: Mon, 20 Nov 2023 11:26:29 +0000 Subject: [PATCH 3/8] Brooks flow controller - asyncio bug --- oxart/frontend/aqctl_brooks_4850.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/oxart/frontend/aqctl_brooks_4850.py b/oxart/frontend/aqctl_brooks_4850.py index c7626dc..83d9ef8 100755 --- a/oxart/frontend/aqctl_brooks_4850.py +++ b/oxart/frontend/aqctl_brooks_4850.py @@ -2,6 +2,7 @@ from oxart.devices.brooks_4850.driver import Brooks4850 from sipyco.pc_rpc import simple_server_loop +import asyncio import sipyco.common_args as sca @@ -9,7 +10,7 @@ def get_argparser(): parser = argparse.ArgumentParser() parser.add_argument("-d", "--device", - default="socket://10.255.6.178:9001", + # default="socket://10.255.6.178:9001", help="address (USB Serial Number or IP:port)") sca.simple_network_args(parser, 3255) sca.verbosity_args(parser) @@ -23,7 +24,7 @@ def main(): try: simple_server_loop({"Brooks4850": dev}, sca.bind_address_from_args(args), - args.port) + args.port, loop=asyncio.get_event_loop()) finally: dev.close() From 083867872c08831562d4e05e9c0bf747d4310b06 Mon Sep 17 00:00:00 2001 From: Molly Smith Date: Mon, 6 Jan 2025 15:28:16 +0000 Subject: [PATCH 4/8] Added driver for new flowmeter --- oxart/devices/brooks_SLA5853/__init__.py | 0 oxart/devices/brooks_SLA5853/driver.py | 995 +++++++++++++++++++++++ oxart/devices/brooks_SLA5853/logger.py | 85 ++ oxart/frontend/aqctl_brooks_sla5853.py | 37 + 4 files changed, 1117 insertions(+) create mode 100644 oxart/devices/brooks_SLA5853/__init__.py create mode 100644 oxart/devices/brooks_SLA5853/driver.py create mode 100644 oxart/devices/brooks_SLA5853/logger.py create mode 100755 oxart/frontend/aqctl_brooks_sla5853.py diff --git a/oxart/devices/brooks_SLA5853/__init__.py b/oxart/devices/brooks_SLA5853/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oxart/devices/brooks_SLA5853/driver.py b/oxart/devices/brooks_SLA5853/driver.py new file mode 100644 index 0000000..9c69640 --- /dev/null +++ b/oxart/devices/brooks_SLA5853/driver.py @@ -0,0 +1,995 @@ +import serial +import socket +import select +import hart_protocol as hart +import numpy as np +import struct +import time +import sys +import logging as log + + +CMD_READ_UNIQUE_IDENTIFIER = 0 +CMD_READ_PRIMARY_VARIABLE = 1 +CMD_READ_CURRENT_AND_ALL_DYNAMIC_VARIABLES = 3 +CMD_READ_UNIQUE_IDENTIFIER_ASSOCIATED_WITH_TAG = 11 +CMD_READ_MESSAGE = 12 +CMD_SET_PRIMARY_VARIABLE_LOWER_RANGE_VALUE = 37 +CMD_RESET_CONFIGURATION_CHANGED_FLAG = 38 +CMD_PERFORM_MASTER_RESET = 42 +CMD_READ_ADDITIONAL_TRANSMITTER_STATUS = 48 +CMD_READ_DYNAMIC_VARIABLE_ASSIGNMENT = 50 +CMD_READ_GAS_NAME = 150 +CMD_READ_GAS_INFO = 151 +CMD_READ_FULL_SCALE_FLOW_RANGE = 152 +CMD_READ_STANDARD_TEMPERATURE_AND_PRESSURE = 190 +CMD_WRITE_STANDARD_TEMPERATURE_AND_PRESSURE = 191 +CMD_READ_OPERATIONAL_FLOW_SETTINGS = 193 +CMD_SELECT_PRESSURE_APPLICATION_NUMBER = 194 +CMD_SELECT_GAS_CALIBRATION_NUMBER = 195 +CMD_SELECT_FLOW_UNIT = 196 +CMD_SELECT_TEMPERATURE_UNIT = 197 +CMD_SELECT_PRESSURE_OR_FLOW_CONTROL = 199 +CMD_READ_SETPOINT_SETTINGS = 215 +CMD_SELECT_SETPOINT_SOURCE = 216 +CMD_SELECT_SOFTSTART = 218 +CMD_WRITE_LINEAR_SOFTSTART_RAMP_VALUE = 219 +CMD_READ_PID_VALUES = 220 +CMD_WRITE_PID_VALUES = 221 +CMD_READ_VALVE_RANGE_AND_OFFSET = 222 +CMD_WRITE_VALVE_RANGE_AND_OFFSET = 223 +CMD_GET_VALVE_OVERRIDE_STATUS = 230 +CMD_SET_VALVE_OVERRIDE_STATUS = 231 +CMD_READ_SETPOINT = 235 +CMD_WRITE_SETPOINT = 236 +CMD_READ_VALVE_CONTROL_VALUE = 237 +CMD_READ_PRESSURE_ALARM = 243 +CMD_WRITE_PRESSURE_ALARM = 244 +CMD_READ_ALARM_ENABLE_SETTING = 245 +CMD_WRITE_ALARM_ENABLE_SETTING = 246 +CMD_READ_FLOW_ALARM = 247 +CMD_WRITE_FLOW_ALARM = 248 + +#The unit dictionaries below don't contain all units listed in the flowmeter's manual (yet). +# Dictionaries for flow rate units +flow_rate_code_to_str_dict = {17: "l/min", 19: "m^3/h", 24: "l/s", 28: "m^3/s", 70: "g/s", 71: "g/min", 72: "g/h", 73: "kg/s", 74: "kg/min", + 75: "kg/h", 131: "m^3/min", 138: "l/h", 170: "ml/s", 171: "ml/min", 172: "ml/h"} +flow_rate_str_to_code_dict = dict([reversed(i) for i in flow_rate_code_to_str_dict.items()]) + +# Dictionary for flow reference conditions +flow_reference_dict = {0: "Normal (273.15 Kelvin/1013.33 mbar)", 1: "Standard (user-defined through separate command)", 2: "As defined at calibration"} + +# Dictionaries for density units +density_code_to_str_dict = {91: "g/cm^3", 92: "kg/m^3", 95: "g/ml", 96: "kg/l", 97: "g/l"} +density_str_to_code_dict = dict([reversed(i) for i in density_code_to_str_dict.items()]) + +# Dictionaries for temperature units +temperature_code_to_str_dict = {32: "deg Celsius", 33: "deg Fahrenheit", 35: "Kelvin"} +temperature_str_to_code_dict = dict([reversed(i) for i in temperature_code_to_str_dict.items()]) + +# Dictionaries for pressure units +pressure_code_to_str_dict = {5: "PSI", 7: "bar", 8: "mbar", 11: "Pa", 12: "kPa", 13: "Torr", 14: "Std Atmosphere", 232: "atm", + 238: "bar", 241: "Counts", 242: "%", 244: "mTorr" } +pressure_str_to_code_dict = dict([reversed(i) for i in pressure_code_to_str_dict.items()]) + +# Dictionary to look up assignment of dynamic variables +dynamic_variables_dict = {0: "Flow rate", 1: "Temperature", 2: "Pressure"} + +# Dictionaries for valve override +valve_override_code_to_str_dict = {0: "off", 1: "open", 2: "close", 4: "manual"} +valve_override_str_to_code_dict = dict([reversed(i) for i in valve_override_code_to_str_dict.items()]) + + + +# Create a dictionary to decode packed-ASCII messages +# First, append all entries from "@" to "_" to the dictionary +packed_ascii_to_char_dict = {} +for i in range(0x1F + 1): + packed_ascii_to_char_dict[i] = chr(i + 64) +# Next, append all entries from (SPACE) to "?" to the dictionary +for i in range(32): + packed_ascii_to_char_dict[0x20 + i] = chr(i + 32) + +# Also create the reversed dictionary to encode packed-ASCII messages +char_to_packed_ascii_dict = dict([reversed(i) for i in packed_ascii_to_char_dict.items()]) + + + +class CorruptionError(RuntimeError): + pass + + +class BrooksSLA5853: + """Brooks SLA5853 mass flow controller driver""" + def __init__(self, ip_address, port, device_tag = "43200327"): + """ Connect to a Brookes flowmeter""" + self.flowmeter = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # Connect to the ES device using a Raw TCP socket + self.flowmeter.connect((ip_address, port)) + print("Connected to the flowmeter") + self.device_identifier = self.read_unique_identifier_associated_with_tag(device_tag) + self.long_frame_address = self.construct_long_address_from_device_id(self.device_identifier) + print("Created long frame address of flowmeter") + # Set flow unit to l/min + # This also sets the flow reference condition to "as calibrated", if no other reference code is provided + self.select_flow_unit("l/min") + # Set setpoint source to 3, i.e. digital + self.select_setpoint_source(3) + # Make sure that valve override is off + self.set_valve_override_status("off") + + def read_response(self): + """ Read the response from the flowmeter. + Return format is: command (int), status_1 (int), status_2 (int), payload (bytes object)""" + + # Read response of the flowmeter + # The "ready" variable is used to make sure that the recv() function is only called if data is available + ready = select.select([self.flowmeter], [], [], 1) + if ready[0]: + message = self.flowmeter.recv(1024) + else: + raise CorruptionError("Device is not responding") + ready = select.select([self.flowmeter], [], [], 1) + while ready[0]: + message += self.flowmeter.recv(1024) + ready = select.select([self.flowmeter], [], [], 1) + + # Decode the response, i.e., return command, status info and payload + # Skip preamble characters + # The start character of an "acknowledge" message from slave to master is 0x86 + j = 0 + while message[j] != 0x86: + j += 1 + # Also skip the start and address characters (6 bytes in total, assuming long frame addressing) + j += 6 + # The command byte is the one after the address characters + # and the command is represented by an integer value + command = message[j] + j += 1 + # Next byte: byte count character (indicates number of status and data bytes in the response message) + byte_count = message[j] + j += 1 + # The next two bytes are the status characters + status_1 = message[j] + j += 1 + status_2 = message[j] + j += 1 + # Extract the payload consisting of (byte_count - 2) bytes + if byte_count > 2: + payload = message[j:(j + byte_count - 2)] + j += byte_count - 2 + else: + payload = [] + + # Calculate checksum and compare to checksum byte at the end of the message + checksum_message = message[j] + checksum_calculated = self.calculate_longitudinal_parity(message[:j], message_type = 'acknowledge') + if checksum_message != checksum_calculated: + raise CorruptionError("Bad checksum received") + + return command, status_1, status_2, payload + + def send_command(self, command_number, payload = None, data_format_string = None): + """Returns a message from the given input parameters that has the correct format to be sent + to the flowmeter. + """ + + message = struct.pack("B", 0xFF) + for n in range(4): + message += struct.pack("B", 0xFF) # Preamble characters + message += struct.pack("B", 0x82) # Start character (for long frame addressing and Start-Of-Text) + # Add long frame address to message + message += self.long_frame_address + # Add command + message += struct.pack("B", command_number) + # Initialize empty bytes object to store payload + payload_bytes = b'' + if payload is None: + size_payload = 0 + #message += struct.pack("B", size_payload) # this is the "byte count char", indicating the length of the payload in bytes + else: + size_payload = struct.calcsize(data_format_string) + j = 0 + if size_payload > 24: + raise ValueError("Payload is larger than 24 bytes") + if data_format_string[0] in ['<', '>', '!', '=', '@']: + j = 1 + byte_order_string = data_format_string[0] + else: + byte_order_string = '' + for i in range(len(data_format_string) - j): + # Adding information on the byte order to every data format string is important correctly encode the data + format_char = str(byte_order_string) + str(data_format_string[i + j]) + # Count number of "I"s in data_format_string, and subtract 1 from size_payload for each "I" + # This is because the standard size of an unsigned int is 4 bytes, while the SLA5853 expects 3 bytes + if format_char == "I": + payload_bytes += struct.pack("I", payload[i])[0:3] + size_payload = size_payload - 1 + else: + payload_bytes += struct.pack(format_char, payload[i]) + + message += struct.pack("B", size_payload) + message += payload_bytes + + # The longitudinal parity (XOR of all message characters) is used here as the checksum + checksum = self.calculate_longitudinal_parity(message) + message += struct.pack("B", checksum) + + #print(message) + self.flowmeter.send(message) + + def read_unique_identifier_associated_with_tag(self, tag_as_string): + """Return the unique identifier associated with the tag of the flowmeter.""" + tag = hart.tools.pack_ascii(tag_as_string) + self.flowmeter.send(hart.universal.read_unique_identifier_associated_with_tag(tag)) + response = self.read_response() + + # Check if returned command is the expected one + if response[0] != CMD_READ_UNIQUE_IDENTIFIER_ASSOCIATED_WITH_TAG: + raise CorruptionError("Incorrect command returned") + + # Raise potential error messages in status info + #self.decode_status_info(response[1], response[2]) + + # Extract the device id from the payload + device_id = response[3][9:12] + + return device_id + + def calculate_longitudinal_parity(self, message, message_type = 'start_of_text'): + """Calculates the longitudinal parity, which is used as the checksum in the communication + with the SLA5853 flow meter, of the given message (excluding the preamble characters). + + The longitudinal parity is the exclusive-or of all message bytes (characters from the start + character up to just before the checksum). To avoid counting the preamble characters, the function + searches for the first occurrence of either 82 (hex, start character for 'start_of_text' message, i.e. + from master to slave) or 86 ('acknowledge' message, i.e. slave to master) and only starts applying XOR + from that location onwards.""" + + # First, make sure to skip the preamble characters + j = 0 + if message_type == 'start_of_text': + while message[j] != 0x82: + j += 1 + elif message_type == 'acknowledge': + while message[j] != 0x86: + j += 1 + else: + raise ValueError("message_type must be either 'start_of_text' or 'acknowledge' ") + + message_length = len(message) - j + checksum = message[j] ^ message[j + 1] + for i in range(message_length - 2): + checksum = checksum ^ message[i + 2 + j] + + return checksum + + def ping(self): + """Return true if flowmeter reacts to commands (more specifically, the read_flow_rate_and_temperature + command). Return false otherwise.""" + try: + read = self.read_flow_rate_and_temperature() + except IOError: # serial errors inherit from this inbuilt + return False + if read is not None: + return True + else: + return False + + def decode_status_info(self, status_1, status_2): + """ Decode error messages in the two status bytes of a slave-to-master message. + Returns status_1 because for some commands, it can contain additional information. """ + + # Transform status bytes into binary arrays to extract bitwise-encoded information + status_1_array = np.array([[status_1]], dtype = np.uint8) + status_1_binary = np.unpackbits(status_1_array) + status_2_array = np.array([[status_2]], dtype = np.uint8) + status_2_binary = np.unpackbits(status_2_array) + + # If the second status byte is 0 and bit 7 of the first status byte is 1, the communication failed. + # The type of communication error is encoded in status byte 1. + if status_2 == 0 and status_1_binary[0] == 1: + # Go through status byte 1 bit by bit and print all error messages where the bit is of value 1 + error_messages = [ "Parity error", "Overrun error", "Framing error", "Checksum error", "Reserved", "Rx Buffer Overflow"] + for i in range(7): + if status_1_binary[i + 1] == 1: + print(error_messages[i]) + raise CorruptionError("Communication failed") + # If the second status byte is non-zero, communication did not fail. + # In that case: status_1 contains command execution info, status_2 contains info on device status + else: + # First, print command execution info + if status_1 == 2: + raise CorruptionError("Invalid selection") + elif status_1 == 3: + raise ValueError("Passed parameter too large") + elif status_1 == 4: + raise ValueError("Passed parameter too small") + elif status_1 == 5: + raise CorruptionError("Incorrect byte count") + elif status_1 == 6: + raise CorruptionError("Transmitter specific command error") + elif status_1 == 7: + raise CorruptionError("In Write-Protect Mode") + elif status_1 == 16: + raise CorruptionError("Access Restricted") + elif status_1 == 32: + raise CorruptionError("Device is busy") + elif status_1 == 64: + raise CorruptionError("Command Not Implemented") + elif status_1 > 7 and status_1 < 16: + raise CorruptionError("Command-specific error") + # Next, print device status info + # Go through status byte 2 bit by bit and print all error messages where the bit is of value 1 + error_messages_2 = ["Device malfunction", "Configuration changed", "Cold start", "More status available", "Primary variable analog output fixed", "Primary variable analog output saturated", "Non primary variable out of range", "Primary variable out of range"] + for i in range(8): + if status_2_binary[i] == 1: + print(error_messages_2[i]) + # Print more status info if available + if status_2_binary[3] == 1: + self.read_additional_transmitter_status() + + def construct_long_address_from_device_id(self, device_id, manufacturer_id = 10, device_type_code = 100): + """ Returns the long frame address of a device with the given identifiers. + Format of the long frame address is: + Byte 0: Manufacturer_id OR 0x80 + Byte 1: device type code (100 in decimal for Brooks SLA Series) + Bytes 2 to 4: device identifier + """ + + long_frame_address = struct.pack(">B", manufacturer_id | 0x80) + long_frame_address += struct.pack(">B", device_type_code) + for i in range(len(device_id)): + long_frame_address += struct.pack(">B", device_id[i]) + + return long_frame_address + + def read_process_gas_type(self, gas_selection_code = 1): + """Return the gas type corresponding to the given gas selection code (int between 1 and 6).""" + + self.send_command(CMD_READ_GAS_NAME, [gas_selection_code], ">B") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + # Check if the expected gas selection code was returned + if gas_selection_code != response[3][0]: + raise CorruptionError("Incorrect gas selection code returned") + # The remaining payload of the response contains name or chemical formula of the gas, encoded in ASCII + # Decode the gas type and return it as a string + #gas_type_string_length = len(response[3])-1 + gas_type_string = "".join(chr(i) for i in response[3][1:]) + + return gas_type_string + + def select_gas_calibration(self, calibration_number = 1): + """Select a gas calibration from the available calibrations. + Refer to the Product/Calibration Data Sheet(s) shipped with each device to determine the proper + gas calibration number for the desired gas/flow conditions. + """ + if calibration_number < 1 or calibration_number > 6: + raise ValueError("Undefined calibration number.") + + self.send_command(CMD_SELECT_GAS_CALIBRATION_NUMBER, [calibration_number], ">B") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + # Check if the expected gas selection code was returned + returned_calibration_number = response[3][0] + if returned_calibration_number != calibration_number: + raise CorruptionError("Incorrect calibration number returned") + + print("Set calibration number of SLA5853 flowmeter to ", returned_calibration_number) + + def select_temperature_unit(self, temperature_unit = "Kelvin"): + """Sets the temperature unit to either deg Celsius, deg Fahrenheit or Kelvin. + The corresponding temperature unit codes are: + 32: deg Celsius + 33: deg Fahrenheit + 35: Kelvin + """ + if temperature_unit not in temperature_str_to_code_dict: + raise ValueError("Undefined temperature unit. Choose either deg Celsius, deg Fahrenheit or Kelvin.") + + temperature_unit_code = temperature_str_to_code_dict[temperature_unit] + + self.send_command(CMD_SELECT_TEMPERATURE_UNIT, [temperature_unit_code], ">B") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + # Check if the expected gas selection code was returned + returned_temp_unit_code = response[3][0] + if temperature_unit_code != returned_temp_unit_code: + raise CorruptionError("Incorrect temperature unit code returned") + + print("Set temperature unit of SLA5853 flowmeter to " + temperature_unit) + + def read_setpoint(self): + """Returns current setpoint value in % of full scale and in selected unit. + + The selected unit for the flowmeter used for comet, lab 3, with serial number 06C43200327 is "ml/min", + which corresponds to flow rate unit code 171. Many more flow rate units are available; to use this code with + a different device and a potentially different unit, add the respective code and unit description in the code below. + """ + self.send_command(CMD_READ_SETPOINT) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + setpoint_in_percent = struct.unpack(">f", response[3][1:5])[0] + selected_unit_code = response[3][5] + if selected_unit_code in flow_rate_code_to_str_dict: + selected_unit_string = flow_rate_code_to_str_dict[selected_unit_code] + else: + raise ValueError("Undefined flow unit code") + setpoint_in_selected_unit = struct.unpack(">f", response[3][6:10])[0] + + return setpoint_in_percent, setpoint_in_selected_unit, selected_unit_string + + def write_setpoint(self, setpoint_value, unit_code = 57): + """Sets flow setpoint to the input parameter setpoint_value, given either in percent (default, unit_code = 57) of + full scale or selected unit (unit_code = 250). + """ + self.send_command(CMD_WRITE_SETPOINT, [unit_code, setpoint_value], ">Bf") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + returned_percent_unit_code = response[3][0] + # Check if the returned percent unit code is correct (should be 57) + if returned_percent_unit_code != 57: + raise CorruptionError("Returned incorrect percent unit code") + new_setpoint_in_percent = struct.unpack(">f", response[3][1:5])[0] + selected_unit = response[3][5] + if selected_unit in flow_rate_code_to_str_dict: + selected_unit_string = flow_rate_code_to_str_dict[selected_unit] + else: + raise ValueError("Undefined flow unit code") + new_setpoint_in_selected_unit = struct.unpack(">f", response[3][6:10])[0] + + return new_setpoint_in_percent, new_setpoint_in_selected_unit, selected_unit_string + + def read_full_scale_flow_range(self, gas_select_code = 1): + """Returns full scale flow range for the selected gas type (gas_select_code is int between 1 and 6, if the device has + only been calibrated for one type of gas it's always 1). + + The selected unit for the flowmeter used for comet, lab 3, with serial number 06C43200327 is "ml/min", + which corresponds to flow rate unit code 171. Many more flow rate units are available; to use this code with + a different device and a potentially different unit, add the respective code and unit description in the code below. + """ + self.send_command(CMD_READ_FULL_SCALE_FLOW_RANGE, [gas_select_code], "f", response[3][1:5])[0] + + return flow_rate_range_in_selected_unit, selected_unit_string + + def read_standard_temperature_and_pressure_range(self): + """Read the standard temperature and pressure values from the device’s memory. + The standard temperature and pressure are reference values which can be set by the user """ + + self.send_command(CMD_READ_STANDARD_TEMPERATURE_AND_PRESSURE) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + temperature_unit_code = response[3][0] + if temperature_unit_code in temperature_code_to_str_dict: + temperature_unit_string = temperature_code_to_str_dict[temperature_unit_code] + else: + raise ValueError("Undefined temperature unit code") + standard_temperature = struct.unpack(">f", response[3][1:5])[0] + pressure_unit_code = response[3][5] + if pressure_unit_code in pressure_code_to_str_dict: + pressure_unit_string = pressure_code_to_str_dict[pressure_unit_code] + else: + raise ValueError("Undefined pressure unit code") + standard_pressure = struct.unpack(">f", response[3][6:10])[0] + + return standard_temperature, temperature_unit_string, standard_pressure, pressure_unit_string + + def write_standard_temperature_and_pressure(self, standard_temp, standard_pressure, temp_unit = "Kelvin", pressure_unit = "Pa"): + """Write the standard temperature and pressure values into the device's memory and return the newly set values, + together with their units (as strings). + + The standard temperature and pressure are reference values which can be set by the user and which are used + in the conversion of flow units. """ + + # Check if the given temperature unit is defined, and if yes, find the corresponding unit code. + if temp_unit not in temperature_str_to_code_dict: + raise ValueError("Undefined temperature unit. Choose either deg Celsius, deg Fahrenheit or Kelvin.") + temperature_unit_code = temperature_str_to_code_dict[temp_unit] + # Check if the given pressure unit is defined, and if yes, find the corresponding unit code. + if pressure_unit not in pressure_str_to_code_dict: + raise ValueError("Undefined pressure unit") + pressure_unit_code = pressure_str_to_code_dict[pressure_unit] + + self.send_command(CMD_WRITE_STANDARD_TEMPERATURE_AND_PRESSURE, [temperature_unit_code, standard_temp, pressure_unit_code, standard_pressure], "f", response[3][1:5])[0] + pressure_unit_code = response[3][5] + if pressure_unit_code in pressure_code_to_str_dict: + pressure_unit_string = pressure_code_to_str_dict[pressure_unit_code] + else: + raise ValueError("Undefined pressure unit code returned") + standard_pressure_new = struct.unpack(">f", response[3][6:10])[0] + + return standard_temperature_new, temperature_unit_string, standard_pressure_new, pressure_unit_string + + def read_flow_settings(self): + """Read the operational settings from the device. These settings consist of the selected gas number, + the selected flow reference condition, the selected flow unit and the selected temperature unit. + """ + self.send_command(CMD_READ_OPERATIONAL_FLOW_SETTINGS) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + selected_gas_number = response[3][0] + selected_flow_reference = response[3][1] + if selected_flow_reference in flow_reference_dict: + selected_flow_reference_string = flow_reference_dict[selected_flow_reference] + else: + raise ValueError("Undefined flow unit code") + selected_flow_unit = response[3][2] + if selected_flow_unit in flow_rate_code_to_str_dict: + selected_flow_unit_string = flow_rate_code_to_str_dict[selected_flow_unit] + else: + raise ValueError("Undefined flow unit code") + selected_temperature_unit = response[3][3] + if selected_temperature_unit in temperature_code_to_str_dict: + selected_temperature_unit_string = temperature_code_to_str_dict[selected_temperature_unit] + else: + raise ValueError("Undefined temperature unit code") + + return selected_gas_number, selected_flow_reference_string, selected_flow_unit_string, selected_temperature_unit_string + + def select_flow_unit(self, selected_flow_unit_string, selected_flow_ref_code = 2): + """Select a flow unit and the flow reference condition. The selected flow unit will be used in the conversion of flow data """ + + # Check if the given flow unit is valid and if yes, find the corresponding unit code + if selected_flow_unit_string not in flow_rate_str_to_code_dict: + raise ValueError("Undefined flow rate unit.") + flow_rate_unit_code = flow_rate_str_to_code_dict[selected_flow_unit_string] + # Check if given reference code is valid + if selected_flow_ref_code not in flow_reference_dict: + raise ValueError("Undefined reference code. Choose either 0 (273.15 Kelvin/1013.33 mbar), 1 (user-defined) or 2 (as calibrated).") + + self.send_command(CMD_SELECT_FLOW_UNIT, [selected_flow_ref_code, flow_rate_unit_code], "BB") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + # Check if the expected flow reference code was returned + returned_flow_ref_code = response[3][0] + if returned_flow_ref_code != selected_flow_ref_code: + raise CorruptionError("Incorrect flow reference code returned") + # Check if the expected flow rate unit code was returned + returned_flow_rate_unit_code = response[3][1] + if returned_flow_rate_unit_code != flow_rate_unit_code: + raise CorruptionError("Incorrect flow rate unit code returned") + + print("Set flow reference to " + flow_reference_dict[returned_flow_ref_code] + " and flow rate unit to " + flow_rate_code_to_str_dict[returned_flow_rate_unit_code]) + + def read_setpoint_settings(self): + """Read the setpoint related settings from the device. + + The settings contain the setpoint source indication: + Setpoint source code = 1: analog 0 - 5 V / 0 - 10 V / 0 - 20 mA + Setpoint source code = 2: analog 4 - 20 mA + Setpoint source code = 3: digital + the type of softstart and the softstart ramp. """ + + self.send_command(CMD_READ_SETPOINT_SETTINGS) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + setpoint_source_selection_code = response[3][0] + # According to the device's manual, the returned setpoint span should always be 1.0 + setpoint_span = struct.unpack(">f", response[3][1:5])[0] + # According to the device's manual, the returned setpoint offset should always be 0.0 + setpoint_offset = struct.unpack(">f", response[3][5:9])[0] + softstart_selection_code = response[3][9] + softstart_ramp_value = struct.unpack(">f", response[3][10:14])[0] + + return setpoint_source_selection_code, setpoint_span, setpoint_offset, softstart_selection_code, softstart_ramp_value + + def select_setpoint_source(self, setpoint_source_code): + """Select the setpoint source to be used as setpoint input. Possible setpoint source codes and their meanings are: + + 1, 2: Analog Input and Output type configured during production + 3: digital + 10: Analog Input and Output 0-5 V + 11: Analog Input and Output 1-5 V + 20: Analog Input and Output 0-20 mA + 21: Analog Input and Output 4-20 mA """ + + # Check if setpoint source code is valid + if setpoint_source_code not in [1, 2, 3, 10, 11, 20, 21]: + raise ValueError("Setpoint source code undefined.") + + self.send_command(CMD_SELECT_SETPOINT_SOURCE, [setpoint_source_code], "B") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + returned_setpoint_source_code = response[3][0] + # Check if input and returned setpoint source codes match + if returned_setpoint_source_code != setpoint_source_code: + raise ValueError("Incorrect setpoint source code returned.") + print("Set setpoint source code to " + str(returned_setpoint_source_code)) + + def read_valve_settings(self): + """Read the Valve Range and Valve Offset values from the device. + + The settings are 24-bit unsigned integers used to fine tune the D/A converter for the valve control. + The numbers are dimensionless and sized to the range of 0 to 62500. 100% flow is achieved with the number + valve offset + valve range. Also, the sum of both should not be over 62500. """ + + self.send_command(CMD_READ_VALVE_RANGE_AND_OFFSET) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + valve_range = int.from_bytes(response[3][0:3],'big',signed=False) + valve_offset = int.from_bytes(response[3][3:6],'big',signed=False) + # Check if the sum of valve range and valve offset is <= 62500 + if (valve_range + valve_offset) > 62500: + raise ValueError("Sum of valve range and offset is too high (> 62500)") + + return valve_range, valve_offset + + def write_valve_settings(self, valve_range = 0, valve_offset = 0): + """Write the Valve Offset values into the device. The Valve Range is not used in devices of the SLA Enhanced Series, + therefore, this value should always be set to 0. + + The settings are 24-bit unsigned integers used to fine tune the D/A converter for the valve control. + The numbers are dimensionless and sized to the range of 0 to 62500. 100% flow is achieved with the number + valve offset + valve range. Also, the sum of both should not be over 62500 """ + + # Check if valve range + offset is within a valid range (i.e., >= 0 and <= 62500) + range_and_offset_sum = valve_range + valve_offset + if range_and_offset_sum < 0 or range_and_offset_sum > 62500: + raise ValueError("Invalid range and/or offset value. Range + offset needs to be <= 62500") + + self.send_command(CMD_WRITE_VALVE_RANGE_AND_OFFSET, [valve_range, valve_offset], "II") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + returned_valve_range = int.from_bytes(response[3][0:3],'big',signed=False) + returned_valve_offset = int.from_bytes(response[3][3:6],'big',signed=False) + # Check if the sum of valve range and valve offset is <= 62500 + if (returned_valve_range + returned_valve_offset) > 62500: + raise ValueError("Sum of returned valve range and offset is too high (> 62500)") + + return returned_valve_range, returned_valve_offset + + def read_gas_info(self, gas_selection_code = 1): + """Read the density of the selected gas (gas selection code is int between 1 and 6), the operational flow range and + the reference temperature and pressure for the flow range. + + The flow range equals the volume flow in engineering units at 100% as calibrated. The reference temperature + and pressure are the conditions at which the volume flow is specified.""" + + self.send_command(CMD_READ_GAS_INFO, [gas_selection_code], "B") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + # Check if the expected gas selection code was returned + if gas_selection_code != response[3][0]: + raise CorruptionError("Incorrect gas selection code returned") + density_unit_code = response[3][1] + if density_unit_code in density_code_to_str_dict: + density_unit_string = density_code_to_str_dict[density_unit_code] + else: + raise ValueError("Undefined density unit code") + gas_density = struct.unpack(">f", response[3][2:6])[0] + reference_temperature_unit_code = response[3][6] + if reference_temperature_unit_code in temperature_code_to_str_dict: + reference_temperature_unit_string = temperature_code_to_str_dict[reference_temperature_unit_code] + else: + raise ValueError("Undefined temperature unit code") + reference_temperature = struct.unpack(">f", response[3][7:11])[0] + reference_pressure_unit_code = response[3][11] + if reference_pressure_unit_code in pressure_code_to_str_dict: + reference_pressure_unit_string = pressure_code_to_str_dict[reference_pressure_unit_code] + else: + raise ValueError("Undefined pressure unit code") + reference_pressure = struct.unpack(">f", response[3][12:16])[0] + reference_flow_rate_unit_code = response[3][16] + if reference_flow_rate_unit_code in flow_rate_code_to_str_dict: + reference_flow_rate_unit_string = flow_rate_code_to_str_dict[reference_flow_rate_unit_code] + else: + raise ValueError("Undefined flow rate unit code") + reference_flow_range = struct.unpack(">f", response[3][17:21])[0] + + return gas_density, density_unit_string, reference_temperature, reference_temperature_unit_string, reference_pressure, reference_pressure_unit_string, reference_flow_range, reference_flow_rate_unit_string + + def read_pid_controller_values(self): + """Return PID controller parameters in the order K_p, K_i, K_d """ + + self.send_command(CMD_READ_PID_VALUES) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + k_p = struct.unpack("f", response[3][0:4])[0] + k_i = struct.unpack("f", response[3][4:8])[0] + k_d = struct.unpack("f", response[3][8:12])[0] + + return k_p, k_i, k_d + + def write_pid_controller_values(self, k_p_new, k_i_new, k_d_new): + """Set PID controller parameters to the values given by k_p_new, k_i_new, k_d_new and + return the new values in the same order if successful. """ + + self.send_command(CMD_WRITE_PID_VALUES, [k_p_new, k_i_new, k_d_new], "fff") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + k_p = struct.unpack("f", response[3][0:4])[0] + k_i = struct.unpack("f", response[3][4:8])[0] + k_d = struct.unpack("f", response[3][8:12])[0] + + return k_p, k_i, k_d + + def close_connection(self): + """Closes connection to flowmeter.""" + self.flowmeter.close() + # print("Disconnected from flowmeter") + + def read_flow_rate_and_temperature(self): + """Returns flow rate and temperature, together with the respective units. + + The underlying command is command #3, "Read current and all dynamic variables". + In case of the Brooks SLA5853 flowmeter (type MFC), there are two dynamic variables, the primary + and the secondary one. """ + + self.send_command(CMD_READ_CURRENT_AND_ALL_DYNAMIC_VARIABLES) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + # Read analog output current (mA) or voltage (V) + analog_output = struct.unpack(">f", response[3][0:4])[0] + # Read primary variable unit code (for SLA5853, primary variable = flow rate) + prim_var_unit_code = response[3][4] + # Create string to describe primary variable unit + if prim_var_unit_code in flow_rate_code_to_str_dict: + prim_var_unit_string = flow_rate_code_to_str_dict[prim_var_unit_code] + else: + raise ValueError("Undefined flow rate unit") + # Read value of primary variable + prim_variable = struct.unpack(">f", response[3][5:9])[0] + # Read secondary variable unit code (for SLA5853, secondary variable = temperature) + scd_var_unit_code = response[3][9] + # Create string to describe secondary variable unit + if scd_var_unit_code in temperature_code_to_str_dict: + scd_var_unit_string = temperature_code_to_str_dict[scd_var_unit_code] + else: + raise ValueError("Undefined temperature unit") + # Read value of secondary variable + scd_variable = struct.unpack(">f", response[3][10:14])[0] + + + + return prim_variable, prim_var_unit_string, scd_variable, scd_var_unit_string + + def zero_flow_rate_sensor(self): + """Zero sensor associated with flow rate measurement. + Only use this command when there is no flow through the device!""" + + self.send_command(CMD_SET_PRIMARY_VARIABLE_LOWER_RANGE_VALUE) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + print("Flow rate sensor has been zeroed.") + + def reset_configuration_changed_flag(self): + """Resets the configuration changed response code, bit #6 of the transmitter status byte. + Primary master devices, address ‘1’, should only issue this command after the configuration + changed response code has been detected and acted upon. """ + + self.send_command(CMD_RESET_CONFIGURATION_CHANGED_FLAG) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + print("Configuration-changed flag has been reset.") + + def reset_flowmeter(self): + """Reset the device’s microprocessor. The device will respond first and then perform the master reset. """ + + self.send_command(CMD_PERFORM_MASTER_RESET) + print("Flowmeter will be reset now.") + + def read_dynamic_variable_assignments(self): + """Read transmitter variable numbers assigned to primary and secondary variable and find their definitions. """ + + self.send_command(CMD_READ_DYNAMIC_VARIABLE_ASSIGNMENT) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + prim_var_code = response[3][0] + scd_var_code = response[3][1] + # Find definitions of both variable codes in dictionary + if prim_var_code in dynamic_variables_dict: + prim_var_description = dynamic_variables_dict[prim_var_code] + else: + raise ValueError("Undefined variable number 1") + if scd_var_code in dynamic_variables_dict: + scd_var_description = dynamic_variables_dict[scd_var_code] + else: + raise ValueError("Undefined variable number 2") + + return prim_var_description, scd_var_description + + def read_valve_control_value(self): + """Read the current valve control value. The valve control value is a dimensionless number in the range + from 0 to 62500. It represents the value sent to the D/A-converter used to control the valve """ + + self.send_command(CMD_READ_VALVE_CONTROL_VALUE) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + valve_control_value = int.from_bytes(response[3][0:3],'big',signed=False) + # Check if valve control value is <= 62500 + if valve_control_value > 62500: + raise ValueError("Invalid valve control value") + + return valve_control_value + + def get_valve_override_status(self): + """Get the current valve override status from the device. The valve override status can be set to + either off (no valve override), close, open or manual. """ + + self.send_command(CMD_GET_VALVE_OVERRIDE_STATUS) + response = self.read_response() + self.decode_status_info(response[1], response[2]) + valve_override_code = response[3][0] + # Get the description of the returned override code from the dictionary + valve_override_description = valve_override_code_to_str_dict[valve_override_code] + + return valve_override_description + + def set_valve_override_status(self, valve_override_status = "off"): + """Set the valve override status of the device. The valve override status can be set to + either off (no valve override), close, open or manual. """ + + # Get the code corresponding to the valve override status description given + valve_override_code = valve_override_str_to_code_dict[valve_override_status] + + self.send_command(CMD_SET_VALVE_OVERRIDE_STATUS, [valve_override_code], ">B") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + returned_valve_override_code = response[3][0] + # Get the description of the returned override code from the dictionary + returned_valve_override_description = valve_override_code_to_str_dict[returned_valve_override_code] + + return returned_valve_override_description + + def select_softstart_mode(self, softstart_selection_code = 0): + """Select the softstart type to be used by the device. The softstart mode can be set to either + disabled or time. When Time is selected, then the Software Ramp value (see Command #219) + will be the time required to ramp to a new setpoint expressed in seconds. """ + + # Check if the given softstart selection code is valid (it must be either 0 or 4) + if softstart_selection_code not in [0, 4]: + raise ValueError("Invalid softstart selection code") + + self.send_command(CMD_SELECT_SOFTSTART, [softstart_selection_code], ">B") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + returned_softstart_selection_code = response[3][0] + # Check if the input and returned softstart selection codes match + if returned_softstart_selection_code != softstart_selection_code: + raise CorruptionError("Incorrect softstart selection code returned") + + if returned_softstart_selection_code == 0: + print("Switched softstart off.") + elif returned_softstart_selection_code == 4: + print("Switched to linear softstart") + + return returned_softstart_selection_code + + def write_linear_softstart_ramp_time(self, ramp_time): + """Write the linear softstart ramp time (in seconds) into the device’s memory. """ + + self.send_command(CMD_WRITE_LINEAR_SOFTSTART_RAMP_VALUE, [ramp_time], ">f") + response = self.read_response() + self.decode_status_info(response[1], response[2]) + returned_ramp_time = struct.unpack(" Date: Tue, 3 Feb 2026 15:20:58 +0000 Subject: [PATCH 5/8] aqctl_thermostat_legacy - added --- oxart/frontend/aqctl_thermostat_legacy.py | 41 +++++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 42 insertions(+) create mode 100644 oxart/frontend/aqctl_thermostat_legacy.py diff --git a/oxart/frontend/aqctl_thermostat_legacy.py b/oxart/frontend/aqctl_thermostat_legacy.py new file mode 100644 index 0000000..2f3ec89 --- /dev/null +++ b/oxart/frontend/aqctl_thermostat_legacy.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import argparse +import logging + +from oxart.devices.thermostat.driver import Thermostat +from sipyco.pc_rpc import simple_server_loop +import sipyco.common_args as sca + +logger = logging.getLogger(__name__) + + +def get_argparser(): + parser = argparse.ArgumentParser(description="ARTIQ controller for " + "sinara Thermostat") + parser.add_argument("-d", "--device", help="device's IP address") + sca.simple_network_args(parser, 4300) + sca.verbosity_args(parser) + return parser + + +def main(): + args = get_argparser().parse_args() + sca.init_logger_from_args(args) + + dev = Thermostat(args.device) + + # Q: Why not use try/finally for port closure? + # A: We don't want to try to close the serial if sys.exit() is called, + # and sys.exit() isn't caught by Exception + try: + simple_server_loop({"Thermostat": dev}, sca.bind_address_from_args(args), + args.port) + except Exception: + dev.close() + else: + dev.close() + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index cdf72ea..9c1f883 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ aqctl_tti_ql355 = "oxart.frontend.aqctl_tti_ql355:main" aqctl_thorlabs_pm100a = "oxart.frontend.aqctl_thorlabs_pm100a:main" aqctl_v3500a = "oxart.frontend.aqctl_v3500a:main" aqctl_thermostat = "oxart.frontend.aqctl_thermostat:main" +aqctl_thermostat_legacy = "oxart.frontend.aqctl_thermostat_legacy:main" arduino_dac_controller = "oxart.frontend.arduino_dac_controller:main" bb_shutter_controller = "oxart.frontend.bb_shutter_controller:main" bme_pulse_picker_timing_controller = "oxart.frontend.bme_pulse_picker_timing_controller:main" From 13d991c454be04142c9558c5d29e04a7590cb90b Mon Sep 17 00:00:00 2001 From: Molly Smith Date: Tue, 3 Feb 2026 15:21:59 +0000 Subject: [PATCH 6/8] thorlabs_tcube/driver.py - add functions to set voltage and voltage limits to a Tdc (as is possible with a Tpz) --- oxart/devices/thorlabs_tcube/driver.py | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/oxart/devices/thorlabs_tcube/driver.py b/oxart/devices/thorlabs_tcube/driver.py index 50f96b5..e42ce60 100644 --- a/oxart/devices/thorlabs_tcube/driver.py +++ b/oxart/devices/thorlabs_tcube/driver.py @@ -1158,6 +1158,54 @@ async def resume_end_of_move_messages(self): """ await self.send(Message(MGMSG.MOT_RESUME_ENDOFMOVEMSGS)) + async def set_output_volts(self, voltage): + """Set the output voltage. + :param voltage: The output voltage in Volts. The value must be in the + range -voltage_limit to +voltage_limit, where voltage_limit is + specified by the :py:meth:`set_tpz_io_settings() + ` + method. + """ + volt = int(voltage * 32767 / 150.0) + payload = st.pack(" Date: Tue, 3 Feb 2026 15:59:18 +0000 Subject: [PATCH 7/8] formatting --- docs/conf.py | 71 +- oxart/__init__.py | 2 +- oxart/devices/andor_camera/camera.py | 49 +- oxart/devices/arduino_dac/driver.py | 18 +- oxart/devices/arduino_dds/driver.py | 47 +- oxart/devices/arroyo/driver.py | 10 +- .../devices/bme_pulse_picker/bme_delay_gen.py | 156 ++-- oxart/devices/bme_pulse_picker/timing.py | 41 +- oxart/devices/booster/driver.py | 114 ++- oxart/devices/booster/logger.py | 19 +- oxart/devices/booster/mediator.py | 1 + oxart/devices/booster/tests.py | 133 ++-- oxart/devices/booster/vcp_driver.py | 14 +- oxart/devices/booster/vcp_logger.py | 19 +- oxart/devices/brooks_4850/driver.py | 18 +- oxart/devices/brooks_4850/logger.py | 42 +- oxart/devices/brooks_SLA5853/driver.py | 432 +++++++---- oxart/devices/brooks_SLA5853/logger.py | 51 +- oxart/devices/conex_agap/driver.py | 34 +- oxart/devices/conex_motor/driver.py | 24 +- oxart/devices/e4405b/driver.py | 11 +- oxart/devices/hex_controller_C88752/driver.py | 14 +- oxart/devices/hoa2_dac/driver.py | 4 +- oxart/devices/hoa2_dac/pirate_dac.py | 10 +- oxart/devices/holzworth_synth/driver.py | 15 +- oxart/devices/holzworth_synth/driver_raw.py | 52 +- oxart/devices/home_built_controller/driver.py | 16 +- oxart/devices/ipcmini/constants.py | 170 ++--- oxart/devices/ipcmini/driver.py | 29 +- oxart/devices/keysight_MSOX3104G/driver.py | 20 +- oxart/devices/keysight_MSO_S/driver.py | 5 +- oxart/devices/lakeshore_335/driver.py | 42 +- oxart/devices/lakeshore_336/driver.py | 2 +- oxart/devices/libusb.py | 11 +- oxart/devices/mediator.py | 15 +- oxart/devices/mqtt_client/driver.py | 29 +- oxart/devices/mqtt_client/mqtt_client.py | 29 +- oxart/devices/ophir/driver.py | 26 +- oxart/devices/picomotor/driver.py | 130 ++-- oxart/devices/picomotor/mediator.py | 18 +- oxart/devices/prologix_gpib/driver.py | 8 +- oxart/devices/scpi_device/driver.py | 8 +- oxart/devices/scpi_dmm/driver.py | 9 +- oxart/devices/solstis/driver.py | 5 +- .../devices/stabilizer/current_stabilizer.py | 105 +-- oxart/devices/streams.py | 9 +- oxart/devices/surf_solver/driver.py | 495 +++++++----- oxart/devices/surf_solver/mediator.py | 721 ++++++++++-------- oxart/devices/thermostat/autotune.py | 123 +-- oxart/devices/thermostat/driver.py | 11 +- oxart/devices/thorlabs_apt/driver.py | 136 ++-- oxart/devices/thorlabs_bpc303/driver.py | 204 +++-- oxart/devices/thorlabs_camera/driver.py | 17 +- oxart/devices/thorlabs_mdt693a/driver.py | 18 +- oxart/devices/thorlabs_mdt69xb/driver.py | 78 +- oxart/devices/thorlabs_mdt69xb/mediator.py | 9 +- oxart/devices/thorlabs_pm/driver.py | 83 +- oxart/devices/thorlabs_tcube/driver.py | 428 +++++++---- oxart/devices/trap_controller/driver.py | 19 +- oxart/devices/tti_ql355/driver.py | 11 +- oxart/devices/tti_ql355/mediator.py | 2 + oxart/devices/varian_multigauge/driver.py | 5 +- .../devices/vaunix_signal_generator/driver.py | 11 +- oxart/frontend/aqctl_agilent_33220a.py | 21 +- oxart/frontend/aqctl_agilent_6671a.py | 24 +- oxart/frontend/aqctl_andor_emccd.py | 7 +- oxart/frontend/aqctl_arroyo.py | 3 +- oxart/frontend/aqctl_booster.py | 5 +- oxart/frontend/aqctl_brooks_4850.py | 13 +- oxart/frontend/aqctl_brooks_sla5853.py | 19 +- oxart/frontend/aqctl_current_stabilizer.py | 10 +- oxart/frontend/aqctl_hexapod_controller.py | 5 +- oxart/frontend/aqctl_keysight_MSOX3104G.py | 8 +- oxart/frontend/aqctl_lakeshore_335.py | 23 +- oxart/frontend/aqctl_orca_camera.py | 22 +- oxart/frontend/aqctl_scpi_dmm.py | 5 +- oxart/frontend/aqctl_surf_solver.py | 18 +- oxart/frontend/aqctl_thermostat.py | 36 +- oxart/frontend/aqctl_thermostat_legacy.py | 10 +- oxart/frontend/aqctl_thorlabs_pm100a.py | 24 +- oxart/frontend/aqctl_thorlabs_tcube.py | 58 +- oxart/frontend/aqctl_tti_ql355.py | 23 +- oxart/frontend/aqctl_v3500a.py | 5 +- oxart/frontend/aqctl_varian_multigauge.py | 15 +- oxart/frontend/arduino_dac_controller.py | 18 +- oxart/frontend/arduino_dds_controller.py | 34 +- oxart/frontend/bb_shutter_controller.py | 3 +- .../bme_pulse_picker_timing_controller.py | 15 +- oxart/frontend/conex_agap.py | 16 +- oxart/frontend/conex_motor_controller.py | 51 +- oxart/frontend/hoa2_dac_controller.py | 18 +- oxart/frontend/holzworth_synth_controller.py | 3 +- oxart/frontend/home_built_controller.py | 6 +- oxart/frontend/llama_scpi_dmm.py | 52 +- oxart/frontend/log_kasli_health.py | 51 +- oxart/frontend/logger_solstis.py | 66 +- oxart/frontend/logger_toptica_dl_pro.py | 84 +- oxart/frontend/ophir_controller.py | 16 +- oxart/frontend/picomotor_controller.py | 12 +- oxart/frontend/rs_fswp.py | 14 +- oxart/frontend/scpi_awg_controller.py | 10 +- oxart/frontend/thorlabs_bpc303_controller.py | 24 +- oxart/frontend/thorlabs_ddr05_controller.py | 28 +- oxart/frontend/thorlabs_ddr25_controller.py | 28 +- oxart/frontend/thorlabs_k10cr1_controller.py | 28 +- oxart/frontend/thorlabs_mdt693a_controller.py | 11 +- oxart/frontend/thorlabs_mdt69xb_controller.py | 17 +- oxart/frontend/toptica_dlc_controller.py | 33 +- .../vaunix_signal_generator_controller.py | 5 +- 109 files changed, 3220 insertions(+), 2235 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 19184d4..36f15d0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,18 +15,18 @@ import os import sys -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- -project = 'oxart-devices' -copyright = '2017-2021, Ion Trap Quantum Computing group' -author = 'Ion Trap Quantum Computing group' +project = "oxart-devices" +copyright = "2017-2021, Ion Trap Quantum Computing group" +author = "Ion Trap Quantum Computing group" # The short X.Y version -version = '' +version = "" # The full version, including alpha/beta/rc tags -release = '' +release = "" # -- General configuration --------------------------------------------------- @@ -38,23 +38,23 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -66,25 +66,25 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # -- Options for HTML output ------------------------------------------------- -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" html_theme_options = { - 'canonical_url': 'https://oxfordiontrapgroup.github.io/oxart-devices/', + "canonical_url": "https://oxfordiontrapgroup.github.io/oxart-devices/", } html_context = { - 'display_github': True, - 'github_user': 'OxfordIonTrapGroup', - 'github_repo': 'oxart-devices', - 'github_version': 'master', - 'conf_py_path': '/docs/' + "display_github": True, + "github_user": "OxfordIonTrapGroup", + "github_repo": "oxart-devices", + "github_version": "master", + "conf_py_path": "/docs/", } # Add any paths that contain custom static files (such as style sheets) here, @@ -105,7 +105,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'oxartdevicesdoc' +htmlhelp_basename = "oxartdevicesdoc" # -- Options for LaTeX output ------------------------------------------------ @@ -113,15 +113,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -131,15 +128,20 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'oxart-devices.tex', 'oxart-devices Documentation', - 'Oxford Ion Trap Quantum Computing group', 'manual'), + ( + master_doc, + "oxart-devices.tex", + "oxart-devices Documentation", + "Oxford Ion Trap Quantum Computing group", + "manual", + ), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, 'oxart-devices', 'oxart-devices Documentation', [author], 1)] +man_pages = [(master_doc, "oxart-devices", "oxart-devices Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -147,8 +149,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'oxart-devices', 'oxart-devices Documentation', author, - 'oxart-devices', 'One line description of project.', 'Miscellaneous'), + ( + master_doc, + "oxart-devices", + "oxart-devices Documentation", + author, + "oxart-devices", + "One line description of project.", + "Miscellaneous", + ), ] # -- Options for Epub output ------------------------------------------------- @@ -166,7 +175,7 @@ # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- diff --git a/oxart/__init__.py b/oxart/__init__.py index 69e3be5..8db66d3 100644 --- a/oxart/__init__.py +++ b/oxart/__init__.py @@ -1 +1 @@ -__path__ = __import__('pkgutil').extend_path(__path__, __name__) +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/oxart/devices/andor_camera/camera.py b/oxart/devices/andor_camera/camera.py index 7aa1516..8d37fdf 100644 --- a/oxart/devices/andor_camera/camera.py +++ b/oxart/devices/andor_camera/camera.py @@ -5,6 +5,7 @@ class CameraSetup(EnvExperiment): """Set camera parameters.""" + _camera_name = "camera" def build(self): @@ -15,8 +16,9 @@ def build(self): self.en_em_gain = self.get_argument("EM enabled", BooleanValue(default=False)) self.em_gain = self.get_argument("EM Gain", NumberValue(default=300)) - self.roi_mode = self.get_argument("Image size", - EnumerationValue(["Full", "ROI"])) + self.roi_mode = self.get_argument( + "Image size", EnumerationValue(["Full", "ROI"]) + ) self.run_acq = self.get_argument("Start acquisition", BooleanValue()) self.set_default_scheduling(pipeline_name="camera") @@ -36,13 +38,13 @@ def run(self): self.camera.set_exposure_time(self.t_exp) if self.en_em_gain: - self.camera.set_horizontal_shift_parameters(17, - em_gain=True, - adc_bit_depth=16) + self.camera.set_horizontal_shift_parameters( + 17, em_gain=True, adc_bit_depth=16 + ) else: - self.camera.set_horizontal_shift_parameters(3, - em_gain=False, - adc_bit_depth=16) + self.camera.set_horizontal_shift_parameters( + 3, em_gain=False, adc_bit_depth=16 + ) self.camera.set_vertical_shift_speed(3.3) self.camera.set_em_gain(self.em_gain) @@ -53,12 +55,14 @@ def issue_applets(self): ddb = self.get_device_db() dev = ddb[self._camera_name] - self.ccb.issue("create_applet", - "Camera", - "${python} -m oxart.applets.camera_viewer " - "--server " + dev["host"] + " " - "--serial " + dev["target_name"], - group="monitor") + self.ccb.issue( + "create_applet", + "Camera", + "${python} -m oxart.applets.camera_viewer " + "--server " + dev["host"] + " " + "--serial " + dev["target_name"], + group="monitor", + ) class ChooseROI(EnvExperiment): @@ -68,13 +72,12 @@ def build(self): self.setattr_device("core") self.setattr_device("camera") - self.roi_length = \ - self.get_argument("ROI length", NumberValue(50, ndecimals=0)) - self.roi_width = \ - self.get_argument("ROI width", NumberValue(50, ndecimals=0)) + self.roi_length = self.get_argument("ROI length", NumberValue(50, ndecimals=0)) + self.roi_width = self.get_argument("ROI width", NumberValue(50, ndecimals=0)) - self.ion_signal_width = \ - self.get_argument("Ion signal width", NumberValue(30, ndecimals=0)) + self.ion_signal_width = self.get_argument( + "Ion signal width", NumberValue(30, ndecimals=0) + ) self.set_default_scheduling(pipeline_name="camera") @@ -93,9 +96,9 @@ def run(self): break time.sleep(10 * ms) - im, sub_region = trim_image(im_full, - n_width=int(self.roi_width), - n_length=int(self.roi_length)) + im, sub_region = trim_image( + im_full, n_width=int(self.roi_width), n_length=int(self.roi_length) + ) roi = [*sub_region[0], *sub_region[1]] diff --git a/oxart/devices/arduino_dac/driver.py b/oxart/devices/arduino_dac/driver.py index 67c2448..3337768 100644 --- a/oxart/devices/arduino_dac/driver.py +++ b/oxart/devices/arduino_dac/driver.py @@ -32,7 +32,7 @@ def __init__(self, serial_name, n_channels=16, v_min=-10, v_max=10): def _send_command(self, command): try: - self.port.write((command + '\n').encode()) + self.port.write((command + "\n").encode()) except serial.SerialTimeoutException: logger.exception("Serial write timeout: Force exit") # This is hacky but makes the server exit @@ -44,11 +44,11 @@ def _read_line(self): Returns '' on timeout """ - s = '' - while len(s) == 0 or s[-1] != '\n': + s = "" + while len(s) == 0 or s[-1] != "\n": c = self.port.read().decode() - if c == '': # Timeout - raise Exception('Serial read timeout') + if c == "": # Timeout + raise Exception("Serial read timeout") s += c return s @@ -81,19 +81,19 @@ def set_voltage(self, channel, voltage, update=True): should be updated on execution of this command, or later with 'update' """ if update: - command = 'VU' + command = "VU" else: - command = 'V' + command = "V" if not self._validate_channel_index(channel): raise Exception("Bad channel index: {}".format(channel)) - command += ' {}'.format(channel) + command += " {}".format(channel) if not self._validate_voltage(voltage): raise Exception("Bad voltage: {}".format(voltage)) - command += ' {:.4f}\n'.format(voltage) + command += " {:.4f}\n".format(voltage) self._send_command(command) diff --git a/oxart/devices/arduino_dds/driver.py b/oxart/devices/arduino_dds/driver.py index 17ae7c5..870cc1f 100644 --- a/oxart/devices/arduino_dds/driver.py +++ b/oxart/devices/arduino_dds/driver.py @@ -29,12 +29,16 @@ def __eq__(self, value): return False if not isinstance(value, ProfileDescriptor): - raise ValueError("Bad value type in ProfileDescriptor equality: " - "{}".format(type(value))) - - if self.freq_word == value.freq_word and\ - self.amp_word == value.amp_word and\ - self.phase_word == value.phase_word: + raise ValueError( + "Bad value type in ProfileDescriptor equality: " + "{}".format(type(value)) + ) + + if ( + self.freq_word == value.freq_word + and self.amp_word == value.amp_word + and self.phase_word == value.phase_word + ): return True else: return False @@ -82,8 +86,10 @@ def set_profile(self, profile, freq, phase=0.0, amp=1.0): # Check if this profile is already present if self.current_profiles[profile] == new_profile: - logger.debug("Not setting profile {} with {}, already set" - "".format(profile, new_profile)) + logger.debug( + "Not setting profile {} with {}, already set" + "".format(profile, new_profile) + ) else: # If it is not present, set it and log it self._set_profile_lsb(profile, freq_word, phase_word, amp_word) @@ -94,17 +100,20 @@ def _set_profile_lsb(self, profile, freq, phase, amp): """Freq, phase, amp are all in units of lsb.""" if profile < 0 or profile > 7 or not isinstance(profile, int): raise ValueError("DDS profile should be an integer between 0 and 7") - if amp > 0x3fff or amp < 0 or not isinstance(amp, int): - raise ValueError("DDS amplitude word should be an integer " - "between 0 and 0x3fff") - if phase > 0xffff or phase < 0 or not isinstance(phase, int): - raise ValueError("DDS phase word should be an integer between " - "0 and 0xffff") - if freq < 0 or freq > 0xffffffff or not isinstance(freq, int): - raise ValueError("DDS frequency word should be an integer " - "between 0 and 0xffffffff") - - self.send('PLSB {} {} {} {}\n'.format(profile, amp, phase, freq)) + if amp > 0x3FFF or amp < 0 or not isinstance(amp, int): + raise ValueError( + "DDS amplitude word should be an integer " "between 0 and 0x3fff" + ) + if phase > 0xFFFF or phase < 0 or not isinstance(phase, int): + raise ValueError( + "DDS phase word should be an integer between " "0 and 0xffff" + ) + if freq < 0 or freq > 0xFFFFFFFF or not isinstance(freq, int): + raise ValueError( + "DDS frequency word should be an integer " "between 0 and 0xffffffff" + ) + + self.send("PLSB {} {} {} {}\n".format(profile, amp, phase, freq)) time.sleep(0.01) def reset(self): diff --git a/oxart/devices/arroyo/driver.py b/oxart/devices/arroyo/driver.py index 9d2b678..c2959f0 100644 --- a/oxart/devices/arroyo/driver.py +++ b/oxart/devices/arroyo/driver.py @@ -9,11 +9,9 @@ class Arroyo: """ def __init__(self, address, timeout=0.1, **kwargs): - self.device = serial.serial_for_url(address, - baudrate=38400, - timeout=timeout, - write_timeout=timeout, - **kwargs) + self.device = serial.serial_for_url( + address, baudrate=38400, timeout=timeout, write_timeout=timeout, **kwargs + ) self.id = self.identify() def close(self): @@ -41,7 +39,7 @@ def get_errors(self): Returns an empty list if there are no errors. """ - errs = iter(s.strip('"') for s in self._query("errstr?").split(',')) + errs = iter(s.strip('"') for s in self._query("errstr?").split(",")) return list(zip(errs, errs)) def set_laser_output(self, enable): diff --git a/oxart/devices/bme_pulse_picker/bme_delay_gen.py b/oxart/devices/bme_pulse_picker/bme_delay_gen.py index 9dc451b..23be262 100644 --- a/oxart/devices/bme_pulse_picker/bme_delay_gen.py +++ b/oxart/devices/bme_pulse_picker/bme_delay_gen.py @@ -1,4 +1,5 @@ """Control a BME delay generator PCI card using the vendor-supplied driver DLL.""" + from ctypes import byref, cdll, c_bool, c_double, c_long, c_ulong from dataclasses import dataclass from enum import Enum, unique @@ -12,6 +13,7 @@ class DelayGenException(Exception): """Raised on passing invalid parameters or hardware communication issues.""" + pass @@ -55,46 +57,47 @@ class StatusFlag(Enum): The definitions can be found in the "Programming BME_SG08P3" chapter of the BME_G0X manual, in the "Command register (read)" section. """ + #: channel A is active - channel_a_active = (1 << 0) + channel_a_active = 1 << 0 #: channel B is active - channel_b_active = (1 << 1) + channel_b_active = 1 << 1 #: channel C is active - channel_c_active = (1 << 2) + channel_c_active = 1 << 2 #: channel D is active - channel_d_active = (1 << 3) + channel_d_active = 1 << 3 #: channel E is active - channel_e_active = (1 << 4) + channel_e_active = 1 << 4 #: channel F is active - channel_f_active = (1 << 5) + channel_f_active = 1 << 5 #: primary trigger is active - primary_trigger_active = (1 << 6) + primary_trigger_active = 1 << 6 #: secondary trigger is active - secondary_trigger_active = (1 << 7) + secondary_trigger_active = 1 << 7 #: force trigger is active - force_trigger_active = (1 << 8) + force_trigger_active = 1 << 8 #: terminal count of preset register has been reached - terminal_count_reached = (1 << 9) + terminal_count_reached = 1 << 9 #: external clock fed via the trigger input connector is used, but no level #: transitions have been detected for the number of periods of the internal clock as #: prescribed by Multipurpose register, byte no. 6 - external_clock_no_transitions_detected = (1 << 10) + external_clock_no_transitions_detected = 1 << 10 #: all wait times for the trigger system have elapsed - all_wait_times_elapsed = (1 << 11) + all_wait_times_elapsed = 1 << 11 #: Load command is active - load_command_active = (1 << 17) + load_command_active = 1 << 17 class Driver: @@ -127,34 +130,77 @@ def get_fn(name, param_types, returns_status=True): try: self.reserve_dg_data = get_fn("Reserve_DG_Data", [c_long]) - self.detect_pci_dgs = get_fn("DetectPciDelayGenerators", [c_long_p], - returns_status=False) - self.get_pci_dg = get_fn("GetPciDelayGenerator", - [c_long_p, c_long_p, c_bool_p, c_long]) + self.detect_pci_dgs = get_fn( + "DetectPciDelayGenerators", [c_long_p], returns_status=False + ) + self.get_pci_dg = get_fn( + "GetPciDelayGenerator", [c_long_p, c_long_p, c_bool_p, c_long] + ) self.initialize_dg = get_fn("Initialize_DG_BME", [c_long, c_long, c_long]) self.deactivate_dg = get_fn("Deactivate_DG_BME", [c_long]) self.activate_dg = get_fn("Activate_DG_BME", [c_long]) self.set_gate_function = get_fn("Set_GateFunction", [c_ulong, c_long]) - self.set_trigger_parameters = get_fn("Set_TriggerParameters", [ - c_bool, c_double, c_double, c_ulong, c_ulong, c_bool, c_bool, c_bool, - c_bool, c_bool, c_bool, c_bool, c_bool, c_long - ]) + self.set_trigger_parameters = get_fn( + "Set_TriggerParameters", + [ + c_bool, + c_double, + c_double, + c_ulong, + c_ulong, + c_bool, + c_bool, + c_bool, + c_bool, + c_bool, + c_bool, + c_bool, + c_bool, + c_long, + ], + ) self.set_resetwhendone = get_fn("Set_ResetWhenDone", [c_bool, c_long]) - self.read_dg_status = get_fn("Read_DG_Status", [c_long], - returns_status=False) + self.read_dg_status = get_fn( + "Read_DG_Status", [c_long], returns_status=False + ) # These are model-specific. - self.set_g08_delay = get_fn("Set_G08_Delay", [ - c_ulong, c_double, c_double, c_ulong, c_ulong, c_ulong, c_bool, c_bool, - c_bool, c_bool, c_bool, c_long - ]) + self.set_g08_delay = get_fn( + "Set_G08_Delay", + [ + c_ulong, + c_double, + c_double, + c_ulong, + c_ulong, + c_ulong, + c_bool, + c_bool, + c_bool, + c_bool, + c_bool, + c_long, + ], + ) self.set_g08_clock_parameters = get_fn( "Set_G08_ClockParameters", - [c_bool, c_ulong, c_ulong, c_ulong, c_ulong, c_long]) - self.set_g08_trigger_parameters = get_fn("Set_G08_TriggerParameters", [ - c_bool, c_double, c_double, c_bool, c_bool, c_double, c_double, c_ulong, - c_ulong, c_long - ]) + [c_bool, c_ulong, c_ulong, c_ulong, c_ulong, c_long], + ) + self.set_g08_trigger_parameters = get_fn( + "Set_G08_TriggerParameters", + [ + c_bool, + c_double, + c_double, + c_bool, + c_bool, + c_double, + c_double, + c_ulong, + c_ulong, + c_long, + ], + ) except Exception as e: raise DelayGenException("Error binding to function from DLL: {}".format(e)) @@ -181,8 +227,10 @@ def init_single_pci_card(self): if device_count < 1: raise DelayGenException("No PCI delay generator detected.") elif device_count > 1: - raise DelayGenException("More than one PCI delay generator " - "detected; currently not supported.") + raise DelayGenException( + "More than one PCI delay generator " + "detected; currently not supported." + ) return BME_SG08p(self, DG_IDX) @@ -191,10 +239,10 @@ class ClockSource(Enum): """The main clock for the delay generator card to use.""" #: Use the on-board 160 MHz oscillator. - internal = 0, + internal = (0,) #: Use an external 80 MHz clock fed to the trigger input. - external_80_mhz = 1, + external_80_mhz = (1,) @unique @@ -270,10 +318,13 @@ def __init__(self, driver_lib, device_idx): raise DelayGenException( "Detected delay generator with invalid " "product id '{}'; currently only BME_SG08p is supported.".format( - product_id)) + product_id + ) + ) if not is_master: - raise DelayGenException("Detected delay generator is not set to " - "master mode") + raise DelayGenException( + "Detected delay generator is not set to " "master mode" + ) self._lib.initialize_dg(slot_id, product_id, self._device_idx) self.reset() @@ -301,10 +352,11 @@ def reset(self) -> None: 0.0, # No time-list step back (time in μs) 1, # No burst triggering. Setting this to 0 seems to break external # triggering. - 0xfc, # Default flags from manual UI (send local primary/force, + 0xFC, # Default flags from manual UI (send local primary/force, # resync pre-scaled m/s clock to input, send step-back/start/ # inhibit/load-data) - self._device_idx) + self._device_idx, + ) # All "straight" delay channels, no combining. self._lib.set_gate_function(0, self._device_idx) @@ -339,7 +391,8 @@ def _set_clock_params(self, source: ClockSource): 1, # Trigger input multiplier s, # 1: crystal, 2: trigger in, 3: trigger in with # crystal as fallback, 4: master/slave bus - self._device_idx) + self._device_idx, + ) def set_trigger(self, use_external_gate: bool, inhibit_us: float) -> None: """""" @@ -363,7 +416,8 @@ def _set_trigger_params(self, use_external_gate, inhibit_us): True, # Reset all outputs 2 μs after all delays have elapsed not use_external_gate, # Whether to always enable trigger regardless # of the gate signal - self._device_idx) + self._device_idx, + ) def set_output_gates(self, modes: Iterable[OutputGateMode]) -> None: """""" @@ -396,9 +450,12 @@ def set_output_gates(self, modes: Iterable[OutputGateMode]) -> None: def set_pulse_parameters(self, params: List[PulseParameters]) -> None: """""" if len(params) != self.CHANNEL_COUNT: - raise DelayGenException("Expected one pulse parameter specification " - "for each of the {} channels, not {}".format( - self.CHANNEL_COUNT, len(params))) + raise DelayGenException( + "Expected one pulse parameter specification " + "for each of the {} channels, not {}".format( + self.CHANNEL_COUNT, len(params) + ) + ) self._deactivate_safely() for i, p in enumerate(params): @@ -446,8 +503,8 @@ def _set_delay_channel(self, idx, params): return self._lib.set_g08_delay( CHANNEL_A_IDX + idx, # Channel index params.delay_us * self.CLOCK_FACTOR, # Time to first edge, in μs - params.width_us * - self.CLOCK_FACTOR, # Pulse width (first to second edge), in μs + params.width_us + * self.CLOCK_FACTOR, # Pulse width (first to second edge), in μs 1, # Modulo counter length 0, # Modulo counter offset 0x1 if params.enabled else 0x0, # Trigger from local primary if enabled @@ -456,4 +513,5 @@ def _set_delay_channel(self, idx, params): False, # Do not disconnect output stage False, # Do not connect onto master/slave bus True, # Positive input polarity (ignored in output mode) - self._device_idx) + self._device_idx, + ) diff --git a/oxart/devices/bme_pulse_picker/timing.py b/oxart/devices/bme_pulse_picker/timing.py index 0703438..2a52792 100644 --- a/oxart/devices/bme_pulse_picker/timing.py +++ b/oxart/devices/bme_pulse_picker/timing.py @@ -32,6 +32,7 @@ class InvalidTimingError(Exception): """Raised when the user specifies a set of parameters that violate the hardware timing constraints.""" + pass @@ -102,7 +103,8 @@ def ensure_valid(self): if abs(self.offset_off_us) > 2e-3: raise InvalidTimingError( - "Channel/channel OFF switch delay longer than 2 ns") + "Channel/channel OFF switch delay longer than 2 ns" + ) class PulsePickerTiming: @@ -121,7 +123,8 @@ def __init__(self, delay_gen: Driver, allow_long_pulses: bool = False): if self._delay_gen: self._delay_gen.set_output_gates( - [OutputGateMode.gate_or, OutputGateMode.gate_or, OutputGateMode.direct]) + [OutputGateMode.gate_or, OutputGateMode.gate_or, OutputGateMode.direct] + ) self._delay_gen.set_trigger(False, 0.0) self.disable() @@ -245,15 +248,25 @@ def split_neg(x): open_at_us = self._times.pre_open_us + self._times.align_us - self._delay_gen.set_pulse_parameters([ - PulseParameters(True, s_a_off, 0.0), - PulseParameters( - True, s_a_off + self._times.pre_open_us + self._times.post_open_us, - 0.0), - PulseParameters(True, s_b_off, 0.0), - PulseParameters( - True, s_b_off + self._times.pre_open_us + self._times.post_open_us, - 0.0), - PulseParameters(True, s_a_on + open_at_us - self._times.open_us / 2, 0.0), - PulseParameters(True, s_b_on + open_at_us + self._times.open_us / 2, 0.0), - ]) + self._delay_gen.set_pulse_parameters( + [ + PulseParameters(True, s_a_off, 0.0), + PulseParameters( + True, + s_a_off + self._times.pre_open_us + self._times.post_open_us, + 0.0, + ), + PulseParameters(True, s_b_off, 0.0), + PulseParameters( + True, + s_b_off + self._times.pre_open_us + self._times.post_open_us, + 0.0, + ), + PulseParameters( + True, s_a_on + open_at_us - self._times.open_us / 2, 0.0 + ), + PulseParameters( + True, s_b_on + open_at_us + self._times.open_us / 2, 0.0 + ), + ] + ) diff --git a/oxart/devices/booster/driver.py b/oxart/devices/booster/driver.py index 4622272..28e49c5 100755 --- a/oxart/devices/booster/driver.py +++ b/oxart/devices/booster/driver.py @@ -4,13 +4,30 @@ import serial Version = collections.namedtuple( - "Version", ["fw_rev", "fw_hash", "fw_build_date", "device_id", "hw_rev"]) - -Status = collections.namedtuple("Status", [ - "detected", "enabled", "interlock", "output_power_mu", "reflected_power_mu", "I29V", - "I6V", "V5VMP", "temp", "output_power", "reflected_power", "input_power", - "fan_speed", "error_occurred", "hw_id", "i2c_error_count" -]) + "Version", ["fw_rev", "fw_hash", "fw_build_date", "device_id", "hw_rev"] +) + +Status = collections.namedtuple( + "Status", + [ + "detected", + "enabled", + "interlock", + "output_power_mu", + "reflected_power_mu", + "I29V", + "I6V", + "V5VMP", + "temp", + "output_power", + "reflected_power", + "input_power", + "fan_speed", + "error_occurred", + "hw_id", + "i2c_error_count", + ], +) class Booster: @@ -28,7 +45,7 @@ def _cmd(self, cmd, channel, arg=None): raise ValueError("invalid channel number {}".format(channel)) if channel is None and arg is None: - cmd = (cmd + '\n') + cmd = cmd + "\n" elif arg is None: cmd = "{} {}\n".format(cmd, channel) else: @@ -36,16 +53,17 @@ def _cmd(self, cmd, channel, arg=None): self.dev.write(cmd.encode()) response = self.dev.readline().decode() - if response == '': - self.dev.write(b'\n') + if response == "": + self.dev.write(b"\n") response = self.dev.readline().decode() self.dev.readline() # blank response from extra write - if response == '': + if response == "": raise serial.SerialTimeoutException( - "Timeout while waiting for response to '{}'".format(cmd.strip())) + "Timeout while waiting for response to '{}'".format(cmd.strip()) + ) response = response.lower().strip() - if '?' in cmd and "error" not in response: + if "?" in cmd and "error" not in response: return response elif response == "ok": return @@ -71,19 +89,25 @@ def _query_float(self, cmd, channel, arg=None): def get_version(self): """Returns the device version information as a named tuple.""" self.dev.write(b"*IDN?\n") - idn = self.dev.readline().decode().strip().lower().split(',') + idn = self.dev.readline().decode().strip().lower().split(",") idn[0] = idn[0].split(" ") - if (idn[0][0] != "rfpa" or not idn[1].startswith(" built ") - or not idn[2].startswith(" id ") or not idn[3].startswith(" hw rev ")): + if ( + idn[0][0] != "rfpa" + or not idn[1].startswith(" built ") + or not idn[2].startswith(" id ") + or not idn[3].startswith(" hw rev ") + ): raise Exception("Unrecognised device identity string: {}".format(idn)) - return Version(fw_rev=idn[0][1], - fw_hash=idn[0][2], - fw_build_date=dateutil.parser.parse(idn[1][7:]), - device_id=idn[2][4:], - hw_rev=idn[3][1:]) + return Version( + fw_rev=idn[0][1], + fw_hash=idn[0][2], + fw_build_date=dateutil.parser.parse(idn[1][7:]), + device_id=idn[2][4:], + hw_rev=idn[3][1:], + ) def get_version_dict(self): """Return device version information, as a dictionary. @@ -146,7 +170,7 @@ def get_status(self, channel): i2c_error_count: number of I2C bus errors that have been detected for this channel. """ - resp = self._cmd("CHAN:DIAG?", channel).split(',') + resp = self._cmd("CHAN:DIAG?", channel).split(",") if len(resp) != 22: raise Exception("Unrecognised response to 'CHAN:DIAG?': {}".format(resp)) @@ -158,23 +182,26 @@ def _bool(value_str): return False raise Exception("Unrecognised response to 'CHAN:DIAG?': {}".format(resp)) - return Status(detected=_bool(resp[0]), - enabled=_bool(resp[1]), - interlock=_bool(resp[2]), - output_power_mu=int(resp[4]), - reflected_power_mu=int(resp[5]), - I29V=float(resp[6]), - I6V=float(resp[7]), - V5VMP=float(resp[8]), - temp=float(resp[9]), - output_power=float(resp[10]), - reflected_power=float(resp[11]), - input_power=float(resp[12]), - fan_speed=float(resp[13]), - error_occurred=_bool(resp[14]), - hw_id="{:x}:{:x}:{:x}:{:x}:{:x}:{:x}".format( - *[int(part) for part in resp[15:21]]), - i2c_error_count=int(resp[21])) + return Status( + detected=_bool(resp[0]), + enabled=_bool(resp[1]), + interlock=_bool(resp[2]), + output_power_mu=int(resp[4]), + reflected_power_mu=int(resp[5]), + I29V=float(resp[6]), + I6V=float(resp[7]), + V5VMP=float(resp[8]), + temp=float(resp[9]), + output_power=float(resp[10]), + reflected_power=float(resp[11]), + input_power=float(resp[12]), + fan_speed=float(resp[13]), + error_occurred=_bool(resp[14]), + hw_id="{:x}:{:x}:{:x}:{:x}:{:x}:{:x}".format( + *[int(part) for part in resp[15:21]] + ), + i2c_error_count=int(resp[21]), + ) def get_status_dict(self, channel): """Return status of a given channel, as a dictionary. @@ -202,7 +229,8 @@ def get_input_power(self, channel): val = self._query_float("MEAS:IN?", channel) if math.isnan(val): raise Exception( - "Input power detector not calibrated for channel {}".format(channel)) + "Input power detector not calibrated for channel {}".format(channel) + ) return val def get_reflected_power(self, channel): @@ -222,8 +250,10 @@ def set_interlock(self, channel, threshold): :param threshold: must lie between 0dBm and 38dBm """ if (threshold < 0) or (threshold > 38): - raise ValueError("Output forward power interlock threshold must " - "lie between 0dBm and +38dBm") + raise ValueError( + "Output forward power interlock threshold must " + "lie between 0dBm and +38dBm" + ) self._cmd("INT:POW", channel, "{:.2f}".format(threshold)) def get_interlock(self, channel): diff --git a/oxart/devices/booster/logger.py b/oxart/devices/booster/logger.py index 21dde33..0cd9810 100755 --- a/oxart/devices/booster/logger.py +++ b/oxart/devices/booster/logger.py @@ -12,12 +12,12 @@ def get_argparser(): parser = argparse.ArgumentParser() parser.add_argument("--device", required=True, help="Booster IP address") - parser.add_argument("--name", - required=True, - help="logical Booster name, defines measurement name") - parser.add_argument("--influx-server", - default="10.255.6.4", - help="Influx server address") + parser.add_argument( + "--name", required=True, help="logical Booster name, defines measurement name" + ) + parser.add_argument( + "--influx-server", default="10.255.6.4", help="Influx server address" + ) parser.add_argument("--database", default="junk", help="Influx database name") return parser @@ -29,10 +29,7 @@ def write(client, booster_name, measurement, data): "tags": { "name": measurement, }, - "fields": { - "ch{}".format(idx): value - for idx, value in enumerate(data) - } + "fields": {"ch{}".format(idx): value for idx, value in enumerate(data)}, } try: client.write_points([point]) @@ -62,7 +59,7 @@ def main(): "i_6V": "I6V", "5V0MP": "V5VMP", "pwr_tx": "output_power", - "pwr_rfl": "input_power" + "pwr_rfl": "input_power", } ind = 0 diff --git a/oxart/devices/booster/mediator.py b/oxart/devices/booster/mediator.py index cb1608f..80a0914 100644 --- a/oxart/devices/booster/mediator.py +++ b/oxart/devices/booster/mediator.py @@ -10,6 +10,7 @@ class Boosters: Methods are dynamically created from the driver class (see the docstring for more info). """ + _driver_cls = Booster def __init__(self, dmgr, devices, mappings): diff --git a/oxart/devices/booster/tests.py b/oxart/devices/booster/tests.py index 3029eca..10f7b4f 100755 --- a/oxart/devices/booster/tests.py +++ b/oxart/devices/booster/tests.py @@ -10,26 +10,32 @@ from oxart.devices.scpi_synth.driver import Synth from oxart.devices.booster.driver import Booster -parser = argparse.ArgumentParser(description="Booster hardware in the loop " - "test suite. Connect one channel of Booster " - "to a synth and RF power meter. Assumes there" - " is a 30dB attenuator between Booster and " - "the power meter.") +parser = argparse.ArgumentParser( + description="Booster hardware in the loop " + "test suite. Connect one channel of Booster " + "to a synth and RF power meter. Assumes there" + " is a 30dB attenuator between Booster and " + "the power meter." +) parser.add_argument("--booster", help="IP address of the Booster to test") parser.add_argument("--synth", help="Address of the synth to use for tests") parser.add_argument("--meter", help="IP address of the power meter RPC server") -parser.add_argument("--p_min", - help="Minimum synth power to use, set to give ~25dBm " - "output power", - type=float) -parser.add_argument("--p_max", - help="Maximum synth power to use, set to give ~35dBm " - "output power", - type=float) -parser.add_argument("--chan", - help="Booster channel connected to synth + power meter", - type=int, - default=0) +parser.add_argument( + "--p_min", + help="Minimum synth power to use, set to give ~25dBm " "output power", + type=float, +) +parser.add_argument( + "--p_max", + help="Maximum synth power to use, set to give ~35dBm " "output power", + type=float, +) +parser.add_argument( + "--chan", + help="Booster channel connected to synth + power meter", + type=int, + default=0, +) args = None # python -m oxart.devices.booster.tests --booster "10.255.6.79" --synth @@ -85,23 +91,47 @@ def setUpClass(cls): I6V = cls.I6V[channel, :] print("Channel {}...".format(channel)) - print("29V current: mean {:.3f} mA, min {:.3f}, max {:.3f} mA, " - "std {:.3f} mA".format(np.mean(I29V * 1e3), np.min(I29V * 1e3), - np.max(I29V * 1e3), np.std(I29V * 1e3))) - print("6V current: mean {:.3f} mA, min {:.3f}, max {:.3f} mA, " - "std {:.3f} mA".format(np.mean(I6V * 1e3), np.min(I6V * 1e3), - np.max(I6V * 1e3), np.std(I6V * 1e3))) + print( + "29V current: mean {:.3f} mA, min {:.3f}, max {:.3f} mA, " + "std {:.3f} mA".format( + np.mean(I29V * 1e3), + np.min(I29V * 1e3), + np.max(I29V * 1e3), + np.std(I29V * 1e3), + ) + ) + print( + "6V current: mean {:.3f} mA, min {:.3f}, max {:.3f} mA, " + "std {:.3f} mA".format( + np.mean(I6V * 1e3), + np.min(I6V * 1e3), + np.max(I6V * 1e3), + np.std(I6V * 1e3), + ) + ) cls.I29V = np.mean(cls.I29V, axis=1) cls.I6V = np.mean(cls.I6V, axis=1) print("Global statistics:") - print("29V current: mean {:.3f} mA, min {:.3f}, max {:.3f} mA, " - "std {:.3f} mA".format(np.mean(cls.I29V * 1e3), np.min(cls.I29V * 1e3), - np.max(cls.I29V * 1e3), np.std(cls.I29V * 1e3))) - print("6V current: mean {:.3f} mA, min {:.3f}, max {:.3f} mA, " - "std {:.3f} mA".format(np.mean(cls.I6V * 1e3), np.min(cls.I6V * 1e3), - np.max(cls.I6V * 1e3), np.std(cls.I6V * 1e3))) + print( + "29V current: mean {:.3f} mA, min {:.3f}, max {:.3f} mA, " + "std {:.3f} mA".format( + np.mean(cls.I29V * 1e3), + np.min(cls.I29V * 1e3), + np.max(cls.I29V * 1e3), + np.std(cls.I29V * 1e3), + ) + ) + print( + "6V current: mean {:.3f} mA, min {:.3f}, max {:.3f} mA, " + "std {:.3f} mA".format( + np.mean(cls.I6V * 1e3), + np.min(cls.I6V * 1e3), + np.max(cls.I6V * 1e3), + np.std(cls.I6V * 1e3), + ) + ) # get baseline measurement of amplifier gain and detector accuracy cls.dev.set_interlock(args.chan, 37) @@ -130,12 +160,21 @@ def setUpClass(cls): cls.synth.set_rf_on(False) time.sleep(cls.t_settle) - print("Amp gain: {} dB (min {} dB, max {} dB)".format( - cls.gain, gain_min, gain_max)) - print("Input detector error: {} dB ({} db - {} dB)".format( - cls.in_detector_err, in_detector_err_min, in_detector_err_max)) - print("Output detector error: {} dB ({} db - {} dB)".format( - cls.detector_err, detector_err_min, detector_err_max)) + print( + "Amp gain: {} dB (min {} dB, max {} dB)".format( + cls.gain, gain_min, gain_max + ) + ) + print( + "Input detector error: {} dB ({} db - {} dB)".format( + cls.in_detector_err, in_detector_err_min, in_detector_err_max + ) + ) + print( + "Output detector error: {} dB ({} db - {} dB)".format( + cls.detector_err, detector_err_min, detector_err_max + ) + ) def assertRange(self, val, min, max): self.assertTrue(val >= min and val <= max) @@ -146,7 +185,7 @@ def assertApproxEq(self, a, b, eps): self.assertTrue(abs(a - b) < eps) def _cmd(self, cmd): - self.dev.dev.write((cmd + '\n').encode()) + self.dev.dev.write((cmd + "\n").encode()) resp = self.dev.dev.readline().decode().strip().lower() if "?" in cmd: return resp @@ -240,8 +279,9 @@ def test_status(self): self.assertApproxEq(status.fan_speed, dev.get_fan_speed(), 2) self.assertApproxEq(status.output_power, dev.get_output_power(chan), 0.25) self.assertApproxEq(status.input_power, dev.get_input_power(chan), 0.25) - self.assertApproxEq(status.reflected_power, dev.get_reflected_power(chan), - 0.25) + self.assertApproxEq( + status.reflected_power, dev.get_reflected_power(chan), 0.25 + ) if status.enabled: self.assertRange(status.V5VMP, 4.9, 5.1) @@ -309,16 +349,23 @@ def test_power(self): def test_fuzz(self): skip = ["test_fuzz"] tests = [ - method_name for method_name in dir(self) - if (callable(getattr(self, method_name)) and method_name.startswith("test_") - and method_name not in skip) + method_name + for method_name in dir(self) + if ( + callable(getattr(self, method_name)) + and method_name.startswith("test_") + and method_name not in skip + ) ] num_iterations = 10000 for test_num in range(num_iterations): test_idx = random.randint(0, len(tests) - 1) test = tests[test_idx] - print("fuzz {} of {}: {}".format(test_num, num_iterations, - tests[test_idx][5:])) + print( + "fuzz {} of {}: {}".format( + test_num, num_iterations, tests[test_idx][5:] + ) + ) getattr(self, test)() diff --git a/oxart/devices/booster/vcp_driver.py b/oxart/devices/booster/vcp_driver.py index f2770f1..2f9d1f3 100644 --- a/oxart/devices/booster/vcp_driver.py +++ b/oxart/devices/booster/vcp_driver.py @@ -17,11 +17,11 @@ def __init__(self, device): def _cmd(self, cmd, lines=1, termination=None): """Sends a command and returns the response string.""" - self.dev.write((cmd + '\n').encode()) + self.dev.write((cmd + "\n").encode()) # echo resp = self.dev.readline().decode() - if resp.startswith('> '): + if resp.startswith("> "): resp = resp[1:] if resp.strip() != cmd: print(resp) @@ -30,8 +30,8 @@ def _cmd(self, cmd, lines=1, termination=None): if termination is not None: resp = [] line = self.dev.readline().decode().strip() - print('line: ', line) - while line != '>': + print("line: ", line) + while line != ">": print("line: ", line) resp.append(line) line = self.dev.readline().decode().strip() @@ -47,15 +47,15 @@ def _cmd(self, cmd, lines=1, termination=None): def get_version(self): """Returns the device version string.""" - return self._cmd('version') + return self._cmd("version") def get_eth_diag(self): """Returns ethernet diagnostic information.""" - return self._cmd('ethdbg', 6) + return self._cmd("ethdbg", 6) def get_logstash(self): """Returns ethernet diagnostic information.""" - return self._cmd('logstash', termination='>') + return self._cmd("logstash", termination=">") def ping(self): """Returns True if we are connected to a Booster.""" diff --git a/oxart/devices/booster/vcp_logger.py b/oxart/devices/booster/vcp_logger.py index beaa621..ca8c458 100644 --- a/oxart/devices/booster/vcp_logger.py +++ b/oxart/devices/booster/vcp_logger.py @@ -67,12 +67,12 @@ def get_messages(h): def get_argparser(): parser = argparse.ArgumentParser() parser.add_argument("--device", required=True, help="Device serial port address") - parser.add_argument("--name", - required=True, - help="logical Booster name, defines measurement name") - parser.add_argument("--influx-server", - default="10.255.6.4", - help="Influx server address") + parser.add_argument( + "--name", required=True, help="logical Booster name, defines measurement name" + ) + parser.add_argument( + "--influx-server", default="10.255.6.4", help="Influx server address" + ) parser.add_argument("--database", default="junk", help="Influx database name") return parser @@ -83,7 +83,7 @@ def write_point(args, client, name, fields, tags={}): "tags": { "name": name, }, - "fields": fields + "fields": fields, } for tag in tags: point["tags"][tag] = tags[tag] @@ -113,8 +113,9 @@ def write_channels(name, values): write_point(args, client, name, fields) try: - write_channels("temp", [(x + y) / 2 - for x, y in zip(status["LTEMP"], status["RTEMP"])]) + write_channels( + "temp", [(x + y) / 2 for x, y in zip(status["LTEMP"], status["RTEMP"])] + ) write_channels("i_30V", status["I30V [A]"]) write_channels("i_6V", status["I5V0 [A]"]) write_channels("5V0MP", status["5V0MP [V]"]) diff --git a/oxart/devices/brooks_4850/driver.py b/oxart/devices/brooks_4850/driver.py index e13f1e9..aa0e321 100755 --- a/oxart/devices/brooks_4850/driver.py +++ b/oxart/devices/brooks_4850/driver.py @@ -57,7 +57,7 @@ def _read_response(self, n_bytes): """Read a response from the device, waiting for n_bytes.""" n_read = 0 data = bytes() - while (n_read < n_bytes): + while n_read < n_bytes: new_data = self.c.read(n_bytes) @@ -89,7 +89,7 @@ def read_flow(self): command, payload = self._read_response(4) flow = struct.unpack(">H", payload)[0] - real_flow = self.max_flow * flow / 10000. + real_flow = self.max_flow * flow / 10000.0 return real_flow def read_temperature(self): @@ -98,7 +98,7 @@ def read_temperature(self): command, payload = self._read_response(4) adc_temp = struct.unpack(">H", payload)[0] - temperature = 100 * ((adc_temp / 65535.) - 1 + (5 / 6.)) + temperature = 100 * ((adc_temp / 65535.0) - 1 + (5 / 6.0)) return temperature def read_gas_info(self): @@ -106,8 +106,7 @@ def read_gas_info(self): self._send_command(CMD_READ_GASINFO) command, payload = self._read_response(8) - max_flow, gas_id, gas_density = \ - struct.unpack(">HHH", payload) + max_flow, gas_id, gas_density = struct.unpack(">HHH", payload) return max_flow, gas_id, gas_density @@ -169,15 +168,16 @@ def read_setpoint(self): command, payload = self._read_response(4) setpoint_raw = struct.unpack(">H", payload)[0] - setpoint = self.max_flow * setpoint_raw / 65535. + setpoint = self.max_flow * setpoint_raw / 65535.0 return setpoint def set_setpoint(self, setpoint): """Set the current flow rate setpoint in sccm.""" if setpoint > self.max_flow or setpoint < 0: - raise ValueError("Setpoint {} is out of range (0,{})!".format( - setpoint, self.max_flow)) + raise ValueError( + "Setpoint {} is out of range (0,{})!".format(setpoint, self.max_flow) + ) setpoint_raw = int(65535 * setpoint / self.max_flow) self._send_command(CMD_WRITE_VAR_INT16, struct.pack(">BH", 20, setpoint_raw)) @@ -193,7 +193,7 @@ def set_setpoint(self, setpoint): print("Temperature (C) = ", t) flow = f.read_flow() - print("Flow rate (sccm) = ", flow * 100.) + print("Flow rate (sccm) = ", flow * 100.0) max_flow, gas_id, gas_density = f.read_gas_info() print("max_flow = ", max_flow) diff --git a/oxart/devices/brooks_4850/logger.py b/oxart/devices/brooks_4850/logger.py index 69cec8d..d5e63c1 100755 --- a/oxart/devices/brooks_4850/logger.py +++ b/oxart/devices/brooks_4850/logger.py @@ -13,20 +13,17 @@ def get_argparser(): parser = argparse.ArgumentParser( description="Cryogenics logger", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("-p", - "--poll-time", - help="time between measurements (s)", - type=int, - default=5) - parser.add_argument("-db", - "--database", - help="influxdb database to log to", - default="comet") - parser.add_argument("-a", - "--address", - help="address of flow controller", - default="10.255.6.178") + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "-p", "--poll-time", help="time between measurements (s)", type=int, default=5 + ) + parser.add_argument( + "-db", "--database", help="influxdb database to log to", default="comet" + ) + parser.add_argument( + "-a", "--address", help="address of flow controller", default="10.255.6.178" + ) parser.add_argument("-p", "--port", help="port for flow controller", default="9001") sca.verbosity_args(parser) # This adds the -q and -v handling return parser @@ -40,10 +37,12 @@ def main(): try: time.sleep(args.poll_time) - influx = influxdb.InfluxDBClient(host="10.255.6.4", - database=args.database, - username="admin", - password="admin") + influx = influxdb.InfluxDBClient( + host="10.255.6.4", + database=args.database, + username="admin", + password="admin", + ) flow_cont = Client(args.address, args.port, "BrooksMassFlowController4850") try: @@ -54,8 +53,11 @@ def main(): influx.write_points([{"measurement": "cryo_T", "fields": {"Flow": flow}}]) - logger.info("{} - Cryostat flow: {}".format( - datetime.now().strftime('%Y-%m-%d %H:%M:%S'), flow)) + logger.info( + "{} - Cryostat flow: {}".format( + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), flow + ) + ) except Exception as err: logger.warning("{}".format(err)) diff --git a/oxart/devices/brooks_SLA5853/driver.py b/oxart/devices/brooks_SLA5853/driver.py index 9c69640..c9d51c1 100644 --- a/oxart/devices/brooks_SLA5853/driver.py +++ b/oxart/devices/brooks_SLA5853/driver.py @@ -50,35 +50,79 @@ CMD_READ_FLOW_ALARM = 247 CMD_WRITE_FLOW_ALARM = 248 -#The unit dictionaries below don't contain all units listed in the flowmeter's manual (yet). +# The unit dictionaries below don't contain all units listed in the flowmeter's manual (yet). # Dictionaries for flow rate units -flow_rate_code_to_str_dict = {17: "l/min", 19: "m^3/h", 24: "l/s", 28: "m^3/s", 70: "g/s", 71: "g/min", 72: "g/h", 73: "kg/s", 74: "kg/min", - 75: "kg/h", 131: "m^3/min", 138: "l/h", 170: "ml/s", 171: "ml/min", 172: "ml/h"} -flow_rate_str_to_code_dict = dict([reversed(i) for i in flow_rate_code_to_str_dict.items()]) +flow_rate_code_to_str_dict = { + 17: "l/min", + 19: "m^3/h", + 24: "l/s", + 28: "m^3/s", + 70: "g/s", + 71: "g/min", + 72: "g/h", + 73: "kg/s", + 74: "kg/min", + 75: "kg/h", + 131: "m^3/min", + 138: "l/h", + 170: "ml/s", + 171: "ml/min", + 172: "ml/h", +} +flow_rate_str_to_code_dict = dict( + [reversed(i) for i in flow_rate_code_to_str_dict.items()] +) # Dictionary for flow reference conditions -flow_reference_dict = {0: "Normal (273.15 Kelvin/1013.33 mbar)", 1: "Standard (user-defined through separate command)", 2: "As defined at calibration"} +flow_reference_dict = { + 0: "Normal (273.15 Kelvin/1013.33 mbar)", + 1: "Standard (user-defined through separate command)", + 2: "As defined at calibration", +} # Dictionaries for density units -density_code_to_str_dict = {91: "g/cm^3", 92: "kg/m^3", 95: "g/ml", 96: "kg/l", 97: "g/l"} +density_code_to_str_dict = { + 91: "g/cm^3", + 92: "kg/m^3", + 95: "g/ml", + 96: "kg/l", + 97: "g/l", +} density_str_to_code_dict = dict([reversed(i) for i in density_code_to_str_dict.items()]) # Dictionaries for temperature units temperature_code_to_str_dict = {32: "deg Celsius", 33: "deg Fahrenheit", 35: "Kelvin"} -temperature_str_to_code_dict = dict([reversed(i) for i in temperature_code_to_str_dict.items()]) +temperature_str_to_code_dict = dict( + [reversed(i) for i in temperature_code_to_str_dict.items()] +) # Dictionaries for pressure units -pressure_code_to_str_dict = {5: "PSI", 7: "bar", 8: "mbar", 11: "Pa", 12: "kPa", 13: "Torr", 14: "Std Atmosphere", 232: "atm", - 238: "bar", 241: "Counts", 242: "%", 244: "mTorr" } -pressure_str_to_code_dict = dict([reversed(i) for i in pressure_code_to_str_dict.items()]) +pressure_code_to_str_dict = { + 5: "PSI", + 7: "bar", + 8: "mbar", + 11: "Pa", + 12: "kPa", + 13: "Torr", + 14: "Std Atmosphere", + 232: "atm", + 238: "bar", + 241: "Counts", + 242: "%", + 244: "mTorr", +} +pressure_str_to_code_dict = dict( + [reversed(i) for i in pressure_code_to_str_dict.items()] +) # Dictionary to look up assignment of dynamic variables dynamic_variables_dict = {0: "Flow rate", 1: "Temperature", 2: "Pressure"} # Dictionaries for valve override valve_override_code_to_str_dict = {0: "off", 1: "open", 2: "close", 4: "manual"} -valve_override_str_to_code_dict = dict([reversed(i) for i in valve_override_code_to_str_dict.items()]) - +valve_override_str_to_code_dict = dict( + [reversed(i) for i in valve_override_code_to_str_dict.items()] +) # Create a dictionary to decode packed-ASCII messages @@ -91,8 +135,9 @@ packed_ascii_to_char_dict[0x20 + i] = chr(i + 32) # Also create the reversed dictionary to encode packed-ASCII messages -char_to_packed_ascii_dict = dict([reversed(i) for i in packed_ascii_to_char_dict.items()]) - +char_to_packed_ascii_dict = dict( + [reversed(i) for i in packed_ascii_to_char_dict.items()] +) class CorruptionError(RuntimeError): @@ -101,14 +146,19 @@ class CorruptionError(RuntimeError): class BrooksSLA5853: """Brooks SLA5853 mass flow controller driver""" - def __init__(self, ip_address, port, device_tag = "43200327"): - """ Connect to a Brookes flowmeter""" + + def __init__(self, ip_address, port, device_tag="43200327"): + """Connect to a Brookes flowmeter""" self.flowmeter = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Connect to the ES device using a Raw TCP socket self.flowmeter.connect((ip_address, port)) print("Connected to the flowmeter") - self.device_identifier = self.read_unique_identifier_associated_with_tag(device_tag) - self.long_frame_address = self.construct_long_address_from_device_id(self.device_identifier) + self.device_identifier = self.read_unique_identifier_associated_with_tag( + device_tag + ) + self.long_frame_address = self.construct_long_address_from_device_id( + self.device_identifier + ) print("Created long frame address of flowmeter") # Set flow unit to l/min # This also sets the flow reference condition to "as calibrated", if no other reference code is provided @@ -119,8 +169,9 @@ def __init__(self, ip_address, port, device_tag = "43200327"): self.set_valve_override_status("off") def read_response(self): - """ Read the response from the flowmeter. - Return format is: command (int), status_1 (int), status_2 (int), payload (bytes object)""" + """Read the response from the flowmeter. + Return format is: command (int), status_1 (int), status_2 (int), payload (bytes object) + """ # Read response of the flowmeter # The "ready" variable is used to make sure that the recv() function is only called if data is available @@ -139,7 +190,7 @@ def read_response(self): # The start character of an "acknowledge" message from slave to master is 0x86 j = 0 while message[j] != 0x86: - j += 1 + j += 1 # Also skip the start and address characters (6 bytes in total, assuming long frame addressing) j += 6 # The command byte is the one after the address characters @@ -156,47 +207,51 @@ def read_response(self): j += 1 # Extract the payload consisting of (byte_count - 2) bytes if byte_count > 2: - payload = message[j:(j + byte_count - 2)] + payload = message[j : (j + byte_count - 2)] j += byte_count - 2 else: payload = [] # Calculate checksum and compare to checksum byte at the end of the message checksum_message = message[j] - checksum_calculated = self.calculate_longitudinal_parity(message[:j], message_type = 'acknowledge') + checksum_calculated = self.calculate_longitudinal_parity( + message[:j], message_type="acknowledge" + ) if checksum_message != checksum_calculated: raise CorruptionError("Bad checksum received") return command, status_1, status_2, payload - def send_command(self, command_number, payload = None, data_format_string = None): + def send_command(self, command_number, payload=None, data_format_string=None): """Returns a message from the given input parameters that has the correct format to be sent - to the flowmeter. + to the flowmeter. """ message = struct.pack("B", 0xFF) for n in range(4): - message += struct.pack("B", 0xFF) # Preamble characters - message += struct.pack("B", 0x82) # Start character (for long frame addressing and Start-Of-Text) + message += struct.pack("B", 0xFF) # Preamble characters + message += struct.pack( + "B", 0x82 + ) # Start character (for long frame addressing and Start-Of-Text) # Add long frame address to message message += self.long_frame_address # Add command message += struct.pack("B", command_number) # Initialize empty bytes object to store payload - payload_bytes = b'' + payload_bytes = b"" if payload is None: size_payload = 0 - #message += struct.pack("B", size_payload) # this is the "byte count char", indicating the length of the payload in bytes + # message += struct.pack("B", size_payload) # this is the "byte count char", indicating the length of the payload in bytes else: size_payload = struct.calcsize(data_format_string) j = 0 if size_payload > 24: raise ValueError("Payload is larger than 24 bytes") - if data_format_string[0] in ['<', '>', '!', '=', '@']: + if data_format_string[0] in ["<", ">", "!", "=", "@"]: j = 1 byte_order_string = data_format_string[0] else: - byte_order_string = '' + byte_order_string = "" for i in range(len(data_format_string) - j): # Adding information on the byte order to every data format string is important correctly encode the data format_char = str(byte_order_string) + str(data_format_string[i + j]) @@ -215,13 +270,15 @@ def send_command(self, command_number, payload = None, data_format_string = None checksum = self.calculate_longitudinal_parity(message) message += struct.pack("B", checksum) - #print(message) + # print(message) self.flowmeter.send(message) def read_unique_identifier_associated_with_tag(self, tag_as_string): """Return the unique identifier associated with the tag of the flowmeter.""" tag = hart.tools.pack_ascii(tag_as_string) - self.flowmeter.send(hart.universal.read_unique_identifier_associated_with_tag(tag)) + self.flowmeter.send( + hart.universal.read_unique_identifier_associated_with_tag(tag) + ) response = self.read_response() # Check if returned command is the expected one @@ -229,14 +286,14 @@ def read_unique_identifier_associated_with_tag(self, tag_as_string): raise CorruptionError("Incorrect command returned") # Raise potential error messages in status info - #self.decode_status_info(response[1], response[2]) + # self.decode_status_info(response[1], response[2]) # Extract the device id from the payload device_id = response[3][9:12] return device_id - def calculate_longitudinal_parity(self, message, message_type = 'start_of_text'): + def calculate_longitudinal_parity(self, message, message_type="start_of_text"): """Calculates the longitudinal parity, which is used as the checksum in the communication with the SLA5853 flow meter, of the given message (excluding the preamble characters). @@ -248,14 +305,16 @@ def calculate_longitudinal_parity(self, message, message_type = 'start_of_text') # First, make sure to skip the preamble characters j = 0 - if message_type == 'start_of_text': + if message_type == "start_of_text": while message[j] != 0x82: j += 1 - elif message_type == 'acknowledge': + elif message_type == "acknowledge": while message[j] != 0x86: j += 1 else: - raise ValueError("message_type must be either 'start_of_text' or 'acknowledge' ") + raise ValueError( + "message_type must be either 'start_of_text' or 'acknowledge' " + ) message_length = len(message) - j checksum = message[j] ^ message[j + 1] @@ -277,20 +336,28 @@ def ping(self): return False def decode_status_info(self, status_1, status_2): - """ Decode error messages in the two status bytes of a slave-to-master message. - Returns status_1 because for some commands, it can contain additional information. """ + """Decode error messages in the two status bytes of a slave-to-master message. + Returns status_1 because for some commands, it can contain additional information. + """ # Transform status bytes into binary arrays to extract bitwise-encoded information - status_1_array = np.array([[status_1]], dtype = np.uint8) + status_1_array = np.array([[status_1]], dtype=np.uint8) status_1_binary = np.unpackbits(status_1_array) - status_2_array = np.array([[status_2]], dtype = np.uint8) + status_2_array = np.array([[status_2]], dtype=np.uint8) status_2_binary = np.unpackbits(status_2_array) # If the second status byte is 0 and bit 7 of the first status byte is 1, the communication failed. # The type of communication error is encoded in status byte 1. if status_2 == 0 and status_1_binary[0] == 1: # Go through status byte 1 bit by bit and print all error messages where the bit is of value 1 - error_messages = [ "Parity error", "Overrun error", "Framing error", "Checksum error", "Reserved", "Rx Buffer Overflow"] + error_messages = [ + "Parity error", + "Overrun error", + "Framing error", + "Checksum error", + "Reserved", + "Rx Buffer Overflow", + ] for i in range(7): if status_1_binary[i + 1] == 1: print(error_messages[i]) @@ -321,7 +388,16 @@ def decode_status_info(self, status_1, status_2): raise CorruptionError("Command-specific error") # Next, print device status info # Go through status byte 2 bit by bit and print all error messages where the bit is of value 1 - error_messages_2 = ["Device malfunction", "Configuration changed", "Cold start", "More status available", "Primary variable analog output fixed", "Primary variable analog output saturated", "Non primary variable out of range", "Primary variable out of range"] + error_messages_2 = [ + "Device malfunction", + "Configuration changed", + "Cold start", + "More status available", + "Primary variable analog output fixed", + "Primary variable analog output saturated", + "Non primary variable out of range", + "Primary variable out of range", + ] for i in range(8): if status_2_binary[i] == 1: print(error_messages_2[i]) @@ -329,13 +405,15 @@ def decode_status_info(self, status_1, status_2): if status_2_binary[3] == 1: self.read_additional_transmitter_status() - def construct_long_address_from_device_id(self, device_id, manufacturer_id = 10, device_type_code = 100): - """ Returns the long frame address of a device with the given identifiers. - Format of the long frame address is: - Byte 0: Manufacturer_id OR 0x80 - Byte 1: device type code (100 in decimal for Brooks SLA Series) - Bytes 2 to 4: device identifier - """ + def construct_long_address_from_device_id( + self, device_id, manufacturer_id=10, device_type_code=100 + ): + """Returns the long frame address of a device with the given identifiers. + Format of the long frame address is: + Byte 0: Manufacturer_id OR 0x80 + Byte 1: device type code (100 in decimal for Brooks SLA Series) + Bytes 2 to 4: device identifier + """ long_frame_address = struct.pack(">B", manufacturer_id | 0x80) long_frame_address += struct.pack(">B", device_type_code) @@ -344,7 +422,7 @@ def construct_long_address_from_device_id(self, device_id, manufacturer_id = 10, return long_frame_address - def read_process_gas_type(self, gas_selection_code = 1): + def read_process_gas_type(self, gas_selection_code=1): """Return the gas type corresponding to the given gas selection code (int between 1 and 6).""" self.send_command(CMD_READ_GAS_NAME, [gas_selection_code], ">B") @@ -355,12 +433,12 @@ def read_process_gas_type(self, gas_selection_code = 1): raise CorruptionError("Incorrect gas selection code returned") # The remaining payload of the response contains name or chemical formula of the gas, encoded in ASCII # Decode the gas type and return it as a string - #gas_type_string_length = len(response[3])-1 + # gas_type_string_length = len(response[3])-1 gas_type_string = "".join(chr(i) for i in response[3][1:]) return gas_type_string - def select_gas_calibration(self, calibration_number = 1): + def select_gas_calibration(self, calibration_number=1): """Select a gas calibration from the available calibrations. Refer to the Product/Calibration Data Sheet(s) shipped with each device to determine the proper gas calibration number for the desired gas/flow conditions. @@ -376,9 +454,12 @@ def select_gas_calibration(self, calibration_number = 1): if returned_calibration_number != calibration_number: raise CorruptionError("Incorrect calibration number returned") - print("Set calibration number of SLA5853 flowmeter to ", returned_calibration_number) + print( + "Set calibration number of SLA5853 flowmeter to ", + returned_calibration_number, + ) - def select_temperature_unit(self, temperature_unit = "Kelvin"): + def select_temperature_unit(self, temperature_unit="Kelvin"): """Sets the temperature unit to either deg Celsius, deg Fahrenheit or Kelvin. The corresponding temperature unit codes are: 32: deg Celsius @@ -386,7 +467,9 @@ def select_temperature_unit(self, temperature_unit = "Kelvin"): 35: Kelvin """ if temperature_unit not in temperature_str_to_code_dict: - raise ValueError("Undefined temperature unit. Choose either deg Celsius, deg Fahrenheit or Kelvin.") + raise ValueError( + "Undefined temperature unit. Choose either deg Celsius, deg Fahrenheit or Kelvin." + ) temperature_unit_code = temperature_str_to_code_dict[temperature_unit] @@ -420,7 +503,7 @@ def read_setpoint(self): return setpoint_in_percent, setpoint_in_selected_unit, selected_unit_string - def write_setpoint(self, setpoint_value, unit_code = 57): + def write_setpoint(self, setpoint_value, unit_code=57): """Sets flow setpoint to the input parameter setpoint_value, given either in percent (default, unit_code = 57) of full scale or selected unit (unit_code = 250). """ @@ -439,9 +522,13 @@ def write_setpoint(self, setpoint_value, unit_code = 57): raise ValueError("Undefined flow unit code") new_setpoint_in_selected_unit = struct.unpack(">f", response[3][6:10])[0] - return new_setpoint_in_percent, new_setpoint_in_selected_unit, selected_unit_string + return ( + new_setpoint_in_percent, + new_setpoint_in_selected_unit, + selected_unit_string, + ) - def read_full_scale_flow_range(self, gas_select_code = 1): + def read_full_scale_flow_range(self, gas_select_code=1): """Returns full scale flow range for the selected gas type (gas_select_code is int between 1 and 6, if the device has only been calibrated for one type of gas it's always 1). @@ -463,14 +550,17 @@ def read_full_scale_flow_range(self, gas_select_code = 1): def read_standard_temperature_and_pressure_range(self): """Read the standard temperature and pressure values from the device’s memory. - The standard temperature and pressure are reference values which can be set by the user """ + The standard temperature and pressure are reference values which can be set by the user + """ self.send_command(CMD_READ_STANDARD_TEMPERATURE_AND_PRESSURE) response = self.read_response() self.decode_status_info(response[1], response[2]) temperature_unit_code = response[3][0] if temperature_unit_code in temperature_code_to_str_dict: - temperature_unit_string = temperature_code_to_str_dict[temperature_unit_code] + temperature_unit_string = temperature_code_to_str_dict[ + temperature_unit_code + ] else: raise ValueError("Undefined temperature unit code") standard_temperature = struct.unpack(">f", response[3][1:5])[0] @@ -481,30 +571,50 @@ def read_standard_temperature_and_pressure_range(self): raise ValueError("Undefined pressure unit code") standard_pressure = struct.unpack(">f", response[3][6:10])[0] - return standard_temperature, temperature_unit_string, standard_pressure, pressure_unit_string + return ( + standard_temperature, + temperature_unit_string, + standard_pressure, + pressure_unit_string, + ) - def write_standard_temperature_and_pressure(self, standard_temp, standard_pressure, temp_unit = "Kelvin", pressure_unit = "Pa"): + def write_standard_temperature_and_pressure( + self, standard_temp, standard_pressure, temp_unit="Kelvin", pressure_unit="Pa" + ): """Write the standard temperature and pressure values into the device's memory and return the newly set values, together with their units (as strings). The standard temperature and pressure are reference values which can be set by the user and which are used - in the conversion of flow units. """ + in the conversion of flow units.""" # Check if the given temperature unit is defined, and if yes, find the corresponding unit code. if temp_unit not in temperature_str_to_code_dict: - raise ValueError("Undefined temperature unit. Choose either deg Celsius, deg Fahrenheit or Kelvin.") + raise ValueError( + "Undefined temperature unit. Choose either deg Celsius, deg Fahrenheit or Kelvin." + ) temperature_unit_code = temperature_str_to_code_dict[temp_unit] # Check if the given pressure unit is defined, and if yes, find the corresponding unit code. if pressure_unit not in pressure_str_to_code_dict: raise ValueError("Undefined pressure unit") pressure_unit_code = pressure_str_to_code_dict[pressure_unit] - self.send_command(CMD_WRITE_STANDARD_TEMPERATURE_AND_PRESSURE, [temperature_unit_code, standard_temp, pressure_unit_code, standard_pressure], "f", response[3][1:5])[0] @@ -515,11 +625,16 @@ def write_standard_temperature_and_pressure(self, standard_temp, standard_pressu raise ValueError("Undefined pressure unit code returned") standard_pressure_new = struct.unpack(">f", response[3][6:10])[0] - return standard_temperature_new, temperature_unit_string, standard_pressure_new, pressure_unit_string + return ( + standard_temperature_new, + temperature_unit_string, + standard_pressure_new, + pressure_unit_string, + ) def read_flow_settings(self): """Read the operational settings from the device. These settings consist of the selected gas number, - the selected flow reference condition, the selected flow unit and the selected temperature unit. + the selected flow reference condition, the selected flow unit and the selected temperature unit. """ self.send_command(CMD_READ_OPERATIONAL_FLOW_SETTINGS) response = self.read_response() @@ -527,7 +642,9 @@ def read_flow_settings(self): selected_gas_number = response[3][0] selected_flow_reference = response[3][1] if selected_flow_reference in flow_reference_dict: - selected_flow_reference_string = flow_reference_dict[selected_flow_reference] + selected_flow_reference_string = flow_reference_dict[ + selected_flow_reference + ] else: raise ValueError("Undefined flow unit code") selected_flow_unit = response[3][2] @@ -537,14 +654,21 @@ def read_flow_settings(self): raise ValueError("Undefined flow unit code") selected_temperature_unit = response[3][3] if selected_temperature_unit in temperature_code_to_str_dict: - selected_temperature_unit_string = temperature_code_to_str_dict[selected_temperature_unit] + selected_temperature_unit_string = temperature_code_to_str_dict[ + selected_temperature_unit + ] else: raise ValueError("Undefined temperature unit code") - return selected_gas_number, selected_flow_reference_string, selected_flow_unit_string, selected_temperature_unit_string + return ( + selected_gas_number, + selected_flow_reference_string, + selected_flow_unit_string, + selected_temperature_unit_string, + ) - def select_flow_unit(self, selected_flow_unit_string, selected_flow_ref_code = 2): - """Select a flow unit and the flow reference condition. The selected flow unit will be used in the conversion of flow data """ + def select_flow_unit(self, selected_flow_unit_string, selected_flow_ref_code=2): + """Select a flow unit and the flow reference condition. The selected flow unit will be used in the conversion of flow data""" # Check if the given flow unit is valid and if yes, find the corresponding unit code if selected_flow_unit_string not in flow_rate_str_to_code_dict: @@ -552,9 +676,13 @@ def select_flow_unit(self, selected_flow_unit_string, selected_flow_ref_code = 2 flow_rate_unit_code = flow_rate_str_to_code_dict[selected_flow_unit_string] # Check if given reference code is valid if selected_flow_ref_code not in flow_reference_dict: - raise ValueError("Undefined reference code. Choose either 0 (273.15 Kelvin/1013.33 mbar), 1 (user-defined) or 2 (as calibrated).") + raise ValueError( + "Undefined reference code. Choose either 0 (273.15 Kelvin/1013.33 mbar), 1 (user-defined) or 2 (as calibrated)." + ) - self.send_command(CMD_SELECT_FLOW_UNIT, [selected_flow_ref_code, flow_rate_unit_code], "BB") + self.send_command( + CMD_SELECT_FLOW_UNIT, [selected_flow_ref_code, flow_rate_unit_code], "BB" + ) response = self.read_response() self.decode_status_info(response[1], response[2]) # Check if the expected flow reference code was returned @@ -566,7 +694,12 @@ def select_flow_unit(self, selected_flow_unit_string, selected_flow_ref_code = 2 if returned_flow_rate_unit_code != flow_rate_unit_code: raise CorruptionError("Incorrect flow rate unit code returned") - print("Set flow reference to " + flow_reference_dict[returned_flow_ref_code] + " and flow rate unit to " + flow_rate_code_to_str_dict[returned_flow_rate_unit_code]) + print( + "Set flow reference to " + + flow_reference_dict[returned_flow_ref_code] + + " and flow rate unit to " + + flow_rate_code_to_str_dict[returned_flow_rate_unit_code] + ) def read_setpoint_settings(self): """Read the setpoint related settings from the device. @@ -575,7 +708,7 @@ def read_setpoint_settings(self): Setpoint source code = 1: analog 0 - 5 V / 0 - 10 V / 0 - 20 mA Setpoint source code = 2: analog 4 - 20 mA Setpoint source code = 3: digital - the type of softstart and the softstart ramp. """ + the type of softstart and the softstart ramp.""" self.send_command(CMD_READ_SETPOINT_SETTINGS) response = self.read_response() @@ -588,7 +721,13 @@ def read_setpoint_settings(self): softstart_selection_code = response[3][9] softstart_ramp_value = struct.unpack(">f", response[3][10:14])[0] - return setpoint_source_selection_code, setpoint_span, setpoint_offset, softstart_selection_code, softstart_ramp_value + return ( + setpoint_source_selection_code, + setpoint_span, + setpoint_offset, + softstart_selection_code, + softstart_ramp_value, + ) def select_setpoint_source(self, setpoint_source_code): """Select the setpoint source to be used as setpoint input. Possible setpoint source codes and their meanings are: @@ -598,7 +737,7 @@ def select_setpoint_source(self, setpoint_source_code): 10: Analog Input and Output 0-5 V 11: Analog Input and Output 1-5 V 20: Analog Input and Output 0-20 mA - 21: Analog Input and Output 4-20 mA """ + 21: Analog Input and Output 4-20 mA""" # Check if setpoint source code is valid if setpoint_source_code not in [1, 2, 3, 10, 11, 20, 21]: @@ -618,44 +757,50 @@ def read_valve_settings(self): The settings are 24-bit unsigned integers used to fine tune the D/A converter for the valve control. The numbers are dimensionless and sized to the range of 0 to 62500. 100% flow is achieved with the number - valve offset + valve range. Also, the sum of both should not be over 62500. """ + valve offset + valve range. Also, the sum of both should not be over 62500.""" self.send_command(CMD_READ_VALVE_RANGE_AND_OFFSET) response = self.read_response() self.decode_status_info(response[1], response[2]) - valve_range = int.from_bytes(response[3][0:3],'big',signed=False) - valve_offset = int.from_bytes(response[3][3:6],'big',signed=False) + valve_range = int.from_bytes(response[3][0:3], "big", signed=False) + valve_offset = int.from_bytes(response[3][3:6], "big", signed=False) # Check if the sum of valve range and valve offset is <= 62500 if (valve_range + valve_offset) > 62500: raise ValueError("Sum of valve range and offset is too high (> 62500)") return valve_range, valve_offset - def write_valve_settings(self, valve_range = 0, valve_offset = 0): + def write_valve_settings(self, valve_range=0, valve_offset=0): """Write the Valve Offset values into the device. The Valve Range is not used in devices of the SLA Enhanced Series, therefore, this value should always be set to 0. The settings are 24-bit unsigned integers used to fine tune the D/A converter for the valve control. The numbers are dimensionless and sized to the range of 0 to 62500. 100% flow is achieved with the number - valve offset + valve range. Also, the sum of both should not be over 62500 """ + valve offset + valve range. Also, the sum of both should not be over 62500""" # Check if valve range + offset is within a valid range (i.e., >= 0 and <= 62500) range_and_offset_sum = valve_range + valve_offset if range_and_offset_sum < 0 or range_and_offset_sum > 62500: - raise ValueError("Invalid range and/or offset value. Range + offset needs to be <= 62500") + raise ValueError( + "Invalid range and/or offset value. Range + offset needs to be <= 62500" + ) - self.send_command(CMD_WRITE_VALVE_RANGE_AND_OFFSET, [valve_range, valve_offset], "II") + self.send_command( + CMD_WRITE_VALVE_RANGE_AND_OFFSET, [valve_range, valve_offset], "II" + ) response = self.read_response() self.decode_status_info(response[1], response[2]) - returned_valve_range = int.from_bytes(response[3][0:3],'big',signed=False) - returned_valve_offset = int.from_bytes(response[3][3:6],'big',signed=False) + returned_valve_range = int.from_bytes(response[3][0:3], "big", signed=False) + returned_valve_offset = int.from_bytes(response[3][3:6], "big", signed=False) # Check if the sum of valve range and valve offset is <= 62500 if (returned_valve_range + returned_valve_offset) > 62500: - raise ValueError("Sum of returned valve range and offset is too high (> 62500)") + raise ValueError( + "Sum of returned valve range and offset is too high (> 62500)" + ) return returned_valve_range, returned_valve_offset - def read_gas_info(self, gas_selection_code = 1): + def read_gas_info(self, gas_selection_code=1): """Read the density of the selected gas (gas selection code is int between 1 and 6), the operational flow range and the reference temperature and pressure for the flow range. @@ -676,27 +821,42 @@ def read_gas_info(self, gas_selection_code = 1): gas_density = struct.unpack(">f", response[3][2:6])[0] reference_temperature_unit_code = response[3][6] if reference_temperature_unit_code in temperature_code_to_str_dict: - reference_temperature_unit_string = temperature_code_to_str_dict[reference_temperature_unit_code] + reference_temperature_unit_string = temperature_code_to_str_dict[ + reference_temperature_unit_code + ] else: raise ValueError("Undefined temperature unit code") reference_temperature = struct.unpack(">f", response[3][7:11])[0] reference_pressure_unit_code = response[3][11] if reference_pressure_unit_code in pressure_code_to_str_dict: - reference_pressure_unit_string = pressure_code_to_str_dict[reference_pressure_unit_code] + reference_pressure_unit_string = pressure_code_to_str_dict[ + reference_pressure_unit_code + ] else: raise ValueError("Undefined pressure unit code") reference_pressure = struct.unpack(">f", response[3][12:16])[0] reference_flow_rate_unit_code = response[3][16] if reference_flow_rate_unit_code in flow_rate_code_to_str_dict: - reference_flow_rate_unit_string = flow_rate_code_to_str_dict[reference_flow_rate_unit_code] + reference_flow_rate_unit_string = flow_rate_code_to_str_dict[ + reference_flow_rate_unit_code + ] else: raise ValueError("Undefined flow rate unit code") reference_flow_range = struct.unpack(">f", response[3][17:21])[0] - return gas_density, density_unit_string, reference_temperature, reference_temperature_unit_string, reference_pressure, reference_pressure_unit_string, reference_flow_range, reference_flow_rate_unit_string + return ( + gas_density, + density_unit_string, + reference_temperature, + reference_temperature_unit_string, + reference_pressure, + reference_pressure_unit_string, + reference_flow_range, + reference_flow_rate_unit_string, + ) def read_pid_controller_values(self): - """Return PID controller parameters in the order K_p, K_i, K_d """ + """Return PID controller parameters in the order K_p, K_i, K_d""" self.send_command(CMD_READ_PID_VALUES) response = self.read_response() @@ -709,7 +869,7 @@ def read_pid_controller_values(self): def write_pid_controller_values(self, k_p_new, k_i_new, k_d_new): """Set PID controller parameters to the values given by k_p_new, k_i_new, k_d_new and - return the new values in the same order if successful. """ + return the new values in the same order if successful.""" self.send_command(CMD_WRITE_PID_VALUES, [k_p_new, k_i_new, k_d_new], "fff") response = self.read_response() @@ -730,7 +890,7 @@ def read_flow_rate_and_temperature(self): The underlying command is command #3, "Read current and all dynamic variables". In case of the Brooks SLA5853 flowmeter (type MFC), there are two dynamic variables, the primary - and the secondary one. """ + and the secondary one.""" self.send_command(CMD_READ_CURRENT_AND_ALL_DYNAMIC_VARIABLES) response = self.read_response() @@ -756,8 +916,6 @@ def read_flow_rate_and_temperature(self): # Read value of secondary variable scd_variable = struct.unpack(">f", response[3][10:14])[0] - - return prim_variable, prim_var_unit_string, scd_variable, scd_var_unit_string def zero_flow_rate_sensor(self): @@ -772,7 +930,7 @@ def zero_flow_rate_sensor(self): def reset_configuration_changed_flag(self): """Resets the configuration changed response code, bit #6 of the transmitter status byte. Primary master devices, address ‘1’, should only issue this command after the configuration - changed response code has been detected and acted upon. """ + changed response code has been detected and acted upon.""" self.send_command(CMD_RESET_CONFIGURATION_CHANGED_FLAG) response = self.read_response() @@ -780,13 +938,13 @@ def reset_configuration_changed_flag(self): print("Configuration-changed flag has been reset.") def reset_flowmeter(self): - """Reset the device’s microprocessor. The device will respond first and then perform the master reset. """ + """Reset the device’s microprocessor. The device will respond first and then perform the master reset.""" self.send_command(CMD_PERFORM_MASTER_RESET) print("Flowmeter will be reset now.") def read_dynamic_variable_assignments(self): - """Read transmitter variable numbers assigned to primary and secondary variable and find their definitions. """ + """Read transmitter variable numbers assigned to primary and secondary variable and find their definitions.""" self.send_command(CMD_READ_DYNAMIC_VARIABLE_ASSIGNMENT) response = self.read_response() @@ -807,12 +965,13 @@ def read_dynamic_variable_assignments(self): def read_valve_control_value(self): """Read the current valve control value. The valve control value is a dimensionless number in the range - from 0 to 62500. It represents the value sent to the D/A-converter used to control the valve """ + from 0 to 62500. It represents the value sent to the D/A-converter used to control the valve + """ self.send_command(CMD_READ_VALVE_CONTROL_VALUE) response = self.read_response() self.decode_status_info(response[1], response[2]) - valve_control_value = int.from_bytes(response[3][0:3],'big',signed=False) + valve_control_value = int.from_bytes(response[3][0:3], "big", signed=False) # Check if valve control value is <= 62500 if valve_control_value > 62500: raise ValueError("Invalid valve control value") @@ -821,20 +980,22 @@ def read_valve_control_value(self): def get_valve_override_status(self): """Get the current valve override status from the device. The valve override status can be set to - either off (no valve override), close, open or manual. """ + either off (no valve override), close, open or manual.""" self.send_command(CMD_GET_VALVE_OVERRIDE_STATUS) response = self.read_response() self.decode_status_info(response[1], response[2]) valve_override_code = response[3][0] # Get the description of the returned override code from the dictionary - valve_override_description = valve_override_code_to_str_dict[valve_override_code] + valve_override_description = valve_override_code_to_str_dict[ + valve_override_code + ] return valve_override_description - def set_valve_override_status(self, valve_override_status = "off"): + def set_valve_override_status(self, valve_override_status="off"): """Set the valve override status of the device. The valve override status can be set to - either off (no valve override), close, open or manual. """ + either off (no valve override), close, open or manual.""" # Get the code corresponding to the valve override status description given valve_override_code = valve_override_str_to_code_dict[valve_override_status] @@ -844,14 +1005,16 @@ def set_valve_override_status(self, valve_override_status = "off"): self.decode_status_info(response[1], response[2]) returned_valve_override_code = response[3][0] # Get the description of the returned override code from the dictionary - returned_valve_override_description = valve_override_code_to_str_dict[returned_valve_override_code] + returned_valve_override_description = valve_override_code_to_str_dict[ + returned_valve_override_code + ] return returned_valve_override_description - def select_softstart_mode(self, softstart_selection_code = 0): + def select_softstart_mode(self, softstart_selection_code=0): """Select the softstart type to be used by the device. The softstart mode can be set to either disabled or time. When Time is selected, then the Software Ramp value (see Command #219) - will be the time required to ramp to a new setpoint expressed in seconds. """ + will be the time required to ramp to a new setpoint expressed in seconds.""" # Check if the given softstart selection code is valid (it must be either 0 or 4) if softstart_selection_code not in [0, 4]: @@ -873,7 +1036,7 @@ def select_softstart_mode(self, softstart_selection_code = 0): return returned_softstart_selection_code def write_linear_softstart_ramp_time(self, ramp_time): - """Write the linear softstart ramp time (in seconds) into the device’s memory. """ + """Write the linear softstart ramp time (in seconds) into the device’s memory.""" self.send_command(CMD_WRITE_LINEAR_SOFTSTART_RAMP_VALUE, [ramp_time], ">f") response = self.read_response() @@ -884,7 +1047,7 @@ def write_linear_softstart_ramp_time(self, ramp_time): def read_additional_transmitter_status(self): """Retrieve additional transmitter status information. This function is supposed to be called whenn byte 2 of the status bytes - contains the message "More status available". """ + contains the message "More status available".""" self.send_command(CMD_READ_ADDITIONAL_TRANSMITTER_STATUS) response = self.read_response() @@ -894,13 +1057,13 @@ def read_additional_transmitter_status(self): add_status_byte_4 = response[3][3] # Transform additional status info bytes into binary arrays to extract bitwise-encoded information - add_status_1_array = np.array([[add_status_byte_1]], dtype = np.uint8) + add_status_1_array = np.array([[add_status_byte_1]], dtype=np.uint8) add_status_1_binary = np.unpackbits(add_status_1_array) - add_status_2_array = np.array([[add_status_byte_2]], dtype = np.uint8) + add_status_2_array = np.array([[add_status_byte_2]], dtype=np.uint8) add_status_2_binary = np.unpackbits(add_status_2_array) - add_status_3_array = np.array([[add_status_byte_3]], dtype = np.uint8) + add_status_3_array = np.array([[add_status_byte_3]], dtype=np.uint8) add_status_3_binary = np.unpackbits(add_status_3_array) - add_status_4_array = np.array([[add_status_byte_4]], dtype = np.uint8) + add_status_4_array = np.array([[add_status_byte_4]], dtype=np.uint8) add_status_4_binary = np.unpackbits(add_status_4_array) start_string = "Additional status info: " @@ -923,7 +1086,7 @@ def read_additional_transmitter_status(self): # Decode information in additional status byte 3 if add_status_3_binary[0] == 1: - print(start_string + "Low flow alarm")# + print(start_string + "Low flow alarm") # if add_status_3_binary[1] == 1: print(start_string + "High flow alarm") if add_status_3_binary[2] == 1: @@ -944,24 +1107,29 @@ def read_additional_transmitter_status(self): print(start_string + "Device overhaul due") - - - - - if __name__ == "__main__": - f = BrooksSLA5853("10.179.22.99", 9001) - gas_density, density_unit, ref_temp, temp_unit, ref_pressure, pressure_unit, ref_flow, flow_unit = f.read_gas_info() + ( + gas_density, + density_unit, + ref_temp, + temp_unit, + ref_pressure, + pressure_unit, + ref_flow, + flow_unit, + ) = f.read_gas_info() print("Gas density = ", gas_density, density_unit) print("Reference temperature = ", ref_temp, temp_unit) print("Reference pressure = ", ref_pressure, pressure_unit) print("Reference flow range = ", ref_flow, flow_unit) # Read current flow and temperature - current_flow, current_flow_unit, current_temp, current_temp_unit = f.read_flow_rate_and_temperature() + current_flow, current_flow_unit, current_temp, current_temp_unit = ( + f.read_flow_rate_and_temperature() + ) print("Temperature = ", current_temp, current_temp_unit) print("Flow rate = ", current_flow, current_flow_unit) @@ -985,11 +1153,3 @@ def read_additional_transmitter_status(self): # print("Flow rate = ", current_flow, current_flow_unit) # f.close_connection() - - - - - - - - diff --git a/oxart/devices/brooks_SLA5853/logger.py b/oxart/devices/brooks_SLA5853/logger.py index cda4562..0e14ef7 100644 --- a/oxart/devices/brooks_SLA5853/logger.py +++ b/oxart/devices/brooks_SLA5853/logger.py @@ -15,21 +15,18 @@ def get_argparser(): parser = argparse.ArgumentParser( description="Cryogenics logger", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("-p", - "--poll-time", - help="time between measurements (s)", - type=int, - default=2) - parser.add_argument("-db", - "--database", - help="influxdb database to log to", - default="comet") - parser.add_argument("-a", - "--address", - help="ip address of flow controller", - default="10.179.22.99") - parser.add_argument("port", help="port for flow controller", type = int) + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "-p", "--poll-time", help="time between measurements (s)", type=int, default=2 + ) + parser.add_argument( + "-db", "--database", help="influxdb database to log to", default="comet" + ) + parser.add_argument( + "-a", "--address", help="ip address of flow controller", default="10.179.22.99" + ) + parser.add_argument("port", help="port for flow controller", type=int) sca.verbosity_args(parser) # This adds the -q and -v handling return parser @@ -56,24 +53,28 @@ def main(): host=secret["host"], database=args.database, username=secret["username"], - password=secret["password"]) + password=secret["password"], + ) - #flow_cont = Client(args.master, port, "LakeShore335") + # flow_cont = Client(args.master, port, "LakeShore335") flow_cont = BrooksSLA5853(args.address, args.port) try: assert flow_cont.ping(), "Cannot connect to device" - flow, flow_unit, temperature, temp_unit = flow_cont.read_flow_rate_and_temperature() + flow, flow_unit, temperature, temp_unit = ( + flow_cont.read_flow_rate_and_temperature() + ) finally: flow_cont.close_connection() - influx.write_points([{ - "measurement": "cryo", - "fields": { - "large_flow": flow - }}]) + influx.write_points( + [{"measurement": "cryo", "fields": {"large_flow": flow}}] + ) - logger.info("{} - Cryogen flow rate: {}l/min".format( - datetime.now().strftime('%Y-%m-%d %H:%M:%S'), flow)) + logger.info( + "{} - Cryogen flow rate: {}l/min".format( + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), flow + ) + ) except Exception as err: logger.warning("{}".format(err)) diff --git a/oxart/devices/conex_agap/driver.py b/oxart/devices/conex_agap/driver.py index 5bcbdcd..c7dbcfa 100644 --- a/oxart/devices/conex_agap/driver.py +++ b/oxart/devices/conex_agap/driver.py @@ -5,19 +5,27 @@ logger = logging.getLogger(__name__) -StateType = Enum("StateType", [ - "NotReferenced", "Configuration", "Moving", "Stepping", "Ready", "Disable", "Other" -]) +StateType = Enum( + "StateType", + [ + "NotReferenced", + "Configuration", + "Moving", + "Stepping", + "Ready", + "Disable", + "Other", + ], +) class ConexMirror: """Driver for Newport CONEX motor controller.""" def __init__(self, id, serial_addr): - self.port = serial.Serial(serial_addr, - baudrate=921600, - timeout=0.1, - write_timeout=0.1) + self.port = serial.Serial( + serial_addr, baudrate=921600, timeout=0.1, write_timeout=0.1 + ) self.id = id s = self.get_status() if s != StateType.Ready: @@ -36,10 +44,10 @@ def _read_line(self): Returns '' on timeout """ - s = '' - while len(s) == 0 or s[-1] != '\r': + s = "" + while len(s) == 0 or s[-1] != "\r": c = self.port.read().decode() - if c == '': # Timeout + if c == "": # Timeout break s += c return s @@ -47,8 +55,8 @@ def _read_line(self): def home(self): """Go to home position - required after reset of controller before any other operation. If blocking, do not return until homing complete""" - self.set_position('U', 0) - self.set_position('V', 0) + self.set_position("U", 0) + self.set_position("V", 0) def set_position(self, ax, pos, absolute=True, blocking=True): """Go to a position. @@ -108,6 +116,6 @@ def get_status(self): st = StateType.Ready elif state_code in ["3c", "3d"]: st = StateType.Disable - elif state_code == '46': + elif state_code == "46": st = StateType.Jogging return st diff --git a/oxart/devices/conex_motor/driver.py b/oxart/devices/conex_motor/driver.py index 67d468c..b0e5786 100644 --- a/oxart/devices/conex_motor/driver.py +++ b/oxart/devices/conex_motor/driver.py @@ -9,7 +9,8 @@ StateType = Enum( "StateType", - ["NotReferenced", "Homing", "Moving", "Ready", "Disable", "Configuartion", "Other"]) + ["NotReferenced", "Homing", "Moving", "Ready", "Disable", "Configuartion", "Other"], +) #: For now, we only use channel 1 of the controller (motor controllers are #: single-channel anyway). @@ -28,10 +29,9 @@ class Conex: """Driver for Newport CONEX motor controller.""" def __init__(self, serial_addr, position_limit=None, auto_home=True): - self.port = serial.serial_for_url(serial_addr, - baudrate=921600, - timeout=10.0, - write_timeout=10.0) + self.port = serial.serial_for_url( + serial_addr, baudrate=921600, timeout=10.0, write_timeout=10.0 + ) if auto_home: self.reset() @@ -60,14 +60,16 @@ def __init__(self, serial_addr, position_limit=None, auto_home=True): s = self.get_status() if s == StateType.Configuartion: logger.warning( - "Controller was left behind in configuration mode; leaving it.") + "Controller was left behind in configuration mode; leaving it." + ) self._execute_checked_command("PW0", delay_before_status_read=10.0) s = self.get_status() if s == StateType.NotReferenced: logger.warning( - "Skipping application of limits, as stage not referenced (homed); " + - "will be applied once home[_to_current]() is called.") + "Skipping application of limits, as stage not referenced (homed); " + + "will be applied once home[_to_current]() is called." + ) elif s == StateType.Ready: self.set_upper_limit(self._cached_upper_limit) else: @@ -290,8 +292,10 @@ def set_lower_limit(self, limit, persist=False): # spurious failures. curr_pos = self.get_position_setpoint() if limit > curr_pos: - raise ValueError(f"Requested lower limit {limit} mm outside range for" - f"current position {curr_pos} mm") + raise ValueError( + f"Requested lower limit {limit} mm outside range for" + f"current position {curr_pos} mm" + ) cmd = f"SL{limit}" if persist: # run in config mode to write to flash diff --git a/oxart/devices/e4405b/driver.py b/oxart/devices/e4405b/driver.py index 1abf258..3c6e493 100644 --- a/oxart/devices/e4405b/driver.py +++ b/oxart/devices/e4405b/driver.py @@ -23,14 +23,14 @@ def ping(self): def close(self): self.stream.close() - def find_peak(self, f0, freq, power, window=5.): + def find_peak(self, f0, freq, power, window=5.0): """Returns the index of the point with the highest power in the frequency range [f0-window/2.0, f0+window/2.0].""" - lower_idx = np.argmin(np.abs(freq - (f0 - window / 2.))) - upper_idx = np.argmin(np.abs(freq - (f0 + window / 2.))) + lower_idx = np.argmin(np.abs(freq - (f0 - window / 2.0))) + upper_idx = np.argmin(np.abs(freq - (f0 + window / 2.0))) - peak = np.argmax(power[lower_idx:(upper_idx + 1)]) + lower_idx + peak = np.argmax(power[lower_idx : (upper_idx + 1)]) + lower_idx return peak def get_sweep_axis(self): @@ -123,4 +123,5 @@ def get_trace(self): """Returns the current trace in the amplitude units.""" self.stream.write(":TRACE? TRACE1\n".encode()) return np.array( - [float(pt) for pt in self.stream.readline().decode().split(',')]) + [float(pt) for pt in self.stream.readline().decode().split(",")] + ) diff --git a/oxart/devices/hex_controller_C88752/driver.py b/oxart/devices/hex_controller_C88752/driver.py index 2dd5d92..6435de0 100644 --- a/oxart/devices/hex_controller_C88752/driver.py +++ b/oxart/devices/hex_controller_C88752/driver.py @@ -5,7 +5,7 @@ class Hexapod: """Driver for the Hexpod controller C-887.52.""" def __init__(self, address: str): - self.dev = GCSDevice('C-887') + self.dev = GCSDevice("C-887") self.dev.ConnectTCPIP(address) # Referencing @@ -13,12 +13,12 @@ def __init__(self, address: str): pitools.waitonreferencing(self.dev) # References the stage using the reference position - pitools.startup(self.dev, stages=None, refmodes='FRF') + pitools.startup(self.dev, stages=None, refmodes="FRF") pitools.waitonreferencing(self.dev) # Activating zero coordinate system self.activate_coordinate_system() - print('Hexapod ready: {}'.format(self.dev.qIDN().strip())) + print("Hexapod ready: {}".format(self.dev.qIDN().strip())) def move(self, axes: list[str], values: list[float]): """MOV: Move to absolute position @@ -32,7 +32,7 @@ def set_velocity(self, velocity=5): """VLS: Set velocity mm/s""" self.dev.VLS(velocity) - def activate_coordinate_system(self, name='Zero'): + def activate_coordinate_system(self, name="Zero"): """KEN: Activate coordinates with given name""" self.dev.KEN(name) @@ -40,9 +40,9 @@ def set_coordinate_system(self, name: str): """KSF: Set coordinate system centered at current positon of hexapod""" self.dev.KSF(name) - def set_pivot_point(self, - axes: list[str] = ['R', 'S', 'T'], - values: list[float] = [0., 0., 0.]): + def set_pivot_point( + self, axes: list[str] = ["R", "S", "T"], values: list[float] = [0.0, 0.0, 0.0] + ): """SPI: Fixing the pivot point, origin is 9mm below topplate surface""" self.dev.SPI(axes=axes, values=values) diff --git a/oxart/devices/hoa2_dac/driver.py b/oxart/devices/hoa2_dac/driver.py index ca164e7..db37c60 100644 --- a/oxart/devices/hoa2_dac/driver.py +++ b/oxart/devices/hoa2_dac/driver.py @@ -24,7 +24,7 @@ def __init__(self, serial_name): self.sr_trap_freq = 1e6 def write_raw_voltages(self, voltages): - assert (len(voltages) == N_CHANNELS) + assert len(voltages) == N_CHANNELS for ch, v in enumerate(voltages): self.dac.set_channel(v, ch=ch, update=False) self.dac.pulse_ldac() @@ -36,7 +36,7 @@ def update_voltages(self): voltages = [0] * 4 # Axial trapping term - freq_scaling = (88 / 170) * (self.sr_trap_freq / 700e3)**2 + freq_scaling = (88 / 170) * (self.sr_trap_freq / 700e3) ** 2 voltages[0] += -1 * freq_scaling voltages[1] += +1 * freq_scaling voltages[2] += -0.445 * freq_scaling diff --git a/oxart/devices/hoa2_dac/pirate_dac.py b/oxart/devices/hoa2_dac/pirate_dac.py index 184240d..6f8e4b9 100644 --- a/oxart/devices/hoa2_dac/pirate_dac.py +++ b/oxart/devices/hoa2_dac/pirate_dac.py @@ -16,11 +16,11 @@ def __init__(self, port): self.spi = SPI(portname=port) self.spi.pins = PIN_POWER | PIN_AUX self.spi.config = CFG_PUSH_PULL - self.spi.speed = '1MHz' + self.spi.speed = "1MHz" self.spi.cs = False # Config +-10V range, power up - self._write(0x1f, reg=2, ch=0) + self._write(0x1F, reg=2, ch=0) self._write(0x4, reg=1, ch=4) def pulse_ldac(self): @@ -35,7 +35,7 @@ def _write(self, data, reg=0, ch=0, rw=0): ch : 0-3 for ch 0-3, 4 -> all """ ctrl = (rw << 7) + ((reg & 0x7) << 3) + (ch & 0x7) - self.spi.write_then_read(3, 0, [ctrl, (data >> 8) & 0xff, data & 0xff]) + self.spi.write_then_read(3, 0, [ctrl, (data >> 8) & 0xFF, data & 0xFF]) def _read(self): self.spi.cs = True @@ -45,7 +45,7 @@ def _read(self): def read_channel(self, ch=0): self._write(0, reg=0, ch=ch, rw=1) raw = self._read() - val_lsb = int.from_bytes(raw[1:], byteorder='big') + val_lsb = int.from_bytes(raw[1:], byteorder="big") val = val_lsb * (10 / 32767) return val @@ -70,7 +70,7 @@ def set_channel(self, v, ch=0, update=True): if __name__ == "__main__": print("Opening ...") - dac = PirateDac('com14') + dac = PirateDac("com14") print("Sweeping DACs") diff --git a/oxart/devices/holzworth_synth/driver.py b/oxart/devices/holzworth_synth/driver.py index a3735ad..04c89bd 100644 --- a/oxart/devices/holzworth_synth/driver.py +++ b/oxart/devices/holzworth_synth/driver.py @@ -6,7 +6,7 @@ import os -class HolzworthSynth(): +class HolzworthSynth: """Driver for Holzworth synth to get and set the frequency, and get and set the ramp rate to track the 674 nm quadrupole laser cavity drift.""" @@ -32,7 +32,8 @@ def __init__(self): loop = asyncio.get_event_loop() loop.run_until_complete( - self.continuously_update_freq(loop)) # Starts continuously_update_freq + self.continuously_update_freq(loop) + ) # Starts continuously_update_freq async def continuously_update_freq(self, loop): """Updates the frequency every 10 seconds and adds itself back to the lop.""" @@ -53,10 +54,9 @@ async def _move_freq(self, freq): # Checking we reach the final frequency, allowing for rounding differences in # the 3rd decimal place for values as large as 2.048 GHZ (max frequency output) - assert (math.isclose(await self.get_freq(), - freq_end, - rel_tol=0.5e-12, - abs_tol=0.0011)) + assert math.isclose( + await self.get_freq(), freq_end, rel_tol=0.5e-12, abs_tol=0.0011 + ) async def set_freq(self, freq): """Sets the Holzworth frequency and saves the value and time to file.""" @@ -97,7 +97,8 @@ async def set_ramp(self, ramp): current_freq = await self.get_freq() await self.set_freq( - current_freq) # needed to ensure update_freq's calculations start from now + current_freq + ) # needed to ensure update_freq's calculations start from now self.data["ramp"] = ramp # Hz per second diff --git a/oxart/devices/holzworth_synth/driver_raw.py b/oxart/devices/holzworth_synth/driver_raw.py index bfe8b1c..c05c724 100644 --- a/oxart/devices/holzworth_synth/driver_raw.py +++ b/oxart/devices/holzworth_synth/driver_raw.py @@ -5,7 +5,7 @@ logger = logging.getLogger(__name__) -class HolzworthSynthRaw(): +class HolzworthSynthRaw: """Raw driver to communicate with the Holzworth Synthesiser using SCPI commands over USB.""" @@ -17,18 +17,17 @@ def __init__(self): self.serialnum = self.dll.getAttachedDevices() - if self.serialnum.decode() == '': + if self.serialnum.decode() == "": raise Exception("No devices connected") rc = self.dll.openDevice(self.serialnum) - assert (rc > 0) + assert rc > 0 self.dll.usbCommWrite.restype = ctypes.c_char_p - self.suffix_dict = {'Hz': 0, 'kHz': 3, 'MHz': 6, 'GHz': 9} # SI suffixes. + self.suffix_dict = {"Hz": 0, "kHz": 3, "MHz": 6, "GHz": 9} # SI suffixes. self.exponent_dict = { - v: k - for k, v in self.suffix_dict.items() + v: k for k, v in self.suffix_dict.items() } # swap keys for values def get_freq(self, limits=0): @@ -36,17 +35,17 @@ def get_freq(self, limits=0): without arguments or limits=0, and returns the maximum and minimum allowed frequency when called with limits=1 and limits =-1 respectively.""" - limits_dict = {0: '', 1: ':MAX', -1: ':MIN'} - command = ctypes.c_char_p((':FREQ' + limits_dict[limits] + '?').encode()) + limits_dict = {0: "", 1: ":MAX", -1: ":MIN"} + command = ctypes.c_char_p((":FREQ" + limits_dict[limits] + "?").encode()) rx = self.dll.usbCommWrite(self.serialnum, command) freq_string = rx.decode() - assert (freq_string != 'Invalid Command') + assert freq_string != "Invalid Command" [value, suffix] = freq_string.strip().split() try: - freq = float(value) * (10**self.suffix_dict[suffix]) + freq = float(value) * (10 ** self.suffix_dict[suffix]) except KeyError as e: raise Exception('Invalid suffix "' + e.args[0] + '"') return round(freq, 3) # rounding as the synth reads to 3 d.p. precision @@ -57,33 +56,36 @@ def set_freq(self, freq): if (freq < 1e5) or (freq > 2.048e9): raise Exception("Frequency out of range") - exponent = int(3.0 * np.floor( - np.log10(freq) / 3.0)) # find nearest SI suffix exponent (i.e. 0,3,6 or 9) + exponent = int( + 3.0 * np.floor(np.log10(freq) / 3.0) + ) # find nearest SI suffix exponent (i.e. 0,3,6 or 9) try: # rounding to 3 d.p. as otherwise synth can set to the wrong frequency. - freq_string = str(round(freq / (10**exponent), - exponent + 3)) + self.exponent_dict[exponent] + freq_string = ( + str(round(freq / (10**exponent), exponent + 3)) + + self.exponent_dict[exponent] + ) except KeyError as e: raise Exception('Invalid exponent"' + e.args[0] + '"') - command = ctypes.c_char_p((':FREQ:' + freq_string).encode()) + command = ctypes.c_char_p((":FREQ:" + freq_string).encode()) rx = self.dll.usbCommWrite(self.serialnum, command) - assert (rx.decode() == 'Frequency Set') + assert rx.decode() == "Frequency Set" def get_pow(self, limits=0): """Returns the current set power of the Holzworth synth when called without arguments or limits=0, and returns the maximum and minimum allowed frequency when called with limits=1 and limits =-1 respectively.""" - limits_dict = {0: '', 1: ':MAX', -1: ':MIN'} - command = ctypes.c_char_p((':PWR' + limits_dict[limits] + '?').encode()) + limits_dict = {0: "", 1: ":MAX", -1: ":MIN"} + command = ctypes.c_char_p((":PWR" + limits_dict[limits] + "?").encode()) rx = self.dll.usbCommWrite(self.serialnum, command) pow_string = rx.decode() - assert (pow_string != 'Invalid Command') + assert pow_string != "Invalid Command" - power = float(pow_string.strip(' dBm')) + power = float(pow_string.strip(" dBm")) return round(power, 3) # rounding as the synth reads to 3 d.p. precision def set_pow(self, power): @@ -94,20 +96,20 @@ def set_pow(self, power): pow_string = str(round(power, 2)) - command = ctypes.c_char_p((':PWR:' + pow_string + 'dBm').encode()) + command = ctypes.c_char_p((":PWR:" + pow_string + "dBm").encode()) rx = self.dll.usbCommWrite(self.serialnum, command) - assert (rx.decode() == 'Power Set') + assert rx.decode() == "Power Set" def identity(self): """Retrieves the Manufacturer, Device Name, Board Number, Firmware Version, Instrument Serial Number.""" - command = ctypes.c_char_p((':IDN?').encode()) + command = ctypes.c_char_p((":IDN?").encode()) rx = self.dll.usbCommWrite(self.serialnum, command) return rx.decode() def ping(self): """Needed to check connnection is alive.""" - if self.identity() == '': + if self.identity() == "": raise Exception("No devices connected") return True @@ -117,4 +119,4 @@ def close(self): Must be called when disconnecting else future connections may not work """ self.dll.close_all() - print('Connection to Holzworth synth closed safely') + print("Connection to Holzworth synth closed safely") diff --git a/oxart/devices/home_built_controller/driver.py b/oxart/devices/home_built_controller/driver.py index 4ef0b7e..b664c4c 100644 --- a/oxart/devices/home_built_controller/driver.py +++ b/oxart/devices/home_built_controller/driver.py @@ -11,7 +11,7 @@ class MeasurementType(Enum): current = "current_ampere" -class TemperatureController(): +class TemperatureController: def __init__(self, addr): self.ser = serial.serial_for_url(addr, baudrate=115200, timeout=2) @@ -24,17 +24,17 @@ def __init__(self, addr): def set_pid_parameters(self, tset, kp, ki, kd): self.ser.write("gain {:.2f} {:.2f} {:.2f}\n".format(kp, ki, kd).encode()) self.ser.write("gain?\n".encode()) - logger.info('Gain settings? ', self.ser.readline().decode().strip()) + logger.info("Gain settings? ", self.ser.readline().decode().strip()) - self.ser.write('tset {:.2f}\n'.format(tset).encode()) + self.ser.write("tset {:.2f}\n".format(tset).encode()) self.ser.write("tset?\n".encode()) logger.info(f"Temperature setpoint?: {self.ser.readline().decode().strip()}") - self.ser.write('resistiveload 0\n'.encode()) + self.ser.write("resistiveload 0\n".encode()) self.ser.write("resistiveload?\n".encode()) logger.info(f"Unipolar?: {self.ser.readline().decode().strip()}") - self.ser.write('calib {:.2f} {:.2f} {:.2f}\n'.format(25, 0.9651, 3895).encode()) + self.ser.write("calib {:.2f} {:.2f} {:.2f}\n".format(25, 0.9651, 3895).encode()) self.ser.write("calib?\n".encode()) logger.info(f"Thermistor calibration?: {self.ser.readline().decode().strip()}") @@ -44,18 +44,18 @@ def set_current(self, iset): logger.info(f"Current: {self.ser.readline().decode().strip()}") def enable_pid(self, enable): - self.ser.write('reg {:.0f}\n'.format(1 if enable else 0).encode()) + self.ser.write("reg {:.0f}\n".format(1 if enable else 0).encode()) self.ser.write("reg?\n".encode()) print(f"Regulator enabled?: {self.ser.readline().decode().strip()}") def get_measurement(self): self.ser.write("status?\n".encode()) data = self.ser.readline().decode().strip() - data = [float(num) for num in data.split(' ')] + data = [float(num) for num in data.split(" ")] logger.info(f"Temperature: {data[0]}C, current: {data[2]}A") result = { MeasurementType.temperature: data[0], - MeasurementType.current: data[2] + MeasurementType.current: data[2], } return result diff --git a/oxart/devices/ipcmini/constants.py b/oxart/devices/ipcmini/constants.py index f5959d1..514aeac 100644 --- a/oxart/devices/ipcmini/constants.py +++ b/oxart/devices/ipcmini/constants.py @@ -1,17 +1,18 @@ """Constants for use with IPCMini ion pump controller.""" + import numpy as np -stx = b'\x02' -etx = b'\x03' -read = b'\x30' -write = b'\x31' -addr = b'\x80' +stx = b"\x02" +etx = b"\x03" +read = b"\x30" +write = b"\x31" +addr = b"\x80" -ack = b'\x06' -nack = b'\x15' -win_unknown = b'\x32' -data_error = b'\x33' # wrong type or out of range -win_disabled = b'\x35' +ack = b"\x06" +nack = b"\x15" +win_unknown = b"\x32" +data_error = b"\x33" # wrong type or out of range +win_disabled = b"\x35" def calculate_crc(bytes_): @@ -29,7 +30,7 @@ def encode_write(win, data): def encode_message(win, com, data=None): - msg = addr + '{:03}'.format(win).encode() + com + msg = addr + "{:03}".format(win).encode() + com if data is not None: msg += data msg += etx @@ -91,155 +92,72 @@ def check_return_code(code): "mode": { "win": 8, "type": "N", - "docstring": "mode, allowed values are Serial/Remote/Local/LAN" - }, - "hv_enable": { - "win": 11, - "type": "L" + "docstring": "mode, allowed values are Serial/Remote/Local/LAN", }, + "hv_enable": {"win": 11, "type": "L"}, "baud_rate": { "win": 108, "type": "N", - "docstring": "baud rate, allowed values are 1200/2400/4800/9600" - }, - "status": { - "win": 205, - "type": "N", - "writable": False - }, - "error_code": { - "win": 206, - "type": "N", - "writable": False - }, - "controller_model": { - "win": 319, - "type": "A" - }, - "serial_number": { - "win": 323, - "type": "A" - }, - "rs485_address": { - "win": 503, - "type": "N" - }, - "serial_type": { - "win": 504, - "type": "L" - }, + "docstring": "baud rate, allowed values are 1200/2400/4800/9600", + }, + "status": {"win": 205, "type": "N", "writable": False}, + "error_code": {"win": 206, "type": "N", "writable": False}, + "controller_model": {"win": 319, "type": "A"}, + "serial_number": {"win": 323, "type": "A"}, + "rs485_address": {"win": 503, "type": "N"}, + "serial_type": {"win": 504, "type": "L"}, "pressure_units": { "win": 600, "type": "N", - "docstring": "pressure units, allowed values are Torr/mbar/Pa" - }, - "autostart": { - "win": 601, - "type": "L" - }, - "protect": { - "win": 602, - "type": "L" - }, - "fixed_step": { - "win": 603, - "type": "L" - }, - "device_number": { - "win": 610, - "type": "N" - }, - "max_power": { - "win": 612, - "type": "N" + "docstring": "pressure units, allowed values are Torr/mbar/Pa", }, + "autostart": {"win": 601, "type": "L"}, + "protect": {"win": 602, "type": "L"}, + "fixed_step": {"win": 603, "type": "L"}, + "device_number": {"win": 610, "type": "N"}, + "max_power": {"win": 612, "type": "N"}, "v_target": { "win": 613, "type": "N", - "docstring": "target voltage from 3000-7000 V" + "docstring": "target voltage from 3000-7000 V", }, "i_protect": { "win": 614, "type": "N", - "docstring": "protection current in uA from 1-10000 in integer steps" + "docstring": "protection current in uA from 1-10000 in integer steps", }, "setpoint": { "win": 615, "type": "A", - "docstring": "pressure setpoint in " - }, - "temp_power": { - "win": 800, - "type": "N", - "writable": False - }, - "temp_internal": { - "win": 801, - "type": "N", - "writable": False - }, - "setpoint_status": { - "win": 804, - "type": "L", - "writable": False - }, - "v_measured": { - "win": 810, - "type": "N", - "writable": False - }, - "i_measured": { - "win": 811, - "type": "A", - "writable": False - }, - "pressure": { - "win": 812, - "type": "A", - "writable": False - }, - "label": { - "win": 890, - "type": "A" - }, + "docstring": "pressure setpoint in ", + }, + "temp_power": {"win": 800, "type": "N", "writable": False}, + "temp_internal": {"win": 801, "type": "N", "writable": False}, + "setpoint_status": {"win": 804, "type": "L", "writable": False}, + "v_measured": {"win": 810, "type": "N", "writable": False}, + "i_measured": {"win": 811, "type": "A", "writable": False}, + "pressure": {"win": 812, "type": "A", "writable": False}, + "label": {"win": 890, "type": "A"}, } lookups = { - "mode": { - 0: "Serial", - 1: "Remote", - 2: "Local", - 3: "LAN" - }, - "baud_rate": { - 1: 1200, - 2: 2400, - 3: 4800, - 4: 9600 - }, + "mode": {0: "Serial", 1: "Remote", 2: "Local", 3: "LAN"}, + "baud_rate": {1: 1200, 2: 2400, 3: 4800, 4: 9600}, "serial_type": { 0: "RS232", 1: "RS485", }, - "pressure_units": { - 0: "Torr", - 1: "mbar", - 2: "Pa" - }, + "pressure_units": {0: "Torr", 1: "mbar", 2: "Pa"}, "error_code": { 0: "No Error", 4: "Over Temperature", 32: "Interlock Cable", 64: "Short Circuit", 128: "Protect", - } + }, } reverse_lookups = { - name: { - v: k - for k, v in lookup.items() - } - for name, lookup in lookups.items() + name: {v: k for k, v in lookup.items()} for name, lookup in lookups.items() } floats = {"pressure", "setpoint", "i_measured"} diff --git a/oxart/devices/ipcmini/driver.py b/oxart/devices/ipcmini/driver.py index f3bc6b4..06507d4 100644 --- a/oxart/devices/ipcmini/driver.py +++ b/oxart/devices/ipcmini/driver.py @@ -1,4 +1,5 @@ import sys + if sys.version_info.major <= 3 and sys.version_info.minor <= 12: # telnetlib last in standard library in version 3.12 import telnetlib @@ -35,11 +36,11 @@ def _get(): if helper is not None: return helper(data) - elif type_ == 'L': + elif type_ == "L": return bool(data) - elif type_ == 'N': + elif type_ == "N": return int(data) - elif type_ == 'A': + elif type_ == "A": return data return _get @@ -50,11 +51,11 @@ def _set(value): if helper is not None: value = helper(value) - if type_ == 'L': + if type_ == "L": data = "{:02}".format(bool(value)).encode() - elif type_ == 'N': + elif type_ == "N": data = "{:06}".format(value).encode() - elif type_ == 'A': + elif type_ == "A": data = "{}".format(value).encode() msg = c.encode_write(win, data) @@ -72,6 +73,7 @@ def read_helper(data): def write_helper(value): return c.reverse_lookups[win_desc][value] + elif win_desc in c.floats: def read_helper(data): @@ -79,22 +81,23 @@ def read_helper(data): def write_helper(value): return "{:10g}".format(value) + else: read_helper = None write_helper = None - docstring = win_info.get('docstring', None) + docstring = win_info.get("docstring", None) - get_fn = self._proto_get(win_info['win'], win_info['type'], helper=read_helper) + get_fn = self._proto_get(win_info["win"], win_info["type"], helper=read_helper) if docstring is not None: get_fn.__doc__ = "Get " + docstring get_fn.__name__ = "get_{}".format(win_desc) setattr(self, get_fn.__name__, get_fn) - if win_info.get('writable', True): - set_fn = self._proto_set(win_info['win'], - win_info['type'], - helper=write_helper) + if win_info.get("writable", True): + set_fn = self._proto_set( + win_info["win"], win_info["type"], helper=write_helper + ) if docstring is not None: set_fn.__doc__ = "Set " + docstring set_fn.__name__ = "set_{}".format(win_desc) @@ -104,4 +107,4 @@ def close(self): self.tn.close() def ping(self): - return self.get_error_code() == c.lookups['error_code'][0] + return self.get_error_code() == c.lookups["error_code"][0] diff --git a/oxart/devices/keysight_MSOX3104G/driver.py b/oxart/devices/keysight_MSOX3104G/driver.py index fe86da6..ce75a9f 100644 --- a/oxart/devices/keysight_MSOX3104G/driver.py +++ b/oxart/devices/keysight_MSOX3104G/driver.py @@ -6,7 +6,7 @@ def list_resources(): - rm = pyvisa.ResourceManager('@py') + rm = pyvisa.ResourceManager("@py") return rm.list_resources() @@ -14,7 +14,7 @@ class MSOX3104G: """Keysight InfiniiVision MSOX3104G driver.""" def __init__(self, address: str): - self._rm = pyvisa.ResourceManager('@py') + self._rm = pyvisa.ResourceManager("@py") self.dev = self._rm.open_resource(address) self.dev.timeout = 15000 * 4 @@ -27,13 +27,11 @@ def __init__(self, address: str): self.y_origin = None self.y_reference = None - print('Oscilloscope ready: %s' % (address)) + print("Oscilloscope ready: %s" % (address)) - def setup_trigger_from_edge(self, - mode: str, - source: int, - level: float = 20.0, - slope_positive: bool = True): + def setup_trigger_from_edge( + self, mode: str, source: int, level: float = 20.0, slope_positive: bool = True + ): self.dev.write(":TRIGger:MODE EDGE") self.dev.write(":TRIGger:SWEep NORMal") self.dev.write(f":TRIGger:EDGE:SOURce CHANnel{source}") @@ -62,8 +60,9 @@ def setup_acquisition(self, channel: int): self.dev.write(":WAVeform:POINts:MODE RAW") preamble_string = self.dev.query(":WAVeform:PREamble?") - (_, _, _, _, x_increment, x_origin, _, y_increment, y_origin, y_reference) = \ + (_, _, _, _, x_increment, x_origin, _, y_increment, y_origin, y_reference) = ( preamble_string.split(",") + ) self.x_increment = float(x_increment) self.y_increment = float(y_increment) @@ -75,7 +74,8 @@ def acquire_waveform(self, channel: int): if self.x_increment is None: logger.error("Forgot to call setup_acquisition()?") values = self.dev.query_binary_values( - f":WAVeform:SOURce CHANnel{channel} ;DATA?", "h", False) + f":WAVeform:SOURce CHANnel{channel} ;DATA?", "h", False + ) times = np.empty(len(values)) voltages = np.empty(len(values)) diff --git a/oxart/devices/keysight_MSO_S/driver.py b/oxart/devices/keysight_MSO_S/driver.py index 2a92c22..8a650d3 100644 --- a/oxart/devices/keysight_MSO_S/driver.py +++ b/oxart/devices/keysight_MSO_S/driver.py @@ -36,8 +36,9 @@ def get_waveform(self): for that). """ self.dev.write(":WAV:DATA?\n".encode()) - return np.array(self.dev.readline().decode().strip('\n,\r ').split(','), - dtype=np.float32) + return np.array( + self.dev.readline().decode().strip("\n,\r ").split(","), dtype=np.float32 + ) def get_x_axis(self): """Returns an x-axis with a given number of points.""" diff --git a/oxart/devices/lakeshore_335/driver.py b/oxart/devices/lakeshore_335/driver.py index 31cbc3a..f8ddce6 100644 --- a/oxart/devices/lakeshore_335/driver.py +++ b/oxart/devices/lakeshore_335/driver.py @@ -14,15 +14,15 @@ def identify(self): return self.stream.readline().decode() def get_temp(self, input="A"): - """ Returns the temperature of an input channel as a float in Kelvin + """Returns the temperature of an input channel as a float in Kelvin : param input: either "A" or "B" """ self.stream.write("KRDG? {}\n".format(input).encode()) return float(self.stream.readline()) def ping(self): - idn = self.identify().split(',') - return idn[0:2] == ['LSCI', 'MODEL335'] + idn = self.identify().split(",") + return idn[0:2] == ["LSCI", "MODEL335"] def get_manual_heater_output(self, output=1): """Returns the output power/current of the heater, scale 0-100%. @@ -56,7 +56,7 @@ def get_heater_output(self, output=1): return float(self.stream.readline()) def get_heater_mode(self, output=1): - """ Returns the output mode of the heater + """Returns the output mode of the heater : param output: either 1 or 2 for heater channels : return: , , """ @@ -64,7 +64,7 @@ def get_heater_mode(self, output=1): return float(self.stream.readline()) def set_heater_mode(self, mode, channel=1, output=1, powerup_enable=0): - """ Set the output mode of the heater + """Set the output mode of the heater : param mode: Control mode: 0 = Off, 1 = PID, 2 = Zone, 3 = Open Loop, 4 = Monitor out, 5 = Warmup Supply : param output: either 1 or 2 for heater channels @@ -74,12 +74,15 @@ def set_heater_mode(self, mode, channel=1, output=1, powerup_enable=0): 0 = powerup enable off, 1 = powerup enable on. : return: output, mode, channel, powerup_enable """ - self.stream.write("OUTMODE {},{},{},{}\n".format(output, mode, channel, - powerup_enable).encode()) + self.stream.write( + "OUTMODE {},{},{},{}\n".format( + output, mode, channel, powerup_enable + ).encode() + ) return output, mode, channel, powerup_enable def get_heater_range(self, output=1): - """ Returns the heater range + """Returns the heater range : param output: either 1 or 2 for heater channels : return: For Outputs 1 and 2 in Current mode: 0 = Off, 1 = Low, 2 = Medium, 3 = High @@ -89,7 +92,7 @@ def get_heater_range(self, output=1): return float(self.stream.readline()) def set_heater_range(self, htr_range, output=1): - """ Returns the heater range + """Returns the heater range : param output: either 1 or 2 for heater channels : return: For Outputs 1 and 2 in Current mode: 0 = Off, 1 = Low, 2 = Medium, 3 = High @@ -99,7 +102,7 @@ def set_heater_range(self, htr_range, output=1): return output, htr_range def get_pid(self, output=1): - """ Returns the PID settings + """Returns the PID settings : param output: either 1 or 2 for heater channels : return:

, , """ @@ -107,7 +110,7 @@ def get_pid(self, output=1): return self.stream.readline().decode() def set_pid(self, p, i, d, output=1): - """ Set PID paramaters + """Set PID paramaters : param output: either 1 or 2 for heater channels : param p: Proportional 0.1 to 1000. : param i: Integral 0.1 to 1000. @@ -118,7 +121,7 @@ def set_pid(self, p, i, d, output=1): return output, p, i, d def get_setpoint(self, output=1): - """ Get the temeperature set point for PID + """Get the temeperature set point for PID : param output: either 1 or 2 for heater channels : return: Temperature of PID setpoint (K) """ @@ -126,7 +129,7 @@ def get_setpoint(self, output=1): return float(self.stream.readline()) def set_setpoint(self, value, output=1): - """ Set the temeperature set point for PID + """Set the temeperature set point for PID : param output: either 1 or 2 for heater channels : param value: temperature (K) : return: output, value @@ -135,7 +138,7 @@ def set_setpoint(self, value, output=1): return output, value def heater_setup(self, out_type, htr_res, I_max, I_max_user, display, output=1): - """ Configures the heater + """Configures the heater : param output: either 1 or 2 for heater channels : param out_type: Output type (Output 2 only): 0=Current, 1=Voltage : param htr_res: Heater Resistance Setting: 1 = 25 Ohm, 2 = 50 Ohm @@ -149,18 +152,21 @@ def heater_setup(self, out_type, htr_res, I_max, I_max_user, display, output=1): 2 = power : return: output, out_type, htr_res, I_max, I_max_user, display """ - self.stream.write("HTRSET {},{},{},{},{},{}\n".format( - output, out_type, htr_res, I_max, I_max_user, display).encode()) + self.stream.write( + "HTRSET {},{},{},{},{},{}\n".format( + output, out_type, htr_res, I_max, I_max_user, display + ).encode() + ) return output, out_type, htr_res, I_max, I_max_user, display def get_heater_setup(self, output=1): - """ Returns the current configuration of the heater + """Returns the current configuration of the heater : param output: either 1 or 2 for heater channels see above 'heater_setup' function for return meanings : return: output, out_type, htr_res, I_max, I_max_user, display """ self.stream.write("HTRSET? {}\n".format(output).encode()) - out = self.stream.readline().decode().split(',') + out = self.stream.readline().decode().split(",") return out def close(self): diff --git a/oxart/devices/lakeshore_336/driver.py b/oxart/devices/lakeshore_336/driver.py index 0507ce0..489cca6 100644 --- a/oxart/devices/lakeshore_336/driver.py +++ b/oxart/devices/lakeshore_336/driver.py @@ -23,7 +23,7 @@ def identify(self): return self._read_line() def get_temp(self, input="A"): - """ Returns the temperature of an input channel as a float in Kelin + """Returns the temperature of an input channel as a float in Kelin : param input: either "A" or "B" """ self._send_cmd("KRDG? {}".format(input)) diff --git a/oxart/devices/libusb.py b/oxart/devices/libusb.py index 4c0cdf1..fd5d448 100644 --- a/oxart/devices/libusb.py +++ b/oxart/devices/libusb.py @@ -17,8 +17,9 @@ def get_libusb_library_path(): """ if shutil.which("libusb-config") is None: raise FileNotFoundError( - "libusb-config not found in $PATH. Is libusb-dev installed (or, for Nix, " + - "nixpkgs.libusb-compat-0_1)?") + "libusb-config not found in $PATH. Is libusb-dev installed (or, for Nix, " + + "nixpkgs.libusb-compat-0_1)?" + ) try: result = subprocess.run( @@ -39,6 +40,8 @@ def get_libusb_library_path(): paths = [arg[2:] for arg in output.split() if arg.startswith("-L") and len(arg) > 2] if len(paths) != 1: - raise RuntimeError("Expected to find exactly one library path (-L…) in " + - f"'libusb-config --libs' output, got: '{output}'") + raise RuntimeError( + "Expected to find exactly one library path (-L…) in " + + f"'libusb-config --libs' output, got: '{output}'" + ) return paths[0] diff --git a/oxart/devices/mediator.py b/oxart/devices/mediator.py index 32fcda0..701c050 100644 --- a/oxart/devices/mediator.py +++ b/oxart/devices/mediator.py @@ -18,7 +18,7 @@ def wrapper(self, *args, **kwargs): if got_channel_kwarg: kwargs[self._channel_arg] = channel_num else: - args = args[:idx] + (channel_num, ) + args[idx + 1:] + args = args[:idx] + (channel_num,) + args[idx + 1 :] elif not got_channel_kwarg: args = args[1:] @@ -58,9 +58,10 @@ def __init__(self, dmgr, devices, mappings): funcs = [ (func_name, func) - for (func_name, - func) in inspect.getmembers(mediator_cls._driver_cls, inspect.isfunction) - if func_name[0] != '_' + for (func_name, func) in inspect.getmembers( + mediator_cls._driver_cls, inspect.isfunction + ) + if func_name[0] != "_" ] for func_name, func in funcs: @@ -77,8 +78,10 @@ def __init__(self, dmgr, devices, mappings): else: params.insert( 1, - inspect.Parameter(name="channel_name", - kind=inspect.Parameter.POSITIONAL_OR_KEYWORD)) + inspect.Parameter( + name="channel_name", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD + ), + ) wrapper = _wrap_function(func_name, func) wrapper.__signature__ = sig.replace(parameters=params) setattr(mediator_cls, func_name, wrapper) diff --git a/oxart/devices/mqtt_client/driver.py b/oxart/devices/mqtt_client/driver.py index 8479c29..9059d80 100644 --- a/oxart/devices/mqtt_client/driver.py +++ b/oxart/devices/mqtt_client/driver.py @@ -49,7 +49,8 @@ def _cleanup_path_name(self, path: str): if path[0] == "/": path = path[1:] assert self._check_path( - path), f"Requested path '{path}' is not a valid subtopic of '/settings'." + path + ), f"Requested path '{path}' is not a valid subtopic of '/settings'." return path def set_setting(self, setting: str, value: Any): @@ -63,8 +64,9 @@ def set_setting(self, setting: str, value: Any): setting = self._cleanup_path_name(setting) async def _set(): - await asyncio.wait_for(self.interface.set("/" + setting, value), - self.timeout) + await asyncio.wait_for( + self.interface.set("/" + setting, value), self.timeout + ) asyncio.run(_set()) @@ -78,8 +80,9 @@ def get_setting(self, setting: str): setting = self._cleanup_path_name(setting) async def _get(): - res = await asyncio.wait_for(self.interface.get("/" + setting), - self.timeout) + res = await asyncio.wait_for( + self.interface.get("/" + setting), self.timeout + ) return res return asyncio.run(_get()) @@ -105,13 +108,14 @@ def ping(self): def get_argparser(): parser = argparse.ArgumentParser( - description="Test connection to MQTT server by printing a list of all topics " + - "under :prefix:/settings.") + description="Test connection to MQTT server by printing a list of all topics " + + "under :prefix:/settings." + ) parser.add_argument( "--prefix", required=True, - help="Prefix of MQTT /settings topic tree, of the form " + - "'dt/sinara/:device:/:mac_address:'", + help="Prefix of MQTT /settings topic tree, of the form " + + "'dt/sinara/:device:/:mac_address:'", ) parser.add_argument( "--address", @@ -131,10 +135,9 @@ def main(): args = get_argparser().parse_args() - client = MQTTClient(dmgr=None, - prefix=args.prefix, - address=args.address, - timeout=args.timeout) + client = MQTTClient( + dmgr=None, prefix=args.prefix, address=args.address, timeout=args.timeout + ) settings = {} for path in client.paths: value = client.get_setting(path) diff --git a/oxart/devices/mqtt_client/mqtt_client.py b/oxart/devices/mqtt_client/mqtt_client.py index fcd6019..acc4aac 100644 --- a/oxart/devices/mqtt_client/mqtt_client.py +++ b/oxart/devices/mqtt_client/mqtt_client.py @@ -3,6 +3,7 @@ Mostly wrapping gmqtt clients for convenience. Requires `gmqtt` """ + # TODO: Unify with the existing driver in the previous file. import logging @@ -53,8 +54,9 @@ class MqttInterface: Stabilizer only supports QoS 0 for now). """ - def __init__(self, topic_base: str, broker_address: NetworkAddress, *args, - **kwargs): + def __init__( + self, topic_base: str, broker_address: NetworkAddress, *args, **kwargs + ): r"""Factory method to create a new MQTT connection :param broker_address: Address of the MQTT broker :type broker_address: NetworkAddress :param topic_base: Base topic of the device to connect to. :type topic_base: str @@ -120,10 +122,9 @@ def publish(self, topic, value, retain=True): This is e.g. appropriate for publishing UI changes. """ payload = json.dumps(value).encode("utf-8") - self._client.publish(f"{self._topic_base}/{topic}", - payload, - qos=0, - retain=retain) + self._client.publish( + f"{self._topic_base}/{topic}", payload, qos=0, retain=retain + ) async def request(self, topic: str, argument: Any, retain: bool = False): """Send a request to Stabilizer and wait for the response.""" @@ -151,7 +152,8 @@ async def request(self, topic: str, argument: Any, retain: bool = False): async def fail_after_timeout(): await asyncio.sleep(self._timeout) result.set_exception( - TimeoutError(f"No response to {topic} request after {self._timeout} s")) + TimeoutError(f"No response to {topic} request after {self._timeout} s") + ) self._pending.pop(correlation_data) _, pending = await asyncio.wait( @@ -175,8 +177,10 @@ def _on_message(self, client, topic, payload, qos, properties) -> int: cd = properties.get("correlation_data", []) if len(cd) != 1: logger.warning( - ("Received response without (valid) correlation data" - "(topic '%s', payload %s) "), + ( + "Received response without (valid) correlation data" + "(topic '%s', payload %s) " + ), topic, payload, ) @@ -185,8 +189,9 @@ def _on_message(self, client, topic, payload, qos, properties) -> int: if seq_id not in self._pending: # This is fine if Stabilizer restarts, though. - logger.warning("Received unexpected/late response for '%s' (id %s)", topic, - seq_id) + logger.warning( + "Received unexpected/late response for '%s' (id %s)", topic, seq_id + ) return 0 result = self._pending.pop(seq_id) @@ -211,7 +216,7 @@ def full_topic(topic): return f"{self._topic_base}/{topic}" decoded_values = [None for _ in topics] - for (i, topic) in enumerate(topics): + for i, topic in enumerate(topics): event = asyncio.Event() decoded_value = None diff --git a/oxart/devices/ophir/driver.py b/oxart/devices/ophir/driver.py index f483c28..e8274d3 100644 --- a/oxart/devices/ophir/driver.py +++ b/oxart/devices/ophir/driver.py @@ -1,6 +1,7 @@ import logging import sys -if sys.platform == 'win32': + +if sys.platform == "win32": try: import win32com.client except ImportError: @@ -23,8 +24,10 @@ def __init__(self, serial_number=None, channel=0): com_version = self.com.GetVersion() if com_version < 902: - logger.warn("Ophir COM object version %s old and untested; " - "consider installing StarLab 3.40 or higher.") + logger.warn( + "Ophir COM object version %s old and untested; " + "consider installing StarLab 3.40 or higher." + ) else: logger.info("Using Ophir COM object version %s.", com_version) @@ -40,7 +43,9 @@ def __init__(self, serial_number=None, channel=0): if len(device_list) != 1: raise Exception( "More than one Ophir power meter connected, " - "specify a serial number", device_list) + "specify a serial number", + device_list, + ) self.device_serial_number = device_list[-1] logger.info("Connecting to serial number %s...", self.device_serial_number) @@ -48,10 +53,15 @@ def __init__(self, serial_number=None, channel=0): if not self.com.IsSensorExists(self.device, self.channel): raise Exception("No sensor connected to power meter") - self.sensor_serial_number, sensor_type, sensor_model = (self.com.GetSensorInfo( - self.device, self.channel)) - logger.info("Connected; %s sensor (%s; serial number %s).", sensor_type, - sensor_model, self.sensor_serial_number) + self.sensor_serial_number, sensor_type, sensor_model = self.com.GetSensorInfo( + self.device, self.channel + ) + logger.info( + "Connected; %s sensor (%s; serial number %s).", + sensor_type, + sensor_model, + self.sensor_serial_number, + ) def start_acquisition(self): self.com.StartStream(self.device, self.channel) diff --git a/oxart/devices/picomotor/driver.py b/oxart/devices/picomotor/driver.py index 562675c..448924a 100644 --- a/oxart/devices/picomotor/driver.py +++ b/oxart/devices/picomotor/driver.py @@ -17,53 +17,53 @@ def __init__(self, device_ip): # dict containing commands as keys and a list with entries # [takes_channel, takes_argument, arg_min, arg_max] self.commands = { - '*IDN?': [False, False], # Identification string query - '*RCL': [False, True, 0, 1], # Recall parameters - '*RST': [False, False], # Reset instrument - 'AB': [False, False], # Abort motion - 'AC': [True, True, 1, 200000], # Set acceleration - 'AC?': [True, False], # Get acceleration - 'DH': [True, True, -2147483648, 2147483647], # Define home position - 'DH?': [True, False], # Get home position - 'MC': [False, False], # Motor check - 'MD?': [True, False], # Get motion done status - 'MV': [True, True, '-', '+'], # Move indefinitely - 'MV?': [True, False], # Get motion direction - 'PA': [True, True, -2147483648, 2147483647], + "*IDN?": [False, False], # Identification string query + "*RCL": [False, True, 0, 1], # Recall parameters + "*RST": [False, False], # Reset instrument + "AB": [False, False], # Abort motion + "AC": [True, True, 1, 200000], # Set acceleration + "AC?": [True, False], # Get acceleration + "DH": [True, True, -2147483648, 2147483647], # Define home position + "DH?": [True, False], # Get home position + "MC": [False, False], # Motor check + "MD?": [True, False], # Get motion done status + "MV": [True, True, "-", "+"], # Move indefinitely + "MV?": [True, False], # Get motion direction + "PA": [True, True, -2147483648, 2147483647], # Move to a target position - 'PA?': [True, False], # Get destination position - 'PR': [True, True, -2147483648, 2147483647], # Move relative - 'PR?': [True, False], # Get destination position - 'QM': [True, True, 0, 3], # Set motor type - 'QM?': [True, False], # Get motor type - 'RS': [False, False], # Reset the controller - 'SA': [False, True, 1, 31], # Set controller address - 'SA?': [False, False], # Get controller address - 'SC': [False, True, 0, 2], # Scan RS-485 network - 'SC?': [False, False], # Get RS-485 network controller addresses - 'SD?': [False, False], # Get scan status - 'SM': [False, False], # Save to non-volatile memory - 'ST': [True, False], # Stop motion - 'TB?': [False, False], # Get error message - 'TE?': [False, False], # Get error number - 'TP?': [True, False], # Get position - 'VA': [True, True, 1, 2000], # Set velocity - 'VA?': [True, False], # Get velocity - 'VE?': [False, False], # Firmware version string query - 'XX': [False, False], # Purge memory - 'ZZ': [False, True, None], # Set configuration register - 'ZZ?': [False, False], # Get configuration register - 'GATEWAY': [False, False], # Default gateway address - 'GATEWAY?': [False, False], # Default gateway address query - 'HOSTNAME': [False, True, None], # Hostname - 'HOSTNAME?': [False, False], # Hostname query - 'IPADDR': [False, True, None], # IP address - 'IPADDR?': [False, False], # IP address query - 'IPMODE': [False, True, 0, 1], # IP mode - 'IPMODE?': [False, False], # IP mode query - 'MACADDR?': [False, False], # MAC address query - 'NETMASK': [False, True, None], # Network mask address - 'NETMASK?': [False, False] # Network mask address query + "PA?": [True, False], # Get destination position + "PR": [True, True, -2147483648, 2147483647], # Move relative + "PR?": [True, False], # Get destination position + "QM": [True, True, 0, 3], # Set motor type + "QM?": [True, False], # Get motor type + "RS": [False, False], # Reset the controller + "SA": [False, True, 1, 31], # Set controller address + "SA?": [False, False], # Get controller address + "SC": [False, True, 0, 2], # Scan RS-485 network + "SC?": [False, False], # Get RS-485 network controller addresses + "SD?": [False, False], # Get scan status + "SM": [False, False], # Save to non-volatile memory + "ST": [True, False], # Stop motion + "TB?": [False, False], # Get error message + "TE?": [False, False], # Get error number + "TP?": [True, False], # Get position + "VA": [True, True, 1, 2000], # Set velocity + "VA?": [True, False], # Get velocity + "VE?": [False, False], # Firmware version string query + "XX": [False, False], # Purge memory + "ZZ": [False, True, None], # Set configuration register + "ZZ?": [False, False], # Get configuration register + "GATEWAY": [False, False], # Default gateway address + "GATEWAY?": [False, False], # Default gateway address query + "HOSTNAME": [False, True, None], # Hostname + "HOSTNAME?": [False, False], # Hostname query + "IPADDR": [False, True, None], # IP address + "IPADDR?": [False, False], # IP address query + "IPMODE": [False, True, 0, 1], # IP mode + "IPMODE?": [False, False], # IP mode query + "MACADDR?": [False, False], # MAC address query + "NETMASK": [False, True, None], # Network mask address + "NETMASK?": [False, False], # Network mask address query } # dict containing all error code messages self.error_codes = { @@ -113,7 +113,7 @@ def __init__(self, device_ip): def send_command(self, command, axis=None, argument=None): c = command - assert (command in self.commands), "Invalid Command" + assert command in self.commands, "Invalid Command" if self.commands[command][0]: assert self._is_valid_axis(axis), "Invalid Axis" c = str(axis) + c @@ -141,11 +141,11 @@ def _is_valid_argument(self, com, arg): return argmin <= arg <= argmax or arg is argmin or arg is argmax def _motor_detect_and_save_velocities(self): - self.send_command('MC') - self.send_command('SM') + self.send_command("MC") + self.send_command("SM") def _error_code_query(self): - self.send_command('TE?') + self.send_command("TE?") return int(self.receive()) # functions @@ -155,53 +155,53 @@ def wait_until_done(self, axis): time.sleep(0.1) def abort_motion(self): - self.send_command('AB') + self.send_command("AB") def set_home(self, axis, home): - self.send_command('DH', axis, home) + self.send_command("DH", axis, home) def get_home(self, axis): - self.send_command('DH?', axis) + self.send_command("DH?", axis) return int(self.receive()) def set_velocity(self, axis, vel): - self.send_command('VA', axis, vel) + self.send_command("VA", axis, vel) def get_velocity(self, axis): - self.send_command('VA?', axis) + self.send_command("VA?", axis) return int(self.receive()) def set_acceleration(self, axis, acc): - self.send_command('AC', axis, acc) + self.send_command("AC", axis, acc) def get_acceleration(self, axis): - self.send_command('AC?', axis) + self.send_command("AC?", axis) return int(self.receive()) def motion_done(self, axis): - self.send_command('MD?', axis) + self.send_command("MD?", axis) return bool(int(self.receive())) def move_absolute(self, axis, position): - self.send_command('PA', axis, position) + self.send_command("PA", axis, position) self.wait_until_done(axis) def move_relative(self, axis, distance): - self.send_command('PR', axis, distance) + self.send_command("PR", axis, distance) self.wait_until_done(axis) def move_indefinitely(self, axis, direction): - self.send_command('MV', axis, direction) + self.send_command("MV", axis, direction) def get_position(self, axis): - self.send_command('TP?', axis) + self.send_command("TP?", axis) return int(self.receive()) def stop_motion(self, axis): - self.send_command('ST', axis) + self.send_command("ST", axis) def reset_and_reboot(self): - self.send_command('RS') + self.send_command("RS") def get_errors(self): errors = [] @@ -225,7 +225,7 @@ def get_device_ip(self): return self.ip_addr def get_identity(self): - self.send_command('*IDN?') + self.send_command("*IDN?") return self.receive() def close(self): diff --git a/oxart/devices/picomotor/mediator.py b/oxart/devices/picomotor/mediator.py index 88fc6a4..d8751e6 100644 --- a/oxart/devices/picomotor/mediator.py +++ b/oxart/devices/picomotor/mediator.py @@ -29,12 +29,16 @@ def set_velocities(self, vel): self.devs[i].set_velocity(self.chnls[i], vel[i]) def get_velocities(self): - return (self.devs[0].get_velocity(self.chnls[0]), - self.devs[1].get_velocity(self.chnls[1])) + return ( + self.devs[0].get_velocity(self.chnls[0]), + self.devs[1].get_velocity(self.chnls[1]), + ) def get_position(self): - return (self.devs[0].get_position(self.chnls[0]), - self.devs[1].get_position(self.chnls[1])) + return ( + self.devs[0].get_position(self.chnls[0]), + self.devs[1].get_position(self.chnls[1]), + ) def move_indefinitely(self, axis, direction): # axis is either 0 (horizontal) or 1 (vertical) @@ -50,8 +54,10 @@ def set_home(self, home=(0, 0)): self.devs[i].set_home(self.chnls[i], home[i]) def get_home(self): - return (self.devs[0].get_home(self.chnls[0]), - self.devs[1].get_home(self.chnls[1])) + return ( + self.devs[0].get_home(self.chnls[0]), + self.devs[1].get_home(self.chnls[1]), + ) def ping(self): return True diff --git a/oxart/devices/prologix_gpib/driver.py b/oxart/devices/prologix_gpib/driver.py index ecfd4c7..49675d9 100644 --- a/oxart/devices/prologix_gpib/driver.py +++ b/oxart/devices/prologix_gpib/driver.py @@ -13,7 +13,7 @@ class GPIB: """ def __init__(self, device, gpib_addr=0, timeout=None): - """" :param device: pySerial-compatible address of the GPIB controller. + """ " :param device: pySerial-compatible address of the GPIB controller. Ethernet <-> GPIB adapters use port 1234 :param gpib_addr: initial GPIB addr to read/write from/to @@ -93,18 +93,18 @@ def close(self): self.gpib_addr = None def ping(self): - """" Return True if controller responds with correct version string. + """ " Return True if controller responds with correct version string. A timeout results in False being returned. """ - idn = self.identify().split(' ') + idn = self.identify().split(" ") return idn[0] == "Prologix" and idn[1].startswith("GPIB-") class Stream: """PySerial-compatible interface to a single GPIB device.""" def __init__(self, bus, addr): - """ :param bus: a GPIB controller """ + """:param bus: a GPIB controller""" self.bus = bus self.addr = addr diff --git a/oxart/devices/scpi_device/driver.py b/oxart/devices/scpi_device/driver.py index 12b1e7e..0a0983e 100644 --- a/oxart/devices/scpi_device/driver.py +++ b/oxart/devices/scpi_device/driver.py @@ -18,15 +18,17 @@ def __init__(self, addr, port=5025, serial_number=None): self.sock.connect((self.addr, port)) # Store identity as a list of the comma separated fields returned - self.idn = self.identity().split(',') + self.idn = self.identity().split(",") # Some devices may not implement *IDN? in the same way, but most place # serial number in this field, allowing us to check that we have the # correct device if serial_number is not None: if self.idn[2] != serial_number: - raise ValueError("Serial number {} did not match expected ({})" - "".format(self.idn[2], serial_number)) + raise ValueError( + "Serial number {} did not match expected ({})" + "".format(self.idn[2], serial_number) + ) def close(self): self.sock.close() diff --git a/oxart/devices/scpi_dmm/driver.py b/oxart/devices/scpi_dmm/driver.py index a3eff5b..fb3f82c 100644 --- a/oxart/devices/scpi_dmm/driver.py +++ b/oxart/devices/scpi_dmm/driver.py @@ -61,8 +61,9 @@ def set_range(self, measurement_range): def set_auto_range(self, enabled): """Enable or disable auto range for the current measurement mode.""" mode = self.get_measurement_mode().upper() - self.stream.write("{}:RANGE:AUTO {}\n".format( - mode, "ON" if enabled else "OFF").encode()) + self.stream.write( + "{}:RANGE:AUTO {}\n".format(mode, "ON" if enabled else "OFF").encode() + ) def set_resolution(self, resolution): """Sets the measurement resolution without initiating a measurement. @@ -84,7 +85,7 @@ def set_integration_time(self, t_int): t_int = float(t_int) if isinstance(t_int, str): t_int = t_int[0:3].lower() - if t_int not in [0.02, 0.2, 1., 10., 100., "min", "max"]: + if t_int not in [0.02, 0.2, 1.0, 10.0, 100.0, "min", "max"]: raise ValueError("invalid t_int") mode = self.get_measurement_mode() @@ -97,7 +98,7 @@ def set_bw(self, bandwidth): bandwidth = float(bandwidth) if isinstance(bandwidth, str): bandwidth = bandwidth[0:3].lower() - if bandwidth not in [3., 20., 200., "min", "max"]: + if bandwidth not in [3.0, 20.0, 200.0, "min", "max"]: raise ValueError("invalid measurement bandwidth") self.stream.write("SENSE:DET:BAND {}\n".format(bandwidth).encode()) diff --git a/oxart/devices/solstis/driver.py b/oxart/devices/solstis/driver.py index a10f523..ce06d71 100644 --- a/oxart/devices/solstis/driver.py +++ b/oxart/devices/solstis/driver.py @@ -28,8 +28,9 @@ async def run(self): """Connect to the Solstis websocket server and receive messages until connection drops.""" logger.info("Connecting to Solstis at {}:{}".format(self.server, self.port)) - async with connect("ws://{}:{}".format(self.server, self.port), - ping_interval=None) as websocket: + async with connect( + "ws://{}:{}".format(self.server, self.port), ping_interval=None + ) as websocket: while True: try: raw_msg = await wait_for(websocket.recv(), timeout=self.timeout) diff --git a/oxart/devices/stabilizer/current_stabilizer.py b/oxart/devices/stabilizer/current_stabilizer.py index 0dd9b65..a45c720 100755 --- a/oxart/devices/stabilizer/current_stabilizer.py +++ b/oxart/devices/stabilizer/current_stabilizer.py @@ -22,7 +22,7 @@ class IIR: def __init__(self): self.ba = np.zeros(5) - self.y_offset = 0. + self.y_offset = 0.0 self.y_min = -self.full_scale - 1 self.y_max = self.full_scale @@ -34,29 +34,29 @@ def as_dict(self): iir["y_max"] = self.y_max return iir - def configure_pi(self, kp, ki, g=0.): + def configure_pi(self, kp, ki, g=0.0): ki = np.copysign(ki, kp) * self.t_update * 2 g = np.copysign(g, kp) eps = np.finfo(np.float32).eps if abs(ki) < eps: - a1, b0, b1 = 0., kp, 0. + a1, b0, b1 = 0.0, kp, 0.0 else: if abs(g) < eps: - c = 1. + c = 1.0 else: - c = 1. / (1. + ki / g) - a1 = 2 * c - 1. + c = 1.0 / (1.0 + ki / g) + a1 = 2 * c - 1.0 b0 = ki * c + kp b1 = ki * c - a1 * kp if abs(b0 + b1) < eps: raise ValueError("low integrator gain and/or gain limit") self.ba[0] = b0 self.ba[1] = b1 - self.ba[2] = 0. + self.ba[2] = 0.0 self.ba[3] = a1 - self.ba[4] = 0. + self.ba[4] = 0.0 - def configure_biquad(self, zeros, poles, gain=1.): + def configure_biquad(self, zeros, poles, gain=1.0): """Calulate biquad iir filter coeficents The function constructs the iir coeficents for a transfer function with desired zeros and poles. @@ -70,18 +70,18 @@ def configure_biquad(self, zeros, poles, gain=1.): def get_polynomial_coefs(factors): "convert factors to coeficents" if len(factors) == 0: - return [1., 0., 0.] + return [1.0, 0.0, 0.0] elif len(factors) == 1: - if factors[0] != 0.: - return [1., 1. / factors[0], 0.] + if factors[0] != 0.0: + return [1.0, 1.0 / factors[0], 0.0] else: - return [0., 1., 0.] + return [0.0, 1.0, 0.0] elif len(factors) == 2: div = factors[0] * factors[1] - if div == 0.: - return [0., factors[0] + factors[1], 1.] + if div == 0.0: + return [0.0, factors[0] + factors[1], 1.0] else: - return [1., (factors[0] + factors[1]) / div, 1. / div] + return [1.0, (factors[0] + factors[1]) / div, 1.0 / div] else: raise ValueError("Invalid number of factors") @@ -103,10 +103,12 @@ def z_transform(s_coefs, t_update): ] return z_coefs - num_coefs = z_transform(get_polynomial_coefs([2 * np.pi * x for x in zeros]), - self.t_update) - denom_coefs = z_transform(get_polynomial_coefs([2 * np.pi * x for x in poles]), - self.t_update) + num_coefs = z_transform( + get_polynomial_coefs([2 * np.pi * x for x in zeros]), self.t_update + ) + denom_coefs = z_transform( + get_polynomial_coefs([2 * np.pi * x for x in poles]), self.t_update + ) # normalise to a0 = 1 & apply gain factor num_coefs = [np.real(x / denom_coefs[0]) * gain for x in num_coefs] @@ -124,7 +126,7 @@ def set_x_offset(self, o): class CPU_DAC: - full_scale = 0xfff + full_scale = 0xFFF cpu_dac_range = 48 # mA conversion_factor = full_scale / cpu_dac_range @@ -147,13 +149,14 @@ def as_dict(self): class GPIO_HDR_SPI: - full_scale = 0xffff + full_scale = 0xFFFF cpu_dac_range = 250 # mA conversion_factor = full_scale / cpu_dac_range def set_gpio_hdr(self, gpio_hdr_word): - assert gpio_hdr_word >= 0 and gpio_hdr_word <= 0xffff, \ - "GPIO_HDR_SPI setting out of range" + assert ( + gpio_hdr_word >= 0 and gpio_hdr_word <= 0xFFFF + ), "GPIO_HDR_SPI setting out of range" self.gpio_hdr_word = math.ceil(gpio_hdr_word * self.conversion_factor) @@ -190,7 +193,9 @@ async def exchange_json(connection, request): except asyncio.TimeoutError: logger.exception( "Stabilizer failed to respond within %s seconds " - "(firmware possibly crashed); exiting.", TIMEOUT) + "(firmware possibly crashed); exiting.", + TIMEOUT, + ) sys.exit(1) except ConnectionResetError: logger.exception("Connection to Stabilizer lost; exiting.") @@ -203,15 +208,25 @@ async def exchange_json(connection, request): async def set_feedback(connection, channel, iir, dac, gpio_hdr): - up = OrderedDict([("channel", channel), ("iir", iir.as_dict()), - ("cpu_dac", dac.as_dict()), - ("gpio_hdr_spi", gpio_hdr.gpio_hdr_word)]) + up = OrderedDict( + [ + ("channel", channel), + ("iir", iir.as_dict()), + ("cpu_dac", dac.as_dict()), + ("gpio_hdr_spi", gpio_hdr.gpio_hdr_word), + ] + ) return await exchange_json(connection, up) async def set_feedforward(connection, ff): - msg = OrderedDict([("sin_amplitudes", ff.sin_amps), ("cos_amplitudes", ff.cos_amps), - ("offset", ff.offset)]) + msg = OrderedDict( + [ + ("sin_amplitudes", ff.sin_amps), + ("cos_amplitudes", ff.cos_amps), + ("offset", ff.offset), + ] + ) return await exchange_json(connection, msg) @@ -236,12 +251,14 @@ def __init__(self, fb_connection, ff_connection): def ping(self): return True - async def set_feedback(self, - frontend_offset=250, - proportional_gain=1, - integral_gain=0, - feedback_offset=0, - channel_offset=0): + async def set_feedback( + self, + frontend_offset=250, + proportional_gain=1, + integral_gain=0, + feedback_offset=0, + channel_offset=0, + ): d = CPU_DAC() d.set_out(feedback_offset) d.set_en(True) @@ -254,13 +271,15 @@ async def set_feedback(self, assert self.channel in range(2) await set_feedback(self.fb_connection, self.channel, i, d, g) - async def set_feedback_biquad(self, - frontend_offset=250, - zeros=[], - poles=[], - gain=1., - feedback_offset=0, - channel_offset=0): + async def set_feedback_biquad( + self, + frontend_offset=250, + zeros=[], + poles=[], + gain=1.0, + feedback_offset=0, + channel_offset=0, + ): d = CPU_DAC() d.set_out(feedback_offset) d.set_en(True) diff --git a/oxart/devices/streams.py b/oxart/devices/streams.py index 49f0bb9..4784a65 100644 --- a/oxart/devices/streams.py +++ b/oxart/devices/streams.py @@ -18,11 +18,10 @@ def get_stream(device, baudrate=115200, port=None, timeout=None): IO operations to block. """ if not device.startswith("gpib://"): - return serial.serial_for_url(device, - baudrate=baudrate, - timeout=timeout, - write_timeout=timeout) + return serial.serial_for_url( + device, baudrate=baudrate, timeout=timeout, write_timeout=timeout + ) - controller_addr, gpib_port = device[7:].split('-') + controller_addr, gpib_port = device[7:].split("-") controller = GPIB(controller_addr, timeout=timeout) return controller.get_stream(int(gpib_port)) diff --git a/oxart/devices/surf_solver/driver.py b/oxart/devices/surf_solver/driver.py index dfc61ec..f3e1597 100644 --- a/oxart/devices/surf_solver/driver.py +++ b/oxart/devices/surf_solver/driver.py @@ -46,8 +46,10 @@ def can_use_last(self, zs, elec_fn, field_fn): def get(self, zs, elec_fn, field_fn): if self.can_use_last(zs, elec_fn, field_fn): return self.last_result - self.last_result = (self.jl.eval("mk_electrodes_grid")(zs, elec_fn), - self.jl.eval("mk_field_grid")(zs, field_fn)) + self.last_result = ( + self.jl.eval("mk_electrodes_grid")(zs, elec_fn), + self.jl.eval("mk_field_grid")(zs, field_fn), + ) self.last_zs = zs self.last_elec_fn = elec_fn self.last_field_fn = field_fn @@ -57,11 +59,13 @@ def get(self, zs, elec_fn, field_fn): class SURF: """SURF Uncomplicated Regional Fields (python driver)""" - def __init__(self, - trap_model_path="/home/ion/scratch/julia_projects/SURF/" - "trap_model/comet_model.jld", - cache_path=None, - **kwargs): + def __init__( + self, + trap_model_path="/home/ion/scratch/julia_projects/SURF/" + "trap_model/comet_model.jld", + cache_path=None, + **kwargs, + ): """ :param trap_model_path: path to the SURF trap model file :param cache_path: path on which to cache results. None disables cache. @@ -78,13 +82,15 @@ def __init__(self, self.load_config(trap_model_path, cache_path, **kwargs) print("ready") - def load_config(self, - trap_model_path=None, - cache_path=None, - omega_rf=None, - mass=None, - v_rf=None, - force_reload=False): + def load_config( + self, + trap_model_path=None, + cache_path=None, + omega_rf=None, + mass=None, + v_rf=None, + force_reload=False, + ): """Load trap model and default solver settings from the specified file and set solution solution cache path. @@ -102,8 +108,9 @@ def load_config(self, self.trap_model_path = trap_model_path # create cache directory as needed if cache_path is not None: - cache_path = os.path.join(cache_path, - "m{}_w{}_v{}".format(mass, omega_rf, v_rf)) + cache_path = os.path.join( + cache_path, "m{}_w{}_v{}".format(mass, omega_rf, v_rf) + ) if not os.path.isdir(cache_path): os.mkdir(cache_path) self.cache_path = cache_path @@ -113,16 +120,15 @@ def load_config(self, "cache_path": self.cache_path, "omega_rf": omega_rf, "mass": mass, - "v_rf": v_rf + "v_rf": v_rf, } if args == self.current_config_args and not force_reload: return self.get_config() self.current_config_args = args - model = self.jl.eval("SURF.Load.load_model")(self.trap_model_path, - omega_rf=omega_rf, - mass=mass, - v_rf=v_rf) + model = self.jl.eval("SURF.Load.load_model")( + self.trap_model_path, omega_rf=omega_rf, mass=mass, v_rf=v_rf + ) self.raw_elec_grid, self.raw_field_grid = model[0:2] self.elec_fn = self.jl.eval("mk_electrodes_fn")(self.raw_elec_grid) self.field_fn = self.jl.eval("mk_field_fn")(self.raw_field_grid) @@ -131,10 +137,16 @@ def load_config(self, dynamic_split_settings = model[6] else: # Provide defaults based on split solver. - settings = (model[5].split_scale_tup, model[5].spectator_scale_tup, - model[5].v_weight, (0.0, 0.0), model[5].v_max) - dynamic_split_settings = self._mk_solver_settings(*settings, - solver="DynamicSplit") + settings = ( + model[5].split_scale_tup, + model[5].spectator_scale_tup, + model[5].v_weight, + (0.0, 0.0), + model[5].v_max, + ) + dynamic_split_settings = self._mk_solver_settings( + *settings, solver="DynamicSplit" + ) # recommended default values for user self.user_defaults = { @@ -155,11 +167,14 @@ def get_div_grad_phi(self, z): # assignment in julia repel main name-space julia.Main.last_field_fn = self.field_fn div_grad_phi = self.jl.eval("last_field_fn.d2\N{Greek Capital Letter Phi}dx2")( - z) + z + ) div_grad_phi += self.jl.eval("last_field_fn.d2\N{Greek Capital Letter Phi}dy2")( - z) + z + ) div_grad_phi += self.jl.eval("last_field_fn.d2\N{Greek Capital Letter Phi}dz2")( - z) + z + ) return div_grad_phi def get_config(self): @@ -216,18 +231,29 @@ def static(self, **param_dict): if param_dict.get("static_settings", None) is None: settings = self.user_defaults["static_settings"] else: - settings = self._mk_solver_settings(*param_dict["static_settings"], - solver="Static") + settings = self._mk_solver_settings( + *param_dict["static_settings"], solver="Static" + ) # need to fix argument order! arg_key = pyon.encode( - (elec_fn.names, zs, param_dict.get("static_settings", - None), param_dict["wells"]["z"], - param_dict["wells"]["width"], param_dict["wells"]["dphidx"], - param_dict["wells"]["dphidy"], param_dict["wells"]["dphidz"], - param_dict["wells"]["rx_axial"], param_dict["wells"]["ry_axial"], - param_dict["wells"]["phi_radial"], param_dict["wells"]["d2phidaxial2"], - param_dict["wells"]["d3phidz3"], param_dict["wells"]["d2phidradial_h2"])) + ( + elec_fn.names, + zs, + param_dict.get("static_settings", None), + param_dict["wells"]["z"], + param_dict["wells"]["width"], + param_dict["wells"]["dphidx"], + param_dict["wells"]["dphidy"], + param_dict["wells"]["dphidz"], + param_dict["wells"]["rx_axial"], + param_dict["wells"]["ry_axial"], + param_dict["wells"]["phi_radial"], + param_dict["wells"]["d2phidaxial2"], + param_dict["wells"]["d3phidz3"], + param_dict["wells"]["d2phidradial_h2"], + ) + ) if self.cache_path is not None: with shelve.open(os.path.join(self.cache_path, "static")) as db: try: @@ -270,46 +296,48 @@ def split(self, **param_dict): zs = self.user_defaults["zs"] # need to fix argument order! - arg_key = pyon.encode(( - elec_fn.names, - zs, - param_dict.get("split_settings", None), - param_dict["scan_start"]["z"], - param_dict["scan_start"]["width"], - param_dict["scan_start"]["dphidx"], - param_dict["scan_start"]["dphidy"], - param_dict["scan_start"]["dphidz"], - param_dict["scan_start"]["rx_axial"], - param_dict["scan_start"]["ry_axial"], - param_dict["scan_start"]["phi_radial"], - param_dict["scan_start"]["d2phidaxial2"], - param_dict["scan_start"]["d3phidz3"], - param_dict["scan_start"]["d2phidradial_h2"], - param_dict["scan_end"]["z"], - param_dict["scan_end"]["width"], - param_dict["scan_end"]["dphidx"], - param_dict["scan_end"]["dphidy"], - param_dict["scan_end"]["dphidz"], - param_dict["scan_end"]["rx_axial"], - param_dict["scan_end"]["ry_axial"], - param_dict["scan_end"]["phi_radial"], - param_dict["scan_end"]["d2phidaxial2"], - param_dict["scan_end"]["d3phidz3"], - param_dict["scan_end"]["d2phidradial_h2"], - param_dict["spectators"]["z"], - param_dict["spectators"]["width"], - param_dict["spectators"]["dphidx"], - param_dict["spectators"]["dphidy"], - param_dict["spectators"]["dphidz"], - param_dict["spectators"]["rx_axial"], - param_dict["spectators"]["ry_axial"], - param_dict["spectators"]["phi_radial"], - param_dict["spectators"]["d2phidaxial2"], - param_dict["spectators"]["d3phidz3"], - param_dict["spectators"]["d2phidradial_h2"], - param_dict["n_step"], - param_dict["n_scan"], - )) + arg_key = pyon.encode( + ( + elec_fn.names, + zs, + param_dict.get("split_settings", None), + param_dict["scan_start"]["z"], + param_dict["scan_start"]["width"], + param_dict["scan_start"]["dphidx"], + param_dict["scan_start"]["dphidy"], + param_dict["scan_start"]["dphidz"], + param_dict["scan_start"]["rx_axial"], + param_dict["scan_start"]["ry_axial"], + param_dict["scan_start"]["phi_radial"], + param_dict["scan_start"]["d2phidaxial2"], + param_dict["scan_start"]["d3phidz3"], + param_dict["scan_start"]["d2phidradial_h2"], + param_dict["scan_end"]["z"], + param_dict["scan_end"]["width"], + param_dict["scan_end"]["dphidx"], + param_dict["scan_end"]["dphidy"], + param_dict["scan_end"]["dphidz"], + param_dict["scan_end"]["rx_axial"], + param_dict["scan_end"]["ry_axial"], + param_dict["scan_end"]["phi_radial"], + param_dict["scan_end"]["d2phidaxial2"], + param_dict["scan_end"]["d3phidz3"], + param_dict["scan_end"]["d2phidradial_h2"], + param_dict["spectators"]["z"], + param_dict["spectators"]["width"], + param_dict["spectators"]["dphidx"], + param_dict["spectators"]["dphidy"], + param_dict["spectators"]["dphidz"], + param_dict["spectators"]["rx_axial"], + param_dict["spectators"]["ry_axial"], + param_dict["spectators"]["phi_radial"], + param_dict["spectators"]["d2phidaxial2"], + param_dict["spectators"]["d3phidz3"], + param_dict["spectators"]["d2phidradial_h2"], + param_dict["n_step"], + param_dict["n_scan"], + ) + ) if self.cache_path is not None: with shelve.open(os.path.join(self.cache_path, "split.db")) as db: try: @@ -324,13 +352,21 @@ def split(self, **param_dict): if param_dict.get("split_settings", None) is None: settings = self.user_defaults["split_settings"] else: - settings = self._mk_solver_settings(*param_dict["split_settings"], - solver="Split") - voltages, sep_vec = self._solve_split(scan_start, scan_end, spectators, - param_dict["n_step"], - param_dict["n_scan"], elec_fn, - self.field_fn, elec_grid, field_grid, - settings) + settings = self._mk_solver_settings( + *param_dict["split_settings"], solver="Split" + ) + voltages, sep_vec = self._solve_split( + scan_start, + scan_end, + spectators, + param_dict["n_step"], + param_dict["n_scan"], + elec_fn, + self.field_fn, + elec_grid, + field_grid, + settings, + ) if self.cache_path is not None: with shelve.open(os.path.join(self.cache_path, "split.db")) as db: @@ -364,36 +400,38 @@ def dynamic_split(self, **param_dict): zs = self.user_defaults["zs"] # need to fix argument order! - arg_key = pyon.encode(( - elec_fn.names, - zs, - param_dict.get("split_settings", None), - param_dict["split_well"]["z"], - param_dict["split_well"]["width"], - param_dict["split_well"]["dphidx"], - param_dict["split_well"]["dphidy"], - param_dict["split_well"]["dphidz"], - param_dict["split_well"]["rx_axial"], - param_dict["split_well"]["ry_axial"], - param_dict["split_well"]["phi_radial"], - param_dict["split_well"]["d2phidaxial2"], - param_dict["split_well"]["d3phidz3"], - param_dict["split_well"]["d2phidradial_h2"], - param_dict["spectators"]["z"], - param_dict["spectators"]["width"], - param_dict["spectators"]["dphidx"], - param_dict["spectators"]["dphidy"], - param_dict["spectators"]["dphidz"], - param_dict["spectators"]["rx_axial"], - param_dict["spectators"]["ry_axial"], - param_dict["spectators"]["phi_radial"], - param_dict["spectators"]["d2phidaxial2"], - param_dict["spectators"]["d3phidz3"], - param_dict["spectators"]["d2phidradial_h2"], - param_dict["start_separation"], - param_dict["end_separation"], - param_dict["n_step"], - )) + arg_key = pyon.encode( + ( + elec_fn.names, + zs, + param_dict.get("split_settings", None), + param_dict["split_well"]["z"], + param_dict["split_well"]["width"], + param_dict["split_well"]["dphidx"], + param_dict["split_well"]["dphidy"], + param_dict["split_well"]["dphidz"], + param_dict["split_well"]["rx_axial"], + param_dict["split_well"]["ry_axial"], + param_dict["split_well"]["phi_radial"], + param_dict["split_well"]["d2phidaxial2"], + param_dict["split_well"]["d3phidz3"], + param_dict["split_well"]["d2phidradial_h2"], + param_dict["spectators"]["z"], + param_dict["spectators"]["width"], + param_dict["spectators"]["dphidx"], + param_dict["spectators"]["dphidy"], + param_dict["spectators"]["dphidz"], + param_dict["spectators"]["rx_axial"], + param_dict["spectators"]["ry_axial"], + param_dict["spectators"]["phi_radial"], + param_dict["spectators"]["d2phidaxial2"], + param_dict["spectators"]["d3phidz3"], + param_dict["spectators"]["d2phidradial_h2"], + param_dict["start_separation"], + param_dict["end_separation"], + param_dict["n_step"], + ) + ) if self.cache_path is not None: with shelve.open(os.path.join(self.cache_path, "dynamic_split.db")) as db: try: @@ -408,8 +446,9 @@ def dynamic_split(self, **param_dict): if param_dict.get("split_settings", None) is None: settings = self.user_defaults["dynamic_split_settings"] else: - settings = self._mk_solver_settings(*param_dict["split_settings"], - solver="DynamicSplit") + settings = self._mk_solver_settings( + *param_dict["split_settings"], solver="DynamicSplit" + ) voltages, sep_vec = self._solve_dynamic_split( split_well, param_dict["start_separation"], @@ -458,36 +497,38 @@ def dynamic(self, **param_dict): v1 = [param_dict["volt_end"][name] for name in elec_fn.names] # need to fix argument order! - arg_key = pyon.encode(( - elec_fn.names, - zs, - param_dict.get("dynamic_settings", None), - v0, - v1, - param_dict["wells0"]["z"], - param_dict["wells0"]["width"], - param_dict["wells0"]["dphidx"], - param_dict["wells0"]["dphidy"], - param_dict["wells0"]["dphidz"], - param_dict["wells0"]["rx_axial"], - param_dict["wells0"]["ry_axial"], - param_dict["wells0"]["phi_radial"], - param_dict["wells0"]["d2phidaxial2"], - param_dict["wells0"]["d3phidz3"], - param_dict["wells0"]["d2phidradial_h2"], - param_dict["wells1"]["z"], - param_dict["wells1"]["width"], - param_dict["wells1"]["dphidx"], - param_dict["wells1"]["dphidy"], - param_dict["wells1"]["dphidz"], - param_dict["wells1"]["rx_axial"], - param_dict["wells1"]["ry_axial"], - param_dict["wells1"]["phi_radial"], - param_dict["wells1"]["d2phidaxial2"], - param_dict["wells1"]["d3phidz3"], - param_dict["wells1"]["d2phidradial_h2"], - param_dict["n_step"], - )) + arg_key = pyon.encode( + ( + elec_fn.names, + zs, + param_dict.get("dynamic_settings", None), + v0, + v1, + param_dict["wells0"]["z"], + param_dict["wells0"]["width"], + param_dict["wells0"]["dphidx"], + param_dict["wells0"]["dphidy"], + param_dict["wells0"]["dphidz"], + param_dict["wells0"]["rx_axial"], + param_dict["wells0"]["ry_axial"], + param_dict["wells0"]["phi_radial"], + param_dict["wells0"]["d2phidaxial2"], + param_dict["wells0"]["d3phidz3"], + param_dict["wells0"]["d2phidradial_h2"], + param_dict["wells1"]["z"], + param_dict["wells1"]["width"], + param_dict["wells1"]["dphidx"], + param_dict["wells1"]["dphidy"], + param_dict["wells1"]["dphidz"], + param_dict["wells1"]["rx_axial"], + param_dict["wells1"]["ry_axial"], + param_dict["wells1"]["phi_radial"], + param_dict["wells1"]["d2phidaxial2"], + param_dict["wells1"]["d3phidz3"], + param_dict["wells1"]["d2phidradial_h2"], + param_dict["n_step"], + ) + ) if self.cache_path is not None: with shelve.open(os.path.join(self.cache_path, "dynamic.db")) as db: try: @@ -502,10 +543,12 @@ def dynamic(self, **param_dict): if param_dict.get("dynamic_settings", None) is None: settings = self.user_defaults["dynamic_settings"] else: - settings = self._mk_solver_settings(*param_dict["dynamic_settings"], - solver="Dynamic") - voltages = self._solve_dynamic(trajectory, v0, v1, elec_grid, field_grid, - settings) + settings = self._mk_solver_settings( + *param_dict["dynamic_settings"], solver="Dynamic" + ) + voltages = self._solve_dynamic( + trajectory, v0, v1, elec_grid, field_grid, settings + ) if self.cache_path is not None: with shelve.open(os.path.join(self.cache_path, "dynamic.db")) as db: @@ -519,8 +562,21 @@ def get_all_electrode_names(self): """Return a list of all electrode names defined in the trap model.""" return self.elec_fn.names - def _mk_wells(self, z, width, dphidx, dphidy, dphidz, rx_axial, ry_axial, - phi_radial, d2phidaxial2, d3phidz3, d2phidradial_h2, **kwargs): + def _mk_wells( + self, + z, + width, + dphidx, + dphidy, + dphidz, + rx_axial, + ry_axial, + phi_radial, + d2phidaxial2, + d3phidz3, + d2phidradial_h2, + **kwargs, + ): """Struct, characterising target potential wells at a specific time. For n coexisting wells: @@ -542,9 +598,19 @@ def _mk_wells(self, z, width, dphidx, dphidy, dphidz, rx_axial, ry_axial, :param d2phidradial_h2: horizontal radial mode frequency :param **kwargs: additional kwargs are ignored """ - return self.jl.eval("PotentialWells")(z, width, dphidx, dphidy, dphidz, - rx_axial, ry_axial, phi_radial, - d2phidaxial2, d3phidz3, d2phidradial_h2) + return self.jl.eval("PotentialWells")( + z, + width, + dphidx, + dphidy, + dphidz, + rx_axial, + ry_axial, + phi_radial, + d2phidaxial2, + d3phidz3, + d2phidradial_h2, + ) def _mk_trajectory(self, wells_start, wells_end, n_step): """Trajectory smoothly evolving wells_start to wells_end. @@ -556,7 +622,8 @@ def _mk_trajectory(self, wells_start, wells_end, n_step): trajectory (Tuple of n_step wells structs) """ return self.jl.eval("SURF.ModelTrajectories.create_shuttle_trajectory")( - wells_start, wells_end, n_step) + wells_start, wells_end, n_step + ) def _mk_grids(self, zs, elec_fn, field_fn): """Sample electrodes and external fields at positions zs. @@ -589,14 +656,22 @@ def _solve_static(self, wells, elec_grid, field_grid, settings): cost_fn = self.jl.eval("SURF.Static.cost_function") constraint_fn = self.jl.eval("SURF.Static.constraint") - volt_set = self.jl.eval("SURF.Static.solver")(wells, elec_grid, field_grid, - weights_fn, cull_fn, - calc_target_fn, cost_fn, - constraint_fn, settings) + volt_set = self.jl.eval("SURF.Static.solver")( + wells, + elec_grid, + field_grid, + weights_fn, + cull_fn, + calc_target_fn, + cost_fn, + constraint_fn, + settings, + ) return np.ascontiguousarray(np.array(volt_set)) - def _solve_dynamic(self, trajectory, v_set_start, v_set_end, elec_grid, field_grid, - settings): + def _solve_dynamic( + self, trajectory, v_set_start, v_set_end, elec_grid, field_grid, settings + ): """Find voltages to best produce target trajectory (fixed start & end). :param trajectory: as returned by mk_trajectory @@ -615,15 +690,34 @@ def _solve_dynamic(self, trajectory, v_set_start, v_set_end, elec_grid, field_gr cost_fn = self.jl.eval("SURF.Dynamic.cost_function") constraint_fn = self.jl.eval("SURF.Dynamic.constraint") - volt_set = self.jl.eval("SURF.Dynamic.solver")(trajectory, elec_grid, - field_grid, v_set_start, - v_set_end, weights_fn, cull_fn, - calc_target_fn, cost_fn, - constraint_fn, settings) + volt_set = self.jl.eval("SURF.Dynamic.solver")( + trajectory, + elec_grid, + field_grid, + v_set_start, + v_set_end, + weights_fn, + cull_fn, + calc_target_fn, + cost_fn, + constraint_fn, + settings, + ) return np.ascontiguousarray(np.array(volt_set)) - def _solve_split(self, scan_start, scan_end, spectator, n_step, n_scan, elec_fn, - field_fn, elec_grid, field_grid, settings): + def _solve_split( + self, + scan_start, + scan_end, + spectator, + n_step, + n_scan, + elec_fn, + field_fn, + elec_grid, + field_grid, + settings, + ): """Find voltages for splitting/merging a well with spectator wells. The solver operates on a single well. This well evolves from well_start to @@ -644,13 +738,34 @@ def _solve_split(self, scan_start, scan_end, spectator, n_step, n_scan, elec_fn, weights_fn = self.jl.eval("mk_gaussian_weights") cull_fn = self.jl.eval("get_cull_indices") volt_set, sep_vec = self.jl.eval("SURF.Split.solver")( - scan_start, scan_end, spectator, n_step, n_scan, elec_fn, field_fn, - elec_grid, field_grid, weights_fn, cull_fn, settings) + scan_start, + scan_end, + spectator, + n_step, + n_scan, + elec_fn, + field_fn, + elec_grid, + field_grid, + weights_fn, + cull_fn, + settings, + ) return (np.ascontiguousarray(np.array(volt_set)), np.array(sep_vec)) - def _solve_dynamic_split(self, split_well, start_separation, end_separation, n_step, - spectator, elec_fn, field_fn, elec_grid, field_grid, - settings): + def _solve_dynamic_split( + self, + split_well, + start_separation, + end_separation, + n_step, + spectator, + elec_fn, + field_fn, + elec_grid, + field_grid, + settings, + ): """Find voltages for splitting/merging a well with spectator wells dynamically. @@ -670,8 +785,18 @@ def _solve_dynamic_split(self, split_well, start_separation, end_separation, n_s """ cull_fn = self.jl.eval("get_cull_indices") volt_set, sep_vec = self.jl.eval("SURF.DynamicSplit.solver")( - split_well, start_separation, end_separation, n_step, spectator, elec_fn, - field_fn, elec_grid, field_grid, cull_fn, settings) + split_well, + start_separation, + end_separation, + n_step, + spectator, + elec_fn, + field_fn, + elec_grid, + field_grid, + cull_fn, + settings, + ) return (np.ascontiguousarray(np.array(volt_set)), np.array(sep_vec)) def ping(self): @@ -715,7 +840,8 @@ def get_model_field(self, zs, volt_vec_list, el_vec, field="phi"): julia.Main.volt_vec_list = [np.array(vs)[order] for vs in volt_vec_list] field = field.replace("phi", "\N{Greek Capital Letter Phi}") return self.jl.eval( - f"(elec_grid.{field} * hcat(volt_vec_list...) .+ field_grid.{field})'") + f"(elec_grid.{field} * hcat(volt_vec_list...) .+ field_grid.{field})'" + ) if __name__ == "__main__": @@ -723,8 +849,19 @@ def get_model_field(self, zs, volt_vec_list, el_vec, field="phi"): driver = SURF("Comet", "C:\\Users\\Marius\\scratch\\SURF\\src\\UserTraps\\") tmp = 6.333873616858256e8 - wells = driver.mk_wells([0.], [2e-5], [0.], [0.], [0.], [0.], [0.], [0.], [tmp / 6], - [0], [1.05 * tmp]) + wells = driver.mk_wells( + [0.0], + [2e-5], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [0.0], + [tmp / 6], + [0], + [1.05 * tmp], + ) grids = driver.mk_grids(driver.user_defaults["zs"], driver.elec_fn, driver.field_fn) print("calling solver") volt = driver.solve_static(wells, *grids, driver.user_defaults["static_settings"]) diff --git a/oxart/devices/surf_solver/mediator.py b/oxart/devices/surf_solver/mediator.py index 9462753..d4f6447 100644 --- a/oxart/devices/surf_solver/mediator.py +++ b/oxart/devices/surf_solver/mediator.py @@ -5,8 +5,10 @@ from scipy.constants import value as const Wells = namedtuple( - "Wells", "name,z,width,dphidx,dphidy,dphidz,rx_axial,ry_axial," - "phi_radial,d2phidaxial2,d3phidz3,d2phidradial_h2") + "Wells", + "name,z,width,dphidx,dphidy,dphidz,rx_axial,ry_axial," + "phi_radial,d2phidaxial2,d3phidz3,d2phidradial_h2", +) Wells.__doc__ = """Represents a snapshot of the parameters of potential wells all parameters are tuples with 'length = ' @@ -30,19 +32,21 @@ class SURFMediator: chaining common operations. """ - def __init__(self, - dmgr, - device, - default_electrode_override=None, - default_z_grid_override=None, - default_f_axial=1e6, - default_f_rad_x=5e6, - default_split_start_curvature=1.e7, - default_split_end_curvature=-1.e7, - default_split_well_seperation=140e-6, - default_split_positions=np.array([0.]), - charge=1, - mass=43): + def __init__( + self, + dmgr, + device, + default_electrode_override=None, + default_z_grid_override=None, + default_f_axial=1e6, + default_f_rad_x=5e6, + default_split_start_curvature=1.0e7, + default_split_end_curvature=-1.0e7, + default_split_well_seperation=140e-6, + default_split_positions=np.array([0.0]), + charge=1, + mass=43, + ): """ :param dmgr: device manager :param device: name of SURF driver @@ -91,10 +95,12 @@ def _mk_waveform(self, volt_vec, el_vec, wells): electrodes and voltages are matched by index """ el_vec = tuple(el for el in el_vec) # unpack into tuple - wave = Waveform(voltage_vec_list=[volt_vec], - el_vec=el_vec, - fixed_wells=[wells], - wells_idx=[0]) + wave = Waveform( + voltage_vec_list=[volt_vec], + el_vec=el_vec, + fixed_wells=[wells], + wells_idx=[0], + ) return wave def concatenate(self, *waves): @@ -108,11 +114,9 @@ def concatenate(self, *waves): concat_wave.fixed_wells.extend(wave.fixed_wells) return concat_wave - def _volt_from_wells(self, - wells, - electrodes=None, - z_grid=None, - static_settings=None): + def _volt_from_wells( + self, wells, electrodes=None, z_grid=None, static_settings=None + ): """Find the voltage-sets to produce specified potential wells. :param wells: Wells object @@ -126,8 +130,9 @@ def _volt_from_wells(self, electrodes = self.default_electrodes el_names = self.get_all_electrode_names() - assert set(electrodes).issubset(set(el_names)), \ - "\n{}\n{}".format(set(electrodes), set(el_names)) + assert set(electrodes).issubset(set(el_names)), "\n{}\n{}".format( + set(electrodes), set(el_names) + ) if z_grid is None: z_grid = self.default_z_grid @@ -136,7 +141,7 @@ def _volt_from_wells(self, "zs": z_grid, "electrodes": electrodes, "wells": wells._asdict(), - "static_settings": static_settings + "static_settings": static_settings, } v_vec, el_vec = self.driver.static(**param) @@ -161,8 +166,9 @@ def get_model_field(self, zs, wave: Waveform, field="dphidz"): 'd2phidydz', 'd3phidz3', or 'd4phidz4'. :returns: Matrix of field values """ - return self.driver.get_model_field(zs, wave.voltage_vec_list, wave.el_vec, - field) + return self.driver.get_model_field( + zs, wave.voltage_vec_list, wave.el_vec, field + ) def get_all_electrode_names(self): """Return a list of all electrode names defined in the trap model.""" @@ -195,33 +201,29 @@ def get_sum_square_freq(self, z): :param z: position where the sum of square frequencies is found [in m] """ - return self.field_to_f(self.driver.get_div_grad_phi(z))**2 - - def reload_trap_model(self, - trap_model_path=None, - cache_path=None, - omega_rf=None, - mass=None, - v_rf=None): + return self.field_to_f(self.driver.get_div_grad_phi(z)) ** 2 + + def reload_trap_model( + self, trap_model_path=None, cache_path=None, omega_rf=None, mass=None, v_rf=None + ): """Reload the trap model caching and trap parameters. Equivalent to ``load_trap_model(…, force_reload=True)``; see :meth:`load_trap_model` for details. """ - return self.load_trap_model(trap_model_path, - cache_path, - omega_rf, - mass, - v_rf, - force_reload=True) - - def load_trap_model(self, - trap_model_path=None, - cache_path=None, - omega_rf=None, - mass=None, - v_rf=None, - force_reload=False): + return self.load_trap_model( + trap_model_path, cache_path, omega_rf, mass, v_rf, force_reload=True + ) + + def load_trap_model( + self, + trap_model_path=None, + cache_path=None, + omega_rf=None, + mass=None, + v_rf=None, + force_reload=False, + ): """Load the trap model caching and trap parameters. All parameters have sane defaults. @@ -243,29 +245,33 @@ def load_trap_model(self, """ if mass is not None: self.mass = mass - return self.driver.load_config(trap_model_path, - cache_path, - omega_rf, - self.mass, - v_rf, - force_reload=force_reload) - - def get_new_waveform(self, - z, - f_axial=None, - f_rad_x=None, - width=5e-6, - dphidx=0., - dphidy=0., - dphidz=0., - rx_axial=0., - ry_axial=0., - phi_radial=0., - d3phidz3=0., - name=None, - *, - electrodes=None, - z_grid=None): + return self.driver.load_config( + trap_model_path, + cache_path, + omega_rf, + self.mass, + v_rf, + force_reload=force_reload, + ) + + def get_new_waveform( + self, + z, + f_axial=None, + f_rad_x=None, + width=5e-6, + dphidx=0.0, + dphidy=0.0, + dphidz=0.0, + rx_axial=0.0, + ry_axial=0.0, + phi_radial=0.0, + d3phidz3=0.0, + name=None, + *, + electrodes=None, + z_grid=None + ): """Start a new Waveform with given potential wells. Each well is identified by its index within parameter lists. An @@ -305,8 +311,20 @@ def get_new_waveform(self, :param z_grid: z-grid on which to perform optimisation. If `None` SURF will use the default grid. """ - well = self._mk_wells(z, f_axial, f_rad_x, width, dphidx, dphidy, dphidz, - rx_axial, ry_axial, phi_radial, d3phidz3, name) + well = self._mk_wells( + z, + f_axial, + f_rad_x, + width, + dphidx, + dphidy, + dphidz, + rx_axial, + ry_axial, + phi_radial, + d3phidz3, + name, + ) volt, el = self._volt_from_wells(well, electrodes, z_grid) wave = self._mk_waveform(volt, el, well) return wave @@ -320,17 +338,21 @@ def continue_with_new_waveform(self, old_waveform, well_idx=-1): """ return self._mk_waveform( old_waveform.voltage_vec_list[old_waveform.wells_idx[well_idx]], - old_waveform.el_vec, old_waveform.fixed_wells[well_idx]) - - def modify(self, - change_dict, - wave, - n_step=55, - *, - electrodes=None, - z_grid=None, - static_settings=None, - dynamic_settings=None): + old_waveform.el_vec, + old_waveform.fixed_wells[well_idx], + ) + + def modify( + self, + change_dict, + wave, + n_step=55, + *, + electrodes=None, + z_grid=None, + static_settings=None, + dynamic_settings=None + ): """Calculate evolution to modified parameters. :param change_dict: dictionary of changes to last Wells in wave @@ -351,8 +373,9 @@ def modify(self, electrodes = wave.el_vec el_names = wave.el_vec - assert set(electrodes).issubset(set(el_names)), \ - "\n{}\n{}".format(set(electrodes), set(el_names)) + assert set(electrodes).issubset(set(el_names)), "\n{}\n{}".format( + set(electrodes), set(el_names) + ) if z_grid is None: z_grid = self.default_z_grid @@ -361,7 +384,8 @@ def modify(self, for name, param_dict in change_dict.items(): if "f_rad_x" in param_dict: param_dict["d2phidradial_h2"] = self.f_to_field( - param_dict.pop("f_rad_x")) + param_dict.pop("f_rad_x") + ) if "f_axial" in param_dict: param_dict["d2phidaxial2"] = self.f_to_field(param_dict.pop("f_axial")) @@ -370,13 +394,13 @@ def modify(self, # exploit mutability of list new_wells._asdict()[param][idx] = value - new_volt, new_el = self._volt_from_wells(new_wells, electrodes, z_grid, - static_settings) + new_volt, new_el = self._volt_from_wells( + new_wells, electrodes, z_grid, static_settings + ) # dict to pass to do_solve start_volt_dict = { - el: wave.voltage_vec_list[-1][idx] - for idx, el in enumerate(wave.el_vec) + el: wave.voltage_vec_list[-1][idx] for idx, el in enumerate(wave.el_vec) } end_volt_dict = {el: 0.0 for el in wave.el_vec} @@ -390,13 +414,14 @@ def modify(self, "volt_start": start_volt_dict, "volt_end": end_volt_dict, "n_step": n_step, - "dynamic_settings": dynamic_settings + "dynamic_settings": dynamic_settings, } volt_evol, el_evol = self.driver.dynamic(**evol_param) # could handle this case, but should be the same in reasonable cases assert tuple(el for el in el_evol) == wave.el_vec, "{}; {}".format( - el_evol, wave.el_vec) + el_evol, wave.el_vec + ) # append to wave wave.voltage_vec_list.extend([volt_evol[:, i] for i in range(n_step)]) @@ -404,23 +429,25 @@ def modify(self, wave.wells_idx.append(len(wave.voltage_vec_list) - 1) return wave - def split(self, - name, - wave, - n_step, - n_scan=55, - n_itpl=101, - out_name0=None, - out_name1=None, - *, - scan_curv_start=None, - scan_curv_end=None, - well_separation=None, - axial_tilt=None, - electrodes=None, - z_grid=None, - static_settings=None, - split_settings=None): + def split( + self, + name, + wave, + n_step, + n_scan=55, + n_itpl=101, + out_name0=None, + out_name1=None, + *, + scan_curv_start=None, + scan_curv_end=None, + well_separation=None, + axial_tilt=None, + electrodes=None, + z_grid=None, + static_settings=None, + split_settings=None + ): """Calculate evolution to modified parameters. :param name: name of well to be split @@ -453,8 +480,9 @@ def split(self, electrodes = wave.el_vec el_names = wave.el_vec - assert set(electrodes).issubset(set(el_names)), \ - "\n{}\n{}".format(set(electrodes), set(el_names)) + assert set(electrodes).issubset(set(el_names)), "\n{}\n{}".format( + set(electrodes), set(el_names) + ) if z_grid is None: z_grid = self.default_z_grid @@ -470,14 +498,15 @@ def split(self, split_idx = spectators.name.index(name) # list.pop() target well - target_well = Wells(*([spectators[i].pop(split_idx)] - for i in range(len(spectators)))) + target_well = Wells( + *([spectators[i].pop(split_idx)] for i in range(len(spectators))) + ) # determine a sensible initial and final split well scan_start = deepcopy(target_well) - scan_start.rx_axial[0] = 0. - scan_start.ry_axial[0] = 0. - scan_start.phi_radial[0] = 0. + scan_start.rx_axial[0] = 0.0 + scan_start.ry_axial[0] = 0.0 + scan_start.phi_radial[0] = 0.0 if axial_tilt is not None: scan_start.dphidz[0] = axial_tilt scan_start.d2phidaxial2[0] = scan_curv_start @@ -504,65 +533,76 @@ def split(self, name if out_name0 is None else out_name0, (name + "_1") if out_name1 is None else out_name1, ] - split_wells = Wells(name=names, - z=[ - target_well.z[0] - well_separation / 2, - target_well.z[0] + well_separation / 2 - ], - width=[target_well.width[0]] * 2, - dphidx=[target_well.dphidx[0]] * 2, - dphidy=[target_well.dphidy[0]] * 2, - dphidz=[target_well.dphidz[0]] * 2, - rx_axial=[target_well.rx_axial[0]] * 2, - ry_axial=[target_well.ry_axial[0]] * 2, - phi_radial=[target_well.phi_radial[0]] * 2, - d2phidaxial2=[target_well.d2phidaxial2[0]] * 2, - d3phidz3=[target_well.d3phidz3[0]] * 2, - d2phidradial_h2=[target_well.d2phidradial_h2[0]] * 2) + split_wells = Wells( + name=names, + z=[ + target_well.z[0] - well_separation / 2, + target_well.z[0] + well_separation / 2, + ], + width=[target_well.width[0]] * 2, + dphidx=[target_well.dphidx[0]] * 2, + dphidy=[target_well.dphidy[0]] * 2, + dphidz=[target_well.dphidz[0]] * 2, + rx_axial=[target_well.rx_axial[0]] * 2, + ry_axial=[target_well.ry_axial[0]] * 2, + phi_radial=[target_well.phi_radial[0]] * 2, + d2phidaxial2=[target_well.d2phidaxial2[0]] * 2, + d3phidz3=[target_well.d3phidz3[0]] * 2, + d2phidradial_h2=[target_well.d2phidradial_h2[0]] * 2, + ) # new wells & voltage-set - final_wells = Wells(*(spectators[i][:split_idx] + split_wells[i] + - spectators[i][split_idx:] - for i in range(len(split_wells)))) + final_wells = Wells( + *( + spectators[i][:split_idx] + split_wells[i] + spectators[i][split_idx:] + for i in range(len(split_wells)) + ) + ) # ToDo: may want to check if there is sufficient space - final_volt, final_el = self._volt_from_wells(final_wells, - electrodes=wave.el_vec, - z_grid=z_grid, - static_settings=static_settings) + final_volt, final_el = self._volt_from_wells( + final_wells, + electrodes=wave.el_vec, + z_grid=z_grid, + static_settings=static_settings, + ) # interpolate start and finish wave.voltage_vec_list.extend( - self._interpolate(wave.voltage_vec_list[-1], volt_split[0], n_itpl)) + self._interpolate(wave.voltage_vec_list[-1], volt_split[0], n_itpl) + ) wave.voltage_vec_list.extend(volt_split) # use _poly_interpolate to achieve smoother ion acceleration wave.voltage_vec_list.extend( - self._poly_interpolate(wave.voltage_vec_list[-1], final_volt, n_itpl)) + self._poly_interpolate(wave.voltage_vec_list[-1], final_volt, n_itpl) + ) wave.fixed_wells.append(final_wells) wave.wells_idx.append(len(wave.voltage_vec_list) - 1) return wave - def merge(self, - name0, - name1, - wave, - n_step, - n_scan=55, - n_itpl=101, - out_name=None, - prepare_wells=True, - n_prepare=101, - *, - scan_curv_start=None, - scan_curv_end=None, - well_separation=None, - axial_tilt=None, - merge_pos=None, - electrodes=None, - z_grid=None, - static_settings=None, - dynamic_settings=None, - split_settings=None): + def merge( + self, + name0, + name1, + wave, + n_step, + n_scan=55, + n_itpl=101, + out_name=None, + prepare_wells=True, + n_prepare=101, + *, + scan_curv_start=None, + scan_curv_end=None, + well_separation=None, + axial_tilt=None, + merge_pos=None, + electrodes=None, + z_grid=None, + static_settings=None, + dynamic_settings=None, + split_settings=None + ): """Calculate evolution to modified parameters. :param name0: name of well0 to be merged @@ -601,8 +641,9 @@ def merge(self, electrodes = wave.el_vec el_names = wave.el_vec - assert set(electrodes).issubset(set(el_names)), \ - "\n{}\n{}".format(set(electrodes), set(el_names)) + assert set(electrodes).issubset(set(el_names)), "\n{}\n{}".format( + set(electrodes), set(el_names) + ) if z_grid is None: z_grid = self.default_z_grid @@ -618,16 +659,24 @@ def merge(self, merge_idx = [spectators.name.index(name0), spectators.name.index(name1)] # ToDo: assert no wells between wells to be merged - target_well = Wells(*([spectators[i][well_idx] for well_idx in merge_idx] - for i in range(len(spectators)))) + target_well = Wells( + *( + [spectators[i][well_idx] for well_idx in merge_idx] + for i in range(len(spectators)) + ) + ) spectators = Wells( - *([val for i, val in enumerate(spectators[i]) if i not in merge_idx] - for i in range(len(spectators)))) + *( + [val for i, val in enumerate(spectators[i]) if i not in merge_idx] + for i in range(len(spectators)) + ) + ) if merge_pos is None: pos_idx = np.argmin( - np.abs(np.mean(target_well.z) - self.default_split_positions)) + np.abs(np.mean(target_well.z) - self.default_split_positions) + ) merge_pos = self.default_split_positions[pos_idx] # merging is inverse splitting! -> use splitting solver # determine a sensible initial and final split well @@ -638,9 +687,9 @@ def merge(self, dphidx=[np.mean(target_well.dphidx)], dphidy=[np.mean(target_well.dphidy)], dphidz=[np.mean(target_well.dphidz) if axial_tilt is None else axial_tilt], - rx_axial=[0.], - ry_axial=[0.], - phi_radial=[0.], + rx_axial=[0.0], + ry_axial=[0.0], + phi_radial=[0.0], d2phidaxial2=[scan_curv_start], d3phidz3=[np.mean(target_well.d3phidz3)], d2phidradial_h2=[np.mean(target_well.d2phidradial_h2)], @@ -669,12 +718,8 @@ def merge(self, # self.modify to merge start position name_order = np.sign(target_well.z[1] - target_well.z[0]) move_dict = { - name0: { - "z": scan_start.z[0] - name_order * well_separation / 2 - }, - name1: { - "z": scan_start.z[0] + name_order * well_separation / 2 - }, + name0: {"z": scan_start.z[0] - name_order * well_separation / 2}, + name1: {"z": scan_start.z[0] + name_order * well_separation / 2}, } wave = self.modify(move_dict, wave, n_prepare) @@ -683,46 +728,57 @@ def merge(self, merged_well.d2phidaxial2[0] = np.mean(target_well.d2phidaxial2) # new wells & voltage-set - final_wells = Wells(*(spectators[i][:min(merge_idx)] + merged_well[i] + - spectators[i][min(merge_idx):] - for i in range(len(merged_well)))) + final_wells = Wells( + *( + spectators[i][: min(merge_idx)] + + merged_well[i] + + spectators[i][min(merge_idx) :] + for i in range(len(merged_well)) + ) + ) # ToDo: may want to check if wells cross other wells. - final_volt, final_el = self._volt_from_wells(final_wells, - electrodes=wave.el_vec, - z_grid=z_grid, - static_settings=static_settings) + final_volt, final_el = self._volt_from_wells( + final_wells, + electrodes=wave.el_vec, + z_grid=z_grid, + static_settings=static_settings, + ) # interpolate start and finish # use _poly_interpolate to achieve smoother ion acceleration wave.voltage_vec_list.extend( - self._poly_interpolate(wave.voltage_vec_list[-1], volt_merge[0], n_itpl)) + self._poly_interpolate(wave.voltage_vec_list[-1], volt_merge[0], n_itpl) + ) wave.voltage_vec_list.extend(volt_merge) wave.voltage_vec_list.extend( - self._interpolate(volt_merge[-1], final_volt, n_itpl)) + self._interpolate(volt_merge[-1], final_volt, n_itpl) + ) wave.fixed_wells.append(final_wells) wave.wells_idx.append(len(wave.voltage_vec_list) - 1) return wave - def dynamic_split(self, - name, - wave, - n_step, - n_itpl=101, - out_name0=None, - out_name1=None, - *, - curv_start=None, - sep_start=None, - sep_end=None, - well_separation=None, - axial_tilt=None, - electrodes=None, - z_grid=None, - static_settings=None, - split_settings=None): + def dynamic_split( + self, + name, + wave, + n_step, + n_itpl=101, + out_name0=None, + out_name1=None, + *, + curv_start=None, + sep_start=None, + sep_end=None, + well_separation=None, + axial_tilt=None, + electrodes=None, + z_grid=None, + static_settings=None, + split_settings=None + ): """Calculate evolution to modified parameters. :param name: name of well to be split @@ -757,8 +813,9 @@ def dynamic_split(self, electrodes = wave.el_vec el_names = wave.el_vec - assert set(electrodes).issubset(set(el_names)), \ - "\n{}\n{}".format(set(electrodes), set(el_names)) + assert set(electrodes).issubset(set(el_names)), "\n{}\n{}".format( + set(electrodes), set(el_names) + ) if z_grid is None: z_grid = self.default_z_grid @@ -776,12 +833,13 @@ def dynamic_split(self, split_idx = spectators.name.index(name) # list.pop() target well - target_well = Wells(*([spectators[i].pop(split_idx)] - for i in range(len(spectators)))) + target_well = Wells( + *([spectators[i].pop(split_idx)] for i in range(len(spectators))) + ) # determine a sensible initial split well - target_well.rx_axial[0] = 0. - target_well.ry_axial[0] = 0. - target_well.phi_radial[0] = 0. + target_well.rx_axial[0] = 0.0 + target_well.ry_axial[0] = 0.0 + target_well.phi_radial[0] = 0.0 if axial_tilt is not None: target_well.dphidz[0] = axial_tilt target_well.d2phidaxial2[0] = curv_start @@ -805,65 +863,76 @@ def dynamic_split(self, name if out_name0 is None else out_name0, (name + "_1") if out_name1 is None else out_name1, ] - split_wells = Wells(name=names, - z=[ - target_well.z[0] - well_separation / 2, - target_well.z[0] + well_separation / 2 - ], - width=[target_well.width[0]] * 2, - dphidx=[target_well.dphidx[0]] * 2, - dphidy=[target_well.dphidy[0]] * 2, - dphidz=[target_well.dphidz[0]] * 2, - rx_axial=[target_well.rx_axial[0]] * 2, - ry_axial=[target_well.ry_axial[0]] * 2, - phi_radial=[target_well.phi_radial[0]] * 2, - d2phidaxial2=[target_well.d2phidaxial2[0]] * 2, - d3phidz3=[target_well.d3phidz3[0]] * 2, - d2phidradial_h2=[target_well.d2phidradial_h2[0]] * 2) + split_wells = Wells( + name=names, + z=[ + target_well.z[0] - well_separation / 2, + target_well.z[0] + well_separation / 2, + ], + width=[target_well.width[0]] * 2, + dphidx=[target_well.dphidx[0]] * 2, + dphidy=[target_well.dphidy[0]] * 2, + dphidz=[target_well.dphidz[0]] * 2, + rx_axial=[target_well.rx_axial[0]] * 2, + ry_axial=[target_well.ry_axial[0]] * 2, + phi_radial=[target_well.phi_radial[0]] * 2, + d2phidaxial2=[target_well.d2phidaxial2[0]] * 2, + d3phidz3=[target_well.d3phidz3[0]] * 2, + d2phidradial_h2=[target_well.d2phidradial_h2[0]] * 2, + ) # new wells & voltage-set - final_wells = Wells(*(spectators[i][:split_idx] + split_wells[i] + - spectators[i][split_idx:] - for i in range(len(split_wells)))) + final_wells = Wells( + *( + spectators[i][:split_idx] + split_wells[i] + spectators[i][split_idx:] + for i in range(len(split_wells)) + ) + ) # ToDo: may want to check if there is sufficient space - final_volt, final_el = self._volt_from_wells(final_wells, - electrodes=wave.el_vec, - z_grid=z_grid, - static_settings=static_settings) + final_volt, final_el = self._volt_from_wells( + final_wells, + electrodes=wave.el_vec, + z_grid=z_grid, + static_settings=static_settings, + ) # interpolate start and finish wave.voltage_vec_list.extend( - self._interpolate(wave.voltage_vec_list[-1], volt_split[0], n_itpl)) + self._interpolate(wave.voltage_vec_list[-1], volt_split[0], n_itpl) + ) wave.voltage_vec_list.extend(volt_split) # use _poly_interpolate to achieve smoother ion acceleration wave.voltage_vec_list.extend( - self._poly_interpolate(wave.voltage_vec_list[-1], final_volt, n_itpl)) + self._poly_interpolate(wave.voltage_vec_list[-1], final_volt, n_itpl) + ) wave.fixed_wells.append(final_wells) wave.wells_idx.append(len(wave.voltage_vec_list) - 1) return wave - def dynamic_merge(self, - name0, - name1, - wave, - n_step, - n_itpl=101, - out_name=None, - prepare_wells=True, - n_prepare=101, - *, - curv_start=None, - sep_start=None, - sep_end=None, - well_separation=None, - axial_tilt=None, - merge_pos=None, - electrodes=None, - z_grid=None, - static_settings=None, - dynamic_settings=None, - split_settings=None): + def dynamic_merge( + self, + name0, + name1, + wave, + n_step, + n_itpl=101, + out_name=None, + prepare_wells=True, + n_prepare=101, + *, + curv_start=None, + sep_start=None, + sep_end=None, + well_separation=None, + axial_tilt=None, + merge_pos=None, + electrodes=None, + z_grid=None, + static_settings=None, + dynamic_settings=None, + split_settings=None + ): """Calculate evolution to modified parameters. :param name0: name of well0 to be merged @@ -903,8 +972,9 @@ def dynamic_merge(self, electrodes = wave.el_vec el_names = wave.el_vec - assert set(electrodes).issubset(set(el_names)), \ - "\n{}\n{}".format(set(electrodes), set(el_names)) + assert set(electrodes).issubset(set(el_names)), "\n{}\n{}".format( + set(electrodes), set(el_names) + ) if z_grid is None: z_grid = self.default_z_grid @@ -922,16 +992,24 @@ def dynamic_merge(self, merge_idx = [spectators.name.index(name0), spectators.name.index(name1)] # ToDo: assert no wells between wells to be merged - target_well = Wells(*([spectators[i][well_idx] for well_idx in merge_idx] - for i in range(len(spectators)))) + target_well = Wells( + *( + [spectators[i][well_idx] for well_idx in merge_idx] + for i in range(len(spectators)) + ) + ) spectators = Wells( - *([val for i, val in enumerate(spectators[i]) if i not in merge_idx] - for i in range(len(spectators)))) + *( + [val for i, val in enumerate(spectators[i]) if i not in merge_idx] + for i in range(len(spectators)) + ) + ) if merge_pos is None: pos_idx = np.argmin( - np.abs(np.mean(target_well.z) - self.default_split_positions)) + np.abs(np.mean(target_well.z) - self.default_split_positions) + ) merge_pos = self.default_split_positions[pos_idx] # merging is inverse splitting! -> use splitting solver # determine a sensible initial and final split well @@ -942,9 +1020,9 @@ def dynamic_merge(self, dphidx=[np.mean(target_well.dphidx)], dphidy=[np.mean(target_well.dphidy)], dphidz=[np.mean(target_well.dphidz) if axial_tilt is None else axial_tilt], - rx_axial=[0.], - ry_axial=[0.], - phi_radial=[0.], + rx_axial=[0.0], + ry_axial=[0.0], + phi_radial=[0.0], d2phidaxial2=[curv_start], d3phidz3=[np.mean(target_well.d3phidz3)], d2phidradial_h2=[np.mean(target_well.d2phidradial_h2)], @@ -971,12 +1049,8 @@ def dynamic_merge(self, # self.modify to merge start position name_order = np.sign(target_well.z[1] - target_well.z[0]) move_dict = { - name0: { - "z": split_well.z[0] - name_order * well_separation / 2 - }, - name1: { - "z": split_well.z[0] + name_order * well_separation / 2 - }, + name0: {"z": split_well.z[0] - name_order * well_separation / 2}, + name1: {"z": split_well.z[0] + name_order * well_separation / 2}, } wave = self.modify(move_dict, wave, n_prepare) @@ -985,24 +1059,33 @@ def dynamic_merge(self, merged_well.d2phidaxial2[0] = np.mean(target_well.d2phidaxial2) # new wells & voltage-set - final_wells = Wells(*(spectators[i][:min(merge_idx)] + merged_well[i] + - spectators[i][min(merge_idx):] - for i in range(len(merged_well)))) + final_wells = Wells( + *( + spectators[i][: min(merge_idx)] + + merged_well[i] + + spectators[i][min(merge_idx) :] + for i in range(len(merged_well)) + ) + ) # ToDo: may want to check if wells cross other wells. - final_volt, final_el = self._volt_from_wells(final_wells, - electrodes=wave.el_vec, - z_grid=z_grid, - static_settings=static_settings) + final_volt, final_el = self._volt_from_wells( + final_wells, + electrodes=wave.el_vec, + z_grid=z_grid, + static_settings=static_settings, + ) # interpolate start and finish # use _poly_interpolate to achieve smoother ion acceleration wave.voltage_vec_list.extend( - self._poly_interpolate(wave.voltage_vec_list[-1], volt_merge[0], n_itpl)) + self._poly_interpolate(wave.voltage_vec_list[-1], volt_merge[0], n_itpl) + ) wave.voltage_vec_list.extend(volt_merge) wave.voltage_vec_list.extend( - self._interpolate(volt_merge[-1], final_volt, n_itpl)) + self._interpolate(volt_merge[-1], final_volt, n_itpl) + ) wave.fixed_wells.append(final_wells) wave.wells_idx.append(len(wave.voltage_vec_list) - 1) @@ -1051,8 +1134,9 @@ def spawn_wells(self, z, wave, n_step=5, *, z_grid=None, **kwargs): tmp_wells = self._mk_wells(z, **kwargs) new_wells = deepcopy(old_wells) - new_wells = Wells(*[[*new_wells[i], *tmp_wells[i]] - for i in range(len(new_wells))]) + new_wells = Wells( + *[[*new_wells[i], *tmp_wells[i]] for i in range(len(new_wells))] + ) new_volt, el = self._volt_from_wells(new_wells, wave.el_vec, z_grid) v_steps = self._interpolate(old_volt, new_volt, n_step) @@ -1086,34 +1170,43 @@ def _poly_interpolate(self, volt0, volt1, n_step): def f_to_field(self, frequency): "convert mode frequency to field curvature" - return (self.mass * const("atomic mass constant") / - (self.charge * const("atomic unit of charge")) * - (2 * np.pi * frequency)**2) + return ( + self.mass + * const("atomic mass constant") + / (self.charge * const("atomic unit of charge")) + * (2 * np.pi * frequency) ** 2 + ) def field_to_f(self, field_curvature): "convert field curvature to mode frequency" - return np.sqrt(field_curvature * self.charge * const("atomic unit of charge") / - (self.mass * const("atomic mass constant"))) / (2 * np.pi) + return np.sqrt( + field_curvature + * self.charge + * const("atomic unit of charge") + / (self.mass * const("atomic mass constant")) + ) / (2 * np.pi) def field_to_two_ion_sep(self, field_curvature): """Convert field curvature to two-ion separation.""" eps_4pi = 4 * np.pi * const("vacuum electric permittivity") q = self.charge * const("atomic unit of charge") - return (q / (eps_4pi * field_curvature))**(1 / 3) - - def _mk_wells(self, - z, - f_axial=None, - f_rad_x=None, - width=5e-6, - dphidx=0., - dphidy=0., - dphidz=0., - rx_axial=0., - ry_axial=0., - phi_radial=0., - d3phidz3=0., - name=None): + return (q / (eps_4pi * field_curvature)) ** (1 / 3) + + def _mk_wells( + self, + z, + f_axial=None, + f_rad_x=None, + width=5e-6, + dphidx=0.0, + dphidy=0.0, + dphidz=0.0, + rx_axial=0.0, + ry_axial=0.0, + phi_radial=0.0, + d3phidz3=0.0, + name=None, + ): """Wells should be specified as parameter iterables of equal length. Each well is identified by its index within parameter lists. An @@ -1190,5 +1283,17 @@ def _mk_wells(self, else: [n if n is not None else str(i) for i, n in enumerate(name)] - return Wells(name, z, width, dphidx, dphidy, dphidz, rx_axial, ry_axial, - phi_radial, d2phidaxial2, d3phidz3, d2phidradial_h2) + return Wells( + name, + z, + width, + dphidx, + dphidy, + dphidz, + rx_axial, + ry_axial, + phi_radial, + d2phidaxial2, + d3phidz3, + d2phidradial_h2, + ) diff --git a/oxart/devices/thermostat/autotune.py b/oxart/devices/thermostat/autotune.py index a576f50..cf04a72 100644 --- a/oxart/devices/thermostat/autotune.py +++ b/oxart/devices/thermostat/autotune.py @@ -1,6 +1,7 @@ """Adapted from https://git.m-labs.hk/M-Labs/thermostat/src/branch/master/pytec/pytec/autotune.py """ + import math import logging import asyncio @@ -14,15 +15,15 @@ class PIDAutotuneState(Enum): - STATE_OFF = 'off' - STATE_RELAY_STEP_UP = 'relay step up' - STATE_RELAY_STEP_DOWN = 'relay step down' - STATE_SUCCEEDED = 'succeeded' - STATE_FAILED = 'failed' + STATE_OFF = "off" + STATE_RELAY_STEP_UP = "relay step up" + STATE_RELAY_STEP_DOWN = "relay step down" + STATE_SUCCEEDED = "succeeded" + STATE_FAILED = "failed" class PIDAutotune: - PIDParams = namedtuple('PIDParams', ['Kp', 'Ki', 'Kd']) + PIDParams = namedtuple("PIDParams", ["Kp", "Ki", "Kd"]) PEAK_AMPLITUDE_TOLERANCE = 0.05 @@ -32,20 +33,22 @@ class PIDAutotune: "ciancone-marlin": [0.303, 0.1364, 0.0481], "pessen-integral": [0.7, 1.75, 0.105], "some-overshoot": [0.333, 0.667, 0.111], - "no-overshoot": [0.2, 0.4, 0.0667] + "no-overshoot": [0.2, 0.4, 0.0667], } - def __init__(self, - setpoint, - out_initial=0, - out_min=-3, - out_max=3, - out_step=0.1, - lookback=60, - noiseband=0.1, - sampletime=0.1): + def __init__( + self, + setpoint, + out_initial=0, + out_min=-3, + out_max=3, + out_step=0.1, + lookback=60, + noiseband=0.1, + sampletime=0.1, + ): if setpoint is None: - raise ValueError('setpoint must be specified') + raise ValueError("setpoint must be specified") self._inputs = deque(maxlen=round(lookback / sampletime)) self._setpoint = setpoint @@ -77,7 +80,7 @@ def tuning_rules(self): """Get a list of all available tuning rules.""" return self._tuning_rules.keys() - def get_pid_parameters(self, tuning_rule='ziegler-nichols'): + def get_pid_parameters(self, tuning_rule="ziegler-nichols"): """Get PID parameters. Args: @@ -102,27 +105,33 @@ def run(self, input_val, time_input): """ now = time_input * 1000 - if (self._state == PIDAutotuneState.STATE_OFF - or self._state == PIDAutotuneState.STATE_SUCCEEDED - or self._state == PIDAutotuneState.STATE_FAILED): + if ( + self._state == PIDAutotuneState.STATE_OFF + or self._state == PIDAutotuneState.STATE_SUCCEEDED + or self._state == PIDAutotuneState.STATE_FAILED + ): self._state = PIDAutotuneState.STATE_RELAY_STEP_UP self._last_run_timestamp = now # check input and change relay state if necessary - if (self._state == PIDAutotuneState.STATE_RELAY_STEP_UP - and input_val > self._setpoint + self._noiseband): + if ( + self._state == PIDAutotuneState.STATE_RELAY_STEP_UP + and input_val > self._setpoint + self._noiseband + ): self._state = PIDAutotuneState.STATE_RELAY_STEP_DOWN - logging.debug('switched state: {0}'.format(self._state)) - logging.debug('input: {0}'.format(input_val)) - elif (self._state == PIDAutotuneState.STATE_RELAY_STEP_DOWN - and input_val < self._setpoint - self._noiseband): + logging.debug("switched state: {0}".format(self._state)) + logging.debug("input: {0}".format(input_val)) + elif ( + self._state == PIDAutotuneState.STATE_RELAY_STEP_DOWN + and input_val < self._setpoint - self._noiseband + ): self._state = PIDAutotuneState.STATE_RELAY_STEP_UP - logging.debug('switched state: {0}'.format(self._state)) - logging.debug('input: {0}'.format(input_val)) + logging.debug("switched state: {0}".format(self._state)) + logging.debug("input: {0}".format(input_val)) # set output - if (self._state == PIDAutotuneState.STATE_RELAY_STEP_UP): + if self._state == PIDAutotuneState.STATE_RELAY_STEP_UP: self._output = self._initial_output - self._outputstep elif self._state == PIDAutotuneState.STATE_RELAY_STEP_DOWN: self._output = self._initial_output + self._outputstep @@ -165,8 +174,8 @@ def run(self, input_val, time_input): self._peak_count += 1 self._peaks.append(input_val) self._peak_timestamps.append(now) - logging.debug('found peak: {0}'.format(input_val)) - logging.debug('peak count: {0}'.format(self._peak_count)) + logging.debug("found peak: {0}".format(input_val)) + logging.debug("peak count: {0}".format(self._peak_count)) # check for convergence of induced oscillation # convergence of amplitude assessed on last 4 peaks (1.5 cycles) @@ -183,11 +192,12 @@ def run(self, input_val, time_input): self._induced_amplitude /= 6.0 # check convergence criterion for amplitude of induced oscillation - amplitude_dev = ((0.5 * (abs_max - abs_min) - self._induced_amplitude) / - self._induced_amplitude) + amplitude_dev = ( + 0.5 * (abs_max - abs_min) - self._induced_amplitude + ) / self._induced_amplitude - logging.debug('amplitude: {0}'.format(self._induced_amplitude)) - logging.debug('amplitude deviation: {0}'.format(amplitude_dev)) + logging.debug("amplitude: {0}".format(self._induced_amplitude)) + logging.debug("amplitude deviation: {0}".format(amplitude_dev)) if amplitude_dev < PIDAutotune.PEAK_AMPLITUDE_TOLERANCE: self._state = PIDAutotuneState.STATE_SUCCEEDED @@ -201,28 +211,29 @@ def run(self, input_val, time_input): if self._state == PIDAutotuneState.STATE_SUCCEEDED: self._output = 0 - logging.debug('peak finding successful') + logging.debug("peak finding successful") # calculate ultimate gain - self._Ku = 4.0 * self._outputstep / \ - (self._induced_amplitude * math.pi) - print('Ku: {0}'.format(self._Ku)) + self._Ku = 4.0 * self._outputstep / (self._induced_amplitude * math.pi) + print("Ku: {0}".format(self._Ku)) # calculate ultimate period in seconds period1 = self._peak_timestamps[3] - self._peak_timestamps[1] period2 = self._peak_timestamps[4] - self._peak_timestamps[2] self._Pu = 0.5 * (period1 + period2) / 1000.0 - print('Pu: {0}'.format(self._Pu)) + print("Pu: {0}".format(self._Pu)) for rule in self._tuning_rules: params = self.get_pid_parameters(rule) - print('rule: {0}'.format(rule)) - print('Kp: {0}'.format(params.Kp)) - print('Ki: {0}'.format(params.Ki)) - print('Kd: {0}'.format(params.Kd)) - - logging.info("Use the controller's 'set_param()' method to update the PID " - "parameters.") + print("rule: {0}".format(rule)) + print("Kp: {0}".format(params.Kp)) + print("Ki: {0}".format(params.Ki)) + print("Kd: {0}".format(params.Kd)) + + logging.info( + "Use the controller's 'set_param()' method to update the PID " + "parameters." + ) return True return False @@ -239,14 +250,22 @@ async def autotune(args, interface): data = await anext(reporter) interface._log_report_to_influx(data) ch = data[args.channel] - tuner = PIDAutotune(args.target, ch['i_set'], i_min, i_max, args.step, - args.lookback, args.noiseband, ch['interval']) + tuner = PIDAutotune( + args.target, + ch["i_set"], + i_min, + i_max, + args.step, + args.lookback, + args.noiseband, + ch["interval"], + ) async for data in reporter: interface._log_report_to_influx(data) ch = data[args.channel] - temperature = ch['temperature'] - if tuner.run(temperature, ch['time']): + temperature = ch["temperature"] + if tuner.run(temperature, ch["time"]): break tuner_out = tuner.output() interface.set_param("pwm", args.channel, "i_set", tuner_out) diff --git a/oxart/devices/thermostat/driver.py b/oxart/devices/thermostat/driver.py index 9f4631a..ba2268e 100644 --- a/oxart/devices/thermostat/driver.py +++ b/oxart/devices/thermostat/driver.py @@ -73,8 +73,11 @@ def _check_zero_limits(self): for pwm_channel in pwm_report: for limit in ["max_i_neg", "max_i_pos", "max_v"]: if pwm_channel[limit]["value"] == 0.0: - logging.warning("`{}` limit is set to zero on channel {}".format( - limit, pwm_channel["channel"])) + logging.warning( + "`{}` limit is set to zero on channel {}".format( + limit, pwm_channel["channel"] + ) + ) def _read_line(self): # read more lines @@ -82,7 +85,7 @@ def _read_line(self): chunk = self._socket.recv(4096) if not chunk: return None - buf = self._lines[-1] + chunk.decode('utf-8', errors='ignore') + buf = self._lines[-1] + chunk.decode("utf-8", errors="ignore") self._lines = buf.split("\n") line = self._lines[0] @@ -90,7 +93,7 @@ def _read_line(self): return line def _command(self, *command): - self._socket.sendall((" ".join(command).strip() + "\n").encode('utf-8')) + self._socket.sendall((" ".join(command).strip() + "\n").encode("utf-8")) line = self._read_line() response = json.loads(line) diff --git a/oxart/devices/thorlabs_apt/driver.py b/oxart/devices/thorlabs_apt/driver.py index f4a6722..a2a7c35 100755 --- a/oxart/devices/thorlabs_apt/driver.py +++ b/oxart/devices/thorlabs_apt/driver.py @@ -158,8 +158,13 @@ class Status(IntEnum): SETTLED = 0x2000 POSITION_ERROR = 0x1000000 ENABLED = 0x80000000 - MOVING = (MOVING_HOME | MOVING_FORWARD | MOVING_REVERSE - | JOGGING_FORWARD | JOGGING_REVERSE) + MOVING = ( + MOVING_HOME + | MOVING_FORWARD + | MOVING_REVERSE + | JOGGING_FORWARD + | JOGGING_REVERSE + ) class Direction(IntEnum): @@ -178,13 +183,15 @@ class MsgError(Exception): class Message: - def __init__(self, - _id, - param1=0, - param2=0, - dest=SRC_DEST.GENERIC_USB_HW.value, - src=SRC_DEST.HOST_CONTROLLER.value, - data=None): + def __init__( + self, + _id, + param1=0, + param2=0, + dest=SRC_DEST.GENERIC_USB_HW.value, + src=SRC_DEST.HOST_CONTROLLER.value, + data=None, + ): if data is not None: dest |= 0x80 self._id = _id @@ -195,9 +202,12 @@ def __init__(self, self.data = data def __str__(self): - return ("".format(self._id, self.param1, self.param2, - self.dest, self.src)) + return ( + "".format( + self._id, self.param1, self.param2, self.dest, self.src + ) + ) @staticmethod def unpack(data): @@ -205,19 +215,26 @@ def unpack(data): data = data[6:] if dest & 0x80: if data and len(data) != param1 | (param2 << 8): - raise ValueError("If data are provided, param1 and param2" - " should contain the data length") + raise ValueError( + "If data are provided, param1 and param2" + " should contain the data length" + ) else: data = None return Message(MGMSG(_id), param1, param2, dest, src, data) def pack(self): if self.has_data: - return struct.pack(" 25: @@ -292,7 +312,8 @@ def identify(self): def set_channel_enable(self, enable=True, channel=0): active = 1 if enable else 2 self._send_message( - Message(MGMSG.MOD_SET_CHANENABLESTATE, param1=channel, param2=active)) + Message(MGMSG.MOD_SET_CHANENABLESTATE, param1=channel, param2=active) + ) def set_home_params(self, velocity=0, offset=0, channel=0): direction = Direction.REVERSE @@ -305,14 +326,16 @@ def set_velocity_params(self, vel_min=0, vel_max=0, acc=0, channel=0): self._send_message(Message(MGMSG.MOT_SET_VELPARAMS, data=payload)) def get_status(self): - msg = self._send_request(MGMSG.MOT_REQ_DCSTATUSUPDATE, - wait_for=[MGMSG.MOT_GET_DCSTATUSUPDATE]) + msg = self._send_request( + MGMSG.MOT_REQ_DCSTATUSUPDATE, wait_for=[MGMSG.MOT_GET_DCSTATUSUPDATE] + ) chan, position, velocity, _, status = struct.unpack("=HiHHI", msg.data) return chan, position, velocity, status def get_status_bits(self): - msg = self._send_request(MGMSG.MOT_REQ_STATUSBITS, - wait_for=[MGMSG.MOT_GET_STATUSBITS]) + msg = self._send_request( + MGMSG.MOT_REQ_STATUSBITS, wait_for=[MGMSG.MOT_GET_STATUSBITS] + ) _, status = struct.unpack("=HI", msg.data) return status @@ -327,22 +350,24 @@ def ack_status_update(self): def home(self, channel=0): logger.debug("Homing...") - self._send_request(MGMSG.MOT_MOVE_HOME, - param1=channel, - wait_for=[MGMSG.MOT_MOVE_HOMED, MGMSG.MOT_MOVE_STOPPED]) + self._send_request( + MGMSG.MOT_MOVE_HOME, + param1=channel, + wait_for=[MGMSG.MOT_MOVE_HOMED, MGMSG.MOT_MOVE_STOPPED], + ) logger.debug("Homed") def move(self, position, channel=0): payload = struct.pack(" 0: - self.set_angle(angle, - check_position=check_position, - auto_retry=auto_retry - 1, - acceptable_error=acceptable_error) + self.set_angle( + angle, + check_position=check_position, + auto_retry=auto_retry - 1, + acceptable_error=acceptable_error, + ) else: raise @@ -439,8 +466,11 @@ def check_angle_mu(self, acceptable_error=0): angle_mu = self.get_position() if abs(self._last_angle_mu - angle_mu) > acceptable_error: - raise ValueError("Last angle set does not match current angle", - self._last_angle_mu, angle_mu) + raise ValueError( + "Last angle set does not match current angle", + self._last_angle_mu, + angle_mu, + ) else: # if we're off by an acceptable amount, store the actual value self._last_angle_mu = angle_mu @@ -498,14 +528,22 @@ def req_hw_info(self): data = struct.unpack("=l8sH4B48s12sHHH", msg.data) serial_no = data[0] - model_no = data[1].rstrip(b'\x00').decode() + model_no = data[1].rstrip(b"\x00").decode() type_ = data[2] - fw_version = '.'.join(map(str, data[3:6])) - notes = ', '.join(bs.rstrip(b'\x00').decode() for bs in data[7:9]) + fw_version = ".".join(map(str, data[3:6])) + notes = ", ".join(bs.rstrip(b"\x00").decode() for bs in data[7:9]) hw_version, modstate, nchs = data[9:] - return (serial_no, model_no, type_, fw_version, notes, hw_version, modstate, - nchs) + return ( + serial_no, + model_no, + type_, + fw_version, + notes, + hw_version, + modstate, + nchs, + ) class DDR25(_KBD101): diff --git a/oxart/devices/thorlabs_bpc303/driver.py b/oxart/devices/thorlabs_bpc303/driver.py index e42f802..a418191 100755 --- a/oxart/devices/thorlabs_bpc303/driver.py +++ b/oxart/devices/thorlabs_bpc303/driver.py @@ -28,12 +28,17 @@ def __init__(self, port): # Detect occupied bays self.bays = [] for bay_idx in range(NUM_SLOTS_MAX): - msg = self._send_request(MGMSG.RACK_REQ_BAYUSED, - param1=bay_idx, - wait_for=[MGMSG.RACK_GET_BAYUSED]) - bay_is_occupied = (msg.param2 == 0x01) - logger.info("{} is {}".format( - bay_idx, "occupied" if bay_is_occupied else "not occupied")) + msg = self._send_request( + MGMSG.RACK_REQ_BAYUSED, + param1=bay_idx, + wait_for=[MGMSG.RACK_GET_BAYUSED], + ) + bay_is_occupied = msg.param2 == 0x01 + logger.info( + "{} is {}".format( + bay_idx, "occupied" if bay_is_occupied else "not occupied" + ) + ) if bay_is_occupied: self.bays.append(SRC_DEST["RACK_BAY_{}".format(bay_idx)]) @@ -47,20 +52,22 @@ def _read_message(self): header = self.h.read(6) data = b"" if header[4] & 0x80: - (length, ) = struct.unpack(" 25: @@ -97,40 +105,50 @@ def set_enable(self, bay_id, enable=True, channel=0): """Enables a bay.""" active = 1 if enable else 2 self._send_message( - Message(MGMSG.MOD_SET_CHANENABLESTATE, - param1=channel, - param2=active, - dest=self.bays[bay_id - 1])) + Message( + MGMSG.MOD_SET_CHANENABLESTATE, + param1=channel, + param2=active, + dest=self.bays[bay_id - 1], + ) + ) def get_enable(self, bay_id, channel=0): """Return whether that bay is enabled.""" - msg = self._send_request(MGMSG.MOD_REQ_CHANENABLESTATE, - wait_for=[MGMSG.MOD_GET_CHANENABLESTATE], - dest=self.bays[bay_id - 1], - param1=channel) - if (msg.param2 == 0x01): + msg = self._send_request( + MGMSG.MOD_REQ_CHANENABLESTATE, + wait_for=[MGMSG.MOD_GET_CHANENABLESTATE], + dest=self.bays[bay_id - 1], + param1=channel, + ) + if msg.param2 == 0x01: return True - if (msg.param2 == 0x02): + if msg.param2 == 0x02: return False def get_status_bits(self, bay_id): - msg = self._send_request(MGMSG.PZ_REQ_PZSTATUSBITS, - wait_for=[MGMSG.PZ_GET_PZSTATUSBITS], - dest=self.bays[bay_id - 1]) + msg = self._send_request( + MGMSG.PZ_REQ_PZSTATUSBITS, + wait_for=[MGMSG.PZ_GET_PZSTATUSBITS], + dest=self.bays[bay_id - 1], + ) chan, status = struct.unpack("=HI", msg.data) return status def ack_status_update(self): self._send_message( - Message(MGMSG.PZ_ACK_PZSTATUSUPDATE, - dest=SRC_DEST["RACK_CONTROLLER"].value)) + Message(MGMSG.PZ_ACK_PZSTATUSUPDATE, dest=SRC_DEST["RACK_CONTROLLER"].value) + ) def get_serial(self): - msg = self._send_request(MGMSG.HW_REQ_INFO, - wait_for=[MGMSG.HW_GET_INFO], - dest=SRC_DEST["RACK_CONTROLLER"].value) - serial, model, hw_type, firmware, _, hw_version, mod, num_channels = \ + msg = self._send_request( + MGMSG.HW_REQ_INFO, + wait_for=[MGMSG.HW_GET_INFO], + dest=SRC_DEST["RACK_CONTROLLER"].value, + ) + serial, model, hw_type, firmware, _, hw_version, mod, num_channels = ( struct.unpack("=L8sH4s60sHHH", msg.data) + ) return serial def ping(self): @@ -157,8 +175,9 @@ class BPC303(_APTCardSlotDevice): def __init__(self, port, enable_feedback=False): super().__init__(port) - logger.info("Device vlimit is {}, travel is {}um".format( - PZ_MAX_VOLTAGE, PZ_TRAVEL_UM)) + logger.info( + "Device vlimit is {}, travel is {}um".format(PZ_MAX_VOLTAGE, PZ_TRAVEL_UM) + ) self.fname = "piezo_{}.pyon".format(self.get_serial()) self.voltages = {"volt_{}".format(i): -1 for i in range(len(self.bays))} self.positions = {"pos_{}".format(i): -1 for i in range(len(self.bays))} @@ -179,7 +198,7 @@ def setup(self): def set_channel(self, channel_char, value): """Set one of the axes ('x', 'y', 'z') to a certain value.""" self._check_valid_channel(channel_char) - bay_id = ord(channel_char) - ord('w') + bay_id = ord(channel_char) - ord("w") if self.enable_feedback: self.set_position(bay_id, value) else: @@ -188,7 +207,7 @@ def set_channel(self, channel_char, value): def get_channel(self, channel_char): """Get last value set from this driver (don't query hardware)""" self._check_valid_channel(channel_char) - bay_idx = ord(channel_char) - ord('x') + bay_idx = ord(channel_char) - ord("x") if self.enable_feedback: return self.positions["pos_{}".format(bay_idx)] else: @@ -197,7 +216,7 @@ def get_channel(self, channel_char): def get_channel_output(self, channel_char): """Get actual output value (query hardware)""" self._check_valid_channel(channel_char) - bay_id = ord(channel_char) - ord('w') + bay_id = ord(channel_char) - ord("w") if self.enable_feedback: return self.get_position(bay_id) else: @@ -215,15 +234,18 @@ def set_voltage(self, bay_id, voltage, channel=0): self._check_voltage_in_limit(voltage) payload = struct.pack(" PZ_MAX_VOLTAGE or voltage < 0: - raise ValueError("{}V not between 0 and vlimit={}".format( - voltage, PZ_MAX_VOLTAGE)) + raise ValueError( + "{}V not between 0 and vlimit={}".format(voltage, PZ_MAX_VOLTAGE) + ) def _check_position_in_limit(self, position): """Raises a ValueError if the position is not in limit for the current controller settings.""" if position > PZ_TRAVEL_UM or position < 0: - raise ValueError("Position {}μm not between 0μm and travel={}μm".format( - position, PZ_TRAVEL_UM)) + raise ValueError( + "Position {}μm not between 0μm and travel={}μm".format( + position, PZ_TRAVEL_UM + ) + ) def _check_valid_channel(self, channel): """Raises a ValueError if the channel is not valid.""" - if channel not in 'xyz': + if channel not in "xyz": raise ValueError("Channel must be one of 'x', 'y', or 'z'") # @@ -336,16 +376,22 @@ def _load_setpoints(self): """Load setpoints from a file.""" try: self.voltages, self.positions = pyon.load_file(self.fname) - logger.info("Loaded '{}', voltages: {}, positions: {}".format( - self.fname, self.voltages, self.positions)) + logger.info( + "Loaded '{}', voltages: {}, positions: {}".format( + self.fname, self.voltages, self.positions + ) + ) except FileNotFoundError: logger.warning("Couldn't find '{}', no setpoints loaded".format(self.fname)) def _save_setpoints(self): """Write the setpoints out to file.""" pyon.store_file(self.fname, [self.voltages, self.positions]) - logger.debug("Saved '{}', voltages: {}, positions: {}".format( - self.fname, self.voltages, self.positions)) + logger.debug( + "Saved '{}', voltages: {}, positions: {}".format( + self.fname, self.voltages, self.positions + ) + ) def save_setpoints(self): """Deprecated: setpoints are saved internally on every set command""" diff --git a/oxart/devices/thorlabs_camera/driver.py b/oxart/devices/thorlabs_camera/driver.py index d7b612d..c6d2be2 100644 --- a/oxart/devices/thorlabs_camera/driver.py +++ b/oxart/devices/thorlabs_camera/driver.py @@ -32,11 +32,9 @@ def get_device_info_str(self): def get_sensor_info_str(self): return repr(super().get_sensor_info()) - def wait_for_frame(self, - since="now", - nframes=1, - timeout=20.0, - error_on_stopped=False): + def wait_for_frame( + self, since="now", nframes=1, timeout=20.0, error_on_stopped=False + ): super().wait_for_frame( since=since, nframes=nframes, @@ -84,12 +82,9 @@ def set_roi(self, hstart=260, hend=800, vstart=0, vend=50, hbin=1, vbin=1): interest should be larger than the min value and smaller than the max (the size of the detector). Check with self.get_roi() to see the effective frame size. """ - super().set_roi(hstart=hstart, - hend=hend, - vstart=vstart, - vend=vend, - hbin=hbin, - vbin=vbin) + super().set_roi( + hstart=hstart, hend=hend, vstart=vstart, vend=vend, hbin=hbin, vbin=vbin + ) def get_roi_limits_dict(self, hbin=1, vbin=1): """Override parent class to output a dictionary. diff --git a/oxart/devices/thorlabs_mdt693a/driver.py b/oxart/devices/thorlabs_mdt693a/driver.py index 3f273fc..d4ee66d 100644 --- a/oxart/devices/thorlabs_mdt693a/driver.py +++ b/oxart/devices/thorlabs_mdt693a/driver.py @@ -13,9 +13,9 @@ class PiezoController: """ def __init__(self, device): - self.dev = serial.serial_for_url("socket://{}:9001".format(device), - baudrate=115200, - timeout=1) + self.dev = serial.serial_for_url( + "socket://{}:9001".format(device), baudrate=115200, timeout=1 + ) assert self.ping() def close(self): @@ -36,18 +36,18 @@ def _read_ping(self): def _read_line(self): """Send a command, and return the output of the command as a string.""" s = self.dev.readline().decode() - a1 = re.search(r'\[(.*)\]', s) - a2 = re.search(r'\*([0-9\.]+\Z)', s) - while a1 is None and a2 is None and s != '': + a1 = re.search(r"\[(.*)\]", s) + a2 = re.search(r"\*([0-9\.]+\Z)", s) + while a1 is None and a2 is None and s != "": s = self.dev.readline().decode() - a1 = re.search(r'\[(.*)\]', s) - a2 = re.search(r'\*([0-9\.]+\Z)', s) + a1 = re.search(r"\[(.*)\]", s) + a2 = re.search(r"\*([0-9\.]+\Z)", s) if a1 is not None: return a1.group(1) elif a2 is not None: return a2.group(1) else: - raise Exception('No information returned from command') + raise Exception("No information returned from command") def read_voltage(self, ch): msg = ch + "R?" diff --git a/oxart/devices/thorlabs_mdt69xb/driver.py b/oxart/devices/thorlabs_mdt69xb/driver.py index 3e9e398..12d28d8 100644 --- a/oxart/devices/thorlabs_mdt69xb/driver.py +++ b/oxart/devices/thorlabs_mdt69xb/driver.py @@ -25,10 +25,9 @@ class PiezoController: """ def __init__(self, serial_addr): - self.port = serial.serial_for_url(serial_addr, - baudrate=115200, - timeout=0.1, - write_timeout=0.1) + self.port = serial.serial_for_url( + serial_addr, baudrate=115200, timeout=0.1, write_timeout=0.1 + ) self.echo = None self._purge() @@ -52,7 +51,7 @@ def __init__(self, serial_addr): self.data_dir = _get_data_dir() self.filename = "piezo_{}.pyon".format(self.get_serial()) self.abs_filename = os.path.join(self.data_dir, self.filename) - self.channels = {'x': -1, 'y': -1, 'z': -1} + self.channels = {"x": -1, "y": -1, "z": -1} self._load_setpoints() def close(self): @@ -69,7 +68,7 @@ def feedback_enabled(self): def _send(self, cmd): """Wrapper for send that will exit server if error occurs.""" try: - str_ = cmd + '\r' + str_ = cmd + "\r" logger.debug("Sending " + repr(str_)) self.port.write(str_.encode()) except serial.SerialTimeoutException: @@ -89,10 +88,10 @@ def _read_line(self): Returns '' on timeout """ - line = '' - while len(line) == 0 or line[-1] != '\r': + line = "" + while len(line) == 0 or line[-1] != "\r": c = self.port.read().decode() - if c == '': + if c == "": # Timeout occurred break line += c @@ -101,13 +100,13 @@ def _read_line(self): def _purge(self): """Make sure we start from a clean slate with the controller.""" - self._send('') + self._send("") self._reset_input_timeout() def _reset_input_timeout(self): """Read everything off the input and discard.""" _ = self.port.read().decode() - while _ != '': + while _ != "": _ = self.port.read().decode() def _reset_input(self): @@ -137,21 +136,21 @@ def _get_float(self, cmd, **kwargs): def _get_multiline(self, cmd, **kwargs): """Has to wait for timeout.""" - cmd_str = '{}?'.format(cmd) + cmd_str = "{}?".format(cmd) self._send_command(cmd_str) - para = '' + para = "" line = self._read_line() - while line != '': + while line != "": para += line line = self._read_line() - return para.replace('\r', '\n') + return para.replace("\r", "\n") # # v1.06 # def _set_1_06(self, cmd, val, check=False): """<= v1.06 set command.""" - cmd_str = '{}={}'.format(cmd, val) + cmd_str = "{}={}".format(cmd, val) self._send_command(cmd_str) if check: @@ -161,7 +160,7 @@ def _set_1_06(self, cmd, val, check=False): def _get_1_06(self, cmd, check=False): """<= v1.06 get command.""" - cmd_str = '{}?'.format(cmd) + cmd_str = "{}?".format(cmd) self._send_command(cmd_str) if check: @@ -173,9 +172,9 @@ def _get_1_06(self, cmd, check=False): def _check_1_06(self): c = self.port.read().decode() - if c == '*': + if c == "*": return None - elif c == '!': + elif c == "!": raise CommandNotDefined() else: raise ParseError() @@ -185,7 +184,7 @@ def _check_1_06(self): # def _set_1_09(self, cmd, val, check=True): """>= v1.09 set command.""" - cmd_str = '{}={}'.format(cmd, val) + cmd_str = "{}={}".format(cmd, val) self._send_command(cmd_str) if check: @@ -195,7 +194,7 @@ def _set_1_09(self, cmd, val, check=True): def _get_1_09(self, cmd, check=True): """>= v1.09 get command.""" - cmd_str = '{}?'.format(cmd) + cmd_str = "{}?".format(cmd) self._send_command(cmd_str) response = self._read_line().strip() @@ -209,24 +208,24 @@ def _get_1_09(self, cmd, check=True): def _check_1_09(self): c = self.port.read().decode() - if c == '>': + if c == ">": return None else: s = c - while s[-1] != '>': + while s[-1] != ">": c = self.port.read().decode() - if c == '': + if c == "": # Timeout occurred break s += c - if s == 'CMD_NOT_DEFINED>': + if s == "CMD_NOT_DEFINED>": raise CommandNotDefined() else: raise ParseError() def _reset_input_1_09(self): _ = self.port.read().decode() - while _ != '>' and _ != '': + while _ != ">" and _ != "": _ = self.port.read().decode() # @@ -263,7 +262,7 @@ def get_id(self): # Due to the crappy Thorlabs protocol (no clear finish marker) we have # to wait for a timeout to ensure that we have read everything # (only for true for versions <1.09) - return self._get_multiline('id') + return self._get_multiline("id") def get_firmware_version(self): id_ = self.get_id() @@ -288,7 +287,7 @@ def set_channel(self, channel, voltage): self._check_valid_channel(channel) self._check_voltage_in_limit(voltage) - cmd = channel + 'voltage' + cmd = channel + "voltage" self._set(cmd, voltage) self.channels[channel] = voltage self._save_setpoints() @@ -300,7 +299,7 @@ def get_channel_output(self, channel): and DAC offsets. """ self._check_valid_channel(channel) - cmd = channel + 'voltage' + cmd = channel + "voltage" return self._get_float(cmd) def get_channel(self, channel): @@ -313,7 +312,7 @@ def get_voltage_limit(self): This is either 75V, 100V or 150V, set by the switch on the device back panel) """ - return self._get_float('vlimit') + return self._get_float("vlimit") # # Boring check/parsing functions @@ -327,8 +326,9 @@ def _check_voltage_in_limit(self, voltage): """Raises a ValueError if the voltage is not in limit for the current controller settings.""" if voltage > self.v_limit or voltage < 0: - raise ValueError("Voltage must be between 0 and vlimit={}".format( - self.v_limit)) + raise ValueError( + "Voltage must be between 0 and vlimit={}".format(self.v_limit) + ) def _strip_brackets(self, line): """Take string enclosed in square brackets and return string.""" @@ -344,11 +344,15 @@ def _load_setpoints(self): """Load setpoints from a file.""" try: self.channels = pyon.load_file(self.abs_filename) - logger.info("Loaded '{}', channels: {}".format(self.filename, - self.channels)) + logger.info( + "Loaded '{}', channels: {}".format(self.filename, self.channels) + ) except FileNotFoundError: - logger.warning("Couldn't find '{}' in '{}', no setpoints loaded".format( - self.filename, self.data_dir)) + logger.warning( + "Couldn't find '{}' in '{}', no setpoints loaded".format( + self.filename, self.data_dir + ) + ) def _save_setpoints(self): """Write the setpoints out to file.""" @@ -371,7 +375,7 @@ def ping(self): class SimulationPiezoController: def __init__(self, *args, **kwargs): - logger.debug('Initialised') + logger.debug("Initialised") def close(self): logger.debug("Called close") diff --git a/oxart/devices/thorlabs_mdt69xb/mediator.py b/oxart/devices/thorlabs_mdt69xb/mediator.py index 38feefc..f82dd41 100644 --- a/oxart/devices/thorlabs_mdt69xb/mediator.py +++ b/oxart/devices/thorlabs_mdt69xb/mediator.py @@ -34,8 +34,10 @@ def set_channel(self, logical_ch, value, force=False): step = self.slow_scan[logical_ch] current = device.get_channel(channel) if current < 0: - err_msg = ("'{}' has no setpoint. ".format(logical_ch) + - "Calibrate with laser unlocked before reuse.") + err_msg = ( + "'{}' has no setpoint. ".format(logical_ch) + + "Calibrate with laser unlocked before reuse." + ) raise NoSetpointError(err_msg) else: sgn = np.sign(value - current) @@ -87,14 +89,17 @@ def _get_dev_channel(self, logical_ch): class UnknownLogicalChannel(Exception): """Logical channel given not found in mappings dictionary.""" + pass class UnknownDeviceName(Exception): """Device name for given logical channel not found in devices list.""" + pass class NoSetpointError(Exception): """No setpoint available for a slow scan piezo, needs calibration.""" + pass diff --git a/oxart/devices/thorlabs_pm/driver.py b/oxart/devices/thorlabs_pm/driver.py index d4b33a7..5be008a 100644 --- a/oxart/devices/thorlabs_pm/driver.py +++ b/oxart/devices/thorlabs_pm/driver.py @@ -1,5 +1,17 @@ -from ctypes import (c_bool, c_char_p, c_double, c_int, c_int16, c_long, c_uint32, - c_voidp, byref, cdll, create_string_buffer, sizeof) +from ctypes import ( + c_bool, + c_char_p, + c_double, + c_int, + c_int16, + c_long, + c_uint32, + c_voidp, + byref, + cdll, + create_string_buffer, + sizeof, +) import logging # The following constants are taken from the TLPM.h header file in the @@ -42,10 +54,12 @@ class _TLPM: def __init__(self): if sizeof(c_voidp) == 4: self.dll = cdll.LoadLibrary( - "C:/Program Files/IVI Foundation/VISA/Win64/Bin/TLPM_32.dll") + "C:/Program Files/IVI Foundation/VISA/Win64/Bin/TLPM_32.dll" + ) else: self.dll = cdll.LoadLibrary( - "C:/Program Files/IVI Foundation/VISA/Win64/Bin/TLPM_64.dll") + "C:/Program Files/IVI Foundation/VISA/Win64/Bin/TLPM_64.dll" + ) self.dev_session = c_long() self.dev_session.value = 0 @@ -96,9 +110,12 @@ def open(self, device_name, query_id, reset_device): if hasattr(device_name, "encode"): device_name = device_name.encode() - pInvokeResult = self.dll.TLPM_init(create_string_buffer(device_name), - c_bool(query_id), c_bool(reset_device), - byref(self.dev_session)) + pInvokeResult = self.dll.TLPM_init( + create_string_buffer(device_name), + c_bool(query_id), + c_bool(reset_device), + byref(self.dev_session), + ) self._testForError(pInvokeResult) def close(self): @@ -181,16 +198,21 @@ def fetch_device_info(self, index): manufacturer = create_string_buffer(TLPM_BUFFER_SIZE) available = c_int16() - pInvokeResult = self.dll.TLPM_getRsrcInfo(self.dev_session, c_int(index), - model_name, serial_number, - manufacturer, byref(available)) + pInvokeResult = self.dll.TLPM_getRsrcInfo( + self.dev_session, + c_int(index), + model_name, + serial_number, + manufacturer, + byref(available), + ) self._testForError(pInvokeResult) info_dict = { "model_name": c_char_p(model_name.raw).value.decode(), "serial_number": c_char_p(serial_number.raw).value.decode(), "manufacturer": c_char_p(manufacturer.raw).value.decode(), - "available": available.value + "available": available.value, } return info_dict @@ -217,8 +239,9 @@ def self_test(self, selfTestResult, description): Returns: int: The return value, 0 is for success """ - pInvokeResult = self.dll.TLPM_selfTest(self.dev_session, selfTestResult, - description) + pInvokeResult = self.dll.TLPM_selfTest( + self.dev_session, selfTestResult, description + ) self._testForError(pInvokeResult) return pInvokeResult @@ -241,9 +264,9 @@ def query_revision(self, instrumentDriverRevision, firmwareRevision): Returns: int: The return value, 0 is for success """ - pInvokeResult = self.dll.TLPM_revisionQuery(self.dev_session, - instrumentDriverRevision, - firmwareRevision) + pInvokeResult = self.dll.TLPM_revisionQuery( + self.dev_session, instrumentDriverRevision, firmwareRevision + ) self._testForError(pInvokeResult) return pInvokeResult @@ -274,10 +297,13 @@ def query_id(self, manufacturerName, deviceName, serialNumber, firmwareRevision) Returns: int: The return value, 0 is for success """ - pInvokeResult = self.dll.TLPM_identificationQuery(self.dev_session, - manufacturerName, deviceName, - serialNumber, - firmwareRevision) + pInvokeResult = self.dll.TLPM_identificationQuery( + self.dev_session, + manufacturerName, + deviceName, + serialNumber, + firmwareRevision, + ) self._testForError(pInvokeResult) return pInvokeResult @@ -291,16 +317,17 @@ def __init__(self, device_name, query_device=True, reset_device=False): def set_wavelength(self, wavelength): """Set wavelength in nanometers to use for measurements.""" - pInvokeResult = self.dll.TLPM_setWavelength(self.dev_session, - c_double(wavelength)) + pInvokeResult = self.dll.TLPM_setWavelength( + self.dev_session, c_double(wavelength) + ) self._testForError(pInvokeResult) def get_wavelength(self): """Return the wavelength in nanometers currently set for measurements.""" wavelength = c_double() - pInvokeResult = self.dll.TLPM_getWavelength(self.dev_session, - c_int16(TLPM_ATTR_SET_VAL), - byref(wavelength)) + pInvokeResult = self.dll.TLPM_getWavelength( + self.dev_session, c_int16(TLPM_ATTR_SET_VAL), byref(wavelength) + ) self._testForError(pInvokeResult) return wavelength.value @@ -316,9 +343,9 @@ def set_power_range(self, power): def get_power_range(self): """Return the power range currently set for measurements.""" pow_range = c_double() - pInvokeResult = self.dll.TLPM_getPowerRange(self.dev_session, - c_int16(TLPM_ATTR_SET_VAL), - byref(pow_range)) + pInvokeResult = self.dll.TLPM_getPowerRange( + self.dev_session, c_int16(TLPM_ATTR_SET_VAL), byref(pow_range) + ) self._testForError(pInvokeResult) return pow_range.value diff --git a/oxart/devices/thorlabs_tcube/driver.py b/oxart/devices/thorlabs_tcube/driver.py index e42ce60..b514bb7 100644 --- a/oxart/devices/thorlabs_tcube/driver.py +++ b/oxart/devices/thorlabs_tcube/driver.py @@ -145,9 +145,12 @@ def __init__(self, id, param1=0, param2=0, dest=0x50, src=0x01, data=None): self.data = data def __str__(self): - return ("".format(self.id, self.param1, self.param2, - self.dest, self.src)) + return ( + "".format( + self.id, self.param1, self.param2, self.dest, self.src + ) + ) @staticmethod def unpack(data): @@ -155,19 +158,26 @@ def unpack(data): data = data[6:] if dest & 0x80: if data and len(data) != param1 | (param2 << 8): - raise ValueError("If data are provided, param1 and param2" - " should contain the data length") + raise ValueError( + "If data are provided, param1 and param2" + " should contain the data length" + ) else: data = None return Message(MGMSG(id), param1, param2, dest, src, data) def pack(self): if self.has_data: - return st.pack(" self.voltage_limit: - raise ValueError("Voltage must be in range [0;{}]".format( - self.voltage_limit)) + raise ValueError( + "Voltage must be in range [0;{}]".format(self.voltage_limit) + ) volt = int(voltage * 32767 / self.voltage_limit) payload = st.pack("` method. :rtype: A 2 int tuple. """ - get_msg = await self.send_request(MGMSG.MOT_REQ_LIMSWITCHPARAMS, - [MGMSG.MOT_GET_LIMSWITCHPARAMS], 1) + get_msg = await self.send_request( + MGMSG.MOT_REQ_LIMSWITCHPARAMS, [MGMSG.MOT_GET_LIMSWITCHPARAMS], 1 + ) return st.unpack("` command.""" - await self.send_request(MGMSG.MOT_MOVE_RELATIVE, - [MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED], 1) + await self.send_request( + MGMSG.MOT_MOVE_RELATIVE, + [MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED], + 1, + ) async def move_relative(self, relative_distance): """Start a relative move :param relative_distance: The distance to move in position encoder counts.""" payload = st.pack("` command. """ - await self.send_request(MGMSG.MOT_MOVE_ABSOLUTE, - [MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED], - param1=1) + await self.send_request( + MGMSG.MOT_MOVE_ABSOLUTE, + [MGMSG.MOT_MOVE_COMPLETED, MGMSG.MOT_MOVE_STOPPED], + param1=1, + ) async def move_absolute(self, absolute_distance): """Start an absolute move. @@ -975,19 +1040,23 @@ async def move_absolute(self, absolute_distance): specifies the absolute distance in position encoder counts. """ payload = st.pack("` for description. :rtype: A 3 int tuple """ - get_msg = await self.send_request(MGMSG.MOT_REQ_BUTTONPARAMS, - [MGMSG.MOT_GET_BUTTONPARAMS], 1) + get_msg = await self.send_request( + MGMSG.MOT_REQ_BUTTONPARAMS, [MGMSG.MOT_GET_BUTTONPARAMS], 1 + ) return st.unpack(" self.voltage_limit: - raise ValueError("Voltage must be in range [0;{}]".format( - self.voltage_limit)) - volt = (int(voltage * 32767 / self.hw_voltage_range) // 2 - ) # Seems to be inconsistency in the API documentation + raise ValueError( + "Voltage must be in range [0;{}]".format(self.voltage_limit) + ) + volt = ( + int(voltage * 32767 / self.hw_voltage_range) // 2 + ) # Seems to be inconsistency in the API documentation payload = st.pack(" 512: raise ValueError( - "LUT index should be in range [0;512] and not {}".format(lut_index)) + "LUT index should be in range [0;512] and not {}".format(lut_index) + ) self.lut[lut_index] = output def get_output_lut(self): return 0, 0 # FIXME: the API description here doesn't make any sense - def set_output_lut_parameters(self, mode, cycle_length, num_cycles, delay_time, - precycle_rest, postcycle_rest): + def set_output_lut_parameters( + self, mode, cycle_length, num_cycles, delay_time, precycle_rest, postcycle_rest + ): self.mode = mode self.cycle_length = cycle_length self.num_cycles = num_cycles @@ -1336,8 +1426,14 @@ def set_output_lut_parameters(self, mode, cycle_length, num_cycles, delay_time, self.postcycle_rest = postcycle_rest def get_output_lut_parameters(self): - return (self.mode, self.cycle_length, self.num_cycles, self.delay_time, - self.precycle_rest, self.postcycle_rest) + return ( + self.mode, + self.cycle_length, + self.num_cycles, + self.delay_time, + self.precycle_rest, + self.postcycle_rest, + ) def start_lut_output(self): pass @@ -1383,8 +1479,16 @@ def set_pot_parameters(self, zero_wnd, vel1, wnd1, vel2, wnd2, vel3, wnd3, vel4) self.vel4 = vel4 def get_pot_parameters(self): - return (self.zero_wnd, self.vel1, self.wnd1, self.vel2, self.wnd2, self.vel3, - self.wnd3, self.vel4) + return ( + self.zero_wnd, + self.vel1, + self.wnd1, + self.vel2, + self.wnd2, + self.vel3, + self.wnd3, + self.vel4, + ) def hub_get_bay_used(self): return False @@ -1408,8 +1512,9 @@ def set_velocity_parameters(self, acceleration, max_velocity): def get_velocity_parameters(self): return self.acceleration, self.max_velocity - def set_jog_parameters(self, mode, step_size, acceleration, max_velocity, - stop_mode): + def set_jog_parameters( + self, mode, step_size, acceleration, max_velocity, stop_mode + ): self.jog_mode = mode self.step_size = step_size self.acceleration = acceleration @@ -1417,8 +1522,13 @@ def set_jog_parameters(self, mode, step_size, acceleration, max_velocity, self.stop_mode = stop_mode def get_jog_parameters(self): - return (self.jog_mode, self.step_size, self.acceleration, self.max_velocity, - self.stop_mode) + return ( + self.jog_mode, + self.step_size, + self.acceleration, + self.max_velocity, + self.stop_mode, + ) def set_gen_move_parameters(self, backlash_distance): self.backlash_distance = backlash_distance @@ -1475,12 +1585,9 @@ def move_velocity(self, direction): def move_stop(self, stop_mode): pass - def set_dc_pid_parameters(self, - proportional, - integral, - differential, - integral_limit, - filter_control=0x0F): + def set_dc_pid_parameters( + self, proportional, integral, differential, integral_limit, filter_control=0x0F + ): self.proportional = proportional self.integral = integral self.differential = differential @@ -1488,8 +1595,13 @@ def set_dc_pid_parameters(self, self.filter_control = filter_control def get_dc_pid_parameters(self): - return (self.proportional, self.integral, self.differential, - self.integral_limit, self.filter_control) + return ( + self.proportional, + self.integral, + self.differential, + self.integral_limit, + self.filter_control, + ) def set_av_modes(self, mode_bits): self.mode_bits = mode_bits diff --git a/oxart/devices/trap_controller/driver.py b/oxart/devices/trap_controller/driver.py index cc1dc2d..c6acb75 100644 --- a/oxart/devices/trap_controller/driver.py +++ b/oxart/devices/trap_controller/driver.py @@ -37,7 +37,8 @@ def _update_physical_voltages(self, update_hw=True): # Calculate the new physical values, through matrix multiplication self._dc_physical_voltages = self._dc_translation_matrix.dot( - self._dc_logical_voltages) + self._dc_logical_voltages + ) print(self._dc_logical_channel_names) print(self._dc_logical_voltages) @@ -48,8 +49,9 @@ def _update_physical_voltages(self, update_hw=True): if update_hw: # For each channel, set the voltage for i, voltage in enumerate(self._dc_physical_voltages): - self._dc_hw_devices_lut[i].set_voltage(self._dc_hw_channels_lut[i], - voltage) + self._dc_hw_devices_lut[i].set_voltage( + self._dc_hw_channels_lut[i], voltage + ) def set_dc_voltage(self, logical_electrode, value, update_hw=True): """Set the value of a given logical dc electrode. @@ -98,7 +100,8 @@ def _parse_dc_config(self, dmgr, dc_config): # The matrix converting between logical and phyiscal # voltages self._dc_translation_matrix = np.zeros( - (len(self._dc_physical_channel_names), len(self._dc_logical_channel_names))) + (len(self._dc_physical_channel_names), len(self._dc_logical_channel_names)) + ) # Populate the matrix. for i, physical_name in enumerate(self._dc_physical_channel_names): @@ -117,7 +120,8 @@ def _parse_dc_config(self, dmgr, dc_config): device_name = dc_config["physical_channels"][name]["device"] self._dc_hw_devices_lut.append(dmgr.get(device_name)) self._dc_hw_channels_lut.append( - dc_config["physical_channels"][name]["channel_id"]) + dc_config["physical_channels"][name]["channel_id"] + ) # Get the list of trap settings self._dc_trap_settings = {} @@ -125,8 +129,9 @@ def _parse_dc_config(self, dmgr, dc_config): logical_voltage_array = np.zeros(len(self._dc_logical_voltages)) for electrode in dc_config["trap_settings"][name].keys(): index = self._dc_logical_channel_names.index(electrode) - logical_voltage_array[index] = \ - dc_config["trap_settings"][name][electrode] + logical_voltage_array[index] = dc_config["trap_settings"][name][ + electrode + ] self._dc_trap_settings[name] = logical_voltage_array # Store the default trap setting diff --git a/oxart/devices/tti_ql355/driver.py b/oxart/devices/tti_ql355/driver.py index e089e8b..0839f19 100644 --- a/oxart/devices/tti_ql355/driver.py +++ b/oxart/devices/tti_ql355/driver.py @@ -23,7 +23,7 @@ def __init__(self, device): self._purge() assert self.ping() - ident = self.identify().split(',') + ident = self.identify().split(",") if ident[1].strip() == "QL355P": self.type = PsuType.QL355P elif ident[1].strip() == "QL355TP": @@ -38,7 +38,7 @@ def _purge(self): see aqctl_tti_ql355. """ # Send a carriage return to clear the controller's input buffer - self.stream.write('\r'.encode()) + self.stream.write("\r".encode()) # Read any old gibberish from input until a timeout occurs while self.stream.read() != b"": pass @@ -135,9 +135,10 @@ def ping(self): for attempt in range(5): if attempt > 0: logger.warning("Retrying identify()") - ident = ident_raw.split(',') - if (ident[0] in ["THURLBY-THANDAR", "THURLBY THANDAR"] - and ident[1].strip() in ["QL355P", "QL355TP"]): + ident = ident_raw.split(",") + if ident[0] in ["THURLBY-THANDAR", "THURLBY THANDAR"] and ident[ + 1 + ].strip() in ["QL355P", "QL355TP"]: break logger.warning("Received unexpected ident string: '%s'", ident_raw) else: diff --git a/oxart/devices/tti_ql355/mediator.py b/oxart/devices/tti_ql355/mediator.py index 0b03969..479676d 100644 --- a/oxart/devices/tti_ql355/mediator.py +++ b/oxart/devices/tti_ql355/mediator.py @@ -63,10 +63,12 @@ def _get_dev_channel(self, logicalChannel): class UnknownLogicalChannelError(Exception): """The logical channel given was not found in the mappings dictionary.""" + pass class UnknownDeviceNameError(Exception): """The device name for the given logical channel was not found in the devices list.""" + pass diff --git a/oxart/devices/varian_multigauge/driver.py b/oxart/devices/varian_multigauge/driver.py index 229bb4c..b556a1f 100644 --- a/oxart/devices/varian_multigauge/driver.py +++ b/oxart/devices/varian_multigauge/driver.py @@ -22,8 +22,9 @@ def _send_command(self, command): response = self._read_response() if response[0] != ord(b">"): - raise VarianError("Received error response from gauge: " + - response.decode()) + raise VarianError( + "Received error response from gauge: " + response.decode() + ) return response[1:] diff --git a/oxart/devices/vaunix_signal_generator/driver.py b/oxart/devices/vaunix_signal_generator/driver.py index 5f8c19e..365c335 100644 --- a/oxart/devices/vaunix_signal_generator/driver.py +++ b/oxart/devices/vaunix_signal_generator/driver.py @@ -39,8 +39,9 @@ def get_device(serial_number): ser_num = vnx.fnLSG_GetSerialNumber(Devices[i]) print("Serial number of device %d:" % i, str(ser_num)) - raise (RuntimeError("Device with serial number %d could not be found" % - serial_number)) + raise ( + RuntimeError("Device with serial number %d could not be found" % serial_number) + ) class VaunixSG(object): @@ -133,7 +134,8 @@ def set_frequency(self, freq): if freq_100kHz > self.max_freq_100kHz or freq_100kHz < self.min_freq_100kHz: raise ValueError( - "Frequency (rounded to nearest multiple of 100kHz) out of range") + "Frequency (rounded to nearest multiple of 100kHz) out of range" + ) result = vnx.fnLSG_SetFrequency(self.dev, int(freq_100kHz)) if result != 0: @@ -148,7 +150,8 @@ def set_power(self, pow): if pow_dBm > self.max_power_dBm or pow_dBm < self.min_power_dBm: raise ValueError( - "Power (rounded to nearest multiple of 0.5dBm) out of range") + "Power (rounded to nearest multiple of 0.5dBm) out of range" + ) # And then the power is set as an integer number of 0.25 dBm units pow_025dBm = pow_05dBm * 2 diff --git a/oxart/frontend/aqctl_agilent_33220a.py b/oxart/frontend/aqctl_agilent_33220a.py index 8255b37..3e721b0 100644 --- a/oxart/frontend/aqctl_agilent_33220a.py +++ b/oxart/frontend/aqctl_agilent_33220a.py @@ -10,11 +10,11 @@ def get_argparser(): parser = argparse.ArgumentParser( - description="ARTIQ controller for Agilent 33220a AWG") - parser.add_argument("-d", - "--device", - default="10.255.6.26", - help="IP address of device") + description="ARTIQ controller for Agilent 33220a AWG" + ) + parser.add_argument( + "-d", "--device", default="10.255.6.26", help="IP address of device" + ) # parser.add_argument("-p", # "--port", # default="5025", @@ -28,16 +28,19 @@ def get_argparser(): def main(): args = get_argparser().parse_args() sca.init_logger_from_args(args) - logging.info("Trying to establish connection " - "to Agilent 33220A AWG at {}...".format(args.device)) + logging.info( + "Trying to establish connection " + "to Agilent 33220A AWG at {}...".format(args.device) + ) dev = Agilent33220A(args.device, args.port) logging.info("Established connection.") try: logging.info("Starting server at port {}...".format(args.port)) print("hello") - simple_server_loop({"Agilent33220A": dev}, sca.bind_address_from_args(args), - args.port) + simple_server_loop( + {"Agilent33220A": dev}, sca.bind_address_from_args(args), args.port + ) finally: dev.close() diff --git a/oxart/frontend/aqctl_agilent_6671a.py b/oxart/frontend/aqctl_agilent_6671a.py index 6999148..8c11f3b 100644 --- a/oxart/frontend/aqctl_agilent_6671a.py +++ b/oxart/frontend/aqctl_agilent_6671a.py @@ -10,11 +10,14 @@ def get_argparser(): parser = argparse.ArgumentParser( - description="ARTIQ controller for Agilent 6671A PSU") - parser.add_argument("-d", - "--device", - default="gpib://socket://10.255.6.10:1234-0", - help="hardware address of device") + description="ARTIQ controller for Agilent 6671A PSU" + ) + parser.add_argument( + "-d", + "--device", + default="gpib://socket://10.255.6.10:1234-0", + help="hardware address of device", + ) sca.simple_network_args(parser, 4310) sca.verbosity_args(parser) @@ -24,15 +27,18 @@ def get_argparser(): def main(): args = get_argparser().parse_args() sca.init_logger_from_args(args) - logging.info("Trying to establish connection " - "to Agilent 6671A PSU at {}...".format(args.device)) + logging.info( + "Trying to establish connection " + "to Agilent 6671A PSU at {}...".format(args.device) + ) dev = Agilent6671A(args.device) logging.info("Established connection.") try: logging.info("Starting server at port {}...".format(args.port)) - simple_server_loop({"Agilent6671A": dev}, sca.bind_address_from_args(args), - args.port) + simple_server_loop( + {"Agilent6671A": dev}, sca.bind_address_from_args(args), args.port + ) finally: dev.close() diff --git a/oxart/frontend/aqctl_andor_emccd.py b/oxart/frontend/aqctl_andor_emccd.py index 3b19cd1..23b35d3 100755 --- a/oxart/frontend/aqctl_andor_emccd.py +++ b/oxart/frontend/aqctl_andor_emccd.py @@ -44,8 +44,11 @@ def ping(self): cams = andorEmccd.initialise_all_cameras() if not cams: raise ValueError("No cameras found") - logger.info("Done. Cameras serials: {}".format(", ".join( - str(sn) for sn in list(cams.keys())))) + logger.info( + "Done. Cameras serials: {}".format( + ", ".join(str(sn) for sn in list(cams.keys())) + ) + ) for cam in cams.values(): cam.ping = types.MethodType(ping, cam) diff --git a/oxart/frontend/aqctl_arroyo.py b/oxart/frontend/aqctl_arroyo.py index 7b8bd0f..939496b 100644 --- a/oxart/frontend/aqctl_arroyo.py +++ b/oxart/frontend/aqctl_arroyo.py @@ -9,7 +9,8 @@ def get_argparser(): parser = argparse.ArgumentParser( - description="ARTIQ controller for Arroyo laser/TEC controllers") + description="ARTIQ controller for Arroyo laser/TEC controllers" + ) parser.add_argument("-d", "--device", help="hardware address of device") sca.simple_network_args(parser, 4310) sca.verbosity_args(parser) diff --git a/oxart/frontend/aqctl_booster.py b/oxart/frontend/aqctl_booster.py index aee0bcd..c7f42f2 100644 --- a/oxart/frontend/aqctl_booster.py +++ b/oxart/frontend/aqctl_booster.py @@ -11,8 +11,9 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for Booster" - " 8-channel RF power amplifier") + parser = argparse.ArgumentParser( + description="ARTIQ controller for Booster" " 8-channel RF power amplifier" + ) parser.add_argument("-d", "--device", help="device's IP address") sca.simple_network_args(parser, 4300) sca.verbosity_args(parser) diff --git a/oxart/frontend/aqctl_brooks_4850.py b/oxart/frontend/aqctl_brooks_4850.py index cf8e456..e586617 100755 --- a/oxart/frontend/aqctl_brooks_4850.py +++ b/oxart/frontend/aqctl_brooks_4850.py @@ -13,7 +13,8 @@ def get_argparser(): "-d", "--device", # default="socket://10.255.6.178:9001", - help="address (USB Serial Number or IP:port)") + help="address (USB Serial Number or IP:port)", + ) sca.simple_network_args(parser, 3255) sca.verbosity_args(parser) return parser @@ -25,10 +26,12 @@ def main(): dev = Brooks4850(args.device) try: - simple_server_loop({"Brooks4850": dev}, - sca.bind_address_from_args(args), - args.port, - loop=asyncio.get_event_loop()) + simple_server_loop( + {"Brooks4850": dev}, + sca.bind_address_from_args(args), + args.port, + loop=asyncio.get_event_loop(), + ) finally: dev.close() diff --git a/oxart/frontend/aqctl_brooks_sla5853.py b/oxart/frontend/aqctl_brooks_sla5853.py index 7f88cf8..79d30dd 100755 --- a/oxart/frontend/aqctl_brooks_sla5853.py +++ b/oxart/frontend/aqctl_brooks_sla5853.py @@ -7,14 +7,17 @@ def get_argparser(): - parser = argparse.ArgumentParser(description = "Controller for Brooks SLA5853 flowmeter") + parser = argparse.ArgumentParser( + description="Controller for Brooks SLA5853 flowmeter" + ) sca.simple_network_args(parser, 3336) parser.add_argument( "-d", "--ip_address", default="10.179.22.99", - help="IP address of serial-to-ethernet box") - parser.add_argument("port", default=9001, help = "port of flowmeter", type = int) + help="IP address of serial-to-ethernet box", + ) + parser.add_argument("port", default=9001, help="port of flowmeter", type=int) sca.verbosity_args(parser) return parser @@ -25,10 +28,12 @@ def main(): dev = BrooksSLA5853(args.ip_address, args.port) try: - simple_server_loop({"BrooksSLA5853": dev}, - sca.bind_address_from_args(args), - args.port, - loop=asyncio.get_event_loop()) + simple_server_loop( + {"BrooksSLA5853": dev}, + sca.bind_address_from_args(args), + args.port, + loop=asyncio.get_event_loop(), + ) finally: dev.close_connection() diff --git a/oxart/frontend/aqctl_current_stabilizer.py b/oxart/frontend/aqctl_current_stabilizer.py index f4bacda..b837603 100755 --- a/oxart/frontend/aqctl_current_stabilizer.py +++ b/oxart/frontend/aqctl_current_stabilizer.py @@ -13,7 +13,8 @@ def get_argparser(): parser = argparse.ArgumentParser( - description="ARTIQ controller for stabilizer_current_sense controller") + description="ARTIQ controller for stabilizer_current_sense controller" + ) parser.add_argument("-d", "--device", help="Device IP address") sca.simple_network_args(parser, 4300) @@ -35,10 +36,9 @@ def main(): fb, ff = loop.run_until_complete(open_connections(args.device)) dev = Stabilizer(fb, ff) - simple_server_loop({"stabilizer_current_sense": dev}, - args.bind, - args.port, - loop=loop) + simple_server_loop( + {"stabilizer_current_sense": dev}, args.bind, args.port, loop=loop + ) if __name__ == "__main__": diff --git a/oxart/frontend/aqctl_hexapod_controller.py b/oxart/frontend/aqctl_hexapod_controller.py index b9698bf..e65ecca 100644 --- a/oxart/frontend/aqctl_hexapod_controller.py +++ b/oxart/frontend/aqctl_hexapod_controller.py @@ -11,8 +11,9 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for " - "PI Hexapod Controller C-887.52") + parser = argparse.ArgumentParser( + description="ARTIQ controller for " "PI Hexapod Controller C-887.52" + ) parser.add_argument("-d", "--device", help="Hexapod's IP address") sca.simple_network_args(parser, 4302) diff --git a/oxart/frontend/aqctl_keysight_MSOX3104G.py b/oxart/frontend/aqctl_keysight_MSOX3104G.py index 995179c..3bb6c4f 100644 --- a/oxart/frontend/aqctl_keysight_MSOX3104G.py +++ b/oxart/frontend/aqctl_keysight_MSOX3104G.py @@ -11,12 +11,14 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for " - "Keysight InfiniiVision MSOX3104G") + parser = argparse.ArgumentParser( + description="ARTIQ controller for " "Keysight InfiniiVision MSOX3104G" + ) parser.add_argument( "-d", "--device", - help="VISA address (e.g. 'USB0::0x2A8D::0x1787::CN58454180::0::INSTR')") + help="VISA address (e.g. 'USB0::0x2A8D::0x1787::CN58454180::0::INSTR')", + ) sca.simple_network_args(parser, 4301) sca.verbosity_args(parser) diff --git a/oxart/frontend/aqctl_lakeshore_335.py b/oxart/frontend/aqctl_lakeshore_335.py index caa06b9..6665eb7 100644 --- a/oxart/frontend/aqctl_lakeshore_335.py +++ b/oxart/frontend/aqctl_lakeshore_335.py @@ -11,13 +11,17 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for Lake " - "Shore Cryogenics model 335 temperature" - "controllers") - parser.add_argument("-d", - "--device", - default="gpib://socket://10.255.6.189:1234-5", - help="device's hardware address") + parser = argparse.ArgumentParser( + description="ARTIQ controller for Lake " + "Shore Cryogenics model 335 temperature" + "controllers" + ) + parser.add_argument( + "-d", + "--device", + default="gpib://socket://10.255.6.189:1234-5", + help="device's hardware address", + ) sca.simple_network_args(parser, 4300) sca.verbosity_args(parser) @@ -31,8 +35,9 @@ def main(): dev = LakeShore335(args.device) try: - simple_server_loop({"LakeShore335": dev}, sca.bind_address_from_args(args), - args.port) + simple_server_loop( + {"LakeShore335": dev}, sca.bind_address_from_args(args), args.port + ) finally: dev.close() diff --git a/oxart/frontend/aqctl_orca_camera.py b/oxart/frontend/aqctl_orca_camera.py index 794dd32..2f48cfb 100644 --- a/oxart/frontend/aqctl_orca_camera.py +++ b/oxart/frontend/aqctl_orca_camera.py @@ -19,14 +19,18 @@ def get_argparser(): parser.add_argument("--zmq-port", default=5555, type=int) parser.add_argument("--roi", default="884,200,956,48") parser.add_argument("--buffer-size", default=10000, type=int) - parser.add_argument("--speed", - default=3, - type=int, - help="integer value - (1: QUIET, 2: STANDARD, 3: FAST)") - parser.add_argument("--exposure_time", - default=0.1, - type=float, - help="default exposure time in seconds") + parser.add_argument( + "--speed", + default=3, + type=int, + help="integer value - (1: QUIET, 2: STANDARD, 3: FAST)", + ) + parser.add_argument( + "--exposure_time", + default=0.1, + type=float, + help="default exposure time in seconds", + ) return parser @@ -45,7 +49,7 @@ def main(): logger.info("Initialising cameras...") roi = [int(x) for x in args.roi.split(",")] - assert (len(roi) == 4) + assert len(roi) == 4 dev = OrcaFusion() dev.open(camera_index=0, framebuffer_len=args.buffer_size) diff --git a/oxart/frontend/aqctl_scpi_dmm.py b/oxart/frontend/aqctl_scpi_dmm.py index dc51a4f..c725a77 100644 --- a/oxart/frontend/aqctl_scpi_dmm.py +++ b/oxart/frontend/aqctl_scpi_dmm.py @@ -11,8 +11,9 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for " - "SCPI Digital Multi Meters") + parser = argparse.ArgumentParser( + description="ARTIQ controller for " "SCPI Digital Multi Meters" + ) parser.add_argument("-d", "--device", help="device's hardware address") sca.simple_network_args(parser, 4300) diff --git a/oxart/frontend/aqctl_surf_solver.py b/oxart/frontend/aqctl_surf_solver.py index bf7d79f..aeefa37 100644 --- a/oxart/frontend/aqctl_surf_solver.py +++ b/oxart/frontend/aqctl_surf_solver.py @@ -11,14 +11,16 @@ def get_argparser(): parser = argparse.ArgumentParser(description="ARTIQ controller for SURF") sca.simple_network_args(parser, 4000) sca.verbosity_args(parser) - parser.add_argument("--trap_model_path", - default="/home/ion/scratch/julia_projects/" - "SURF/trap_model/comet_model.jld", - help="path to the SURF trap model file") - parser.add_argument("--cache_path", - default=None, - help="path on which to cache results. `None` (default)" - " disables the cache.") + parser.add_argument( + "--trap_model_path", + default="/home/ion/scratch/julia_projects/" "SURF/trap_model/comet_model.jld", + help="path to the SURF trap model file", + ) + parser.add_argument( + "--cache_path", + default=None, + help="path on which to cache results. `None` (default)" " disables the cache.", + ) return parser diff --git a/oxart/frontend/aqctl_thermostat.py b/oxart/frontend/aqctl_thermostat.py index 69fd86b..b1651e5 100644 --- a/oxart/frontend/aqctl_thermostat.py +++ b/oxart/frontend/aqctl_thermostat.py @@ -77,8 +77,9 @@ def cb(values): if influx_pusher: influx_pusher.push(channel_name, aggregate_stats_default(values)) - influx_channels[idx][meas_type] = ChunkedChannel(channel_name, cb, - args.chunk_size, 30, loop) + influx_channels[idx][meas_type] = ChunkedChannel( + channel_name, cb, args.chunk_size, 30, loop + ) for i in range(len(influx_channels)): for meas in Measurement: @@ -111,35 +112,40 @@ def setup_args(parser): "--chunk-size", type=int, default=256, - help="Size of chunks logged to Grafana (max. 30 sec worth of samples)") - parser.add_argument("--poll-time", - default=0.15, - type=float, - help="Seconds between measurements in chunk (default: 0.15s)") + help="Size of chunks logged to Grafana (max. 30 sec worth of samples)", + ) + parser.add_argument( + "--poll-time", + default=0.15, + type=float, + help="Seconds between measurements in chunk (default: 0.15s)", + ) subparsers = parser.add_subparsers(title="Subcommands", dest="subcommand") autotune_parser = subparsers.add_parser( - "autotune", help="Auto-tune PID parameters before starting server") + "autotune", help="Auto-tune PID parameters before starting server" + ) autotune_parser.add_argument("-c", "--channel", type=int, help="Channel index") - autotune_parser.add_argument("-t", - "--target", - type=float, - help="Target temperature in degrees celsius") + autotune_parser.add_argument( + "-t", "--target", type=float, help="Target temperature in degrees celsius" + ) autotune_parser.add_argument( "--step", type=float, default=0.1, - help="Current by which output will be changed from zero (default: 0.1A)") + help="Current by which output will be changed from zero (default: 0.1A)", + ) autotune_parser.add_argument( "--lookback", type=float, default=60, - help="Reference period for local minima/maxima (default: 60s)") + help="Reference period for local minima/maxima (default: 60s)", + ) autotune_parser.add_argument( "--noiseband", type=float, default=0.01, - help="How much the input value must over/undershoot the target (default: 0.01K)" + help="How much the input value must over/undershoot the target (default: 0.01K)", ) diff --git a/oxart/frontend/aqctl_thermostat_legacy.py b/oxart/frontend/aqctl_thermostat_legacy.py index 2f3ec89..9284cd7 100644 --- a/oxart/frontend/aqctl_thermostat_legacy.py +++ b/oxart/frontend/aqctl_thermostat_legacy.py @@ -11,8 +11,9 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for " - "sinara Thermostat") + parser = argparse.ArgumentParser( + description="ARTIQ controller for " "sinara Thermostat" + ) parser.add_argument("-d", "--device", help="device's IP address") sca.simple_network_args(parser, 4300) sca.verbosity_args(parser) @@ -29,8 +30,9 @@ def main(): # A: We don't want to try to close the serial if sys.exit() is called, # and sys.exit() isn't caught by Exception try: - simple_server_loop({"Thermostat": dev}, sca.bind_address_from_args(args), - args.port) + simple_server_loop( + {"Thermostat": dev}, sca.bind_address_from_args(args), args.port + ) except Exception: dev.close() else: diff --git a/oxart/frontend/aqctl_thorlabs_pm100a.py b/oxart/frontend/aqctl_thorlabs_pm100a.py index 2792b1b..4b1c920 100644 --- a/oxart/frontend/aqctl_thorlabs_pm100a.py +++ b/oxart/frontend/aqctl_thorlabs_pm100a.py @@ -11,14 +11,17 @@ def get_argparser(): parser = argparse.ArgumentParser( - description="ARTIQ controller for Thorlabs PM100A power meter") + description="ARTIQ controller for Thorlabs PM100A power meter" + ) # A list of available Thorlabs power meter devices can be obtained via # the get_device_names function in module oxart.devices.thorlabs_pm.driver - parser.add_argument("-d", - "--device", - default="USB0::0x1313::0x8079::P1003876::INSTR", - help="Hardware address of device") + parser.add_argument( + "-d", + "--device", + default="USB0::0x1313::0x8079::P1003876::INSTR", + help="Hardware address of device", + ) sca.simple_network_args(parser, 4315) sca.verbosity_args(parser) @@ -28,16 +31,19 @@ def get_argparser(): def main(): args = get_argparser().parse_args() sca.init_logger_from_args(args) - logger.debug("Trying to establish connection to Thorlabs PM100A " - "power meter at {}...".format(args.device)) + logger.debug( + "Trying to establish connection to Thorlabs PM100A " + "power meter at {}...".format(args.device) + ) dev = ThorlabsPM100A(args.device, False, False) logger.debug("Connection established.") try: logger.info("Starting server at port {}...".format(args.port)) - simple_server_loop({"Thorlabs PM100A": dev}, sca.bind_address_from_args(args), - args.port) + simple_server_loop( + {"Thorlabs PM100A": dev}, sca.bind_address_from_args(args), args.port + ) finally: dev.close() diff --git a/oxart/frontend/aqctl_thorlabs_tcube.py b/oxart/frontend/aqctl_thorlabs_tcube.py index 14d1f5b..2b31e45 100644 --- a/oxart/frontend/aqctl_thorlabs_tcube.py +++ b/oxart/frontend/aqctl_thorlabs_tcube.py @@ -13,20 +13,24 @@ def get_argparser(): parser = argparse.ArgumentParser() - parser.add_argument("-P", - "--product", - required=True, - help="type of the Thorlabs T-Cube device to control: " - "tdc001/tpz001/kpc101") - parser.add_argument("-d", - "--device", - default=None, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") - parser.add_argument("--simulation", - action="store_true", - help="Put the driver in simulation mode, even if " - "--device is used.") + parser.add_argument( + "-P", + "--product", + required=True, + help="type of the Thorlabs T-Cube device to control: " "tdc001/tpz001/kpc101", + ) + parser.add_argument( + "-d", + "--device", + default=None, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) + parser.add_argument( + "--simulation", + action="store_true", + help="Put the driver in simulation mode, even if " "--device is used.", + ) common_args.simple_network_args(parser, 3255) common_args.verbosity_args(parser) return parser @@ -40,8 +44,10 @@ def main(): asyncio.set_event_loop(asyncio.ProactorEventLoop()) if not args.simulation and args.device is None: - print("You need to specify either --simulation or -d/--device " - "argument. Use --help for more information.") + print( + "You need to specify either --simulation or -d/--device " + "argument. Use --help for more information." + ) sys.exit(1) product = args.product.lower() @@ -53,8 +59,9 @@ def main(): elif product == "kpc101": dev = KpcSim() else: - print("Invalid product string (-P/--product), " - "choose from tdc001 or tpz001") + print( + "Invalid product string (-P/--product), " "choose from tdc001 or tpz001" + ) sys.exit(1) else: if product == "tdc001": @@ -68,15 +75,18 @@ def main(): loop = asyncio.get_event_loop() loop.run_until_complete(dev.get_kpc_io_settings()) else: - print("Invalid product string (-P/--product), " - "choose from tdc001 or tpz001") + print( + "Invalid product string (-P/--product), " "choose from tdc001 or tpz001" + ) sys.exit(1) try: - simple_server_loop({product: dev}, - common_args.bind_address_from_args(args), - args.port, - loop=asyncio.get_event_loop()) + simple_server_loop( + {product: dev}, + common_args.bind_address_from_args(args), + args.port, + loop=asyncio.get_event_loop(), + ) finally: dev.close() diff --git a/oxart/frontend/aqctl_tti_ql355.py b/oxart/frontend/aqctl_tti_ql355.py index 752e271..f68a6a9 100644 --- a/oxart/frontend/aqctl_tti_ql355.py +++ b/oxart/frontend/aqctl_tti_ql355.py @@ -6,22 +6,27 @@ from oxart.devices.tti_ql355.driver import QL355 from sipyco.pc_rpc import simple_server_loop -from sipyco.common_args import (bind_address_from_args, init_logger_from_args, - simple_network_args, verbosity_args) +from sipyco.common_args import ( + bind_address_from_args, + init_logger_from_args, + simple_network_args, + verbosity_args, +) logger = logging.getLogger(__name__) def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for " - "TTI QL355P (TP) single (triple) channel" - " power supplies") + parser = argparse.ArgumentParser( + description="ARTIQ controller for " + "TTI QL355P (TP) single (triple) channel" + " power supplies" + ) simple_network_args(parser, 4006) verbosity_args(parser) - parser.add_argument("-d", - "--device", - help="device's hardware address", - required=True) + parser.add_argument( + "-d", "--device", help="device's hardware address", required=True + ) return parser diff --git a/oxart/frontend/aqctl_v3500a.py b/oxart/frontend/aqctl_v3500a.py index 9c13149..98cf92f 100644 --- a/oxart/frontend/aqctl_v3500a.py +++ b/oxart/frontend/aqctl_v3500a.py @@ -11,8 +11,9 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for " - "Keysight V3500A RF power meter") + parser = argparse.ArgumentParser( + description="ARTIQ controller for " "Keysight V3500A RF power meter" + ) parser.add_argument("-d", "--device", help="device's address") sca.simple_network_args(parser, 4300) sca.verbosity_args(parser) diff --git a/oxart/frontend/aqctl_varian_multigauge.py b/oxart/frontend/aqctl_varian_multigauge.py index 51e665d..4543a36 100644 --- a/oxart/frontend/aqctl_varian_multigauge.py +++ b/oxart/frontend/aqctl_varian_multigauge.py @@ -7,10 +7,12 @@ def get_argparser(): parser = argparse.ArgumentParser() - parser.add_argument("-d", - "--device", - default="/dev/ttyUSB19", - help="address (USB Serial Number or IP:port)") + parser.add_argument( + "-d", + "--device", + default="/dev/ttyUSB19", + help="address (USB Serial Number or IP:port)", + ) sca.simple_network_args(parser, 5001) sca.verbosity_args(parser) return parser @@ -22,8 +24,9 @@ def main(): dev = VarianIonGauge(args.device) try: - simple_server_loop({"Varian_Multigauge": dev}, sca.bind_address_from_args(args), - args.port) + simple_server_loop( + {"Varian_Multigauge": dev}, sca.bind_address_from_args(args), args.port + ) finally: dev.close() diff --git a/oxart/frontend/arduino_dac_controller.py b/oxart/frontend/arduino_dac_controller.py index f2f6f40..76f299d 100644 --- a/oxart/frontend/arduino_dac_controller.py +++ b/oxart/frontend/arduino_dac_controller.py @@ -10,11 +10,13 @@ def get_argparser(): parser = argparse.ArgumentParser() - parser.add_argument("-d", - "--device", - default=None, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") + parser.add_argument( + "-d", + "--device", + default=None, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) sca.simple_network_args(parser, 2030) sca.verbosity_args(parser) @@ -26,8 +28,10 @@ def main(): sca.init_logger_from_args(args) if args.device is None: - print("You need to specify -d/--device " - "argument. Use --help for more information.") + print( + "You need to specify -d/--device " + "argument. Use --help for more information." + ) sys.exit(1) dev = ArduinoDAC(serial_name=args.device) diff --git a/oxart/frontend/arduino_dds_controller.py b/oxart/frontend/arduino_dds_controller.py index f94e28c..0246f0f 100644 --- a/oxart/frontend/arduino_dds_controller.py +++ b/oxart/frontend/arduino_dds_controller.py @@ -10,19 +10,21 @@ def get_argparser(): parser = argparse.ArgumentParser() - parser.add_argument("-d", - "--device", - default=None, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") - parser.add_argument("--simulation", - action="store_true", - help="Put the driver in simulation mode, even if " - "--device is used.") - parser.add_argument("--clockfreq", - default=1e9, - type=float, - help="clock frequency provided to DDS") + parser.add_argument( + "-d", + "--device", + default=None, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) + parser.add_argument( + "--simulation", + action="store_true", + help="Put the driver in simulation mode, even if " "--device is used.", + ) + parser.add_argument( + "--clockfreq", default=1e9, type=float, help="clock frequency provided to DDS" + ) sca.simple_network_args(parser, 2000) sca.verbosity_args(parser) @@ -34,8 +36,10 @@ def main(): sca.init_logger_from_args(args) if not args.simulation and args.device is None: - print("You need to specify either --simulation or -d/--device " - "argument. Use --help for more information.") + print( + "You need to specify either --simulation or -d/--device " + "argument. Use --help for more information." + ) sys.exit(1) if args.simulation: diff --git a/oxart/frontend/bb_shutter_controller.py b/oxart/frontend/bb_shutter_controller.py index 8f98173..f01f797 100644 --- a/oxart/frontend/bb_shutter_controller.py +++ b/oxart/frontend/bb_shutter_controller.py @@ -9,7 +9,8 @@ def get_argparser(): parser = argparse.ArgumentParser( - description="ARTIQ controller for the BeagleBone 4-channel shutter driver") + description="ARTIQ controller for the BeagleBone 4-channel shutter driver" + ) sca.simple_network_args(parser, 4000) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/bme_pulse_picker_timing_controller.py b/oxart/frontend/bme_pulse_picker_timing_controller.py index c60bd6e..ae4d893 100644 --- a/oxart/frontend/bme_pulse_picker_timing_controller.py +++ b/oxart/frontend/bme_pulse_picker_timing_controller.py @@ -10,13 +10,16 @@ def get_argparser(): parser = argparse.ArgumentParser( - description="ARTIQ controller for BME delay generator PCI card") + description="ARTIQ controller for BME delay generator PCI card" + ) sca.simple_network_args(parser, 4007) - parser.add_argument("-s", - "--simulation", - default=False, - action="store_true", - help="Put the driver in simulation mode") + parser.add_argument( + "-s", + "--simulation", + default=False, + action="store_true", + help="Put the driver in simulation mode", + ) parser.add_argument("--allow-long-pulses", default=False, action="store_true") sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/conex_agap.py b/oxart/frontend/conex_agap.py index 69b36cc..6be0320 100644 --- a/oxart/frontend/conex_agap.py +++ b/oxart/frontend/conex_agap.py @@ -2,18 +2,20 @@ from conex_agap.driver import ConexMirror from sipyco.pc_rpc import simple_server_loop -from sipyco.common_args import (simple_network_args, init_logger_from_args, - bind_address_from_args) +from sipyco.common_args import ( + simple_network_args, + init_logger_from_args, + bind_address_from_args, +) from oxart.tools import add_common_args def main(): parser = argparse.ArgumentParser(description="Conex AGAP mirror controller") simple_network_args(parser, 5000) - parser.add_argument("-d", - "--device", - help="Hardware address of controller", - required=True) + parser.add_argument( + "-d", "--device", help="Hardware address of controller", required=True + ) parser.add_argument("--id", required=True, help="Controller ID") add_common_args(parser) args = parser.parse_args() @@ -21,7 +23,7 @@ def main(): dev = ConexMirror(args.id, args.device) try: - simple_server_loop({'conex_agap': dev}, bind_address_from_args(args), args.port) + simple_server_loop({"conex_agap": dev}, bind_address_from_args(args), args.port) finally: dev.close() diff --git a/oxart/frontend/conex_motor_controller.py b/oxart/frontend/conex_motor_controller.py index b7fd50e..1653c0f 100644 --- a/oxart/frontend/conex_motor_controller.py +++ b/oxart/frontend/conex_motor_controller.py @@ -9,24 +9,31 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for " - "Newport CONEX motorised micrometer") + parser = argparse.ArgumentParser( + description="ARTIQ controller for " "Newport CONEX motorised micrometer" + ) sca.simple_network_args(parser, 4000) - parser.add_argument("-d", - "--device", - default=None, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") - parser.add_argument("--no-auto-home", - action="store_true", - help="Do not home (reset to mechanical zero) on \ + parser.add_argument( + "-d", + "--device", + default=None, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) + parser.add_argument( + "--no-auto-home", + action="store_true", + help="Do not home (reset to mechanical zero) on \ start (this needs to be done each time the hardware is \ - power cycled") - parser.add_argument("--position-limit", - default=None, - type=float, - help="Maximum extension of micrometer (limit loaded \ - into hardware") + power cycled", + ) + parser.add_argument( + "--position-limit", + default=None, + type=float, + help="Maximum extension of micrometer (limit loaded \ + into hardware", + ) sca.verbosity_args(parser) return parser @@ -36,13 +43,15 @@ def main(): sca.init_logger_from_args(args) if args.device is None: - print("You need to specify a -d/--device " - "argument. Use --help for more information.") + print( + "You need to specify a -d/--device " + "argument. Use --help for more information." + ) sys.exit(1) - dev = Conex(args.device, - position_limit=args.position_limit, - auto_home=not args.no_auto_home) + dev = Conex( + args.device, position_limit=args.position_limit, auto_home=not args.no_auto_home + ) # Q: Why not use try/finally for port closure? # A: We don't want to try to close the serial if sys.exit() is called, diff --git a/oxart/frontend/hoa2_dac_controller.py b/oxart/frontend/hoa2_dac_controller.py index b762c17..988f92d 100644 --- a/oxart/frontend/hoa2_dac_controller.py +++ b/oxart/frontend/hoa2_dac_controller.py @@ -10,11 +10,13 @@ def get_argparser(): parser = argparse.ArgumentParser() - parser.add_argument("-d", - "--device", - default=None, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") + parser.add_argument( + "-d", + "--device", + default=None, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) sca.simple_network_args(parser, 2030) sca.verbosity_args(parser) @@ -26,8 +28,10 @@ def main(): sca.init_logger_from_args(args) if args.device is None: - print("You need to specify -d/--device " - "argument. Use --help for more information.") + print( + "You need to specify -d/--device " + "argument. Use --help for more information." + ) sys.exit(1) dev = HOA2Dac(serial_name=args.device) diff --git a/oxart/frontend/holzworth_synth_controller.py b/oxart/frontend/holzworth_synth_controller.py index fc70392..397cc38 100644 --- a/oxart/frontend/holzworth_synth_controller.py +++ b/oxart/frontend/holzworth_synth_controller.py @@ -10,7 +10,8 @@ def get_argparser(): parser = argparse.ArgumentParser( description="ARTIQ controller for the Holzworth synth " - "on the Quadrupole laser system") + "on the Quadrupole laser system" + ) sca.simple_network_args(parser, 4000) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/home_built_controller.py b/oxart/frontend/home_built_controller.py index 840f369..b520f9f 100644 --- a/oxart/frontend/home_built_controller.py +++ b/oxart/frontend/home_built_controller.py @@ -6,8 +6,10 @@ from llama.influxdb import aggregate_stats_default from llama.rpc import add_chunker_methods, run_simple_rpc_server from llama.channels import ChunkedChannel -from oxart.devices.home_built_controller.driver import (TemperatureController, - MeasurementType) +from oxart.devices.home_built_controller.driver import ( + TemperatureController, + MeasurementType, +) class RPCInterface: diff --git a/oxart/frontend/llama_scpi_dmm.py b/oxart/frontend/llama_scpi_dmm.py index 304a1b4..0e09e94 100644 --- a/oxart/frontend/llama_scpi_dmm.py +++ b/oxart/frontend/llama_scpi_dmm.py @@ -18,23 +18,32 @@ def setup_args(parser): - parser.add_argument("-d", - "--device", - help="multimeter hardware address", - required=True) - parser.add_argument("--measurement", - help="name of measurement; also used as InfluxDB series name", - required=True) - parser.add_argument("--max-chunk-size", - type=int, - default=256, - help=("number of measurements to average before sending " + - "to InfluxDB (if not timed out first)")) - parser.add_argument("--max-chunk-duration", - type=float, - default=30, - help=("maximum wall-clock duration of averaging chunk before " + - "sending to InfluxDB (if size not reached first)")) + parser.add_argument( + "-d", "--device", help="multimeter hardware address", required=True + ) + parser.add_argument( + "--measurement", + help="name of measurement; also used as InfluxDB series name", + required=True, + ) + parser.add_argument( + "--max-chunk-size", + type=int, + default=256, + help=( + "number of measurements to average before sending " + + "to InfluxDB (if not timed out first)" + ), + ) + parser.add_argument( + "--max-chunk-duration", + type=float, + default=30, + help=( + "maximum wall-clock duration of averaging chunk before " + + "sending to InfluxDB (if size not reached first)" + ), + ) def setup_interface(args, influx_pusher, loop): @@ -44,8 +53,13 @@ def bin_finished(values): if influx_pusher: influx_pusher.push(args.measurement, aggregate_stats_default(values)) - channel = ChunkedChannel(args.measurement, bin_finished, args.max_chunk_size, - args.max_chunk_duration, loop) + channel = ChunkedChannel( + args.measurement, + bin_finished, + args.max_chunk_size, + args.max_chunk_duration, + loop, + ) def poller_thread(): while True: diff --git a/oxart/frontend/log_kasli_health.py b/oxart/frontend/log_kasli_health.py index b38c413..47ca758 100644 --- a/oxart/frontend/log_kasli_health.py +++ b/oxart/frontend/log_kasli_health.py @@ -15,16 +15,19 @@ def main(): parser.add_argument( "--device-serial", required=True, - help="Kasli serial number (as passed to OpenOCD, e.g. kasli_105)") - parser.add_argument("--device-name", - required=True, - help="Logical Kasli name as used for InfluxDB tag") - parser.add_argument("--influx-server", - default="10.255.6.4", - help="InfluxDB server address") - parser.add_argument("--database", - default="fpga_health", - help="InfluxDB database name") + help="Kasli serial number (as passed to OpenOCD, e.g. kasli_105)", + ) + parser.add_argument( + "--device-name", + required=True, + help="Logical Kasli name as used for InfluxDB tag", + ) + parser.add_argument( + "--influx-server", default="10.255.6.4", help="InfluxDB server address" + ) + parser.add_argument( + "--database", default="fpga_health", help="InfluxDB database name" + ) args = parser.parse_args() script = [ @@ -34,9 +37,9 @@ def main(): "xadc_report xc7.tap", "exit", ] - result = subprocess.run(["openocd", "-c", ";".join(script)], - encoding="utf-8", - capture_output=True) + result = subprocess.run( + ["openocd", "-c", ";".join(script)], encoding="utf-8", capture_output=True + ) if result.returncode != 0: print("OpenOCD call failed:", result) @@ -46,16 +49,18 @@ def main(): if frags[0] in ("TEMP", "VCCINT", "VCCAUX", "VCCBRAM"): data[frags[0].lower()] = float(frags[1]) - influx_client = InfluxDBClient(host=args.influx_server, - database=args.database, - timeout=10) - influx_client.write_points([{ - "measurement": "xc7_xadc", - "fields": data, - "tags": { - "name": args.device_name - } - }]) + influx_client = InfluxDBClient( + host=args.influx_server, database=args.database, timeout=10 + ) + influx_client.write_points( + [ + { + "measurement": "xc7_xadc", + "fields": data, + "tags": {"name": args.device_name}, + } + ] + ) if __name__ == "__main__": diff --git a/oxart/frontend/logger_solstis.py b/oxart/frontend/logger_solstis.py index 6af088b..e7d2509 100755 --- a/oxart/frontend/logger_solstis.py +++ b/oxart/frontend/logger_solstis.py @@ -9,27 +9,26 @@ def get_argparser(): parser = argparse.ArgumentParser() - parser.add_argument("-s", - "--server", - required=True, - help="Laser controller address / hostname") - parser.add_argument("--name", - required=True, - help="Logical laser name, defines measurement name") - parser.add_argument("--influx-server", - default="localhost", - help="Influx server address") + parser.add_argument( + "-s", "--server", required=True, help="Laser controller address / hostname" + ) + parser.add_argument( + "--name", required=True, help="Logical laser name, defines measurement name" + ) + parser.add_argument( + "--influx-server", default="localhost", help="Influx server address" + ) parser.add_argument("--database", default="solstis", help="Influx database name") - parser.add_argument("--poll", - default=30, - type=int, - help="Measurement polling period (seconds)") + parser.add_argument( + "--poll", default=30, type=int, help="Measurement polling period (seconds)" + ) parser.add_argument( "--timeout", default=60, type=int, - help="Time (seconds) between messages from device after which connection is " + - "considered faulty and program exits") + help="Time (seconds) between messages from device after which connection is " + + "considered faulty and program exits", + ) return parser @@ -37,9 +36,9 @@ def main(): args = get_argparser().parse_args() loop = asyncio.get_event_loop() - influx_client = InfluxDBClient(host=args.influx_server, - database=args.database, - timeout=30) + influx_client = InfluxDBClient( + host=args.influx_server, database=args.database, timeout=30 + ) def write_point(fields, tags={}): point = {"measurement": args.name, "fields": fields} @@ -64,7 +63,7 @@ def handle_status_update(msg): "cavity_lock_status": msg["cavity_lock_status"], "doubler_lock_status": msg["doubler_lock_status"], "etalon_lock_status": msg["etalon_lock_status"], - "brf_wavelength": float(msg["wsd_wavelength"]) + "brf_wavelength": float(msg["wsd_wavelength"]), } write_point(fields) @@ -73,22 +72,25 @@ def handle_status_update(msg): def handle_notification(msg): if msg["display_notification"] != 1: return - if msg["notification_message"] in \ - ["Saved Vapour Cell Items", - "Saved Sprout Items", - "Saved Beam Alignment Setup", - "Saved Scope Setup", - "Saved Stitching Setup", - "Saved Wavelength Meter Setup", - "Saved Common Items"]: + if msg["notification_message"] in [ + "Saved Vapour Cell Items", + "Saved Sprout Items", + "Saved Beam Alignment Setup", + "Saved Scope Setup", + "Saved Stitching Setup", + "Saved Wavelength Meter Setup", + "Saved Common Items", + ]: return fields = {"title": msg["notification_message"], "tags": args.name} write_point(fields, tags={"type": "display_notification"}) - notifier = SolstisNotifier(server=args.server, - notification_callback=handle_notification, - status_callback=handle_status_update, - timeout=args.timeout) + notifier = SolstisNotifier( + server=args.server, + notification_callback=handle_notification, + status_callback=handle_status_update, + timeout=args.timeout, + ) loop.run_until_complete(notifier.run()) diff --git a/oxart/frontend/logger_toptica_dl_pro.py b/oxart/frontend/logger_toptica_dl_pro.py index 515e3e7..5b402c8 100644 --- a/oxart/frontend/logger_toptica_dl_pro.py +++ b/oxart/frontend/logger_toptica_dl_pro.py @@ -13,42 +13,40 @@ def get_argparser(): parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - - parser.add_argument("-s", - "--dlc-server", - required=True, - help="Laser controller address / hostname.") - parser.add_argument("--firmware", - default="v3_0_1", - help="Firmware version of DLC Pro") - parser.add_argument("--name", - required=True, - help="Logical laser name, defines measurement name") - parser.add_argument("--influx-server", - default="localhost", - help="Influx server address") - parser.add_argument("--database", - default="dl_pro_status", - help="Influx database name.") - parser.add_argument("--poll", - default=30, - type=int, - help="Measurement polling period (seconds)") - parser.add_argument("--config-file", - help="Path to json file configuring parameters to monitor.") - parser.add_argument("-v", - "--verbose", - help="Increase output verbosity", - action="count", - default=0) + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + + parser.add_argument( + "-s", "--dlc-server", required=True, help="Laser controller address / hostname." + ) + parser.add_argument( + "--firmware", default="v3_0_1", help="Firmware version of DLC Pro" + ) + parser.add_argument( + "--name", required=True, help="Logical laser name, defines measurement name" + ) + parser.add_argument( + "--influx-server", default="localhost", help="Influx server address" + ) + parser.add_argument( + "--database", default="dl_pro_status", help="Influx database name." + ) + parser.add_argument( + "--poll", default=30, type=int, help="Measurement polling period (seconds)" + ) + parser.add_argument( + "--config-file", help="Path to json file configuring parameters to monitor." + ) + parser.add_argument( + "-v", "--verbose", help="Increase output verbosity", action="count", default=0 + ) parser.add_argument( "--timeout", default=60, type=int, - help="Time (seconds) between messages from device after which connection is " + - "considered faulty and program exits", + help="Time (seconds) between messages from device after which connection is " + + "considered faulty and program exits", ) return parser @@ -66,18 +64,19 @@ def main(): # Import corresponding DLC Pro module for the required firmware dlcpro_sdk = importlib.import_module( - ".lasersdk.asyncio.dlcpro.{}".format(args.firmware), "toptica") + ".lasersdk.asyncio.dlcpro.{}".format(args.firmware), "toptica" + ) # Get the parameters to monitor from config file - with open(args.config_file, 'r') as json_file: + with open(args.config_file, "r") as json_file: logger.info(f"Loading parameters from {args.config_file}...") parameters = json.load(json_file) logger.debug(f"Loaded parameters to monitor: {parameters}") loop = asyncio.get_event_loop() - influx_client = InfluxDBClient(host=args.influx_server, - database=args.database, - timeout=30) + influx_client = InfluxDBClient( + host=args.influx_server, database=args.database, timeout=30 + ) def write_point(fields): point = {"measurement": args.name, "fields": fields} @@ -90,18 +89,21 @@ async def stream_data(): msg = {key: None for key in parameters.keys()} logger.info("Connecting to DLC Pro...") while True: - async with dlcpro_sdk.Client(dlcpro_sdk.NetworkConnection( - args.dlc_server)) as dlc: + async with dlcpro_sdk.Client( + dlcpro_sdk.NetworkConnection(args.dlc_server) + ) as dlc: logger.info("Established connection to DLC Pro") while True: time.sleep(args.poll) for key in parameters.keys(): try: - msg[key] = await wait_for(dlc.get(parameters[key], float), - timeout=args.timeout) + msg[key] = await wait_for( + dlc.get(parameters[key], float), timeout=args.timeout + ) except Exception as exception: logger.error( - f"Is DLC pro connected to network?: {exception}") + f"Is DLC pro connected to network?: {exception}" + ) break else: write_point(msg) diff --git a/oxart/frontend/ophir_controller.py b/oxart/frontend/ophir_controller.py index ea3a209..96fa410 100644 --- a/oxart/frontend/ophir_controller.py +++ b/oxart/frontend/ophir_controller.py @@ -8,14 +8,16 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for the " - "Ophir power meter") + parser = argparse.ArgumentParser( + description="ARTIQ controller for the " "Ophir power meter" + ) sca.simple_network_args(parser, 4000) - parser.add_argument("-d", - "--device", - default=None, - help="Device serial number. This is the unit no., " - "not that of the sensor") + parser.add_argument( + "-d", + "--device", + default=None, + help="Device serial number. This is the unit no., " "not that of the sensor", + ) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/picomotor_controller.py b/oxart/frontend/picomotor_controller.py index 3759737..f1645d7 100755 --- a/oxart/frontend/picomotor_controller.py +++ b/oxart/frontend/picomotor_controller.py @@ -10,18 +10,18 @@ def main(): parser = argparse.ArgumentParser(description="ARTIQ Picomotor controller") sca.simple_network_args(parser, 4006) - parser.add_argument("-d", - "--device", - help="IP address of controller", - required=True) + parser.add_argument( + "-d", "--device", help="IP address of controller", required=True + ) sca.verbosity_args(parser) args = parser.parse_args() sca.init_logger_from_args(args) dev = PicomotorController(args.device) try: - simple_server_loop({'picomotorController': dev}, - sca.bind_address_from_args(args), args.port) + simple_server_loop( + {"picomotorController": dev}, sca.bind_address_from_args(args), args.port + ) finally: dev.close() diff --git a/oxart/frontend/rs_fswp.py b/oxart/frontend/rs_fswp.py index 7acf6fd..d1706ea 100644 --- a/oxart/frontend/rs_fswp.py +++ b/oxart/frontend/rs_fswp.py @@ -1,7 +1,10 @@ import argparse from sipyco.pc_rpc import simple_server_loop -from sipyco.common_args import (simple_network_args, init_logger_from_args, - bind_address_from_args) +from sipyco.common_args import ( + simple_network_args, + init_logger_from_args, + bind_address_from_args, +) from oxart.tools import add_common_args from oxart.devices.rs_fswp import RS_FSWP @@ -16,9 +19,10 @@ def main(): dev = RS_FSWP() try: - print('Running phase noise analyser server') - simple_server_loop({'phase_noise_analyser': dev}, bind_address_from_args(args), - args.port) + print("Running phase noise analyser server") + simple_server_loop( + {"phase_noise_analyser": dev}, bind_address_from_args(args), args.port + ) finally: dev.close() diff --git a/oxart/frontend/scpi_awg_controller.py b/oxart/frontend/scpi_awg_controller.py index 4ceeaf4..09702dd 100644 --- a/oxart/frontend/scpi_awg_controller.py +++ b/oxart/frontend/scpi_awg_controller.py @@ -11,10 +11,12 @@ def get_argparser(): parser = argparse.ArgumentParser(description="ARTIQ controller for SCPI AWGs") parser.add_argument("-i", "--ipaddr", default=None, help="IP address of device") - parser.add_argument("-s", - "--serialnumber", - default=None, - help="Serial number of device to check identity") + parser.add_argument( + "-s", + "--serialnumber", + default=None, + help="Serial number of device to check identity", + ) sca.simple_network_args(parser, 4004) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/thorlabs_bpc303_controller.py b/oxart/frontend/thorlabs_bpc303_controller.py index ac63452..9f7272e 100755 --- a/oxart/frontend/thorlabs_bpc303_controller.py +++ b/oxart/frontend/thorlabs_bpc303_controller.py @@ -10,18 +10,20 @@ def get_argparser(): parser = argparse.ArgumentParser( description="ARTIQ controller for the " - "Thorlabs BPC303 3 channel closed-loop piezo controller") + "Thorlabs BPC303 3 channel closed-loop piezo controller" + ) sca.simple_network_args(parser, 5004) - parser.add_argument("-d", - "--device", - default=None, - required=True, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") - parser.add_argument("-c", - "--closedloop", - action="store_true", - help="Use in closed-loop mode?") + parser.add_argument( + "-d", + "--device", + default=None, + required=True, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) + parser.add_argument( + "-c", "--closedloop", action="store_true", help="Use in closed-loop mode?" + ) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/thorlabs_ddr05_controller.py b/oxart/frontend/thorlabs_ddr05_controller.py index 92b678d..34e9736 100644 --- a/oxart/frontend/thorlabs_ddr05_controller.py +++ b/oxart/frontend/thorlabs_ddr05_controller.py @@ -8,19 +8,25 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for the " - "Thorlabs DDR05 motorised rotation mount") + parser = argparse.ArgumentParser( + description="ARTIQ controller for the " + "Thorlabs DDR05 motorised rotation mount" + ) sca.simple_network_args(parser, 4001) - parser.add_argument("-d", - "--device", - default=None, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") - parser.add_argument("--no-auto-home", - action="store_true", - help="Do not home (reset to mechanical zero) on \ + parser.add_argument( + "-d", + "--device", + default=None, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) + parser.add_argument( + "--no-auto-home", + action="store_true", + help="Do not home (reset to mechanical zero) on \ start (this needs to be done each time the hardware \ - is power cycled") + is power cycled", + ) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/thorlabs_ddr25_controller.py b/oxart/frontend/thorlabs_ddr25_controller.py index baf3b49..818a9b4 100644 --- a/oxart/frontend/thorlabs_ddr25_controller.py +++ b/oxart/frontend/thorlabs_ddr25_controller.py @@ -8,19 +8,25 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for the " - "Thorlabs DDR25 motorised rotation mount") + parser = argparse.ArgumentParser( + description="ARTIQ controller for the " + "Thorlabs DDR25 motorised rotation mount" + ) sca.simple_network_args(parser, 4000) - parser.add_argument("-d", - "--device", - default=None, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") - parser.add_argument("--no-auto-home", - action="store_true", - help="Do not home (reset to mechanical zero) on \ + parser.add_argument( + "-d", + "--device", + default=None, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) + parser.add_argument( + "--no-auto-home", + action="store_true", + help="Do not home (reset to mechanical zero) on \ start (this needs to be done each time the hardware \ - is power cycled") + is power cycled", + ) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/thorlabs_k10cr1_controller.py b/oxart/frontend/thorlabs_k10cr1_controller.py index 76f4015..f30e057 100644 --- a/oxart/frontend/thorlabs_k10cr1_controller.py +++ b/oxart/frontend/thorlabs_k10cr1_controller.py @@ -8,19 +8,25 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for the " - "Thorlabs K10CR1 motorised rotation mount") + parser = argparse.ArgumentParser( + description="ARTIQ controller for the " + "Thorlabs K10CR1 motorised rotation mount" + ) sca.simple_network_args(parser, 4000) - parser.add_argument("-d", - "--device", - default=None, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") - parser.add_argument("--no-auto-home", - action="store_true", - help="Do not home (reset to mechanical zero) on \ + parser.add_argument( + "-d", + "--device", + default=None, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) + parser.add_argument( + "--no-auto-home", + action="store_true", + help="Do not home (reset to mechanical zero) on \ start (this needs to be done each time the hardware is \ - power cycled") + power cycled", + ) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/thorlabs_mdt693a_controller.py b/oxart/frontend/thorlabs_mdt693a_controller.py index 13ae44e..9c20125 100644 --- a/oxart/frontend/thorlabs_mdt693a_controller.py +++ b/oxart/frontend/thorlabs_mdt693a_controller.py @@ -10,13 +10,12 @@ def get_argparser(): parser = argparse.ArgumentParser( description="ARTIQ controller for the " - "Thorlabs MDT693A 3-channel open-loop piezo controller") + "Thorlabs MDT693A 3-channel open-loop piezo controller" + ) sca.simple_network_args(parser, 9001) - parser.add_argument("-d", - "--device", - default=None, - required=True, - help="Device ip address") + parser.add_argument( + "-d", "--device", default=None, required=True, help="Device ip address" + ) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/thorlabs_mdt69xb_controller.py b/oxart/frontend/thorlabs_mdt69xb_controller.py index b2448eb..8edcf28 100755 --- a/oxart/frontend/thorlabs_mdt69xb_controller.py +++ b/oxart/frontend/thorlabs_mdt69xb_controller.py @@ -10,14 +10,17 @@ def get_argparser(): parser = argparse.ArgumentParser( description="ARTIQ controller for the " - "Thorlabs MDT693B or MDT694B 3 (1) channel open-loop piezo controller") + "Thorlabs MDT693B or MDT694B 3 (1) channel open-loop piezo controller" + ) sca.simple_network_args(parser, 4002) - parser.add_argument("-d", - "--device", - default=None, - required=True, - help="serial device. See documentation for how to " - "specify a USB Serial Number.") + parser.add_argument( + "-d", + "--device", + default=None, + required=True, + help="serial device. See documentation for how to " + "specify a USB Serial Number.", + ) sca.verbosity_args(parser) return parser diff --git a/oxart/frontend/toptica_dlc_controller.py b/oxart/frontend/toptica_dlc_controller.py index faec1a3..c3ccc33 100644 --- a/oxart/frontend/toptica_dlc_controller.py +++ b/oxart/frontend/toptica_dlc_controller.py @@ -13,24 +13,27 @@ def get_argparser(): - parser = argparse.ArgumentParser(description=( - "ARTIQ controller to read lock status from TopticaDLCpro with lock module")) + parser = argparse.ArgumentParser( + description=( + "ARTIQ controller to read lock status from TopticaDLCpro with lock module" + ) + ) - parser.add_argument("--dlc-server", - required=True, - help="Laser controller address / hostname.") + parser.add_argument( + "--dlc-server", required=True, help="Laser controller address / hostname." + ) sca.simple_network_args(parser, 4305) - parser.add_argument("--firmware", - default="v3_0_1", - help="Firmware version of DLC Pro") + parser.add_argument( + "--firmware", default="v3_0_1", help="Firmware version of DLC Pro" + ) parser.add_argument( "--timeout", default=10, type=int, - help="Time (seconds) between messages from device after which connection is " + - "considered faulty and program exits", + help="Time (seconds) between messages from device after which connection is " + + "considered faulty and program exits", ) sca.verbosity_args(parser) @@ -43,7 +46,8 @@ def main(): sca.init_logger_from_args(args) dlcpro_sdk = importlib.import_module( - ".lasersdk.asyncio.dlcpro.{}".format(args.firmware), "toptica") + ".lasersdk.asyncio.dlcpro.{}".format(args.firmware), "toptica" + ) dlc = dlcpro_sdk.Client(dlcpro_sdk.NetworkConnection(args.dlc_server)) @@ -58,10 +62,9 @@ def main(): print(" :: Connection established", dlc) try: - simple_server_loop({"Lock Status Controller": dlc_driver}, - args.bind, - args.port, - loop=loop) + simple_server_loop( + {"Lock Status Controller": dlc_driver}, args.bind, args.port, loop=loop + ) finally: loop.run_until_complete(dlc_driver.close()) diff --git a/oxart/frontend/vaunix_signal_generator_controller.py b/oxart/frontend/vaunix_signal_generator_controller.py index 26413f2..8d71365 100644 --- a/oxart/frontend/vaunix_signal_generator_controller.py +++ b/oxart/frontend/vaunix_signal_generator_controller.py @@ -8,8 +8,9 @@ def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ controller for the " - "Vaunix Lab brick signal generator") + parser = argparse.ArgumentParser( + description="ARTIQ controller for the " "Vaunix Lab brick signal generator" + ) sca.simple_network_args(parser, 4000) parser.add_argument( "-d", From a07685dc44d7fd97abc01865f887b269affb1900 Mon Sep 17 00:00:00 2001 From: Molly Smith Date: Mon, 2 Mar 2026 11:57:08 +0000 Subject: [PATCH 8/8] gate_decomposer-added decomposer back in --- oxart/devices/decomposer/__init__.py | 0 oxart/devices/decomposer/driver.py | 217 ++++++++++++++++++++++++ oxart/frontend/aqctl_gate_decomposer.py | 31 ++++ 3 files changed, 248 insertions(+) create mode 100644 oxart/devices/decomposer/__init__.py create mode 100644 oxart/devices/decomposer/driver.py create mode 100644 oxart/frontend/aqctl_gate_decomposer.py diff --git a/oxart/devices/decomposer/__init__.py b/oxart/devices/decomposer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oxart/devices/decomposer/driver.py b/oxart/devices/decomposer/driver.py new file mode 100644 index 0000000..a135f9d --- /dev/null +++ b/oxart/devices/decomposer/driver.py @@ -0,0 +1,217 @@ +"""This driver requires PyJulia & working julia install with configured PyCall + +PyJulia doesn't play nice with conda. You should call this driver from the same +environment that you configured for PyCall in Julia. Instructions to configure +PyCall with conda are given in the README file of the SURF Julia library. +""" + +import numpy as np +import os +import warnings + +from julia.api import Julia + +class Result: + """ + Compare attributes to the Julia struct Decomposer.Solver.Result + """ + def __init__(self, jl_result): + self.error = jl_result.error + self.ASFs = jl_result.ASFs + self.PHIs = jl_result.PHIs + self.ASF_max = jl_result.ASF_max + self.ASF_scale = jl_result.ASF_scale + self.wanted_unitaries = jl_result.wanted_unitaries + self.ASF_pi = jl_result.ASF_pi + self.error_per_pi_half_pulse = jl_result.error_per_pi_half_pulse + self.dt = jl_result.dt + self.n_qubits = jl_result.n_qubits + self.n_pulses = jl_result.n_pulses + self.fidelity_errors = jl_result.fidelity_errors + self.squared_differences_error = jl_result.squared_differences_error + self.avg_area_error = jl_result.avg_area_error + self.mean_fidelity_error = jl_result.mean_fidelity_error + self.min_number_of_pi_pulses = jl_result.min_number_of_pi_pulses + self.avg_number_of_pi_pulses = jl_result.avg_number_of_pi_pulses + self.max_number_of_pi_pulses = jl_result.max_number_of_pi_pulses + self.is_converged = jl_result.is_converged + self.cost_type = jl_result.cost_type + self.optimisation_threshold = jl_result.optimisation_threshold + + def __str__(self): + return f"Error: {self.error}, was successful: {self.is_converged}\nASFs: {self.ASFs}\nPHIs: {self.PHIs}" + + def to_dict(self): + return { + "error": self.error, + "ASFs": self.ASFs, + "PHIs": self.PHIs, + "ASF_max": self.ASF_max, + "ASF_scale": self.ASF_scale, + "wanted_unitaries": self.wanted_unitaries, + "ASF_pi": self.ASF_pi, + "error_per_pi_half_pulse": self.error_per_pi_half_pulse, + "dt": self.dt, + "n_qubits": self.n_qubits, + "n_pulses": self.n_pulses, + "fidelity_errors": self.fidelity_errors, + "squared_differences_error": self.squared_differences_error, + "avg_area_error": self.avg_area_error, + "mean_fidelity_error": self.mean_fidelity_error, + "min_number_of_pi_pulses": self.min_number_of_pi_pulses, + "avg_number_of_pi_pulses": self.avg_number_of_pi_pulses, + "max_number_of_pi_pulses": self.max_number_of_pi_pulses, + "is_converged": self.is_converged, + "cost_type": self.cost_type, + "optimisation_threshold": self.optimisation_threshold + } + +class Decomposer: + def __init__(self, + num_threads=1, + cache_path=None) -> None: + """ + :param cache_path: path on which to cache results (default: None) + disables the cache. NOT IMPLEMENTED + """ + self.setup_done = False + self.num_threads = num_threads + self.cache_path = cache_path + + # This doesnt work yet for some reason, set JULIA_NUM_THREADS before starting server + # For small numbers of qubits it appears that the scheduling with multiple threads takes more time than just running it on one thread + # os.environ["JULIA_NUM_THREADS"] = str(self.num_threads) + + self.use_threads = "false" + if int(self.num_threads) != 1: + self.use_threads = "true" + print(f"Use threads? {self.use_threads}") + + self.jl = Julia() # compiled_modules=False) + self.prepare_julia_environment() + + # def precompile(self): + # jl.eval(f"Load.precompile_decomposer({})") + + def prepare_julia_environment(self): + # ===== Load packages ===== + self.jl.eval("using StaticArrays") + self.jl.eval("using LinearAlgebra") + self.jl.eval("using BenchmarkTools") + + self.jl.eval("import Decomposer") + self.jl.eval("using Decomposer.Helper") + self.jl.eval("using Decomposer.Solver") + self.jl.eval("using Decomposer.Load") + + def setup_decomposer(self, n_qubits: int, n_pulses: int, cost_type: str = "FastAreaMinimisation", optimisation_threshold: float = 1e-7, precompile: bool=False) -> None: + """ + Setup the decomposer with the given parameters + + :param n_qubits: number of qubits to compute pulse sequence for + :param n_pulses: number of pulses in pulse sequence + :param cost_type: type of cost function for different area minimisation techniques (NoAreaMinimisation|AreaMinimisation|[FastAreaMinimisation]) + :param optimisation_threshold: optimisation error threshold (default: 1e-7) + :param precompile: precompile the decomposer for faster first execution (default: False) + """ + self.n_qubits = n_qubits + self.n_pulses = n_pulses + self.cost_type = cost_type + self.optimisation_threshold = optimisation_threshold + self.precompile = precompile + self.setup_done = True + + # ===== Prepare variables in julia ===== + self.jl.eval(f"n_qubits::Int64 = {self.n_qubits}") + self.jl.eval(f"n_pulses::Int64 = {self.n_pulses}") + self.jl.eval(f"cost_type::String = \"{self.cost_type}\"") + self.jl.eval(f"use_threads::Bool = {self.use_threads}") + self.jl.eval(f"optimisation_threshold::Float64 = {self.optimisation_threshold}") + + # ===== Precompile decomposition? ===== + if self.precompile: + self.jl.eval(f"Load.precompile_decomposer(n_qubits, n_pulses, cost_type, use_threads)") + + def decompose(self, ASF_pis: list, ASF_max: float, target_rotations: list, ASF_scale: float=10.0) -> None: + """ + Decompose target rotations into ASFs and PHIs. + The rotations are given in the form [[x1,y1,z1], [x2,y2,z2], ...] + If x,y,z rotations are given at the same time, then the rotation matrices are constructed for each axis and then multiplied together in the order z-y-x. + + :param ASF_pis: ASF required to drive pi pulse for each qubit + :param ASF_max: maximum value for the ASF + :param target_rotations: target rotations for each qubit in terms of pi/2 rotations around (x,y,z) axes. Float values can be given for each rotation. + + :return: result: Result object containing all relevant information + """ + if not self.setup_done: + raise ValueError("Decomposer not setup. Call setup_decomposer() first") + if len(ASF_pis) != self.n_qubits: + raise ValueError("Number of ASF_pis should be equal to number of qubits") + if len(target_rotations) != self.n_qubits: + raise ValueError("Number of target rotations should be equal to number of qubits") + + # ===== prepare variables in julia ===== + # setup ASF_pis + self.jl.eval(f"ASF_pis = SVector{{n_qubits,Float64}}([{' '.join(str(i) for i in ASF_pis)}])") + # setup ASF_max + self.jl.eval(f"ASF_max = {ASF_max}") + # setup ASF_scale + self.jl.eval(f"ASF_scale = {ASF_scale}") + # setup target rotations + rotations_string = f"Rs = SVector{{n_qubits,SVector{{3,Float64}}}}([" + for tr in target_rotations: + rotations_string += f"SVector{{3,Float64}}({tr})," + rotations_string = rotations_string[:-1] + "])" + self.jl.eval(rotations_string) + + # ===== Decompose target rotations ===== + self.jl.eval(f"""result = Solver.decompose_xyz_rotations( + n_qubits, + n_pulses, + ASF_pis, + Rs; + ASF_max=ASF_max, + ASF_scale=ASF_scale, + optimisation_threshold=optimisation_threshold, + cost_type=cost_type, + use_threads=use_threads) + """) + + # ===== Get results ===== + result_jl = self.jl.eval("result") + result = Result(result_jl) + print("Result") + print(f"Error: {result.error}") + + return result.to_dict() + + def ping(self): + return True + + def get_active_threads(self): + num_threads = self.jl.eval("Threads.nthreads()") + return num_threads + + # def compute_cliffords(self): + # pass + + # def close(self): + # pass + + # def print_results(self): + # pass + + +if __name__ == "__main__": + print("Setting up Gate Decomposer...") + dev = Decomposer(num_threads=4) + dev.setup_decomposer(2, 6, "FastAreaMinimisation", 1e-7) + result = dev.decompose(ASF_pis=0.1/np.array([1, 1.3]), ASF_max=0.6, target_rotations=[[1.0,0.0,0.0], [-1.0,0.0,0.0]]) + + print(f"Error: {result.error}") + print(f"Optimisation time: {result.dt}") + print(f"ASFs: {result.ASFs}") + print(f"PHIs: {result.PHIs}") + + print(dev.ping()) diff --git a/oxart/frontend/aqctl_gate_decomposer.py b/oxart/frontend/aqctl_gate_decomposer.py new file mode 100644 index 0000000..f8ded4f --- /dev/null +++ b/oxart/frontend/aqctl_gate_decomposer.py @@ -0,0 +1,31 @@ +import argparse + +import sipyco.common_args as sca +from sipyco.pc_rpc import simple_server_loop + +from oxart.devices.decomposer.driver import Decomposer, Result + +def get_argparser(): + parser = argparse.ArgumentParser(description="ARTIQ controller for Gate Decomposer") + sca.simple_network_args(parser, 4001) + sca.verbosity_args(parser) + parser.add_argument("--threads", default=1, + help="Tell julia how many threads to use for optimisation (relatively unexplored feature: Are the threads constantly allocated? How much faster is it?)") + parser.add_argument("--cache_path", default=None, + help="path on which to cache results. `None` (default) disables the cache.") + return parser + +def main(): + print("Starting gate decomposer...") + + args = get_argparser().parse_args() + sca.init_logger_from_args(args) + + # Start decomposer + dev = Decomposer(args.threads, args.cache_path) + + simple_server_loop({"GateDecomposer_controller": dev}, args.bind, args.port) + + +if __name__ == "__main__": + main()