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/__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..c9d51c1 --- /dev/null +++ b/oxart/devices/brooks_SLA5853/driver.py @@ -0,0 +1,1155 @@ +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(" 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/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/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 50f96b5..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("` + method. + """ + volt = int(voltage * 32767 / 150.0) + payload = st.pack(" 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 @@ -1288,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 @@ -1335,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 @@ -1360,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 @@ -1369,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 @@ -1427,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 @@ -1440,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 c5d9e1a..e586617 100755 --- a/oxart/frontend/aqctl_brooks_4850.py +++ b/oxart/frontend/aqctl_brooks_4850.py @@ -3,6 +3,7 @@ from oxart.devices.brooks_4850.driver import Brooks4850 from sipyco.pc_rpc import simple_server_loop import asyncio +import asyncio import sipyco.common_args as sca @@ -12,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 @@ -24,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 new file mode 100755 index 0000000..79d30dd --- /dev/null +++ b/oxart/frontend/aqctl_brooks_sla5853.py @@ -0,0 +1,42 @@ +import argparse + +from oxart.devices.brooks_SLA5853.driver import BrooksSLA5853 +from sipyco.pc_rpc import simple_server_loop +import asyncio +import sipyco.common_args as sca + + +def get_argparser(): + 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) + sca.verbosity_args(parser) + return parser + + +def main(): + args = get_argparser().parse_args() + sca.init_logger_from_args(args) + 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(), + ) + finally: + dev.close_connection() + + +if __name__ == "__main__": + main() 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_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() 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 new file mode 100644 index 0000000..9284cd7 --- /dev/null +++ b/oxart/frontend/aqctl_thermostat_legacy.py @@ -0,0 +1,43 @@ +#!/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/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", 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"