From b84bbd9e7f2c25910a7fcc57c8ef45bee410176f Mon Sep 17 00:00:00 2001 From: Mounce Date: Tue, 8 Oct 2024 11:13:29 -0600 Subject: [PATCH 01/29] feature(drivers): Changing driver syntax to have separate Driver and PropertySettings --- .../drivers/driver_classes/abstract_driver.py | 113 +++++++++++ .../driver_classes/simple_scpi_driver.py | 79 ++++++++ pyscan/drivers/driver_classes/test_driver.py | 27 +++ pyscan/drivers/properties | 0 .../property_settings/property_exceptions.py | 44 +++++ .../property_settings/property_settings.py | 175 ++++++++++++++++++ pyscan/drivers/testing/test_voltagev2.py | 72 +++++++ 7 files changed, 510 insertions(+) create mode 100644 pyscan/drivers/driver_classes/abstract_driver.py create mode 100644 pyscan/drivers/driver_classes/simple_scpi_driver.py create mode 100644 pyscan/drivers/driver_classes/test_driver.py create mode 100644 pyscan/drivers/properties create mode 100644 pyscan/drivers/property_settings/property_exceptions.py create mode 100644 pyscan/drivers/property_settings/property_settings.py create mode 100644 pyscan/drivers/testing/test_voltagev2.py diff --git a/pyscan/drivers/driver_classes/abstract_driver.py b/pyscan/drivers/driver_classes/abstract_driver.py new file mode 100644 index 00000000..9b44fcae --- /dev/null +++ b/pyscan/drivers/driver_classes/abstract_driver.py @@ -0,0 +1,113 @@ +import re +from ...general.item_attribute import ItemAttribute + + +class AbstractDriver(ItemAttribute): + ''' + None + ''' + def __init__(self, instrument=None, debug=False): + self.instrument = instrument + self.debug = debug + + @property + def name(self): + return [k for k, v in globals().items() if v is self] + + def initialize_properties(self): + pass + + def write(self): + pass + + def query(self): + pass + + def read(self): + pass + + def add_device_property(self, settings): + + name = settings['name'] + # Make self._name_settings + settings_name = '_' + name + '_settings' + property_settings = self.property_class(self, settings) + setattr(self, settings_name, property_settings) + + # Make self.name property + property_definition = property( + fget=getattr(self, settings_name).get_property, + fset=lambda obj, new_value: getattr(self, settings_name).set_property(obj, new_value), + doc=self.get_property_docstring(name)) + setattr(self.__class__, name, property_definition) + + @property + def version(self): + return self._version + + def update_properties(self): + properties = self.get_pyscan_properties() + + for prop in properties: + settings = self['_{}_settings'.format(prop)] + if 'write_only' not in settings: + self[prop] + + def get_pyscan_properties(self): + ''' + Finds the pyscan style properties of this driver, i.e. those that end with "_settings" + + Returns + ------- + list : + list of property names for the current driver + ''' + + r = re.compile(".*_settings") + pyscan_properties = list(filter(r.match, self.keys())) + pyscan_properties = [prop[1:] for prop in pyscan_properties] + pyscan_properties = [prop.replace('_settings', '') for prop in pyscan_properties] + return pyscan_properties + + def get_property_docstring(self, prop_name): + ''' + Gets the doc string for a property from an instance of this class + + Parameters + ---------- + prop_name : str + The name of the property to get the doc string of + + Returns + ------- + str : + The two doc string lines for the property + ''' + + doc = self.__doc__.split('\n') + + r = re.compile(" {} :".format(prop_name)) + match = list(filter(r.match, doc)) + + assert len(match) > 0, "No matches for {} documentation".format(prop_name) + assert len(match) == 1, "Too many matches for {} documentation".format(prop_name) + match = match[0] + + for i, string in enumerate(doc): + if string == match: + break + + doc_string = doc[i][4::] + + for j in range(len(doc_string)): + try: + doc[i + 1 + j] + except: + break + if (doc[i + 1 + j][0:1] == '\n') or (len(doc[i + 1 + j][0:7].strip()) != 0): + # print(repr(doc[i + 1 + j])) + break + else: + doc_string = doc_string + '\n' + doc[i + 1 + j][4::] + + return doc_string diff --git a/pyscan/drivers/driver_classes/simple_scpi_driver.py b/pyscan/drivers/driver_classes/simple_scpi_driver.py new file mode 100644 index 00000000..eed1c518 --- /dev/null +++ b/pyscan/drivers/driver_classes/simple_scpi_driver.py @@ -0,0 +1,79 @@ +from .abstract_driver import AbstractDriver +from ..new_instrument import new_instrument +from ..property_settings.property_settings import PropertySettings + + +class SimpleSCPIDriver(AbstractDriver): + ''' + Attributes + ---------- + (Properties) + test : int + Just a test property + ''' + + def __init__(self, instrument, debug=False): + if isinstance(instrument, str): + self.instrument = new_instrument(instrument) + else: + self.instrument = instrument + + try: + inst_str = str(type(self.instrument)) + self._driver_class = inst_str.split("'")[1].split(".")[-1] + except Exception: + pass + + self.property_class = PropertySettings + + self.debug = debug + + def query(self, settings): + ''' + Wrapper to pass string to the instrument object + + Parameters + ---------- + string: str + The message to send to the device + + Returns + ------- + str + Answer from the device. + ''' + + string = settings.query_string + + return self.instrument.query(string) + + def write(self, settings, value): + ''' + Wrapper to write string to the instrument object + + Parameters + ---------- + string: str + The message to be sent + + Returns + ------- + None + ''' + + string = settings.write_string.format(value) + + self.instrument.write(string) + + def read(self): + ''' + Wrapper to read string from the instrument object + + Returns + ------- + str + Message read from the instrument + + ''' + + return self.instrument.read() diff --git a/pyscan/drivers/driver_classes/test_driver.py b/pyscan/drivers/driver_classes/test_driver.py new file mode 100644 index 00000000..6a66f22e --- /dev/null +++ b/pyscan/drivers/driver_classes/test_driver.py @@ -0,0 +1,27 @@ +from .abstract_driver import AbstractDriver + + +class TestDriver(AbstractDriver): + + def query(self, settings): + string = settings.query_string + if string == 'VOLT?': + return str(self._voltage) + elif string == 'POW?': + return str(self._power) + elif string == 'OUTP?': + if self._output_state == 'off': + return '0' + if self._output_state == 'on': + return '1' + # leave for the sake of your personal sanity, trust us + return str(self._output_state) + + def write(self, settings): + string = settings.write_string + if 'VOLT' in string: + return string.strip('VOLT ') + elif 'POW' in string: + return string.strip('POW ') + elif 'OUTP' in string: + return string.strip('OUTP ') diff --git a/pyscan/drivers/properties b/pyscan/drivers/properties new file mode 100644 index 00000000..e69de29b diff --git a/pyscan/drivers/property_settings/property_exceptions.py b/pyscan/drivers/property_settings/property_exceptions.py new file mode 100644 index 00000000..c25d2667 --- /dev/null +++ b/pyscan/drivers/property_settings/property_exceptions.py @@ -0,0 +1,44 @@ + + +class RangeException(Exception): + + def __init__(self, prop, range, value): + + msg = f'{prop} = {value} out of range\n' + msg += f'Valid range for {prop}: {range[0]} <= new_value <= {range[1]}' + + super().__init__(msg) + + +class IndexedValueException(Exception): + + def __init__(self, prop, indexed_values, value): + + msg = f'{prop} = {value} invalid input\n' + msg += f'Valid inputs for indexed value property {prop} are:' + msg += ',\n'.join(indexed_values) + + super().__init__(msg) + + +class ValueException(Exception): + + def __init__(self, prop, values, value): + + msg = f'{prop} = {value} invalid input\n' + msg += f'Valid inputs for values property {prop} are:' + msg += ',\n'.join(values) + + super().__init__(msg) + + +class DictValueException(Exception): + + def __init__(self, prop, dict_values, value): + + msg = f'{prop} = {value} invalid input\n' + msg += f'Valid inputs for values property {prop} are:' + for key, value in dict_values.items(): + msg += f'{key}: {value},\n' + + super().__init__(msg) diff --git a/pyscan/drivers/property_settings/property_settings.py b/pyscan/drivers/property_settings/property_settings.py new file mode 100644 index 00000000..dd6ede86 --- /dev/null +++ b/pyscan/drivers/property_settings/property_settings.py @@ -0,0 +1,175 @@ +from ...general.item_attribute import ItemAttribute +from .property_exceptions import ( + RangeException, IndexedValueException, ValueException, DictValueException) +import numpy as np + + +exception_dict = { + 'range': RangeException, + 'indexed_value': IndexedValueException, + 'values': ValueException, + 'dict_values': DictValueException} + +property_types = list(exception_dict.keys()) + + +class PropertySettings(ItemAttribute): + + def __init__(self, device, settings): + + self.device = device + + for key, value in settings.items(): + self[key] = value + + self.device[self.name] = property( + fget=lambda: self.get_property(), + fset=lambda new_value: self.set_property(self, new_value), + doc=self.device.get_property_docstring(self.name) + ) + + self._name = '_' + self.name + + self.validate_settings(settings) + + def set_property(self, obj, new_value): + ''' + Generator function for settings dictionary + Check that new_value is valid, otherwise throws an exception + + Parameters + ---------- + new_value : + new_value to be set on device + + Returns + ------- + None + ''' + + if self.set_valid(new_value): + self.device.write(self) + self.device[self._name] = new_value + else: + raise self.set_exception(new_value) + + def set_exception(self, new_value): + raise exception_dict[self.property_type] + + def check_range(self, new_value): + print('checking range') + return self.range[0] <= new_value <= self.range[1] + + def check_value(self, new_value): + return new_value in self.values + + def check_indexed_value(self, new_value): + return new_value in self.indexed_values + + def check_dict_value(self, new_value): + return new_value in list(self.dict_values.keys()) + + def get_property(self, obj): + ''' + Generator function for settings dictionary + Check that new_value is valid, otherwise throws an exception + + Parameters + ---------- + new_value : + new_value to be set on `instrument` + + Returns + ------- + Formatted value from query + ''' + value = self.device.query(self) + assert isinstance(value, str), ".query method for device {} did not return string".format(self.device) + value = value.strip("\n") + + if self.property_type == 'values': + value = self.return_type(value) + elif self.property_type == 'indexeD_values': + value = self.indexed_values[int(value)] + elif self.property_type == 'dict_values': + value = self.find_first_key(self.dict_values, value) + else: + value = self.return_type(value) + + setattr(self.device, self._name, value) + + return value + + def format_write_string(self, new_value, *args): + print(new_value, *args) + if self.property_type in ['range', 'values']: + return self.query_string.format(new_value) + elif self.property_type == 'indexed_value': + return self.query_string.format(self.indexed_values.index(new_value)) + elif self.property_type == 'dict_values': + return self.format_query_string.format(self.dict_values[new_value]) + + def validate_settings(self, settings): + + settings_keys = list(settings.keys()) + + # Check that the settings have a "name" + + assert 'name' in settings_keys, 'Invalid settings: property settings must have a name' + assert isinstance(settings['name'], str), 'Setting name must be a string' + + name = settings['name'] + + # Check that settings have a property type key(s) + i = 0 + for prop in property_types: + if prop in settings_keys: + i += 1 + + if 'read_only' not in settings_keys: + assert (i <= 1) and (i > 0), \ + f'{name} invalid settings, must have a single type indicator "values", "indexed_values", "range", or "dict_values"' + else: + other_required_key = ['return_type', 'indexed_values'] + valid = np.sum([other in settings_keys for other in other_required_key]) + assert valid, \ + f'{name} Invalid settings dictionary, if read_only, you must also have "return_type" or "indexed_values"' + + # Check that the type value is correct + if 'range' in settings_keys: + assert len(settings['range']) == 2, f'{name} "range" setting must be a list of lenght 2' + assert isinstance(settings['range'], list), f'{name} "range" property setting must be a list' + assert 'return_type' in settings_keys, f'{name} requires a "return_type" setting' + assert settings['range'][1] > settings['range'][0], f'{name} has bad "range" settings, range[0] < range[1]' + self.property_type = 'range' + if 'ramp_step' in settings_keys: + assert settings_keys['ramp_step'] > 0, f'{name} has a bad "ramp_step" setting ramp_step > 0' + assert 'ramp_dt' in settings_keys, f'{name} does not have a required "ramp_dt"' + assert settings_keys['ramp_dt'] >= 0.001, f'{name} has an invalid "ramp_dt", ramp_dt >= 0.001 (s)' + self.rampable = True + else: + self.rampable = False + self.set_valid = self.check_range + self.set_exception = lambda new_value: RangeException( + self.name, self.range, new_value) + elif 'values' in settings_keys: + assert isinstance(settings['values'], list), f'{name} "values" setting must be a list' + self.property_type = 'values' + self.rampable = False + self.set_valid = self.check_value + self.set_exception = lambda new_value: ValueException( + self.name, self.values, new_value) + elif 'indexed_values' in settings_keys: + assert isinstance(settings['indexed_values'], list), f'{name} "indexed_values" setting must be a list' + self.property_type = 'indexed_values' + self.rampable = False + self.set_valid = self.check_indexed_value + self.set_exception = lambda new_value: IndexedValueException( + self.name, self.indexed_values, new_value) + elif 'dict_values' in settings_keys: + assert isinstance(settings['dict_values'], dict), f'{name} "dict_values" setting must be a dict' + self.property_type = 'dict_values' + self.rampable = False + self.set_valid = self.check_dict_value + self.set_exception = lambda new_value: DictValueException( + self.name, self.dict_values, new_value) diff --git a/pyscan/drivers/testing/test_voltagev2.py b/pyscan/drivers/testing/test_voltagev2.py new file mode 100644 index 00000000..af1cf015 --- /dev/null +++ b/pyscan/drivers/testing/test_voltagev2.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +from ..driver_classes.test_driver import TestDriver +from ..property_settings.property_settings import PropertySettings + + +class TestVoltagev2(TestDriver): + ''' + Class that mimics the operation of a simple voltage source. + + This is used in the demo jupyter notebooks. + + Parameters + ---------- + instrument : mock + Optional parameter. + + Attributes + ---------- + (Properties) + voltage : float + Get/set a mock voltage, with default range [-10, 10] + power : int + Get/set a mock power setting, with available values [1, 10] + output_state : int or str + Get/set a mock output state, with dict values 'on', 1, 'off', or 0 + ''' + + # tells pytest this is not a test case. Was necessary only on lab computer for some reason. + __test__ = False + + def __init__(self, debug=False, instrument=None, *arg, **kwarg): + + super().__init__(instrument=None, *arg, **kwarg) + self.property_class = PropertySettings + self.initialize_properties() + + self.debug = debug + self._voltage = 0 + self._power = 1 + self._output_state = 'off' + self._version = "0.1.0" + self.black_list_for_testing = [] + + def initialize_properties(self): + + self.add_device_property({ + 'name': 'voltage', + 'write_string': 'VOLT {}', + 'query_string': 'VOLT?', + 'range': [0, 10], + 'return_type': float + }) + + self.add_device_property({ + 'name': 'power', + 'write_string': 'POW {}', + 'query_string': 'POW?', + 'values': [1, 10], + 'return_type': int + }) + + self.add_device_property({ + 'name': 'output_state', + 'write_string': 'OUTP {}', + 'query_string': 'OUTP?', + 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0}, + 'return_type': str + }) + + @property + def version(self): + return self._version From 399ec75e22c231c813d5368ac3095ac0e92bfe37 Mon Sep 17 00:00:00 2001 From: Mounce Date: Thu, 10 Oct 2024 18:26:31 -0600 Subject: [PATCH 02/29] feature: abstract driver and propertysettings --- .../driver_classes/simple_scpi_driver.py | 2 +- pyscan/drivers/driver_classes/test_driver.py | 2 +- ...tings.py => abstract_property_settings.py} | 19 +++---- .../scpi_property_settings.py | 53 +++++++++++++++++++ 4 files changed, 62 insertions(+), 14 deletions(-) rename pyscan/drivers/property_settings/{property_settings.py => abstract_property_settings.py} (91%) create mode 100644 pyscan/drivers/property_settings/scpi_property_settings.py diff --git a/pyscan/drivers/driver_classes/simple_scpi_driver.py b/pyscan/drivers/driver_classes/simple_scpi_driver.py index eed1c518..05118ce6 100644 --- a/pyscan/drivers/driver_classes/simple_scpi_driver.py +++ b/pyscan/drivers/driver_classes/simple_scpi_driver.py @@ -61,7 +61,7 @@ def write(self, settings, value): None ''' - string = settings.write_string.format(value) + string = settings.format_write_string(value) self.instrument.write(string) diff --git a/pyscan/drivers/driver_classes/test_driver.py b/pyscan/drivers/driver_classes/test_driver.py index 6a66f22e..e57e2229 100644 --- a/pyscan/drivers/driver_classes/test_driver.py +++ b/pyscan/drivers/driver_classes/test_driver.py @@ -14,7 +14,7 @@ def query(self, settings): return '0' if self._output_state == 'on': return '1' - # leave for the sake of your personal sanity, trust us + # leave for the sake of your personal sanity return str(self._output_state) def write(self, settings): diff --git a/pyscan/drivers/property_settings/property_settings.py b/pyscan/drivers/property_settings/abstract_property_settings.py similarity index 91% rename from pyscan/drivers/property_settings/property_settings.py rename to pyscan/drivers/property_settings/abstract_property_settings.py index dd6ede86..dc807bcb 100644 --- a/pyscan/drivers/property_settings/property_settings.py +++ b/pyscan/drivers/property_settings/abstract_property_settings.py @@ -13,7 +13,7 @@ property_types = list(exception_dict.keys()) -class PropertySettings(ItemAttribute): +class AbstractPropertySettings(ItemAttribute): def __init__(self, device, settings): @@ -31,6 +31,7 @@ def __init__(self, device, settings): self._name = '_' + self.name self.validate_settings(settings) + self.sub_class_settings_validation(settings) def set_property(self, obj, new_value): ''' @@ -51,9 +52,9 @@ def set_property(self, obj, new_value): self.device.write(self) self.device[self._name] = new_value else: - raise self.set_exception(new_value) + raise self.raise_set_exception(new_value) - def set_exception(self, new_value): + def raise_set_exception(self, new_value): raise exception_dict[self.property_type] def check_range(self, new_value): @@ -100,15 +101,6 @@ def get_property(self, obj): return value - def format_write_string(self, new_value, *args): - print(new_value, *args) - if self.property_type in ['range', 'values']: - return self.query_string.format(new_value) - elif self.property_type == 'indexed_value': - return self.query_string.format(self.indexed_values.index(new_value)) - elif self.property_type == 'dict_values': - return self.format_query_string.format(self.dict_values[new_value]) - def validate_settings(self, settings): settings_keys = list(settings.keys()) @@ -173,3 +165,6 @@ def validate_settings(self, settings): self.set_valid = self.check_dict_value self.set_exception = lambda new_value: DictValueException( self.name, self.dict_values, new_value) + + def sub_class_settings_validation(self, settings): + pass diff --git a/pyscan/drivers/property_settings/scpi_property_settings.py b/pyscan/drivers/property_settings/scpi_property_settings.py new file mode 100644 index 00000000..ce15a226 --- /dev/null +++ b/pyscan/drivers/property_settings/scpi_property_settings.py @@ -0,0 +1,53 @@ +from ...general.item_attribute import ItemAttribute +from .property_exceptions import ( + RangeException, IndexedValueException, ValueException, DictValueException) +import numpy as np + + +exception_dict = { + 'range': RangeException, + 'indexed_value': IndexedValueException, + 'values': ValueException, + 'dict_values': DictValueException} + +property_types = list(exception_dict.keys()) + + +class SCPIPropertySettings(ItemAttribute): + + def __init__(self, device, settings): + super().__init__(device, settings) + + def format_write_string(self, new_value, *args): + print(new_value, *args) + if self.property_type in ['range', 'values']: + return self.query_string.format(new_value) + elif self.property_type == 'indexed_value': + return self.query_string.format(self.indexed_values.index(new_value)) + elif self.property_type == 'dict_values': + return self.format_query_string.format(self.dict_values[new_value]) + + def sub_class_settings_validation(self, settings): + ''' + For ScPIPropertySettings, ensures that write_string, query_string, read_only, and write_only + are configured properly + ''' + + settings_keys = list(settings.keys()) + + if 'read_only' in settings_keys: + assert 'write_string' not in settings_keys, \ + f'{self.name} is set to "read_only", for "read_only" properties, "write_string" is an invalid key' + assert 'query_string' in settings_keys, \ + f'{self.name} is missing a "query_string" key' + elif 'write_only' in settings_keys: + assert 'query_string' not in settings_keys, \ + f'{self.name} is set to "write_only", for "write_only" properties, "query_string" is an invalid key' + assert 'write_string' in settings_keys, \ + f'{self.name} is missing a "write_string" key' + else: + assert 'query_string' in settings_keys, \ + f'{self.name} is missing a "query_string" key' + + assert 'write_string' in settings_keys, \ + f'{self.name} is missing a "write_string" key' From f340baef89b1f080456a459564bb568be5297ed3 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Wed, 16 Oct 2024 10:28:59 -0600 Subject: [PATCH 03/29] changed settings to settings_dict and settings_obj --- .../drivers/driver_classes/abstract_driver.py | 113 ----- .../driver_classes/simple_scpi_driver.py | 79 --- pyscan/drivers/instrument_driver.py | 460 ------------------ pyscan/drivers/instrument_driver/__init__.py | 0 .../instrument_driver/abstract_driver.py | 259 ++++++++++ .../instrument_driver/instrument_driver.py | 164 +++++++ pyscan/drivers/property_settings/__init__.py | 4 + .../abstract_property_settings.py | 168 +------ .../dict_values_property_settings.py | 37 ++ .../indexed_property_settings.py | 32 ++ .../property_settings/property_exceptions.py | 44 -- .../range_property_settings.py | 30 ++ .../scpi_property_settings.py | 53 -- .../values_property_settings.py | 31 ++ pyscan/drivers/testing/test_voltage.py | 34 +- .../test_voltage_driver.py} | 4 +- pyscan/drivers/testing/test_voltagev2.py | 72 --- 17 files changed, 578 insertions(+), 1006 deletions(-) delete mode 100644 pyscan/drivers/driver_classes/abstract_driver.py delete mode 100644 pyscan/drivers/driver_classes/simple_scpi_driver.py delete mode 100644 pyscan/drivers/instrument_driver.py create mode 100644 pyscan/drivers/instrument_driver/__init__.py create mode 100644 pyscan/drivers/instrument_driver/abstract_driver.py create mode 100644 pyscan/drivers/instrument_driver/instrument_driver.py create mode 100644 pyscan/drivers/property_settings/__init__.py create mode 100644 pyscan/drivers/property_settings/dict_values_property_settings.py create mode 100644 pyscan/drivers/property_settings/indexed_property_settings.py delete mode 100644 pyscan/drivers/property_settings/property_exceptions.py create mode 100644 pyscan/drivers/property_settings/range_property_settings.py delete mode 100644 pyscan/drivers/property_settings/scpi_property_settings.py create mode 100644 pyscan/drivers/property_settings/values_property_settings.py rename pyscan/drivers/{driver_classes/test_driver.py => testing/test_voltage_driver.py} (90%) delete mode 100644 pyscan/drivers/testing/test_voltagev2.py diff --git a/pyscan/drivers/driver_classes/abstract_driver.py b/pyscan/drivers/driver_classes/abstract_driver.py deleted file mode 100644 index 9b44fcae..00000000 --- a/pyscan/drivers/driver_classes/abstract_driver.py +++ /dev/null @@ -1,113 +0,0 @@ -import re -from ...general.item_attribute import ItemAttribute - - -class AbstractDriver(ItemAttribute): - ''' - None - ''' - def __init__(self, instrument=None, debug=False): - self.instrument = instrument - self.debug = debug - - @property - def name(self): - return [k for k, v in globals().items() if v is self] - - def initialize_properties(self): - pass - - def write(self): - pass - - def query(self): - pass - - def read(self): - pass - - def add_device_property(self, settings): - - name = settings['name'] - # Make self._name_settings - settings_name = '_' + name + '_settings' - property_settings = self.property_class(self, settings) - setattr(self, settings_name, property_settings) - - # Make self.name property - property_definition = property( - fget=getattr(self, settings_name).get_property, - fset=lambda obj, new_value: getattr(self, settings_name).set_property(obj, new_value), - doc=self.get_property_docstring(name)) - setattr(self.__class__, name, property_definition) - - @property - def version(self): - return self._version - - def update_properties(self): - properties = self.get_pyscan_properties() - - for prop in properties: - settings = self['_{}_settings'.format(prop)] - if 'write_only' not in settings: - self[prop] - - def get_pyscan_properties(self): - ''' - Finds the pyscan style properties of this driver, i.e. those that end with "_settings" - - Returns - ------- - list : - list of property names for the current driver - ''' - - r = re.compile(".*_settings") - pyscan_properties = list(filter(r.match, self.keys())) - pyscan_properties = [prop[1:] for prop in pyscan_properties] - pyscan_properties = [prop.replace('_settings', '') for prop in pyscan_properties] - return pyscan_properties - - def get_property_docstring(self, prop_name): - ''' - Gets the doc string for a property from an instance of this class - - Parameters - ---------- - prop_name : str - The name of the property to get the doc string of - - Returns - ------- - str : - The two doc string lines for the property - ''' - - doc = self.__doc__.split('\n') - - r = re.compile(" {} :".format(prop_name)) - match = list(filter(r.match, doc)) - - assert len(match) > 0, "No matches for {} documentation".format(prop_name) - assert len(match) == 1, "Too many matches for {} documentation".format(prop_name) - match = match[0] - - for i, string in enumerate(doc): - if string == match: - break - - doc_string = doc[i][4::] - - for j in range(len(doc_string)): - try: - doc[i + 1 + j] - except: - break - if (doc[i + 1 + j][0:1] == '\n') or (len(doc[i + 1 + j][0:7].strip()) != 0): - # print(repr(doc[i + 1 + j])) - break - else: - doc_string = doc_string + '\n' + doc[i + 1 + j][4::] - - return doc_string diff --git a/pyscan/drivers/driver_classes/simple_scpi_driver.py b/pyscan/drivers/driver_classes/simple_scpi_driver.py deleted file mode 100644 index 05118ce6..00000000 --- a/pyscan/drivers/driver_classes/simple_scpi_driver.py +++ /dev/null @@ -1,79 +0,0 @@ -from .abstract_driver import AbstractDriver -from ..new_instrument import new_instrument -from ..property_settings.property_settings import PropertySettings - - -class SimpleSCPIDriver(AbstractDriver): - ''' - Attributes - ---------- - (Properties) - test : int - Just a test property - ''' - - def __init__(self, instrument, debug=False): - if isinstance(instrument, str): - self.instrument = new_instrument(instrument) - else: - self.instrument = instrument - - try: - inst_str = str(type(self.instrument)) - self._driver_class = inst_str.split("'")[1].split(".")[-1] - except Exception: - pass - - self.property_class = PropertySettings - - self.debug = debug - - def query(self, settings): - ''' - Wrapper to pass string to the instrument object - - Parameters - ---------- - string: str - The message to send to the device - - Returns - ------- - str - Answer from the device. - ''' - - string = settings.query_string - - return self.instrument.query(string) - - def write(self, settings, value): - ''' - Wrapper to write string to the instrument object - - Parameters - ---------- - string: str - The message to be sent - - Returns - ------- - None - ''' - - string = settings.format_write_string(value) - - self.instrument.write(string) - - def read(self): - ''' - Wrapper to read string from the instrument object - - Returns - ------- - str - Message read from the instrument - - ''' - - return self.instrument.read() diff --git a/pyscan/drivers/instrument_driver.py b/pyscan/drivers/instrument_driver.py deleted file mode 100644 index 5add29ce..00000000 --- a/pyscan/drivers/instrument_driver.py +++ /dev/null @@ -1,460 +0,0 @@ -# -*- coding: utf-8 -*- -from pyscan.general.item_attribute import ItemAttribute -from .new_instrument import new_instrument -from collections import OrderedDict -import numpy as np -import re - - -class InstrumentDriver(ItemAttribute): - ''' - Base driver class which creates class attributes based on a - settings dictionary - - Parameters - ---------- - instrument : string or pyvisa :class:`Resource` - visa string or an instantiated instrument - - Methods - ------- - query(string) - write(string) - read() - find_first_key(dictionary, machine_value) - add_device_property(settings) - get_instrument_property() - set_values_property() - set_range_property() - set_index_values_property() - set_dict_values_property() - get_pyscan_properties() - get_property_docstring(prop_name) - ''' - - def __init__(self, instrument, debug=False): - if isinstance(instrument, str): - self.instrument = new_instrument(instrument) - else: - self.instrument = instrument - - try: - inst_str = str(type(self.instrument)) - self._driver_class = inst_str.split("'")[1].split(".")[-1] - except Exception: - pass - - self.debug = debug - - self._instrument_driver_version = '0.2.0' - - def query(self, string): - ''' - Wrapper to pass string to the instrument object - - Parameters - ---------- - string: str - The message to send to the device - - Returns - ------- - str - Answer from the device. - ''' - - return self.instrument.query(string) - - def write(self, string): - ''' - Wrapper to write string to the instrument object - - Parameters - ---------- - string: str - The message to be sent - - Returns - ------- - None - ''' - self.instrument.write(string) - - def read(self): - ''' - Wrapper to read string from the instrument object - - Returns - ------- - str - Message read from the instrument - - ''' - - return self.instrument.read() - - def find_first_key(self, dictionary, machine_value): - for key, val in dictionary.items(): - if str(val) == str(machine_value): - return key - - def add_device_property(self, settings): - ''' - Adds a property to the class based on a settings dictionary - - Parameters - ---------- - settings : dict - dict containing settings for property. Must have: - - the key "values", "range", or "indexed_values", or "dict_values" - - "write_string" and/or "query_string" to communication with instrument - - "return_type" is a function that converts the return string to a python type - - Returns - ------- - None - ''' - - self['_{}_settings'.format(settings['name'])] = settings - - command_strings = ['write_string', 'query_string'] - if not any(string in settings for string in command_strings): - assert False, "'write_string' and/or 'query_string' must be in settings" - - # read_only properties do not need to have a required key in setting, - # but for the docstring to work a read_only property is created. - if 'write_string' not in settings: - if 'read_only' not in settings: - settings['read_only'] = settings['return_type'].__name__ - - if 'query_string' not in settings: - if 'write_only' not in settings: - settings['write_only'] = settings['return_type'].__name__ - - if 'values' in settings: - set_function = self.set_values_property - elif ('range' in settings) and ('ranges' not in settings): - set_function = self.set_range_property - elif 'ranges' in settings: - assert False, "ranges no longer accepted, must use method to set multiple properties at the same time." - elif 'indexed_values' in settings: - set_function = self.set_indexed_values_property - elif 'dict_values' in settings: - set_function = self.set_dict_values_property - elif 'read_only' in settings: - # read_only does not require a set_function - pass - else: - assert False, "Key 'values', 'range', indexed_values', 'read_only', or 'dict_values' must be in settings." - - try: - doc_string = self.get_property_docstring(settings['name']) - except: - doc_string = ("No doc string found for {}.\n".format(settings['name']) - + "Please update the drivers doc string to include this attribute.") - - # read-only - if 'write_string' not in settings: - property_definition = property( - fget=lambda obj: self.get_instrument_property(obj, settings), - fset=None, - doc=doc_string) - # write-only - elif 'query_string' not in settings: - property_definition = property( - fget=None, - fset=lambda obj, new_value: set_function(obj, new_value, settings), - doc=doc_string) - else: - property_definition = property( - fget=lambda obj: self.get_instrument_property(obj, settings), - fset=lambda obj, new_value: set_function(obj, new_value, settings), - doc=doc_string, - ) - - setattr(self.__class__, settings['name'], property_definition) - - def get_instrument_property(self, obj, settings, debug=False): - ''' - Generator function for a query function of the instrument - that sends the query string and formats the return based on - settings['return_type'] - - Parameters - obj : - parent object - settings : dict - settings dictionary - debug : bool - returns query string instead of querying instrument - - Returns - ------- - value formatted to setting's ['return_type'] - ''' - - if not obj.debug: - value = obj.query(settings['query_string']) - assert isinstance(value, str), ".query method for instrument {} did not return string".format(obj) - value = value.strip("\n") - - if ('values' in settings) and ('indexed_' not in settings) and ('dict_' not in settings): - value = settings['return_type'](value) - elif 'indexed_values' in settings: - values = settings['indexed_values'] - value = values[int(value)] - elif 'dict_values' in settings: - dictionary = settings['dict_values'] - value = self.find_first_key(dictionary, value) - else: - value = settings['return_type'](value) - - else: - value = settings['query_string'] - - setattr(obj, '_' + settings['name'], value) - - return value - - def set_values_property(self, obj, new_value, settings): - ''' - Generator function for settings dictionary with 'values' item - Check that new_value is in settings['values'], if not, rejects command - - Parameters - ---------- - obj : - parent class object - new_value : - new_value to be set on instrument - settings : dict - dictionary with ['values'] item - - Returns - ------- - None - ''' - - values = settings['values'] - - if values.count(new_value) > 0: - if not self.debug: - obj.write(settings['write_string'].format(new_value)) - setattr(obj, '_' + settings['name'], new_value) - else: - setattr(obj, '_' + settings['name'], - settings['write_string'].format(new_value)) - else: - possible = [] - for val in values: - possible.append(val) - assert False, "Value Error:\n{} must be one of: {}. You submitted: {}".format(settings['name'], - possible, new_value) - - def set_range_property(self, obj, new_value, settings): - ''' - Generator function for settings dictionary with 'range' item - Check that new_value is in settings['range'], if not, rejects command - - Parameters - ---------- - obj : - parent class object - new_value : - new_value to be set on instrument - settings : dict - dictionary with ['range'] item - - Returns - ------- - None - ''' - - rng = settings['range'] - - assert len(rng) == 2, "range setting requires 2 values" - for val in rng: - assert (isinstance(val, int)) or (isinstance(val, float)), "range settings must be integers or floats" - err_string = "range values must be integers or floats" - assert ( - isinstance( - new_value, int)) or ( - isinstance( - new_value, float)) or ( - isinstance( - new_value, np.float64)), err_string - - if rng[0] <= new_value <= rng[1]: - if not self.debug: - obj.write(settings['write_string'].format(new_value)) - setattr(obj, '_' + settings['name'], new_value) - else: - setattr(obj, '_' + settings['name'], - settings['write_string'].format(new_value)) - else: - assert False, "Range error: {} must be between {} and {}, cannot be {}".format( - settings['name'], rng[0], rng[1], new_value) - - def set_indexed_values_property(self, obj, new_value, settings): - ''' - Generator function for settings dictionary with 'indexed_values' item - Check that new_value is in settings['indexed_values'], if not, - rejects command - - Parameters - ---------- - obj : - parent class object - new_value : - new_value to be set on instrument - settings : dict - dictionary with ['indexed_values'] item - - Returns - ------- - None - ''' - - values = settings['indexed_values'] - - if (isinstance(new_value, int)) or (isinstance(new_value, float)) or (isinstance(new_value, str)): - pass - else: - assert False - - if new_value in values: - index = values.index(new_value) - if not self.debug: - - obj.write(settings['write_string'].format(index)) - setattr(obj, '_' + settings['name'], new_value) - else: - setattr(obj, '_' + settings['name'], - settings['write_string'].format(index)) - else: - possible = [] - for string in values: - possible.append(string) - assert False, "Value error:\n{} must be one of: {}".format(settings['name'], possible) - - def set_dict_values_property(self, obj, input_key, settings): - ''' - Generator function for settings dictionary with 'dict_values' item. - Check that new_value is a value in settings['dict_values']. If so, - sends the associated key to the settings['write_string']; if not, - rejects command. - - Parameters - ---------- - obj : - parent class object - input_key : - input_key whose associated dictionary value will be set on the - instrument - settings : dict - dictionary with ['dict_values'] item - - Returns - ------- - None - ''' - - dictionary = settings['dict_values'] - - # convert all dictionaries to ordered dictionaries to preference first key (user value) for - # same (machine) values when queried - ordered_list = [] - for key, value in dictionary.items(): - ordered_list.append((key, value)) - - settings['dict_values'] = OrderedDict(ordered_list) - dictionary = settings['dict_values'] - # dictionary = OrderedDict(ordered_list) - - # make sure that the input key is in the property's dictionary - if input_key in dictionary.keys(): - machine_value = dictionary[input_key] - if not self.debug: - # send the machine the machine value corresponding to desired state - obj.write(settings['write_string'].format(machine_value)) - # find the first corresponding key to the machine value - first_key = self.find_first_key(dictionary, machine_value) - # set the _ attribute to the priority key value found above - setattr(obj, '_' + settings['name'], first_key) - else: - setattr(obj, '_' + settings['name'], - settings['write_string'].format(machine_value)) - # if not throw an error - else: - possible = [] - for string in dictionary.keys(): - possible.append('{}'.format(string)) - err_string = "Value Error:\n{} must be one of: {}".format(settings['name'], possible) - assert False, err_string - - def update_properties(self): - properties = self.get_pyscan_properties() - - for prop in properties: - settings = self['_{}_settings'.format(prop)] - if 'write_only' not in settings: - self[prop] - - def get_pyscan_properties(self): - ''' - Finds the pyscan style properties of this driver, i.e. those that end with "_settings" - - Returns - ------- - list : - list of property names for the current driver - ''' - - r = re.compile(".*_settings") - pyscan_properties = list(filter(r.match, self.keys())) - pyscan_properties = [prop[1:] for prop in pyscan_properties] - pyscan_properties = [prop.replace('_settings', '') for prop in pyscan_properties] - return pyscan_properties - - def get_property_docstring(self, prop_name): - ''' - Gets the doc string for a property from an instance of this class - - Parameters - ---------- - prop_name : str - The name of the property to get the doc string of - - Returns - ------- - str : - The two doc string lines for the property - ''' - - doc = self.__doc__.split('\n') - - r = re.compile(".*{} :".format(prop_name)) - match = list(filter(r.match, doc)) - - assert len(match) > 0, "No matches for {} documentation".format(prop_name) - assert len(match) == 1, "Too many matches for {} documentation".format(prop_name) - match = match[0] - - for i, string in enumerate(doc): - if string == match: - break - - doc_string = doc[i][4::] - - for j in range(len(doc_string)): - try: - doc[i + 1 + j] - except: - break - if (doc[i + 1 + j][0:1] == '\n') or (len(doc[i + 1 + j][0:7].strip()) != 0): - # print(repr(doc[i + 1 + j])) - break - else: - doc_string = doc_string + '\n' + doc[i + 1 + j][4::] - - return doc_string diff --git a/pyscan/drivers/instrument_driver/__init__.py b/pyscan/drivers/instrument_driver/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py new file mode 100644 index 00000000..4f151f0c --- /dev/null +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +from ....general.item_attribute import ItemAttribute +from ..property_settings import ( + RangePropertySettings, + ValuesPropertySettings, + IndexedPropertySettings, + DictPropertySettings) +import numpy as np +import re +from abc import ABC + + +class AbstractDriver(ItemAttribute, ABC): + ''' + Abstract driver class which creates class attributes based on a + settings dictionary + + Parameters + ---------- + instrument : string or pyvisa :class:`Resource` + visa string or an instantiated instrument + + Methods + ------- + query(string) + write(string) + read() + find_first_key(dictionary, machine_value) + add_device_property(settings) + get_instrument_property() + set_values_property() + set_range_property() + set_index_values_property() + set_dict_values_property() + get_pyscan_properties() + get_property_docstring(prop_name) + ''' + + def __init__(self, instrument, debug=False): + pass + + def query_property(self, settings): + ''' + Abstract method to query property using input settings + ''' + pass + + def write_property(self, settings, new_value): + ''' + Abstract method to write a new value of a property + ''' + pass + + def add_device_property(self, settings_dict): + ''' + Adds a property to the device based on a settings dictionary + + Parameters + ---------- + settings : dict + dict containing settings for property. Must have: + - the key "values", "range", or "indexed_values", or "dict_values" + - "write_string" and/or "query_string" to communication with instrument + - "return_type" is a function that converts the return string to a python type + + Returns + ------- + None + ''' + + name = settings_dict['name'] + settings_name = '_' + name + '_settings' + + property_class = self.validate_settings(settings_dict) + self.validate_subclass_settings(settings_dict) + + # Make property settings attribute + settings_obj = property_class(settings_dict) + setattr(self, settings_name, settings_obj) + + # Make self.name property + property_definition = property( + fget=getattr(self, settings_name).get_instrument_property, + fset=lambda obj, new_value: getattr(self, settings_name).set_instrument_property(obj, new_value), + doc=self.get_property_docstring(name)) + setattr(self.__class__, name, property_definition) + setattr(self, settings_obj._name, None) + + def get_instrument_property(self, obj, settings_obj, debug=False): + ''' + Generator function for a query function of the instrument + that sends the query string and formats the return based on + settings['return_type'] + + Parameters + obj : + parent object + settings : dict + settings dictionary + debug : bool + returns query string instead of querying instrument + + Returns + ------- + value formatted to setting's ['return_type'] + ''' + + value = self.query_property(settings_obj).strip("\n") + value = self.settings_obj.format_query_return(value) + + setattr(self, settings_obj._name, value) + + return value + + def set_instrument_property(self, obj, settings_obj, new_value): + ''' + Generator function for settings dictionary with 'values' item + Check that new_value is in settings['values'], if not, rejects command + + Parameters + ---------- + obj : + parent class object + settings : PropetySettings subclass + RangeSettings, ValuesSettings, IndexedValuesSettings, or DictValuesSettings + new_value : + new_value to be formatted and sent to instrument + + Returns + ------- + None + ''' + + settings_obj.validate_set_value(new_value) + self.write_property(settings_obj, new_value) + setattr(self, settings_obj._name, new_value) + + def update_properties(self): + properties = self.get_pyscan_properties() + + for prop in properties: + settings = self['_{}_settings'.format(prop)] + if 'write_only' not in settings: + self[prop] + + def get_pyscan_properties(self): + ''' + Finds the pyscan style properties of this driver, i.e. those that end with "_settings" + + Returns + ------- + list : + list of property names for the current driver + ''' + + r = re.compile(".*_settings") + pyscan_properties = list(filter(r.match, self.keys())) + pyscan_properties = [prop[1:] for prop in pyscan_properties] + pyscan_properties = [prop.replace('_settings', '') for prop in pyscan_properties] + return pyscan_properties + + def get_property_docstring(self, prop_name): + ''' + Gets the doc string for a property from an instance of this class + + Parameters + ---------- + prop_name : str + The name of the property to get the doc string of + + Returns + ------- + str : + The two doc string lines for the property + ''' + + doc = self.__doc__.split('\n') + + r = re.compile(".*{} :".format(prop_name)) + match = list(filter(r.match, doc)) + + assert len(match) > 0, "No matches for {} documentation".format(prop_name) + assert len(match) == 1, "Too many matches for {} documentation".format(prop_name) + match = match[0] + + for i, string in enumerate(doc): + if string == match: + break + + doc_string = doc[i][4::] + + for j in range(len(doc_string)): + try: + doc[i + 1 + j] + except: + break + if (doc[i + 1 + j][0:1] == '\n') or (len(doc[i + 1 + j][0:7].strip()) != 0): + # print(repr(doc[i + 1 + j])) + break + else: + doc_string = doc_string + '\n' + doc[i + 1 + j][4::] + + return doc_string + + def validate_property_settings(self, settings_dict): + + settings_keys = list(settings_dict.keys()) + + # Check that the settings have a "name" + + assert 'name' in settings_keys, 'Invalid settings: property settings must have a name' + assert isinstance(settings_dict['name'], str), 'Setting name must be a string' + + name = settings_dict['name'] + + # Check that settings_dict has a property type key(s) + i = 0 + + property_types = ['range', 'values', 'indexed_values', 'dict_values'] + + for prop in property_types: + if prop in settings_keys: + i += 1 + + if 'read_only' not in settings_keys: + assert (i <= 1) and (i > 0), \ + f'{name} invalid settings, must have a single type indicator "values", "indexed_values", "range", or "dict_values"' + else: + other_required_key = ['return_type', 'indexed_values'] + valid = np.sum([other in settings_keys for other in other_required_key]) + assert valid, \ + f'{name} Invalid settings dictionary, if read_only, you must also have "return_type" or "indexed_values"' + + # Check that the type value is correct + if 'range' in settings_keys: + assert len(settings_dict['range']) == 2, f'{name} "range" setting must be a list of lenght 2' + assert isinstance(settings_dict['range'], list), f'{name} "range" property setting must be a list' + assert 'return_type' in settings_keys, f'{name} requires a "return_type" setting' + assert settings_dict['range'][1] > settings_dict['range'][0], f'{name} has bad "range" settings, range[0] < range[1]' + property_class = RangePropertySettings + elif 'values' in settings_keys: + assert isinstance(settings_dict['values'], list), f'{name} "values" setting must be a list' + property_class = ValuesPropertySettings + elif 'indexed_values' in settings_keys: + assert isinstance(settings_dict['indexed_values'], list), f'{name} "indexed_values" setting must be a list' + property_class = IndexedPropertySettings + elif 'dict_values' in settings_keys: + assert isinstance(settings_dict['dict_values'], dict), f'{name} "dict_values" setting must be a dict' + property_class = DictPropertySettings + + return property_class + + def sub_class_settings_validation(self, settings_dict): + ''' + Abstract method to be overloaded which checks the validity of input settings + beyond range, values, indexed_values, dict_values, read-only, write-only, etc. + ''' + + pass diff --git a/pyscan/drivers/instrument_driver/instrument_driver.py b/pyscan/drivers/instrument_driver/instrument_driver.py new file mode 100644 index 00000000..514fc901 --- /dev/null +++ b/pyscan/drivers/instrument_driver/instrument_driver.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +from .abstract_driver import AbstractDriver +from .new_instrument import new_instrument + + +class InstrumentDriver(AbstractDriver): + ''' + Driver for SCPI type communications with instruments + + Parameters + ---------- + instrument : string or pyvisa :class:`Resource` + visa string or an instantiated instrument + + Methods + ------- + query(string) + query_property(settings_obj) + write(string) + write_property(settings_obj) + read() + find_first_key(dictionary, machine_value) + + Inherited Methods + add_device_property(settings_dict) + get_instrument_property() + set_values_property() + set_range_property() + set_index_values_property() + set_dict_values_property() + get_pyscan_properties() + get_property_docstring(prop_name) + ''' + + def __init__(self, instrument): + if isinstance(instrument, str): + self.instrument = new_instrument(instrument) + else: + self.instrument = instrument + + try: + inst_str = str(type(self.instrument)) + self._driver_class = inst_str.split("'")[1].split(".")[-1] + except Exception: + pass + + self._instrument_driver_version = '0.2.0' + + def query(self, string): + ''' + Wrapper to pass string to the instrument object + + Parameters + ---------- + string: str + The message to send to the device + + Returns + ------- + str + Answer from the device. + ''' + + return self.instrument.query(string) + + def query_property(self, settings_obj): + ''' + Wrapper to pass string to the instrument object + + Parameters + ---------- + settings_obj: PropertySettings subclass + RangeSettings, ValuesSettings, IndexedValuesSettings, or DictValuesSettings instance + + Returns + ------- + str + Answer from the device. + ''' + + value = self.instrument.query(settings_obj.query_string) + + return self.format_query_return(self, value) + + def write(self, string): + ''' + Wrapper to write string to the instrument object + + Parameters + ---------- + string: str + The message to be sent + + Returns + ------- + None + ''' + + self.instrument.write(string) + + def write_property(self, settings_obj, new_value): + ''' + Format 'new_value' from settings and send to instrument + + Parameters + ---------- + settings_obj : PropertySettings subclass + RangeSettings, ValuesSettings, IndexedValuesSettings, or DictValuesSettings instance + new_value: str, int, or float + New value to be set on the instrument + + Returns + ------- + None + ''' + + value = settings_obj.format_write_value(new_value) + + self.instrument.write(settings_obj.write_string.format(value)) + + setattr(self, settings_obj._name, new_value) + + def read(self): + ''' + Wrapper to read string from the instrument object + + Returns + ------- + str + Message read from the instrument + + ''' + + return self.instrument.read() + + def validate_subclass_settings(self, settings_dict): + ''' + For ScPIPropertySettings, ensures that write_string, query_string, read_only, and write_only + are configured properly + + Parameters + ---------- + settings_dict : dict + Dictionary of settings that generate a pyscan device property object + + ''' + + settings_keys = list(settings_dict.keys()) + + if 'read_only' in settings_keys: + assert 'write_string' not in settings_keys, \ + f'{self.name} is set to "read_only", for "read_only" properties, "write_string" is an invalid key' + assert 'query_string' in settings_keys, \ + f'{self.name} is missing a "query_string" key' + elif 'write_only' in settings_keys: + assert 'query_string' not in settings_keys, \ + f'{self.name} is set to "write_only", for "write_only" properties, "query_string" is an invalid key' + assert 'write_string' in settings_keys, \ + f'{self.name} is missing a "write_string" key' + else: + assert 'query_string' in settings_keys, \ + f'{self.name} is missing a "query_string" key' + assert 'write_string' in settings_keys, \ + f'{self.name} is missing a "write_string" key' diff --git a/pyscan/drivers/property_settings/__init__.py b/pyscan/drivers/property_settings/__init__.py new file mode 100644 index 00000000..8e8ba332 --- /dev/null +++ b/pyscan/drivers/property_settings/__init__.py @@ -0,0 +1,4 @@ +from .dict_values_property_settings import DictPropertySettings +from .indexed_values_property_settings import IndexedPropertySettings +from .range_property_settings import RangePropertySettings +from .values_property_settings import ValuesPropertySettings diff --git a/pyscan/drivers/property_settings/abstract_property_settings.py b/pyscan/drivers/property_settings/abstract_property_settings.py index dc807bcb..b7a30acf 100644 --- a/pyscan/drivers/property_settings/abstract_property_settings.py +++ b/pyscan/drivers/property_settings/abstract_property_settings.py @@ -1,170 +1,32 @@ from ...general.item_attribute import ItemAttribute -from .property_exceptions import ( - RangeException, IndexedValueException, ValueException, DictValueException) -import numpy as np +from abc import ABC -exception_dict = { - 'range': RangeException, - 'indexed_value': IndexedValueException, - 'values': ValueException, - 'dict_values': DictValueException} +class AbstractPropertySettings(ItemAttribute, ABC): -property_types = list(exception_dict.keys()) - - -class AbstractPropertySettings(ItemAttribute): - - def __init__(self, device, settings): + def __init__(self, device, settings_dict): self.device = device - for key, value in settings.items(): - self[key] = value - - self.device[self.name] = property( - fget=lambda: self.get_property(), - fset=lambda new_value: self.set_property(self, new_value), - doc=self.device.get_property_docstring(self.name) - ) - self._name = '_' + self.name - self.validate_settings(settings) - self.sub_class_settings_validation(settings) + for key, value in settings_dict: + self.key = value - def set_property(self, obj, new_value): + def validate_set_value(self, new_value): ''' - Generator function for settings dictionary - Check that new_value is valid, otherwise throws an exception - - Parameters - ---------- - new_value : - new_value to be set on device - - Returns - ------- - None + Abstract method that validates the input value for the instrument ''' + pass - if self.set_valid(new_value): - self.device.write(self) - self.device[self._name] = new_value - else: - raise self.raise_set_exception(new_value) - - def raise_set_exception(self, new_value): - raise exception_dict[self.property_type] - - def check_range(self, new_value): - print('checking range') - return self.range[0] <= new_value <= self.range[1] - - def check_value(self, new_value): - return new_value in self.values - - def check_indexed_value(self, new_value): - return new_value in self.indexed_values - - def check_dict_value(self, new_value): - return new_value in list(self.dict_values.keys()) - - def get_property(self, obj): + def format_write_value(self, new_value): ''' - Generator function for settings dictionary - Check that new_value is valid, otherwise throws an exception - - Parameters - ---------- - new_value : - new_value to be set on `instrument` - - Returns - ------- - Formatted value from query + Abstract method that formats the input value for the instrument ''' - value = self.device.query(self) - assert isinstance(value, str), ".query method for device {} did not return string".format(self.device) - value = value.strip("\n") - - if self.property_type == 'values': - value = self.return_type(value) - elif self.property_type == 'indexeD_values': - value = self.indexed_values[int(value)] - elif self.property_type == 'dict_values': - value = self.find_first_key(self.dict_values, value) - else: - value = self.return_type(value) - - setattr(self.device, self._name, value) - - return value - - def validate_settings(self, settings): - - settings_keys = list(settings.keys()) - - # Check that the settings have a "name" - - assert 'name' in settings_keys, 'Invalid settings: property settings must have a name' - assert isinstance(settings['name'], str), 'Setting name must be a string' - - name = settings['name'] - - # Check that settings have a property type key(s) - i = 0 - for prop in property_types: - if prop in settings_keys: - i += 1 - - if 'read_only' not in settings_keys: - assert (i <= 1) and (i > 0), \ - f'{name} invalid settings, must have a single type indicator "values", "indexed_values", "range", or "dict_values"' - else: - other_required_key = ['return_type', 'indexed_values'] - valid = np.sum([other in settings_keys for other in other_required_key]) - assert valid, \ - f'{name} Invalid settings dictionary, if read_only, you must also have "return_type" or "indexed_values"' - - # Check that the type value is correct - if 'range' in settings_keys: - assert len(settings['range']) == 2, f'{name} "range" setting must be a list of lenght 2' - assert isinstance(settings['range'], list), f'{name} "range" property setting must be a list' - assert 'return_type' in settings_keys, f'{name} requires a "return_type" setting' - assert settings['range'][1] > settings['range'][0], f'{name} has bad "range" settings, range[0] < range[1]' - self.property_type = 'range' - if 'ramp_step' in settings_keys: - assert settings_keys['ramp_step'] > 0, f'{name} has a bad "ramp_step" setting ramp_step > 0' - assert 'ramp_dt' in settings_keys, f'{name} does not have a required "ramp_dt"' - assert settings_keys['ramp_dt'] >= 0.001, f'{name} has an invalid "ramp_dt", ramp_dt >= 0.001 (s)' - self.rampable = True - else: - self.rampable = False - self.set_valid = self.check_range - self.set_exception = lambda new_value: RangeException( - self.name, self.range, new_value) - elif 'values' in settings_keys: - assert isinstance(settings['values'], list), f'{name} "values" setting must be a list' - self.property_type = 'values' - self.rampable = False - self.set_valid = self.check_value - self.set_exception = lambda new_value: ValueException( - self.name, self.values, new_value) - elif 'indexed_values' in settings_keys: - assert isinstance(settings['indexed_values'], list), f'{name} "indexed_values" setting must be a list' - self.property_type = 'indexed_values' - self.rampable = False - self.set_valid = self.check_indexed_value - self.set_exception = lambda new_value: IndexedValueException( - self.name, self.indexed_values, new_value) - elif 'dict_values' in settings_keys: - assert isinstance(settings['dict_values'], dict), f'{name} "dict_values" setting must be a dict' - self.property_type = 'dict_values' - self.rampable = False - self.set_valid = self.check_dict_value - self.set_exception = lambda new_value: DictValueException( - self.name, self.dict_values, new_value) + pass - def sub_class_settings_validation(self, settings): + def format_query_return(self, ret): + ''' + Abstract method that formats the return value from the instrument + ''' pass diff --git a/pyscan/drivers/property_settings/dict_values_property_settings.py b/pyscan/drivers/property_settings/dict_values_property_settings.py new file mode 100644 index 00000000..8371dce2 --- /dev/null +++ b/pyscan/drivers/property_settings/dict_values_property_settings.py @@ -0,0 +1,37 @@ +from itemattribute import ItemAttribute + + +class DictValueException(Exception): + + def __init__(self, prop, dict_values, value): + + msg = f'{prop} = {value} invalid input\n' + msg += f'Valid inputs for values property {prop} are:' + for key, value in dict_values.items(): + msg += f'{key}: {value},\n' + + super().__init__(msg) + + +class DictPropertySettings(ItemAttribute): + + def __init__(self, device, settings_dict): + super().__init__(device, settings_dict) + + def validate_set_value(self, new_value): + if new_value in self.values.keys(): + return True + else: + raise DictValueException(self.name, self.dict_values, new_value) + + def format_write_value(self, new_value): + + return self.dict_values[new_value] + + def format_query_return(self, ret): + + for key, val in self.dict_values.items(): + if str(val) == str(ret): + return key + + return self.find_first_key(ret) diff --git a/pyscan/drivers/property_settings/indexed_property_settings.py b/pyscan/drivers/property_settings/indexed_property_settings.py new file mode 100644 index 00000000..438f8060 --- /dev/null +++ b/pyscan/drivers/property_settings/indexed_property_settings.py @@ -0,0 +1,32 @@ +from itemattribute import ItemAttribute + + +class IndexedValueException(Exception): + + def __init__(self, prop, indexed_values, value): + + msg = f'{prop} = {value} invalid input\n' + msg += f'Valid inputs for indexed value property {prop} are:' + msg += ',\n'.join(indexed_values) + + super().__init__(msg) + + +class IndexedPropertySettings(ItemAttribute): + + def __init__(self, device, settings_dict): + super().__init__(device, settings_dict) + + def validate_set_value(self, new_value): + if new_value in self.index_values: + return True + else: + raise IndexedValueException(self.name, self.range, new_value) + + def format_write_value(self, new_value): + + return self.indexed_values.index(new_value) + + def format_query_return(self, ret): + + return self.indexed_values[int(ret)] diff --git a/pyscan/drivers/property_settings/property_exceptions.py b/pyscan/drivers/property_settings/property_exceptions.py deleted file mode 100644 index c25d2667..00000000 --- a/pyscan/drivers/property_settings/property_exceptions.py +++ /dev/null @@ -1,44 +0,0 @@ - - -class RangeException(Exception): - - def __init__(self, prop, range, value): - - msg = f'{prop} = {value} out of range\n' - msg += f'Valid range for {prop}: {range[0]} <= new_value <= {range[1]}' - - super().__init__(msg) - - -class IndexedValueException(Exception): - - def __init__(self, prop, indexed_values, value): - - msg = f'{prop} = {value} invalid input\n' - msg += f'Valid inputs for indexed value property {prop} are:' - msg += ',\n'.join(indexed_values) - - super().__init__(msg) - - -class ValueException(Exception): - - def __init__(self, prop, values, value): - - msg = f'{prop} = {value} invalid input\n' - msg += f'Valid inputs for values property {prop} are:' - msg += ',\n'.join(values) - - super().__init__(msg) - - -class DictValueException(Exception): - - def __init__(self, prop, dict_values, value): - - msg = f'{prop} = {value} invalid input\n' - msg += f'Valid inputs for values property {prop} are:' - for key, value in dict_values.items(): - msg += f'{key}: {value},\n' - - super().__init__(msg) diff --git a/pyscan/drivers/property_settings/range_property_settings.py b/pyscan/drivers/property_settings/range_property_settings.py new file mode 100644 index 00000000..bb286ed1 --- /dev/null +++ b/pyscan/drivers/property_settings/range_property_settings.py @@ -0,0 +1,30 @@ +from itemattribute import ItemAttribute + + +class RangeException(Exception): + + def __init__(self, prop, range, value): + + msg = f'{prop} = {value} out of range\n' + msg += f'Valid range for {prop}: {range[0]} <= new_value <= {range[1]}' + + super().__init__(msg) + + +class RangePropertySettings(ItemAttribute): + + def __init__(self, device, settings_dict): + super().__init__(device, settings_dict) + + def validate_set_value(self, new_value): + if self.range[0] <= new_value <= self.range[1]: + return True + else: + raise RangeException(self.name, self.range, new_value) + + def format_write_value(self, new_value): + return new_value + + def format_query_return(self, ret): + + return float(ret) diff --git a/pyscan/drivers/property_settings/scpi_property_settings.py b/pyscan/drivers/property_settings/scpi_property_settings.py deleted file mode 100644 index ce15a226..00000000 --- a/pyscan/drivers/property_settings/scpi_property_settings.py +++ /dev/null @@ -1,53 +0,0 @@ -from ...general.item_attribute import ItemAttribute -from .property_exceptions import ( - RangeException, IndexedValueException, ValueException, DictValueException) -import numpy as np - - -exception_dict = { - 'range': RangeException, - 'indexed_value': IndexedValueException, - 'values': ValueException, - 'dict_values': DictValueException} - -property_types = list(exception_dict.keys()) - - -class SCPIPropertySettings(ItemAttribute): - - def __init__(self, device, settings): - super().__init__(device, settings) - - def format_write_string(self, new_value, *args): - print(new_value, *args) - if self.property_type in ['range', 'values']: - return self.query_string.format(new_value) - elif self.property_type == 'indexed_value': - return self.query_string.format(self.indexed_values.index(new_value)) - elif self.property_type == 'dict_values': - return self.format_query_string.format(self.dict_values[new_value]) - - def sub_class_settings_validation(self, settings): - ''' - For ScPIPropertySettings, ensures that write_string, query_string, read_only, and write_only - are configured properly - ''' - - settings_keys = list(settings.keys()) - - if 'read_only' in settings_keys: - assert 'write_string' not in settings_keys, \ - f'{self.name} is set to "read_only", for "read_only" properties, "write_string" is an invalid key' - assert 'query_string' in settings_keys, \ - f'{self.name} is missing a "query_string" key' - elif 'write_only' in settings_keys: - assert 'query_string' not in settings_keys, \ - f'{self.name} is set to "write_only", for "write_only" properties, "query_string" is an invalid key' - assert 'write_string' in settings_keys, \ - f'{self.name} is missing a "write_string" key' - else: - assert 'query_string' in settings_keys, \ - f'{self.name} is missing a "query_string" key' - - assert 'write_string' in settings_keys, \ - f'{self.name} is missing a "write_string" key' diff --git a/pyscan/drivers/property_settings/values_property_settings.py b/pyscan/drivers/property_settings/values_property_settings.py new file mode 100644 index 00000000..dc186e02 --- /dev/null +++ b/pyscan/drivers/property_settings/values_property_settings.py @@ -0,0 +1,31 @@ +from itemattribute import ItemAttribute + + +class ValueException(Exception): + + def __init__(self, prop, values, value): + + msg = f'{prop} = {value} invalid input\n' + msg += f'Valid inputs for values property {prop} are:' + msg += ',\n'.join(values) + + super().__init__(msg) + + +class ValuesPropertySettings(ItemAttribute): + + def __init__(self, device, settings_dict): + super().__init__(device, settings_dict) + + def validate_set_value(self, new_value): + if new_value in self.values: + return True + else: + raise ValueException(self.name, self.range, new_value) + + def format_write_value(self, new_value): + return new_value + + def format_query_return(self, ret): + + return ret diff --git a/pyscan/drivers/testing/test_voltage.py b/pyscan/drivers/testing/test_voltage.py index f487c66a..bc0d242c 100644 --- a/pyscan/drivers/testing/test_voltage.py +++ b/pyscan/drivers/testing/test_voltage.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from pyscan.drivers import InstrumentDriver +from .test_voltage_driver import TestVoltageDriver -class TestVoltage(InstrumentDriver): +class TestVoltage(TestVoltageDriver): ''' Class that mimics the operation of a simple voltage source. @@ -24,7 +24,7 @@ class TestVoltage(InstrumentDriver): Get/set a mock output state, with dict values 'on', 1, 'off', or 0 ''' - # tells pytest this is not a test case. Was necessary only on lab computer for some reason. + # tells pytest this is not a test case. __test__ = False def __init__(self, debug=False, instrument=None, *arg, **kwarg): @@ -36,31 +36,9 @@ def __init__(self, debug=False, instrument=None, *arg, **kwarg): self._voltage = 0 self._power = 1 self._output_state = 'off' - self._version = "0.1.0" + self._version = "1.0.0" self.black_list_for_testing = [] - def query(self, string): - if string == 'VOLT?': - return str(self._voltage) - elif string == 'POW?': - return str(self._power) - elif string == 'OUTP?': - if self._output_state == 'off': - return '0' - if self._output_state == 'on': - return '1' - # leave for the sake of your personal sanity, trust us - return str(self._output_state) - - # we are not currently testing for this in test voltage... doesn't seem particularly useful to do so - def write(self, string): - if 'VOLT' in string: - return string.strip('VOLT ') - elif 'POW' in string: - return string.strip('POW ') - elif 'OUTP' in string: - return string.strip('OUTP ') - def initialize_properties(self): self.add_device_property({ @@ -86,7 +64,3 @@ def initialize_properties(self): 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0}, 'return_type': str }) - - @property - def version(self): - return self._version diff --git a/pyscan/drivers/driver_classes/test_driver.py b/pyscan/drivers/testing/test_voltage_driver.py similarity index 90% rename from pyscan/drivers/driver_classes/test_driver.py rename to pyscan/drivers/testing/test_voltage_driver.py index e57e2229..29f89b58 100644 --- a/pyscan/drivers/driver_classes/test_driver.py +++ b/pyscan/drivers/testing/test_voltage_driver.py @@ -3,7 +3,7 @@ class TestDriver(AbstractDriver): - def query(self, settings): + def query_property(self, settings): string = settings.query_string if string == 'VOLT?': return str(self._voltage) @@ -17,7 +17,7 @@ def query(self, settings): # leave for the sake of your personal sanity return str(self._output_state) - def write(self, settings): + def write_property(self, settings): string = settings.write_string if 'VOLT' in string: return string.strip('VOLT ') diff --git a/pyscan/drivers/testing/test_voltagev2.py b/pyscan/drivers/testing/test_voltagev2.py deleted file mode 100644 index af1cf015..00000000 --- a/pyscan/drivers/testing/test_voltagev2.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -from ..driver_classes.test_driver import TestDriver -from ..property_settings.property_settings import PropertySettings - - -class TestVoltagev2(TestDriver): - ''' - Class that mimics the operation of a simple voltage source. - - This is used in the demo jupyter notebooks. - - Parameters - ---------- - instrument : mock - Optional parameter. - - Attributes - ---------- - (Properties) - voltage : float - Get/set a mock voltage, with default range [-10, 10] - power : int - Get/set a mock power setting, with available values [1, 10] - output_state : int or str - Get/set a mock output state, with dict values 'on', 1, 'off', or 0 - ''' - - # tells pytest this is not a test case. Was necessary only on lab computer for some reason. - __test__ = False - - def __init__(self, debug=False, instrument=None, *arg, **kwarg): - - super().__init__(instrument=None, *arg, **kwarg) - self.property_class = PropertySettings - self.initialize_properties() - - self.debug = debug - self._voltage = 0 - self._power = 1 - self._output_state = 'off' - self._version = "0.1.0" - self.black_list_for_testing = [] - - def initialize_properties(self): - - self.add_device_property({ - 'name': 'voltage', - 'write_string': 'VOLT {}', - 'query_string': 'VOLT?', - 'range': [0, 10], - 'return_type': float - }) - - self.add_device_property({ - 'name': 'power', - 'write_string': 'POW {}', - 'query_string': 'POW?', - 'values': [1, 10], - 'return_type': int - }) - - self.add_device_property({ - 'name': 'output_state', - 'write_string': 'OUTP {}', - 'query_string': 'OUTP?', - 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0}, - 'return_type': str - }) - - @property - def version(self): - return self._version From 6031db7f8a7961f26464405a96c525c8ae87964d Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Thu, 24 Oct 2024 09:35:30 -0600 Subject: [PATCH 04/29] "drivers: Changed values driver property setting to values_list, to not overload the dictionary values method. --- pyscan/drivers/actonsp2300.py | 2 +- pyscan/drivers/agilent33500.py | 36 ++--- pyscan/drivers/agilent8267d.py | 6 +- pyscan/drivers/agilent8275n.py | 2 +- pyscan/drivers/agilentdso900series.py | 35 +++-- pyscan/drivers/bkprecision9130b.py | 4 +- pyscan/drivers/heliotis/helios_sdk.py | 10 +- .../instrument_driver/abstract_driver.py | 25 ++- pyscan/drivers/keithley2260b.py | 4 +- pyscan/drivers/keithley2400.py | 6 +- .../dict_values_property_settings.py | 37 ----- pyscan/drivers/pylonsdk.py | 10 +- pyscan/drivers/stanford396.py | 15 +- pyscan/drivers/stanford400.py | 16 +- pyscan/drivers/stanford830.py | 2 +- pyscan/drivers/testing/auto_test_driver.py | 39 ++--- .../drivers/testing/test_instrument_driver.py | 148 +++++++++--------- pyscan/drivers/testing/test_voltage.py | 25 ++- pyscan/drivers/testing/test_voltage_driver.py | 27 ---- pyscan/drivers/tpi1002a.py | 6 +- pyscan/drivers/zurichhf2.py | 34 ++-- .../test_dict_values_settings.py | 51 ++++++ .../test_indexed_values_settings.py | 44 ++++++ .../test_range_property_settings.py | 46 ++++++ .../test_values_property_setttings.py | 33 ++++ 25 files changed, 395 insertions(+), 268 deletions(-) delete mode 100644 pyscan/drivers/property_settings/dict_values_property_settings.py delete mode 100644 pyscan/drivers/testing/test_voltage_driver.py create mode 100644 test/drivers/test_property_settings/test_dict_values_settings.py create mode 100644 test/drivers/test_property_settings/test_indexed_values_settings.py create mode 100644 test/drivers/test_property_settings/test_range_property_settings.py create mode 100644 test/drivers/test_property_settings/test_values_property_setttings.py diff --git a/pyscan/drivers/actonsp2300.py b/pyscan/drivers/actonsp2300.py index 9f75cb66..ac8e4102 100644 --- a/pyscan/drivers/actonsp2300.py +++ b/pyscan/drivers/actonsp2300.py @@ -114,7 +114,7 @@ def inner(resp): 'name': 'grating', 'write_string': '{} GRATING', 'query_string': '?GRATING', - 'values': [1, 2, 3, 4, 5, 6, 7, 8, 9], + 'values_list': [1, 2, 3, 4, 5, 6, 7, 8, 9], 'return_type': parse_response(int)}) self.add_device_property({ diff --git a/pyscan/drivers/agilent33500.py b/pyscan/drivers/agilent33500.py index 326019c6..7d0428e5 100644 --- a/pyscan/drivers/agilent33500.py +++ b/pyscan/drivers/agilent33500.py @@ -117,21 +117,21 @@ def initialize_properties(self): 'name': 'voltage_autorange_chan1', 'write_string': 'SOUR1:VOLT:RANG:AUTO {}', 'query_string': 'SOUR1:VOLT:RANG:AUTO?', - 'values': [0, 'off', 1, 'on'], + 'values_list': [0, 'off', 1, 'on'], 'return_type': str}) self.add_device_property({ 'name': 'voltage_autorange_chan2', 'write_string': 'SOUR2:VOLT:RANG:AUTO {}', 'query_string': 'SOUR2:VOLT:RANG:AUTO?', - 'values': [0, 'off', 1, 'on'], + 'values_list': [0, 'off', 1, 'on'], 'return_type': str}) self.add_device_property({ 'name': 'function_chan1', 'write_string': 'SOUR1:FUNC {}', 'query_string': 'SOUR1:FUNC?', - 'values': [ + 'values_list': [ "SIN", "SQU", "TRI", @@ -147,7 +147,7 @@ def initialize_properties(self): 'name': 'function_chan2', 'write_string': 'SOUR2:FUNC {}', 'query_string': 'SOUR2:FUNC?', - 'values': [ + 'values_list': [ "SIN", "SQU", "TRI", @@ -163,28 +163,28 @@ def initialize_properties(self): 'name': 'arb_advance_mode_chan1', 'write_string': 'SOUR1:FUNC:ARB:ADV {}', 'query_string': 'SOUR1:FUNC:ARB:ADV?', - 'values': ["TRIG", "SRAT"], + 'values_list': ["TRIG", "SRAT"], 'return_type': str}) self.add_device_property({ 'name': 'arb_advance_mode_chan2', 'write_string': 'SOUR2:FUNC:ARB:ADV {}', 'query_string': 'SOUR2:FUNC:ARB:ADV?', - 'values': ["TRIG", "SRAT"], + 'values_list': ["TRIG", "SRAT"], 'return_type': str}) self.add_device_property({ 'name': 'arb_filter_chan1', 'write_string': 'SOUR1:FUNC:ARB:FILT {}', 'query_string': 'SOUR1:FUNC:ARB:FILT?', - 'values': ["NORM", "STEP", "OFF"], + 'values_list': ["NORM", "STEP", "OFF"], 'return_type': str}) self.add_device_property({ 'name': 'arb_filter_chan2', 'write_string': 'SOUR2:FUNC:ARB:FILT {}', 'query_string': 'SOUR2:FUNC:ARB:FILT?', - 'values': ["NORM", "STEP", "OFF"], + 'values_list': ["NORM", "STEP", "OFF"], 'return_type': str}) self.add_device_property({ @@ -205,14 +205,14 @@ def initialize_properties(self): 'name': 'burst_mode_chan1', 'write_string': 'SOUR1:BURS:MODE {}', 'query_string': 'SOUR1:BURS:MODE?', - 'values': ['TRIG', 'GAT'], + 'values_list': ['TRIG', 'GAT'], 'return_type': str}) self.add_device_property({ 'name': 'burst_mode_chan2', 'write_string': 'SOUR2:BURS:MODE {}', 'query_string': 'SOUR2:BURS:MODE?', - 'values': ['TRIG', 'GAT'], + 'values_list': ['TRIG', 'GAT'], 'return_type': str}) self.add_device_property({ @@ -233,56 +233,56 @@ def initialize_properties(self): 'name': 'burst_state_chan1', 'write_string': 'SOUR1:BURS:STAT {}', 'query_string': 'SOUR1:BURS:STAT?', - 'values': [0, 'Off', 1, 'ON'], + 'values_list': [0, 'Off', 1, 'ON'], 'return_type': str}) self.add_device_property({ 'name': 'burst_state_chan2', 'write_string': 'SOUR2:BURS:STAT {}', 'query_string': 'SOUR2:BURS:STAT?', - 'values': [0, 'Off', 1, 'ON'], + 'values_list': [0, 'Off', 1, 'ON'], 'return_type': str}) self.add_device_property({ 'name': 'output_chan1', 'write_string': 'OUTP1 {}', 'query_string': 'OUTP1?', - 'values': [0, 'Off', 1, 'ON'], + 'values_list': [0, 'Off', 1, 'ON'], 'return_type': str}) self.add_device_property({ 'name': 'output_chan2', 'write_string': 'OUTP2 {}', 'query_string': 'OUTP2?', - 'values': [0, 'Off', 1, 'ON'], + 'values_list': [0, 'Off', 1, 'ON'], 'return_type': str}) self.add_device_property({ 'name': 'output_load_chan1', 'write_string': 'OUTP1:LOAD {}', 'query_string': 'OUTP1:LOAD?', - 'values': [50, 'INF'], + 'values_list': [50, 'INF'], 'return_type': float}) self.add_device_property({ 'name': 'output_load_chan2', 'write_string': 'OUTP2:LOAD {}', 'query_string': 'OUTP2:LOAD?', - 'values': [50, 'INF'], + 'values_list': [50, 'INF'], 'return_type': float}) self.add_device_property({ 'name': 'trigger_source_chan1', 'write_string': 'TRIG1:SOUR {}', 'query_string': 'TRIG1:SOUR?', - 'values': ["IMM", "EXT", "TIM", "BUS"], + 'values_list': ["IMM", "EXT", "TIM", "BUS"], 'return_type': str}) self.add_device_property({ 'name': 'trigger_source_chan2', 'write_string': 'TRIG2:SOUR {}', 'query_string': 'TRIG2:SOUR?', - 'values': ["IMM", "EXT", "TIM", "BUS"], + 'values_list': ["IMM", "EXT", "TIM", "BUS"], 'return_type': str}) self.update_properties() diff --git a/pyscan/drivers/agilent8267d.py b/pyscan/drivers/agilent8267d.py index d7b0bb26..2249f600 100644 --- a/pyscan/drivers/agilent8267d.py +++ b/pyscan/drivers/agilent8267d.py @@ -51,7 +51,7 @@ def initialize_properties(self): 'name': 'frequency_mode', 'write_string': ':SOUR:FREQ:MODE {}', 'query_string': ':SOUR:FREQ:MODE?', - 'values': ['CW', 'LIST'], + 'values_list': ['CW', 'LIST'], 'return_type': str}) self.add_device_property({ @@ -66,12 +66,12 @@ def initialize_properties(self): 'name': 'output', 'write_string': ':OUTP:STAT {}', 'query_string': ':OUTP:STAT?', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'modulation', 'write_string': ':OUTP:MOD:STAT {}', 'query_string': ':OUTP:MOD:STAT?', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) diff --git a/pyscan/drivers/agilent8275n.py b/pyscan/drivers/agilent8275n.py index e7ba9540..5df0e065 100644 --- a/pyscan/drivers/agilent8275n.py +++ b/pyscan/drivers/agilent8275n.py @@ -57,5 +57,5 @@ def initialize_properties(self): 'name': 'output', 'write_string': ':OUTP:STAT {}', 'query_string': ':OUTP:STAT?', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) diff --git a/pyscan/drivers/agilentdso900series.py b/pyscan/drivers/agilentdso900series.py index a36f0d6c..b76306ba 100644 --- a/pyscan/drivers/agilentdso900series.py +++ b/pyscan/drivers/agilentdso900series.py @@ -83,28 +83,29 @@ def initialize_properties(self): 'name': 'sample_rate', 'write_string': ':ACQ:SRAT {}', 'query_string': 'ACQ:SRAT?', - 'values': [1e2, 2e2, 2.5e2, 4e2, 5e2, - 1e3, 2e3, 2.5e3, 4e3, 5e3, - 1e4, 2e4, 2.5e4, 4e4, 5e4, - 1e5, 2e5, 2.5e5, 4e5, 5e5, - 1e6, 2e6, 2.5e6, 4e6, 5e6, - 1e7, 2e7, 2.5e7, 4e7, 5e7, - 1e8, 2e8, 2.5e8, 4e8, 4e9, - 1e9, 2e9, 2.5e9], + 'values_list': [ + 1e2, 2e2, 2.5e2, 4e2, 5e2, + 1e3, 2e3, 2.5e3, 4e3, 5e3, + 1e4, 2e4, 2.5e4, 4e4, 5e4, + 1e5, 2e5, 2.5e5, 4e5, 5e5, + 1e6, 2e6, 2.5e6, 4e6, 5e6, + 1e7, 2e7, 2.5e7, 4e7, 5e7, + 1e8, 2e8, 2.5e8, 4e8, 4e9, + 1e9, 2e9, 2.5e9], 'return_type': float}) self.add_device_property({ 'name': 'trigger_sweep', 'write_string': ':TRIG:SWE {}', 'query_string': ':TRIG:SWE?', - 'values': ['AUTO', 'TRIG', 'SING'], + 'values_list': ['AUTO', 'TRIG', 'SING'], 'return_type': str}) self.add_device_property({ 'name': 'trigger_mode', 'write_string': ':TRIG:MODE {}', 'query_string': ':TRIG:MODE?', - 'values': ['EDGE', 'GLIT', 'PATT', 'STAT', 'DEL', + 'values_list': ['EDGE', 'GLIT', 'PATT', 'STAT', 'DEL', 'TIM', 'TV', 'COMM', 'RUNT', 'SEQ', 'SHOL', 'TRAN', 'WIND', 'PWID', 'ADV', 'EDGE', 'SBUS1', 'SBUS2', 'SBUS3', 'SBUS3'], @@ -114,7 +115,7 @@ def initialize_properties(self): 'name': 'trigger_source', 'write_string': ':TRIG:EDGE:SOUR {}', 'query_string': ':TRIG:EDGE:SOUR?', - 'values': ['CHAN1', 'CHAN2', 'CHAN3', 'CHAN4', 'E'], + 'values_list': ['CHAN1', 'CHAN2', 'CHAN3', 'CHAN4', 'E'], 'return_type': str}) self.add_device_property({ @@ -128,7 +129,7 @@ def initialize_properties(self): 'name': 'time_reference', 'write_string': ':TIM:REF {}', 'query_string': ':TIM:REF?', - 'values': ['LEFT', 'RIGHT', 'CENT'], + 'values_list': ['LEFT', 'RIGHT', 'CENT'], 'return_type': str}) self.add_device_property({ @@ -142,14 +143,14 @@ def initialize_properties(self): 'name': 'acquire_type', 'write_string': 'ACQ:TYPE {}', 'query_string': 'ACQ:TYPE?', - 'values': ['NORM', 'AVER', 'HRES', 'PEAK'], + 'values_list': ['NORM', 'AVER', 'HRES', 'PEAK'], 'return_type': str}) self.add_device_property({ 'name': 'acquire_mode', 'write_string': 'ACQ:MODE {}', 'query_string': 'ACQ:MODE?', - 'values': ['ETIM', 'RTIM', 'PDET', 'HRES', 'SEGM', + 'values_list': ['ETIM', 'RTIM', 'PDET', 'HRES', 'SEGM', 'SEGP', 'SEGH'], 'return_type': str}) @@ -186,7 +187,7 @@ def initialize_properties(self): 'name': 'all_segment_download', 'write_string': ':WAV:SEGM:ALL {}', 'query_string': ':WAV:SEGM:ALL?', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': float}) self.add_device_property({ @@ -228,14 +229,14 @@ def initialize_properties(self): 'name': 'waveform_format', 'write_string': ':WAV:FORM {}', 'query_string': ':WAV:FORM?', - 'values': ['BYTE', 'WORD', 'ASCII'], + 'values_list': ['BYTE', 'WORD', 'ASCII'], 'return_type': str}) self.add_device_property({ 'name': 'waveform_source', 'write_string': ':WAV:SOUR CHAN{}', 'query_string': ':WAV:SOUR?', - 'values': ['1', '2', '3', '4', 'MATH'], + 'values_list': ['1', '2', '3', '4', 'MATH'], 'return_type': str}) def single(self): diff --git a/pyscan/drivers/bkprecision9130b.py b/pyscan/drivers/bkprecision9130b.py index 9247246d..38cbfbaa 100644 --- a/pyscan/drivers/bkprecision9130b.py +++ b/pyscan/drivers/bkprecision9130b.py @@ -41,14 +41,14 @@ def initialize_properties(self): 'name': 'channel', 'write_string': 'INST CH{}', 'query_string': 'INST?', - 'values': [1, 2, 3], + 'values_list': [1, 2, 3], 'return_type': str}) self.add_device_property({ 'name': 'output', 'write_string': 'OUTP {}', 'query_string': 'OUTP?', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ diff --git a/pyscan/drivers/heliotis/helios_sdk.py b/pyscan/drivers/heliotis/helios_sdk.py index de2f4135..bde6995e 100644 --- a/pyscan/drivers/heliotis/helios_sdk.py +++ b/pyscan/drivers/heliotis/helios_sdk.py @@ -28,7 +28,7 @@ def add_device_property(self, settings): Returns function that generates the property of the class ''' - if 'values' in settings: + if 'values_list' in settings: set_function = self.set_values_property elif 'range' in settings: set_function = self.set_range_property @@ -69,18 +69,18 @@ def get_instrument_property(self, obj, settings, debug=False): def set_values_property(self, obj, new_value, settings): ''' - Generator function for settings dictionary with 'values' item - Check that new_value is in settings['values'], if not, rejects command + Generator function for settings dictionary with 'values_list item + Check that new_value is in settings['values_list'], if not, rejects command Args: obj - parent class object new_value - new_value to be set on instrument - settings - dictionarky with ['values'] item + settings - dictionarky with ['values_list'] item returns None ''' - values = settings['values'] + values = settings['values_list'] if new_value in values: if not self.debug: diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 4f151f0c..dcd61fd5 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ....general.item_attribute import ItemAttribute +from ...general.item_attribute import ItemAttribute from ..property_settings import ( RangePropertySettings, ValuesPropertySettings, @@ -71,7 +71,7 @@ def add_device_property(self, settings_dict): name = settings_dict['name'] settings_name = '_' + name + '_settings' - property_class = self.validate_settings(settings_dict) + property_class = self.validate_property_settings(settings_dict) self.validate_subclass_settings(settings_dict) # Make property settings attribute @@ -80,8 +80,8 @@ def add_device_property(self, settings_dict): # Make self.name property property_definition = property( - fget=getattr(self, settings_name).get_instrument_property, - fset=lambda obj, new_value: getattr(self, settings_name).set_instrument_property(obj, new_value), + fget=lambda obj: self.get_instrument_property(obj, settings_obj), + fset=lambda obj, new_value: self.set_instrument_property(obj, settings_obj, new_value), doc=self.get_property_docstring(name)) setattr(self.__class__, name, property_definition) setattr(self, settings_obj._name, None) @@ -104,9 +104,8 @@ def get_instrument_property(self, obj, settings_obj, debug=False): ------- value formatted to setting's ['return_type'] ''' - - value = self.query_property(settings_obj).strip("\n") - value = self.settings_obj.format_query_return(value) + value = self.query_property(settings_obj) + value = settings_obj.format_query_return(value) setattr(self, settings_obj._name, value) @@ -114,8 +113,8 @@ def get_instrument_property(self, obj, settings_obj, debug=False): def set_instrument_property(self, obj, settings_obj, new_value): ''' - Generator function for settings dictionary with 'values' item - Check that new_value is in settings['values'], if not, rejects command + Generator function for settings dictionary with 'values_list' item + Check that new_value is in settings['values_list'], if not, rejects command Parameters ---------- @@ -216,7 +215,7 @@ def validate_property_settings(self, settings_dict): # Check that settings_dict has a property type key(s) i = 0 - property_types = ['range', 'values', 'indexed_values', 'dict_values'] + property_types = ['range', 'values_list', 'indexed_values', 'dict_values'] for prop in property_types: if prop in settings_keys: @@ -238,8 +237,8 @@ def validate_property_settings(self, settings_dict): assert 'return_type' in settings_keys, f'{name} requires a "return_type" setting' assert settings_dict['range'][1] > settings_dict['range'][0], f'{name} has bad "range" settings, range[0] < range[1]' property_class = RangePropertySettings - elif 'values' in settings_keys: - assert isinstance(settings_dict['values'], list), f'{name} "values" setting must be a list' + elif 'values_list' in settings_keys: + assert isinstance(settings_dict['values_list'], list), f'{name} "values" setting must be a list' property_class = ValuesPropertySettings elif 'indexed_values' in settings_keys: assert isinstance(settings_dict['indexed_values'], list), f'{name} "indexed_values" setting must be a list' @@ -250,7 +249,7 @@ def validate_property_settings(self, settings_dict): return property_class - def sub_class_settings_validation(self, settings_dict): + def validate_subclass_settings(self, settings_dict): ''' Abstract method to be overloaded which checks the validity of input settings beyond range, values, indexed_values, dict_values, read-only, write-only, etc. diff --git a/pyscan/drivers/keithley2260b.py b/pyscan/drivers/keithley2260b.py index 474fad30..127e4b8f 100644 --- a/pyscan/drivers/keithley2260b.py +++ b/pyscan/drivers/keithley2260b.py @@ -286,14 +286,14 @@ def initialize_properties(self): 'name': 'transient_trigger_source', 'write_string': 'TRIG:TRAN:SOUR {}', 'query_string': 'TRIG:TRAN:SOUR?', - 'values': ['BUS', 'IMM'], + 'values_list': ['BUS', 'IMM'], 'return_type': str}) self.add_device_property({ 'name': 'output_trigger_source', 'write_string': 'TRIG:OUTP:SOUR {}', 'query_string': 'TRIG:OUTP:SOUR?', - 'values': ['BUS', 'IMM'], + 'values_list': ['BUS', 'IMM'], 'return_type': str}) def measure_current(self): diff --git a/pyscan/drivers/keithley2400.py b/pyscan/drivers/keithley2400.py index edc443a3..7c61fe4d 100644 --- a/pyscan/drivers/keithley2400.py +++ b/pyscan/drivers/keithley2400.py @@ -61,7 +61,7 @@ def init_settings(self): self.source_mode_settings['string_values'] = ['FIX'] self.source_voltage_range_settings = {} - self.source_voltage_range_settings['values'] = [0.21, 2.1, 21] + self.source_voltage_range_settings['values_list'] = [0.21, 2.1, 21] self.source_current_range_settings = {} self.source_current_range_settings['range'] = [ @@ -75,10 +75,10 @@ def init_settings(self): self.sense_settings['string_values'] = ['CURR', 'VOLT'] self.sense_voltage_range_settings = {} - self.sense_voltage_range_settings['values'] = [] + self.sense_voltage_range_settings['values_list'] = [] self.sense_current_range_settings = {} - self.sense_current_range_settings['values'] = [1e-6, 1e-5, 1e-4] + self.sense_current_range_settings['values_list'] = [1e-6, 1e-5, 1e-4] self.current_compliance_settings = {} self.current_compliance_settings['range'] = [0, 0.105] diff --git a/pyscan/drivers/property_settings/dict_values_property_settings.py b/pyscan/drivers/property_settings/dict_values_property_settings.py deleted file mode 100644 index 8371dce2..00000000 --- a/pyscan/drivers/property_settings/dict_values_property_settings.py +++ /dev/null @@ -1,37 +0,0 @@ -from itemattribute import ItemAttribute - - -class DictValueException(Exception): - - def __init__(self, prop, dict_values, value): - - msg = f'{prop} = {value} invalid input\n' - msg += f'Valid inputs for values property {prop} are:' - for key, value in dict_values.items(): - msg += f'{key}: {value},\n' - - super().__init__(msg) - - -class DictPropertySettings(ItemAttribute): - - def __init__(self, device, settings_dict): - super().__init__(device, settings_dict) - - def validate_set_value(self, new_value): - if new_value in self.values.keys(): - return True - else: - raise DictValueException(self.name, self.dict_values, new_value) - - def format_write_value(self, new_value): - - return self.dict_values[new_value] - - def format_query_return(self, ret): - - for key, val in self.dict_values.items(): - if str(val) == str(ret): - return key - - return self.find_first_key(ret) diff --git a/pyscan/drivers/pylonsdk.py b/pyscan/drivers/pylonsdk.py index 903760ef..6ca5ccec 100644 --- a/pyscan/drivers/pylonsdk.py +++ b/pyscan/drivers/pylonsdk.py @@ -17,7 +17,7 @@ def add_device_property(self, settings): Returns function that generates the property of the class ''' - if 'values' in settings: + if 'values_list' in settings: set_function = self.set_values_property elif 'range' in settings: set_function = self.set_range_property @@ -58,18 +58,18 @@ def get_instrument_property(self, obj, settings, debug=False): def set_values_property(self, obj, new_value, settings): ''' - Generator function for settings dictionary with 'values' item - Check that new_value is in settings['values'], if not, rejects command + Generator function for settings dictionary with 'values_list item + Check that new_value is in settings['values_list'], if not, rejects command Args: obj - parent class object new_value - new_value to be set on instrument - settings - dictionarky with ['values'] item + settings - dictionarky with ['values_list'] item returns None ''' - values = settings['values'] + values = settings['values_list'] if new_value in values: if not self.debug: diff --git a/pyscan/drivers/stanford396.py b/pyscan/drivers/stanford396.py index 54096900..4908d7d1 100755 --- a/pyscan/drivers/stanford396.py +++ b/pyscan/drivers/stanford396.py @@ -75,7 +75,7 @@ def initialize_properties(self): 'name': 'output', 'write_string': 'ENBR {}', 'query_string': 'ENBR?', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': float}) # Common settings @@ -107,29 +107,30 @@ def initialize_properties(self): 'name': 'modulation', 'write_string': 'MODL {}', 'query_string': 'MODL?', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'input_coupling', 'write_string': 'COUP {}', 'query_string': 'COUP?', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'modulation_type', 'write_string': 'TYPE {}', 'query_string': 'TYPE?', - 'values': ['am', 'fm', 'phim', 'sweep', 'pulse', - 'blank', 'qam', 'cpm', 'vbs'], + 'values_list': [ + 'am', 'fm', 'phim', 'sweep', 'pulse', + 'blank', 'qam', 'cpm', 'vbs'], 'return_type': int}) self.add_device_property({ 'name': 'modulation_function', 'write_string': 'SFNC {}', 'query_string': 'SFNC?', - 'values': ['sin', 'ramp', 'triangle', 'external', 'waveform'], + 'values_list': ['sin', 'ramp', 'triangle', 'external', 'waveform'], 'return_type': int}) self.add_device_property({ @@ -150,7 +151,7 @@ def initialize_properties(self): 'name': 'sweep_modulation_function', 'write_string': 'SFNC {}', 'query_string': 'SFNC?', - 'values': [0, 1, 2, 5, 11], + 'values_list': [0, 1, 2, 5, 11], 'return_type': int}) self.add_device_property({ diff --git a/pyscan/drivers/stanford400.py b/pyscan/drivers/stanford400.py index b33f4bc5..67de03c8 100755 --- a/pyscan/drivers/stanford400.py +++ b/pyscan/drivers/stanford400.py @@ -106,42 +106,42 @@ def initialize_properties(self): 'name': 'counting_mode', 'write_string': 'CM {}', 'query_string': 'CM', - 'values': [0, 1, 2, 3], + 'values_list': [0, 1, 2, 3], 'return_type': int}) self.add_device_property({ 'name': 'counter_input_A', 'write_string': 'CI 0, {}', 'query_string': 'CI 0', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'counter_input_B', 'write_string': 'CI 1, {}', 'query_string': 'CI 1', - 'values': [1, 2], + 'values_list': [1, 2], 'return_type': int}) self.add_device_property({ 'name': 'counter_input_T', 'write_string': 'CI 2, {}', 'query_string': 'CI 2', - 'values': [0, 1, 2, 3], + 'values_list': [0, 1, 2, 3], 'return_type': int}) self.add_device_property({ 'name': 'n_periods', 'write_string': 'NP {}', 'query_string': 'NP', - 'values': list(range(2001)), + 'values_list': list(range(2001)), 'return_type': int}) self.add_device_property({ 'name': 'end_scan_mode', 'write_string': 'NE {}', 'query_string': 'NE', - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ @@ -269,7 +269,7 @@ def initialize_properties(self): 'name': 'gate_mode_A', 'write_string': 'GM 0, {}', 'query_string': 'GM 0', - 'values': [0, 1, 2], + 'values_list': [0, 1, 2], 'return_type': int}) self.add_device_property({ @@ -290,7 +290,7 @@ def initialize_properties(self): 'name': 'gate_mode_B', 'write_string': 'GM 1, {}', 'query_string': 'GM 1', - 'values': [0, 1, 2], + 'values_list': [0, 1, 2], 'return_type': int}) self.add_device_property({ diff --git a/pyscan/drivers/stanford830.py b/pyscan/drivers/stanford830.py index cde1a890..eb4c9b1d 100644 --- a/pyscan/drivers/stanford830.py +++ b/pyscan/drivers/stanford830.py @@ -831,7 +831,7 @@ def snap_xy(self): # wait_dict = {6: 5 * tc, 12: 7 * tc, 18: 9 * tc, 24: 10 * tc} # bw_dict = {6: 1 / (4 * tc), 12: 1 / (8 * tc), 18: 3 / (32 * tc), 24: 5 / (64 * tc)} - # available_rates = self.sample_rate_['values'][:-2] + # available_rates = self.sample_rate_['values_list'abs][:-2] # sample_rate = available_rates[ # np.bisect_left(available_rates, 1 / wait_dict[slope])] diff --git a/pyscan/drivers/testing/auto_test_driver.py b/pyscan/drivers/testing/auto_test_driver.py index b1013ba2..817874c4 100644 --- a/pyscan/drivers/testing/auto_test_driver.py +++ b/pyscan/drivers/testing/auto_test_driver.py @@ -24,7 +24,7 @@ # not incluing booleans since they can be interpreted ambiguously as ints. Should it? BAD_INPUTS = [-19812938238312948, -1.11123444859, 3.2222111234, 985767665954, 890992238.2345, - 'not ok', 'bad value', 'Andy is cool', + 'not ok', 'bad value', [1, 2412, 19], [1, 191, 13, -5.3], {'Alfred': "Batman's uncle", 'or': 'imposter'}] @@ -137,8 +137,8 @@ def reset_device_properties(device): continue elif 'read_only' in keys: continue - elif ('values' in keys) and ('indexed_' not in keys) and ('dict_' not in keys): - device[var_name] = device[name]['values'][0] + elif ('values_list' in keys): + device[var_name] = device[name]['values_list'][0] elif 'range' in keys: device[var_name] = device[name]['range'][0] elif 'ranges' in keys: @@ -152,7 +152,7 @@ def reset_device_properties(device): break else: assert False, "no valid type present in setting: {}. Must be one of {}.".format( - name, ['values', 'range', 'indexed_values', 'dict_values', 'read_only']) + name, ['values_list', 'range', 'indexed_values', 'dict_values', 'read_only']) if len(blacklisted) > 0: print("Blacklisted settings that will not be tested or changed are: ") @@ -201,24 +201,25 @@ def check_values_property(device, key): # reset value to baseline for consistency between tests try: - device[name] = device[key]['values'][0] + device[name] = device[key]['values_list][0] except Exception: assert False, missing_prop_str.format(name) for val in BAD_INPUTS: - if val not in device[key]['values']: + if val not in device[key]['values_list]: with pytest.raises(Exception): device[name] = val - for val in device[key]['values']: + for val in device[key]['values_list]: device[name] = val # ################ consider within a range here since may not return perfect value. - assert device[name] == val, prop_err_str1.format('values', name, val, device[name]) - assert device["_{}".format(name)] == val, prop_err_str2.format('values', name, val, + assert device[name] == val, \ + prop_err_str1.format('values_list, name, str(val) + str(type(val)), str(device[name]) + str(type(device[name]))) + assert device["_{}".format(name)] == val, prop_err_str2.format('values_list, name, val, "_{}".format(name), device["_{}".format(name)]) # reset value to baseline for consistency between tests - device[name] = device[key]['values'][0] + device[name] = device[key]['values_list][0] # check the set_range_property behavior for a range item @@ -304,11 +305,11 @@ def check_dict_property(device, key): # print(k) device[name] = k err_str1 = ("{} key return not properly formatted. Did not return first key {}, instead returned {}").format( - name, device.find_first_key(ord_dict, ord_dict[k]), device[name]) - assert device[name] == device.find_first_key(ord_dict, ord_dict[k]), err_str1 + name, device[key].find_first_key(ord_dict[k]), device[name]) + assert device[name] == device[key].find_first_key(ord_dict[k]), err_str1 err_str2 = ("_{} key return not properly formatted. Did not return first key {}, instead returned {}").format( - name, device.find_first_key(ord_dict, ord_dict[k]), device[name]) - assert device["_{}".format(name)] == device.find_first_key(ord_dict, ord_dict[k]), err_str2 + name, device[key].find_first_key(ord_dict[k]), device[name]) + assert device["_{}".format(name)] == device[key].find_first_key(ord_dict[k]), err_str2 for bad_input in BAD_INPUTS: if isinstance(bad_input, typing.Hashable): @@ -366,7 +367,7 @@ def check_properties(test_instrument, verbose=True): elif 'write_only' in keys: write_only_settings.append(name) continue - elif ('values' in keys) and ('indexed_' not in keys) and ('dict_' not in keys): + elif ('values_list in keys) and ('indexed_' not in keys) and ('dict_' not in keys): values_counter += 1 # values_idx.append(values_counter) check_values_property(test_instrument, name) @@ -384,7 +385,7 @@ def check_properties(test_instrument, verbose=True): check_dict_property(test_instrument, name) else: assert False, "no valid type present in setting: {}. Must be one of {}.".format( - name, ['values', 'range', 'indexed_values', 'dict_values']) + name, ['values_list, 'range', 'indexed_values', 'dict_values']) print(name) restored_settings = restore_initial_state(test_instrument, saved_settings) @@ -407,9 +408,9 @@ def check_properties(test_instrument, verbose=True): total_tested = range_counter + values_counter + idx_vals_counter + dict_vals_counter print("{} properties tested out of {} total settings.".format(total_tested, total_settings)) - if isinstance(test_instrument, TestInstrumentDriver): - assert values_counter == range_counter == idx_vals_counter == dict_vals_counter == 1 - print("Drivers test unit seems to be working as expected.") + # if isinstance(test_instrument, TestInstrumentDriver): + # assert values_counter == range_counter == idx_vals_counter == dict_vals_counter == 1 + # print("Drivers test unit seems to be working as expected.") if verbose: print("\nSettings restored to: ") diff --git a/pyscan/drivers/testing/test_instrument_driver.py b/pyscan/drivers/testing/test_instrument_driver.py index d3deac10..a38bff0a 100644 --- a/pyscan/drivers/testing/test_instrument_driver.py +++ b/pyscan/drivers/testing/test_instrument_driver.py @@ -1,8 +1,8 @@ -from pyscan.drivers import InstrumentDriver +from ..instrument_driver.abstract_driver import AbstractDriver import pytest -class TestInstrumentDriver(InstrumentDriver): +class TestInstrumentDriver(AbstractDriver): '''Class that exhausts the possible properties of instrument driver to test instrument driver. Parameters @@ -13,16 +13,17 @@ class TestInstrumentDriver(InstrumentDriver): Attributes ---------- (Properties) - float_values : float + float_value : float for testing float values property - str_values : str + str_value : str for testing str values property + bool_value : bool + for testing bool values property range : float for testing range property - - indexed_values : str + indexed_value : str for testing indexed_values property - dict_values : str + dict_value : str for testing dict_values property ''' # tells pytest this is not a test case @@ -35,34 +36,40 @@ def __init__(self, debug=False, instrument=None, *arg, **kwarg): self.instrument = 'instrument#123' self.debug = debug - self._float_values = 2 - self._str_values = '2' - self._bool_values = False + self._float_value = 2.0 + self._str_value = '2' + self._bool_value = False self._range = 0 - self._indexed_values = 'A' - self._dict_values = 'off' + self._indexed_value = 'A' + self._dict_value = 'off' self._version = "0.1.0" - self.update_properties() - self.black_list_for_testing = ['_str_values'] + # self.update_properties() + self.black_list_for_testing = ['_str_value'] + + def query_property(self, settings_obj): - def query(self, string): + string = settings_obj.query_string if string == 'FLOAT_VALUES?': - return str(self._float_values) + val = self._float_value elif string == 'STR_VALUES?': - return str(self._str_values) + val = self._str_value elif string == 'BOOL_VALUES?': - return str(self._bool_values) + val = self._bool_value elif string == 'RANGE?': - return str(self._range) + val = self._range elif string == 'INDEXED_VALUES?': - idx = self._indexed_values_settings['indexed_values'].index(self._indexed_values) - return str(idx) + val = self._indexed_value_settings['indexed_values'].index(self._indexed_value) elif string == 'DICT_VALUES?': - val = self._dict_values_settings['dict_values'][self._dict_values] - return str(val) + for key, val in settings_obj.dict_values.items(): + if str(key) == str(self._dict_value): + break + val + return val + + def write_property(self, settings_obj, new_value): - def write(self, string): + string = settings_obj.write_string.format(new_value) if 'FLOAT_VALUES' in string: return string.strip('FLOAT_VALUES ') if 'STR_VALUES' in string: @@ -79,89 +86,75 @@ def write(self, string): def initialize_properties(self): self.add_device_property({ - 'name': 'float_values', + 'name': 'float_value', 'write_string': 'FLOAT_VALUES {}', 'query_string': 'FLOAT_VALUES?', - 'values': [2, 2.2339340249, 89.129398], - 'return_type': float - }) + 'values_list': [2.0, 2.2339340249, 89.129398]}) self.add_device_property({ - 'name': 'str_values', + 'name': 'str_value', 'write_string': 'STR_VALUES {}', 'query_string': 'STR_VALUES?', - 'values': ['2', 'x', 'False', '(1, 10)', "['1', '10']"], - 'return_type': str - }) + 'values_list': ['2', 'x', 'False']}) + + self.add_device_property({ + 'name': 'bool_value', + 'write_string': 'BOOL_VALUES {}', + 'query_string': 'BOOL_VALUES?', + 'values_list': [True, False]}) self.add_device_property({ 'name': 'range', 'write_string': 'RANGE {}', 'query_string': 'RANGE?', 'range': [0, 10], - 'return_type': float - }) - - with pytest.raises(Exception): - self.add_device_property({ - 'name': 'ranges', - 'write_string': 'RANGES {}', - 'query_string': 'RANGES?', - 'ranges': [[0, 10], [15, 20], [-1, 1]], - 'return_type': list - }) - delattr(self, "_ranges_settings") + 'return_type': float}) self.add_device_property({ - 'name': 'indexed_values', + 'name': 'indexed_value', 'write_string': 'INDEXED_VALUES {}', 'query_string': 'INDEXED_VALUES?', - 'indexed_values': ['A', 'B', 'C', 'D', 196, 2.0, '101001'], - 'return_type': str - }) + 'indexed_values': ['A', 'B', 'C', 'D', 196, 2.0, '101001']}) self.add_device_property({ - 'name': 'dict_values', + 'name': 'dict_value', 'write_string': 'DICT_VALUES {}', 'query_string': 'DICT_VALUES?', - 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0}, - 'return_type': str - }) + 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0, 1: 1, 0: 0}}) + with pytest.raises(Exception): self.add_device_property({ 'name': 'bad_values', 'write_string': 'DICT_VALUES {}', 'query_string': 'DICT_VALUES?', - 'invalid_key_name': {'on': 1, 'off': 0, '1': 1, '0': 0}, - 'return_type': str - }) - delattr(self, "_bad_values_settings") + 'invalid_key_name': {'on': 1, 'off': 0, '1': 1, '0': 0, 0: 0, 1: 1}, + 'return_type': str}) def update_properties(self): - self.float_values - self.str_values - self.bool_values = False + self.float_value + self.str_value + self.bool_value = False self.range - self.indexed_values - self.dict_values + self.indexed_value + self.dict_value @property def version(self): return self._version -class BadInstrumentDriver(InstrumentDriver): +class BadInstrumentDriver(AbstractDriver): '''Class that mimics TestInstrumentDriver, but critically has bad blacklist values. Properties ---------- - values : + value : for testing values property range : for testing range property - indexed_values : + indexed_value : for testing indexed_values property - dict_values : + dict_value : for testing dict_values property ''' # tells pytest this is not a test case @@ -174,30 +167,33 @@ def __init__(self, debug=False, instrument=None, *arg, **kwarg): self.instrument = 'instrument#123' self.debug = debug - self._values = 2 + self._value = 2 self._range = 0 - self._indexed_values = 'A' - self._dict_values = 'off' + self._indexed_value = 'A' + self._dict_value = 'off' self._version = "0.1.0" self.update_properties() self.black_list_for_testing = ['_nonexistent_property_name'] - def query(self, string): + def query_property(self, settings_obj): + string = settings_obj.query_string if string == 'VALUES?': - return str(self._values) + return str(self._value) elif string == 'RANGE?': return str(self._range) elif string == 'INDEXED_VALUES?': - idx = self._indexed_values_settings['indexed_values'].index(self._indexed_values) + idx = self._indexed_value_settings['indexed_values'].index(self._indexed_value) return str(idx) elif string == 'DICT_VALUES?': - val = self._dict_values_settings['dict_values'][self._dict_values] + val = self._dict_values_settings['dict_values'][self._dict_value] return str(val) # we are not currently testing for this in test voltage... doesn't seem particularly useful to do so - def write(self, string): - if 'VALUES' in string: + def write_property(self, settings_obj, new_value): + + string = settings_obj.object.write_string.format(new_value) + if 'values_list' in string: return string.strip('VALUES ') elif 'RANGE' in string: return string.strip('RANGE ') @@ -209,10 +205,10 @@ def write(self, string): def initialize_properties(self): self.add_device_property({ - 'name': 'values', + 'name': 'value', 'write_string': 'VALUES {}', 'query_string': 'VALUES?', - 'values': [2, 'x', False, (1, 10), ['1', '10']], + 'values_list': [2, 'x', False, (1, 10), ['1', '10']], 'return_type': str }) diff --git a/pyscan/drivers/testing/test_voltage.py b/pyscan/drivers/testing/test_voltage.py index bc0d242c..ac3b0683 100644 --- a/pyscan/drivers/testing/test_voltage.py +++ b/pyscan/drivers/testing/test_voltage.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from .test_voltage_driver import TestVoltageDriver +from ..instrument_driver.abstract_driver import AbstractDriver -class TestVoltage(TestVoltageDriver): +class TestVoltage(AbstractDriver): ''' Class that mimics the operation of a simple voltage source. @@ -39,6 +39,25 @@ def __init__(self, debug=False, instrument=None, *arg, **kwarg): self._version = "1.0.0" self.black_list_for_testing = [] + def query_property(self, settings_obj): + + string = settings_obj.query_string + + if string == 'VOLT?': + return str(self._voltage) + elif string == 'POW?': + return str(self._power) + elif string == 'OUTP?': + if self._output_state == 'off': + return '0' + if self._output_state == 'on': + return '1' + return str(self._output_state) + + def write_property(self, settings_obj, new_value): + + return str(new_value) + def initialize_properties(self): self.add_device_property({ @@ -53,7 +72,7 @@ def initialize_properties(self): 'name': 'power', 'write_string': 'POW {}', 'query_string': 'POW?', - 'values': [1, 10], + 'values_list': [1, 10], 'return_type': int }) diff --git a/pyscan/drivers/testing/test_voltage_driver.py b/pyscan/drivers/testing/test_voltage_driver.py deleted file mode 100644 index 29f89b58..00000000 --- a/pyscan/drivers/testing/test_voltage_driver.py +++ /dev/null @@ -1,27 +0,0 @@ -from .abstract_driver import AbstractDriver - - -class TestDriver(AbstractDriver): - - def query_property(self, settings): - string = settings.query_string - if string == 'VOLT?': - return str(self._voltage) - elif string == 'POW?': - return str(self._power) - elif string == 'OUTP?': - if self._output_state == 'off': - return '0' - if self._output_state == 'on': - return '1' - # leave for the sake of your personal sanity - return str(self._output_state) - - def write_property(self, settings): - string = settings.write_string - if 'VOLT' in string: - return string.strip('VOLT ') - elif 'POW' in string: - return string.strip('POW ') - elif 'OUTP' in string: - return string.strip('OUTP ') diff --git a/pyscan/drivers/tpi1002a.py b/pyscan/drivers/tpi1002a.py index 21e67e94..4509245b 100644 --- a/pyscan/drivers/tpi1002a.py +++ b/pyscan/drivers/tpi1002a.py @@ -137,7 +137,7 @@ def get_instrument_property(self, obj, settings, debug=False): @check_errors # unfortunately TPI tends to send back the "ok" code, then the error (when we don't read it). def set_values_property(self, obj, new_value, settings): - values = settings['values'] + values = settings['values_list'] if new_value not in values: print('Value error: %s must be one of %s' % (settings['name'], ', '.join(str(values)))) return @@ -169,7 +169,7 @@ def initialize_properties(self): 'return_bytes': 1, 'return_type': lambda x: {'00': False, '01': True}[x.hex()], 'ok_bytes': 0, - 'values': [0, 1]}) + 'values_list': [0, 1]}) self.add_device_property({ 'name': 'output', @@ -178,7 +178,7 @@ def initialize_properties(self): 'return_bytes': 1, 'return_type': lambda x: {'00': False, '01': True}[x.hex()], 'ok_bytes': 0, - 'values': [0, 1]}) + 'values_list': [0, 1]}) self.add_device_property({ 'name': 'model_number', diff --git a/pyscan/drivers/zurichhf2.py b/pyscan/drivers/zurichhf2.py index b046187b..0f55bc5a 100644 --- a/pyscan/drivers/zurichhf2.py +++ b/pyscan/drivers/zurichhf2.py @@ -26,7 +26,7 @@ def write_string(self, string, new_value): def add_device_property(self, settings): - if 'values' in settings: + if 'values_list' in settings: set_function = self.set_values_property elif 'range' in settings: set_function = self.set_range_property @@ -62,7 +62,7 @@ def get_instrument_property(self, obj, settings, debug=False): return value def set_values_property(self, obj, new_value, settings): - values = settings['values'] + values = settings['values_list'] if new_value in values: if not self.debug: @@ -167,21 +167,21 @@ def init_settings(self): 'name': 'output', 'write_string': '/DEV{}/SIGOUTS/{}/ON '.format(self.dev, self.channel), 'query_string': '/DEV{}/SIGOUTS/{}/ON'.format(self.dev, self.channel), - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'add', 'write_string': '/DEV{}/SIGOUTS/{}/ADD'.format(self.dev, self.channel), 'query_string': '/DEV{}/SIGOUTS/{}/ADD'.format(self.dev, self.channel), - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'range', 'write_string': '/DEV{}/SIGOUTS/{}/RANGE'.format(self.dev, self.channel), 'query_string': '/DEV{}/SIGOUTS/{}/RANGE'.format(self.dev, self.channel), - 'values': [0.01, 0.1, 1, 10], + 'values_list': [0.01, 0.1, 1, 10], 'return_type': float}) self.add_device_property({ @@ -199,7 +199,7 @@ def init_settings(self): self.dev, self.channel, self.wf), 'query_string': '/DEV{}/SIGOUTS/{}/ENABLES/{}'.format( self.dev, self.channel, self.wf), - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ @@ -208,14 +208,14 @@ def init_settings(self): self.dev, self.channel, self.wf), 'query_string': '/DEV{}/SIGOUTS/{}/WAVEFORMS/{}'.format( self.dev, self.channel, self.wf), - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'range', 'write_string': '/DEV{}/SIGOUTS/{}/OFFSET'.format(self.dev, self.channel), 'query_string': '/DEV{}/SIGOUTS/{}/OFFSET'.format(self.dev, self.channel), - 'values': [0.01, 0.1, 1, 10], + 'values_list': [0.01, 0.1, 1, 10], 'return_type': float}) # Oscillator @@ -263,14 +263,14 @@ def init_settings(self): 'name': 'adc', 'write_string': '/DEV{}/DEMODS/{}/ADCSELECT'.format(self.dev, self.channel), 'query_string': '/DEV{}/DEMODS/{}/ADCSELECT'.format(self.dev, self.channel), - 'values': [0, 1, 2, 3, 4, 5], + 'values_list': [0, 1, 2, 3, 4, 5], 'return_type': int}) self.add_device_property({ 'name': 'filter_order', 'write_string': '/DEV{}/DEMODS/{}/ORDER'.format(self.dev, self.channel), 'query_string': '/DEV{}/DEMODS/{}/ORDER'.format(self.dev, self.channel), - 'values': [6, 12, 18, 24, 30, 36, 42, 48], + 'values_list': [6, 12, 18, 24, 30, 36, 42, 48], 'return_type': int}) self.add_device_property({ @@ -305,14 +305,14 @@ def init_settings(self): 'name': 'oscillator', 'write_string': '/DEV{}/DEMODS/{}/OSCSELECT'.format(self.dev, self.channel), 'query_string': '/DEV{}/DEMODS/{}/OSCSELECT'.format(self.dev, self.channel), - 'values': [0, 1, 2, 3, 4, 5, 6, 7], + 'values_list': [0, 1, 2, 3, 4, 5, 6, 7], 'return_type': int}) self.add_device_property({ 'name': 'sinc_filter', 'write_string': '/DEV{}/DEMODS/{}/SINC'.format(self.dev, self.channel), 'query_string': '/DEV{}/DEMODS/{}/SINC'.format(self.dev, self.channel), - 'values': [0, 1], + 'values_list': [0, 1], 'return_type': int}) def get_sample(self): @@ -434,21 +434,21 @@ def init_settings(self): 'name': 'trigger_type', 'write_string': '/dataAcquisitionModule/triggertype', 'query_string': '/dataAcquisitionModule/triggertype', - 'values': [0, 1, 2, 3, 4], + 'values_list': [0, 1, 2, 3, 4], 'return_type': int}) self.add_device_property({ 'name': 'trigger_type', 'write_string': '/dataAcquisitionModule/triggertype', 'query_string': '/dataAcquisitionModule/triggertype', - 'values': [0, 1, 2, 3, 4], + 'values_list': [0, 1, 2, 3, 4], 'return_type': int}) self.add_device_property({ 'name': 'trigger_edge', 'write_string': '/dataAcquisitionModule/triggeredge', 'query_string': '/dataAcquisitionModule/triggeredge', - 'values': [0, 1, 2, 3, 4], + 'values_list': [0, 1, 2, 3, 4], 'return_type': int}) self.add_device_property({ @@ -462,7 +462,7 @@ def init_settings(self): 'name': 'trigger_hysteresis', 'write_string': '/dataAcquisitionModule/triggerhysteresis', 'query_string': '/dataAcquisitionModule/triggerhysteresis', - 'values': [-1, 1], + 'values_list': [-1, 1], 'return_type': float}) self.add_device_property({ @@ -497,7 +497,7 @@ def init_settings(self): 'name': 'grid_mode', 'write_string': '/dataAcquisitionModule/grid/mode', 'query_string': '/dataAcquisitionModule/grid/mode', - 'values': [0, 1, 2, 3, 4], + 'values_list': [0, 1, 2, 3, 4], 'return_type': int}) self.add_device_property({ diff --git a/test/drivers/test_property_settings/test_dict_values_settings.py b/test/drivers/test_property_settings/test_dict_values_settings.py new file mode 100644 index 00000000..c6b9937a --- /dev/null +++ b/test/drivers/test_property_settings/test_dict_values_settings.py @@ -0,0 +1,51 @@ +import pytest +from pyscan.drivers.property_settings.dict_property_settings import DictPropertySettings, DictValueException + + +@pytest.fixture +def settings(): + return DictPropertySettings({ + 'name': 'test', + 'dict_values': { + 1: 'one', + 2: 'two', + 'three': 'three', + 'four': 'two', # Repeated value + 5: 'five', + 'six': 'six', + 7: 'seven', + 'eight': 'six' # Repeated value + } + }) + + +def test_validate_set_value_valid_key(settings): + assert settings.validate_set_value(1) is True + assert settings.validate_set_value('three') is True + + +def test_validate_set_value_invalid_key(settings): + with pytest.raises(DictValueException): + settings.validate_set_value('invalid_key') + + +def test_format_write_value(settings): + assert settings.format_write_value(1) == 'one' + assert settings.format_write_value('three') == 'three' + + +def test_format_query_return(settings): + assert settings.format_query_return('one') == 1 + assert settings.format_query_return('three') == 'three' + + +def test_format_query_return_invalid(settings): + with pytest.raises(KeyError): + settings.format_query_return('invalid_value') + + +def test_find_first_key(settings): + assert settings.find_first_key('one') == 1 + assert settings.find_first_key('three') == 'three' + assert settings.find_first_key('two') in [2, 'four'] # Adjusted for repeated value + assert settings.find_first_key('six') in ['six', 'eight'] # Adjusted for repeated value \ No newline at end of file diff --git a/test/drivers/test_property_settings/test_indexed_values_settings.py b/test/drivers/test_property_settings/test_indexed_values_settings.py new file mode 100644 index 00000000..b1da2c07 --- /dev/null +++ b/test/drivers/test_property_settings/test_indexed_values_settings.py @@ -0,0 +1,44 @@ +import pytest +from pyscan.drivers.property_settings.indexed_property_settings import IndexedPropertySettings, IndexedValueException + + +@pytest.fixture +def settings(): + return IndexedPropertySettings({ + 'name': 'test', + 'indexed_values': [0, 1, 2, 3, 4, 'a', 'b', 'c', 'd', True, False]}) + + +def test_validate_set_value_within_index(settings): + assert settings.validate_set_value(2) is True + + +def test_validate_set_value_below_index(settings): + with pytest.raises(IndexedValueException): + settings.validate_set_value(-1) + + +def test_validate_set_value_above_index(settings): + with pytest.raises(IndexedValueException): + settings.validate_set_value(5) + + +def test_format_write_value(settings): + assert settings.format_write_value(3) == 3 + + +def test_format_query_return(settings): + assert settings.format_query_return('2') == 2 + + +def test_validate_set_value_edge_case_lower(settings): + assert settings.validate_set_value(0) is True + + +def test_validate_set_value_edge_case_upper(settings): + assert settings.validate_set_value(4) is True + + +def test_format_query_return_invalid(settings): + with pytest.raises(ValueError): + settings.format_query_return('invalid') diff --git a/test/drivers/test_property_settings/test_range_property_settings.py b/test/drivers/test_property_settings/test_range_property_settings.py new file mode 100644 index 00000000..0d802c73 --- /dev/null +++ b/test/drivers/test_property_settings/test_range_property_settings.py @@ -0,0 +1,46 @@ +import pytest +from pyscan.drivers.property_settings.range_property_settings import RangePropertySettings, RangeException + + +@pytest.fixture +def settings(): + return RangePropertySettings({'name': 'test', 'range': (0, 10)}) + + +def test_validate_set_value_within_range(settings): + assert settings.validate_set_value(5) is True + + +def test_validate_set_value_below_range(settings): + with pytest.raises(RangeException): + settings.validate_set_value(-1) + + +def test_validate_set_value_above_range(settings): + with pytest.raises(RangeException): + settings.validate_set_value(11) + + +def test_format_write_value(settings): + assert settings.format_write_value(5) == 5 + + +def test_format_query_return(settings): + assert settings.format_query_return('5.5') == 5.5 + + +def test_validate_set_value_edge_case_lower(settings): + assert settings.validate_set_value(0) is True + + +def test_validate_set_value_edge_case_upper(settings): + assert settings.validate_set_value(10) is True + + +def test_format_query_return_integer(settings): + assert settings.format_query_return('5') == 5.0 + + +def test_format_query_return_invalid(settings): + with pytest.raises(ValueError): + settings.format_query_return('invalid') diff --git a/test/drivers/test_property_settings/test_values_property_setttings.py b/test/drivers/test_property_settings/test_values_property_setttings.py new file mode 100644 index 00000000..ede184aa --- /dev/null +++ b/test/drivers/test_property_settings/test_values_property_setttings.py @@ -0,0 +1,33 @@ +import pytest +from pyscan.drivers.property_settings.values_property_settings import ValuesPropertySettings, ValueException + +@pytest.fixture +def settings(): + return ValuesPropertySettings({'name': 'test', 'values_list': [1, 2, 3, 'a', 'b', 'c']}) + + +def test_validate_set_value_valid(settings): + assert settings.validate_set_value(1) is True + assert settings.validate_set_value('a') is True + + +def test_validate_set_value_invalid(settings): + with pytest.raises(ValueException): + settings.validate_set_value(4) + with pytest.raises(ValueException): + settings.validate_set_value('d') + + +def test_format_write_value(settings): + assert settings.format_write_value(1) == 1 + assert settings.format_write_value('a') == 'a' + + +def test_format_query_return_valid(settings): + assert settings.format_query_return('1') == 1 + assert settings.format_query_return('a') == 'a' + + +def test_format_query_return_invalid(settings): + with pytest.raises(ValueError): + settings.format_query_return('d') \ No newline at end of file From 14fb411afd7a78aa6ce613941c55bdbb1ce30161 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 28 Oct 2024 11:41:34 -0600 Subject: [PATCH 05/29] Reverted back to 'values' from 'values_list' --- pyscan/drivers/actonsp2300.py | 2 +- pyscan/drivers/agilent33500.py | 36 +++++++++++++-------------- pyscan/drivers/agilent8267d.py | 6 ++--- pyscan/drivers/agilent8275n.py | 2 +- pyscan/drivers/agilentdso900series.py | 20 +++++++-------- pyscan/drivers/bkprecision9130b.py | 4 +-- pyscan/drivers/heliotis/helios_sdk.py | 10 ++++---- pyscan/drivers/keithley2260b.py | 4 +-- pyscan/drivers/keithley2400.py | 6 ++--- pyscan/drivers/pylonsdk.py | 10 ++++---- pyscan/drivers/stanford396.py | 12 ++++----- pyscan/drivers/stanford400.py | 16 ++++++------ pyscan/drivers/stanford830.py | 2 +- pyscan/drivers/tpi1002a.py | 6 ++--- pyscan/drivers/zurichhf2.py | 34 ++++++++++++------------- 15 files changed, 85 insertions(+), 85 deletions(-) diff --git a/pyscan/drivers/actonsp2300.py b/pyscan/drivers/actonsp2300.py index ac8e4102..9f75cb66 100644 --- a/pyscan/drivers/actonsp2300.py +++ b/pyscan/drivers/actonsp2300.py @@ -114,7 +114,7 @@ def inner(resp): 'name': 'grating', 'write_string': '{} GRATING', 'query_string': '?GRATING', - 'values_list': [1, 2, 3, 4, 5, 6, 7, 8, 9], + 'values': [1, 2, 3, 4, 5, 6, 7, 8, 9], 'return_type': parse_response(int)}) self.add_device_property({ diff --git a/pyscan/drivers/agilent33500.py b/pyscan/drivers/agilent33500.py index 7d0428e5..326019c6 100644 --- a/pyscan/drivers/agilent33500.py +++ b/pyscan/drivers/agilent33500.py @@ -117,21 +117,21 @@ def initialize_properties(self): 'name': 'voltage_autorange_chan1', 'write_string': 'SOUR1:VOLT:RANG:AUTO {}', 'query_string': 'SOUR1:VOLT:RANG:AUTO?', - 'values_list': [0, 'off', 1, 'on'], + 'values': [0, 'off', 1, 'on'], 'return_type': str}) self.add_device_property({ 'name': 'voltage_autorange_chan2', 'write_string': 'SOUR2:VOLT:RANG:AUTO {}', 'query_string': 'SOUR2:VOLT:RANG:AUTO?', - 'values_list': [0, 'off', 1, 'on'], + 'values': [0, 'off', 1, 'on'], 'return_type': str}) self.add_device_property({ 'name': 'function_chan1', 'write_string': 'SOUR1:FUNC {}', 'query_string': 'SOUR1:FUNC?', - 'values_list': [ + 'values': [ "SIN", "SQU", "TRI", @@ -147,7 +147,7 @@ def initialize_properties(self): 'name': 'function_chan2', 'write_string': 'SOUR2:FUNC {}', 'query_string': 'SOUR2:FUNC?', - 'values_list': [ + 'values': [ "SIN", "SQU", "TRI", @@ -163,28 +163,28 @@ def initialize_properties(self): 'name': 'arb_advance_mode_chan1', 'write_string': 'SOUR1:FUNC:ARB:ADV {}', 'query_string': 'SOUR1:FUNC:ARB:ADV?', - 'values_list': ["TRIG", "SRAT"], + 'values': ["TRIG", "SRAT"], 'return_type': str}) self.add_device_property({ 'name': 'arb_advance_mode_chan2', 'write_string': 'SOUR2:FUNC:ARB:ADV {}', 'query_string': 'SOUR2:FUNC:ARB:ADV?', - 'values_list': ["TRIG", "SRAT"], + 'values': ["TRIG", "SRAT"], 'return_type': str}) self.add_device_property({ 'name': 'arb_filter_chan1', 'write_string': 'SOUR1:FUNC:ARB:FILT {}', 'query_string': 'SOUR1:FUNC:ARB:FILT?', - 'values_list': ["NORM", "STEP", "OFF"], + 'values': ["NORM", "STEP", "OFF"], 'return_type': str}) self.add_device_property({ 'name': 'arb_filter_chan2', 'write_string': 'SOUR2:FUNC:ARB:FILT {}', 'query_string': 'SOUR2:FUNC:ARB:FILT?', - 'values_list': ["NORM", "STEP", "OFF"], + 'values': ["NORM", "STEP", "OFF"], 'return_type': str}) self.add_device_property({ @@ -205,14 +205,14 @@ def initialize_properties(self): 'name': 'burst_mode_chan1', 'write_string': 'SOUR1:BURS:MODE {}', 'query_string': 'SOUR1:BURS:MODE?', - 'values_list': ['TRIG', 'GAT'], + 'values': ['TRIG', 'GAT'], 'return_type': str}) self.add_device_property({ 'name': 'burst_mode_chan2', 'write_string': 'SOUR2:BURS:MODE {}', 'query_string': 'SOUR2:BURS:MODE?', - 'values_list': ['TRIG', 'GAT'], + 'values': ['TRIG', 'GAT'], 'return_type': str}) self.add_device_property({ @@ -233,56 +233,56 @@ def initialize_properties(self): 'name': 'burst_state_chan1', 'write_string': 'SOUR1:BURS:STAT {}', 'query_string': 'SOUR1:BURS:STAT?', - 'values_list': [0, 'Off', 1, 'ON'], + 'values': [0, 'Off', 1, 'ON'], 'return_type': str}) self.add_device_property({ 'name': 'burst_state_chan2', 'write_string': 'SOUR2:BURS:STAT {}', 'query_string': 'SOUR2:BURS:STAT?', - 'values_list': [0, 'Off', 1, 'ON'], + 'values': [0, 'Off', 1, 'ON'], 'return_type': str}) self.add_device_property({ 'name': 'output_chan1', 'write_string': 'OUTP1 {}', 'query_string': 'OUTP1?', - 'values_list': [0, 'Off', 1, 'ON'], + 'values': [0, 'Off', 1, 'ON'], 'return_type': str}) self.add_device_property({ 'name': 'output_chan2', 'write_string': 'OUTP2 {}', 'query_string': 'OUTP2?', - 'values_list': [0, 'Off', 1, 'ON'], + 'values': [0, 'Off', 1, 'ON'], 'return_type': str}) self.add_device_property({ 'name': 'output_load_chan1', 'write_string': 'OUTP1:LOAD {}', 'query_string': 'OUTP1:LOAD?', - 'values_list': [50, 'INF'], + 'values': [50, 'INF'], 'return_type': float}) self.add_device_property({ 'name': 'output_load_chan2', 'write_string': 'OUTP2:LOAD {}', 'query_string': 'OUTP2:LOAD?', - 'values_list': [50, 'INF'], + 'values': [50, 'INF'], 'return_type': float}) self.add_device_property({ 'name': 'trigger_source_chan1', 'write_string': 'TRIG1:SOUR {}', 'query_string': 'TRIG1:SOUR?', - 'values_list': ["IMM", "EXT", "TIM", "BUS"], + 'values': ["IMM", "EXT", "TIM", "BUS"], 'return_type': str}) self.add_device_property({ 'name': 'trigger_source_chan2', 'write_string': 'TRIG2:SOUR {}', 'query_string': 'TRIG2:SOUR?', - 'values_list': ["IMM", "EXT", "TIM", "BUS"], + 'values': ["IMM", "EXT", "TIM", "BUS"], 'return_type': str}) self.update_properties() diff --git a/pyscan/drivers/agilent8267d.py b/pyscan/drivers/agilent8267d.py index 2249f600..d7b0bb26 100644 --- a/pyscan/drivers/agilent8267d.py +++ b/pyscan/drivers/agilent8267d.py @@ -51,7 +51,7 @@ def initialize_properties(self): 'name': 'frequency_mode', 'write_string': ':SOUR:FREQ:MODE {}', 'query_string': ':SOUR:FREQ:MODE?', - 'values_list': ['CW', 'LIST'], + 'values': ['CW', 'LIST'], 'return_type': str}) self.add_device_property({ @@ -66,12 +66,12 @@ def initialize_properties(self): 'name': 'output', 'write_string': ':OUTP:STAT {}', 'query_string': ':OUTP:STAT?', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'modulation', 'write_string': ':OUTP:MOD:STAT {}', 'query_string': ':OUTP:MOD:STAT?', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) diff --git a/pyscan/drivers/agilent8275n.py b/pyscan/drivers/agilent8275n.py index 5df0e065..e7ba9540 100644 --- a/pyscan/drivers/agilent8275n.py +++ b/pyscan/drivers/agilent8275n.py @@ -57,5 +57,5 @@ def initialize_properties(self): 'name': 'output', 'write_string': ':OUTP:STAT {}', 'query_string': ':OUTP:STAT?', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) diff --git a/pyscan/drivers/agilentdso900series.py b/pyscan/drivers/agilentdso900series.py index b76306ba..7e953d49 100644 --- a/pyscan/drivers/agilentdso900series.py +++ b/pyscan/drivers/agilentdso900series.py @@ -83,7 +83,7 @@ def initialize_properties(self): 'name': 'sample_rate', 'write_string': ':ACQ:SRAT {}', 'query_string': 'ACQ:SRAT?', - 'values_list': [ + 'values': [ 1e2, 2e2, 2.5e2, 4e2, 5e2, 1e3, 2e3, 2.5e3, 4e3, 5e3, 1e4, 2e4, 2.5e4, 4e4, 5e4, @@ -98,14 +98,14 @@ def initialize_properties(self): 'name': 'trigger_sweep', 'write_string': ':TRIG:SWE {}', 'query_string': ':TRIG:SWE?', - 'values_list': ['AUTO', 'TRIG', 'SING'], + 'values': ['AUTO', 'TRIG', 'SING'], 'return_type': str}) self.add_device_property({ 'name': 'trigger_mode', 'write_string': ':TRIG:MODE {}', 'query_string': ':TRIG:MODE?', - 'values_list': ['EDGE', 'GLIT', 'PATT', 'STAT', 'DEL', + 'values': ['EDGE', 'GLIT', 'PATT', 'STAT', 'DEL', 'TIM', 'TV', 'COMM', 'RUNT', 'SEQ', 'SHOL', 'TRAN', 'WIND', 'PWID', 'ADV', 'EDGE', 'SBUS1', 'SBUS2', 'SBUS3', 'SBUS3'], @@ -115,7 +115,7 @@ def initialize_properties(self): 'name': 'trigger_source', 'write_string': ':TRIG:EDGE:SOUR {}', 'query_string': ':TRIG:EDGE:SOUR?', - 'values_list': ['CHAN1', 'CHAN2', 'CHAN3', 'CHAN4', 'E'], + 'values': ['CHAN1', 'CHAN2', 'CHAN3', 'CHAN4', 'E'], 'return_type': str}) self.add_device_property({ @@ -129,7 +129,7 @@ def initialize_properties(self): 'name': 'time_reference', 'write_string': ':TIM:REF {}', 'query_string': ':TIM:REF?', - 'values_list': ['LEFT', 'RIGHT', 'CENT'], + 'values': ['LEFT', 'RIGHT', 'CENT'], 'return_type': str}) self.add_device_property({ @@ -143,14 +143,14 @@ def initialize_properties(self): 'name': 'acquire_type', 'write_string': 'ACQ:TYPE {}', 'query_string': 'ACQ:TYPE?', - 'values_list': ['NORM', 'AVER', 'HRES', 'PEAK'], + 'values': ['NORM', 'AVER', 'HRES', 'PEAK'], 'return_type': str}) self.add_device_property({ 'name': 'acquire_mode', 'write_string': 'ACQ:MODE {}', 'query_string': 'ACQ:MODE?', - 'values_list': ['ETIM', 'RTIM', 'PDET', 'HRES', 'SEGM', + 'values': ['ETIM', 'RTIM', 'PDET', 'HRES', 'SEGM', 'SEGP', 'SEGH'], 'return_type': str}) @@ -187,7 +187,7 @@ def initialize_properties(self): 'name': 'all_segment_download', 'write_string': ':WAV:SEGM:ALL {}', 'query_string': ':WAV:SEGM:ALL?', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': float}) self.add_device_property({ @@ -229,14 +229,14 @@ def initialize_properties(self): 'name': 'waveform_format', 'write_string': ':WAV:FORM {}', 'query_string': ':WAV:FORM?', - 'values_list': ['BYTE', 'WORD', 'ASCII'], + 'values': ['BYTE', 'WORD', 'ASCII'], 'return_type': str}) self.add_device_property({ 'name': 'waveform_source', 'write_string': ':WAV:SOUR CHAN{}', 'query_string': ':WAV:SOUR?', - 'values_list': ['1', '2', '3', '4', 'MATH'], + 'values': ['1', '2', '3', '4', 'MATH'], 'return_type': str}) def single(self): diff --git a/pyscan/drivers/bkprecision9130b.py b/pyscan/drivers/bkprecision9130b.py index 38cbfbaa..9247246d 100644 --- a/pyscan/drivers/bkprecision9130b.py +++ b/pyscan/drivers/bkprecision9130b.py @@ -41,14 +41,14 @@ def initialize_properties(self): 'name': 'channel', 'write_string': 'INST CH{}', 'query_string': 'INST?', - 'values_list': [1, 2, 3], + 'values': [1, 2, 3], 'return_type': str}) self.add_device_property({ 'name': 'output', 'write_string': 'OUTP {}', 'query_string': 'OUTP?', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ diff --git a/pyscan/drivers/heliotis/helios_sdk.py b/pyscan/drivers/heliotis/helios_sdk.py index bde6995e..4b4cc89a 100644 --- a/pyscan/drivers/heliotis/helios_sdk.py +++ b/pyscan/drivers/heliotis/helios_sdk.py @@ -28,7 +28,7 @@ def add_device_property(self, settings): Returns function that generates the property of the class ''' - if 'values_list' in settings: + if 'values' in settings: set_function = self.set_values_property elif 'range' in settings: set_function = self.set_range_property @@ -69,18 +69,18 @@ def get_instrument_property(self, obj, settings, debug=False): def set_values_property(self, obj, new_value, settings): ''' - Generator function for settings dictionary with 'values_list item - Check that new_value is in settings['values_list'], if not, rejects command + Generator function for settings dictionary with 'values item + Check that new_value is in settings['values'], if not, rejects command Args: obj - parent class object new_value - new_value to be set on instrument - settings - dictionarky with ['values_list'] item + settings - dictionarky with ['values'] item returns None ''' - values = settings['values_list'] + values = settings['values'] if new_value in values: if not self.debug: diff --git a/pyscan/drivers/keithley2260b.py b/pyscan/drivers/keithley2260b.py index 127e4b8f..474fad30 100644 --- a/pyscan/drivers/keithley2260b.py +++ b/pyscan/drivers/keithley2260b.py @@ -286,14 +286,14 @@ def initialize_properties(self): 'name': 'transient_trigger_source', 'write_string': 'TRIG:TRAN:SOUR {}', 'query_string': 'TRIG:TRAN:SOUR?', - 'values_list': ['BUS', 'IMM'], + 'values': ['BUS', 'IMM'], 'return_type': str}) self.add_device_property({ 'name': 'output_trigger_source', 'write_string': 'TRIG:OUTP:SOUR {}', 'query_string': 'TRIG:OUTP:SOUR?', - 'values_list': ['BUS', 'IMM'], + 'values': ['BUS', 'IMM'], 'return_type': str}) def measure_current(self): diff --git a/pyscan/drivers/keithley2400.py b/pyscan/drivers/keithley2400.py index 7c61fe4d..edc443a3 100644 --- a/pyscan/drivers/keithley2400.py +++ b/pyscan/drivers/keithley2400.py @@ -61,7 +61,7 @@ def init_settings(self): self.source_mode_settings['string_values'] = ['FIX'] self.source_voltage_range_settings = {} - self.source_voltage_range_settings['values_list'] = [0.21, 2.1, 21] + self.source_voltage_range_settings['values'] = [0.21, 2.1, 21] self.source_current_range_settings = {} self.source_current_range_settings['range'] = [ @@ -75,10 +75,10 @@ def init_settings(self): self.sense_settings['string_values'] = ['CURR', 'VOLT'] self.sense_voltage_range_settings = {} - self.sense_voltage_range_settings['values_list'] = [] + self.sense_voltage_range_settings['values'] = [] self.sense_current_range_settings = {} - self.sense_current_range_settings['values_list'] = [1e-6, 1e-5, 1e-4] + self.sense_current_range_settings['values'] = [1e-6, 1e-5, 1e-4] self.current_compliance_settings = {} self.current_compliance_settings['range'] = [0, 0.105] diff --git a/pyscan/drivers/pylonsdk.py b/pyscan/drivers/pylonsdk.py index 6ca5ccec..49a1c302 100644 --- a/pyscan/drivers/pylonsdk.py +++ b/pyscan/drivers/pylonsdk.py @@ -17,7 +17,7 @@ def add_device_property(self, settings): Returns function that generates the property of the class ''' - if 'values_list' in settings: + if 'values' in settings: set_function = self.set_values_property elif 'range' in settings: set_function = self.set_range_property @@ -58,18 +58,18 @@ def get_instrument_property(self, obj, settings, debug=False): def set_values_property(self, obj, new_value, settings): ''' - Generator function for settings dictionary with 'values_list item - Check that new_value is in settings['values_list'], if not, rejects command + Generator function for settings dictionary with 'values item + Check that new_value is in settings['values'], if not, rejects command Args: obj - parent class object new_value - new_value to be set on instrument - settings - dictionarky with ['values_list'] item + settings - dictionarky with ['values'] item returns None ''' - values = settings['values_list'] + values = settings['values'] if new_value in values: if not self.debug: diff --git a/pyscan/drivers/stanford396.py b/pyscan/drivers/stanford396.py index 4908d7d1..085d5112 100755 --- a/pyscan/drivers/stanford396.py +++ b/pyscan/drivers/stanford396.py @@ -75,7 +75,7 @@ def initialize_properties(self): 'name': 'output', 'write_string': 'ENBR {}', 'query_string': 'ENBR?', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': float}) # Common settings @@ -107,21 +107,21 @@ def initialize_properties(self): 'name': 'modulation', 'write_string': 'MODL {}', 'query_string': 'MODL?', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'input_coupling', 'write_string': 'COUP {}', 'query_string': 'COUP?', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'modulation_type', 'write_string': 'TYPE {}', 'query_string': 'TYPE?', - 'values_list': [ + 'values': [ 'am', 'fm', 'phim', 'sweep', 'pulse', 'blank', 'qam', 'cpm', 'vbs'], 'return_type': int}) @@ -130,7 +130,7 @@ def initialize_properties(self): 'name': 'modulation_function', 'write_string': 'SFNC {}', 'query_string': 'SFNC?', - 'values_list': ['sin', 'ramp', 'triangle', 'external', 'waveform'], + 'values': ['sin', 'ramp', 'triangle', 'external', 'waveform'], 'return_type': int}) self.add_device_property({ @@ -151,7 +151,7 @@ def initialize_properties(self): 'name': 'sweep_modulation_function', 'write_string': 'SFNC {}', 'query_string': 'SFNC?', - 'values_list': [0, 1, 2, 5, 11], + 'values': [0, 1, 2, 5, 11], 'return_type': int}) self.add_device_property({ diff --git a/pyscan/drivers/stanford400.py b/pyscan/drivers/stanford400.py index 67de03c8..b33f4bc5 100755 --- a/pyscan/drivers/stanford400.py +++ b/pyscan/drivers/stanford400.py @@ -106,42 +106,42 @@ def initialize_properties(self): 'name': 'counting_mode', 'write_string': 'CM {}', 'query_string': 'CM', - 'values_list': [0, 1, 2, 3], + 'values': [0, 1, 2, 3], 'return_type': int}) self.add_device_property({ 'name': 'counter_input_A', 'write_string': 'CI 0, {}', 'query_string': 'CI 0', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'counter_input_B', 'write_string': 'CI 1, {}', 'query_string': 'CI 1', - 'values_list': [1, 2], + 'values': [1, 2], 'return_type': int}) self.add_device_property({ 'name': 'counter_input_T', 'write_string': 'CI 2, {}', 'query_string': 'CI 2', - 'values_list': [0, 1, 2, 3], + 'values': [0, 1, 2, 3], 'return_type': int}) self.add_device_property({ 'name': 'n_periods', 'write_string': 'NP {}', 'query_string': 'NP', - 'values_list': list(range(2001)), + 'values': list(range(2001)), 'return_type': int}) self.add_device_property({ 'name': 'end_scan_mode', 'write_string': 'NE {}', 'query_string': 'NE', - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ @@ -269,7 +269,7 @@ def initialize_properties(self): 'name': 'gate_mode_A', 'write_string': 'GM 0, {}', 'query_string': 'GM 0', - 'values_list': [0, 1, 2], + 'values': [0, 1, 2], 'return_type': int}) self.add_device_property({ @@ -290,7 +290,7 @@ def initialize_properties(self): 'name': 'gate_mode_B', 'write_string': 'GM 1, {}', 'query_string': 'GM 1', - 'values_list': [0, 1, 2], + 'values': [0, 1, 2], 'return_type': int}) self.add_device_property({ diff --git a/pyscan/drivers/stanford830.py b/pyscan/drivers/stanford830.py index eb4c9b1d..25c3d4ae 100644 --- a/pyscan/drivers/stanford830.py +++ b/pyscan/drivers/stanford830.py @@ -831,7 +831,7 @@ def snap_xy(self): # wait_dict = {6: 5 * tc, 12: 7 * tc, 18: 9 * tc, 24: 10 * tc} # bw_dict = {6: 1 / (4 * tc), 12: 1 / (8 * tc), 18: 3 / (32 * tc), 24: 5 / (64 * tc)} - # available_rates = self.sample_rate_['values_list'abs][:-2] + # available_rates = self.sample_rate_['values'abs][:-2] # sample_rate = available_rates[ # np.bisect_left(available_rates, 1 / wait_dict[slope])] diff --git a/pyscan/drivers/tpi1002a.py b/pyscan/drivers/tpi1002a.py index 4509245b..21e67e94 100644 --- a/pyscan/drivers/tpi1002a.py +++ b/pyscan/drivers/tpi1002a.py @@ -137,7 +137,7 @@ def get_instrument_property(self, obj, settings, debug=False): @check_errors # unfortunately TPI tends to send back the "ok" code, then the error (when we don't read it). def set_values_property(self, obj, new_value, settings): - values = settings['values_list'] + values = settings['values'] if new_value not in values: print('Value error: %s must be one of %s' % (settings['name'], ', '.join(str(values)))) return @@ -169,7 +169,7 @@ def initialize_properties(self): 'return_bytes': 1, 'return_type': lambda x: {'00': False, '01': True}[x.hex()], 'ok_bytes': 0, - 'values_list': [0, 1]}) + 'values': [0, 1]}) self.add_device_property({ 'name': 'output', @@ -178,7 +178,7 @@ def initialize_properties(self): 'return_bytes': 1, 'return_type': lambda x: {'00': False, '01': True}[x.hex()], 'ok_bytes': 0, - 'values_list': [0, 1]}) + 'values': [0, 1]}) self.add_device_property({ 'name': 'model_number', diff --git a/pyscan/drivers/zurichhf2.py b/pyscan/drivers/zurichhf2.py index 0f55bc5a..b046187b 100644 --- a/pyscan/drivers/zurichhf2.py +++ b/pyscan/drivers/zurichhf2.py @@ -26,7 +26,7 @@ def write_string(self, string, new_value): def add_device_property(self, settings): - if 'values_list' in settings: + if 'values' in settings: set_function = self.set_values_property elif 'range' in settings: set_function = self.set_range_property @@ -62,7 +62,7 @@ def get_instrument_property(self, obj, settings, debug=False): return value def set_values_property(self, obj, new_value, settings): - values = settings['values_list'] + values = settings['values'] if new_value in values: if not self.debug: @@ -167,21 +167,21 @@ def init_settings(self): 'name': 'output', 'write_string': '/DEV{}/SIGOUTS/{}/ON '.format(self.dev, self.channel), 'query_string': '/DEV{}/SIGOUTS/{}/ON'.format(self.dev, self.channel), - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'add', 'write_string': '/DEV{}/SIGOUTS/{}/ADD'.format(self.dev, self.channel), 'query_string': '/DEV{}/SIGOUTS/{}/ADD'.format(self.dev, self.channel), - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'range', 'write_string': '/DEV{}/SIGOUTS/{}/RANGE'.format(self.dev, self.channel), 'query_string': '/DEV{}/SIGOUTS/{}/RANGE'.format(self.dev, self.channel), - 'values_list': [0.01, 0.1, 1, 10], + 'values': [0.01, 0.1, 1, 10], 'return_type': float}) self.add_device_property({ @@ -199,7 +199,7 @@ def init_settings(self): self.dev, self.channel, self.wf), 'query_string': '/DEV{}/SIGOUTS/{}/ENABLES/{}'.format( self.dev, self.channel, self.wf), - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ @@ -208,14 +208,14 @@ def init_settings(self): self.dev, self.channel, self.wf), 'query_string': '/DEV{}/SIGOUTS/{}/WAVEFORMS/{}'.format( self.dev, self.channel, self.wf), - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) self.add_device_property({ 'name': 'range', 'write_string': '/DEV{}/SIGOUTS/{}/OFFSET'.format(self.dev, self.channel), 'query_string': '/DEV{}/SIGOUTS/{}/OFFSET'.format(self.dev, self.channel), - 'values_list': [0.01, 0.1, 1, 10], + 'values': [0.01, 0.1, 1, 10], 'return_type': float}) # Oscillator @@ -263,14 +263,14 @@ def init_settings(self): 'name': 'adc', 'write_string': '/DEV{}/DEMODS/{}/ADCSELECT'.format(self.dev, self.channel), 'query_string': '/DEV{}/DEMODS/{}/ADCSELECT'.format(self.dev, self.channel), - 'values_list': [0, 1, 2, 3, 4, 5], + 'values': [0, 1, 2, 3, 4, 5], 'return_type': int}) self.add_device_property({ 'name': 'filter_order', 'write_string': '/DEV{}/DEMODS/{}/ORDER'.format(self.dev, self.channel), 'query_string': '/DEV{}/DEMODS/{}/ORDER'.format(self.dev, self.channel), - 'values_list': [6, 12, 18, 24, 30, 36, 42, 48], + 'values': [6, 12, 18, 24, 30, 36, 42, 48], 'return_type': int}) self.add_device_property({ @@ -305,14 +305,14 @@ def init_settings(self): 'name': 'oscillator', 'write_string': '/DEV{}/DEMODS/{}/OSCSELECT'.format(self.dev, self.channel), 'query_string': '/DEV{}/DEMODS/{}/OSCSELECT'.format(self.dev, self.channel), - 'values_list': [0, 1, 2, 3, 4, 5, 6, 7], + 'values': [0, 1, 2, 3, 4, 5, 6, 7], 'return_type': int}) self.add_device_property({ 'name': 'sinc_filter', 'write_string': '/DEV{}/DEMODS/{}/SINC'.format(self.dev, self.channel), 'query_string': '/DEV{}/DEMODS/{}/SINC'.format(self.dev, self.channel), - 'values_list': [0, 1], + 'values': [0, 1], 'return_type': int}) def get_sample(self): @@ -434,21 +434,21 @@ def init_settings(self): 'name': 'trigger_type', 'write_string': '/dataAcquisitionModule/triggertype', 'query_string': '/dataAcquisitionModule/triggertype', - 'values_list': [0, 1, 2, 3, 4], + 'values': [0, 1, 2, 3, 4], 'return_type': int}) self.add_device_property({ 'name': 'trigger_type', 'write_string': '/dataAcquisitionModule/triggertype', 'query_string': '/dataAcquisitionModule/triggertype', - 'values_list': [0, 1, 2, 3, 4], + 'values': [0, 1, 2, 3, 4], 'return_type': int}) self.add_device_property({ 'name': 'trigger_edge', 'write_string': '/dataAcquisitionModule/triggeredge', 'query_string': '/dataAcquisitionModule/triggeredge', - 'values_list': [0, 1, 2, 3, 4], + 'values': [0, 1, 2, 3, 4], 'return_type': int}) self.add_device_property({ @@ -462,7 +462,7 @@ def init_settings(self): 'name': 'trigger_hysteresis', 'write_string': '/dataAcquisitionModule/triggerhysteresis', 'query_string': '/dataAcquisitionModule/triggerhysteresis', - 'values_list': [-1, 1], + 'values': [-1, 1], 'return_type': float}) self.add_device_property({ @@ -497,7 +497,7 @@ def init_settings(self): 'name': 'grid_mode', 'write_string': '/dataAcquisitionModule/grid/mode', 'query_string': '/dataAcquisitionModule/grid/mode', - 'values_list': [0, 1, 2, 3, 4], + 'values': [0, 1, 2, 3, 4], 'return_type': int}) self.add_device_property({ From fa3c97a651471288af048d1ab8d545b42189a5c2 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 28 Oct 2024 11:45:08 -0600 Subject: [PATCH 06/29] added updated class based properties --- pyscan/drivers/__init__.py | 3 - pyscan/drivers/instrument_driver/__init__.py | 1 + .../instrument_driver/abstract_driver.py | 14 +- .../instrument_driver/instrument_driver.py | 2 +- pyscan/drivers/property_settings/__init__.py | 4 +- .../abstract_property_settings.py | 12 +- .../dict_property_settings.py | 52 +++++ .../indexed_property_settings.py | 27 ++- .../range_property_settings.py | 8 +- .../values_property_settings.py | 37 +++- .../test_instrument_driver_test_log.txt | 2 +- .../test_voltage_test_log.txt | 2 +- pyscan/general/pyscan_json_encoder.py | 37 +--- pyscan/measurement/abstract_experiment.py | 7 +- .../test_values_property_setttings.py | 2 +- test/drivers/test_test_instrument_driver.py | 193 ------------------ 16 files changed, 134 insertions(+), 269 deletions(-) create mode 100644 pyscan/drivers/property_settings/dict_property_settings.py delete mode 100644 test/drivers/test_test_instrument_driver.py diff --git a/pyscan/drivers/__init__.py b/pyscan/drivers/__init__.py index b7cae1c4..b68929c5 100755 --- a/pyscan/drivers/__init__.py +++ b/pyscan/drivers/__init__.py @@ -1,8 +1,5 @@ import os -# Objects -from .instrument_driver import InstrumentDriver - # Instrument Drivers from .agilent33500 import Agilent33500 from .agilent34410 import Agilent34410 diff --git a/pyscan/drivers/instrument_driver/__init__.py b/pyscan/drivers/instrument_driver/__init__.py index e69de29b..1481bed4 100644 --- a/pyscan/drivers/instrument_driver/__init__.py +++ b/pyscan/drivers/instrument_driver/__init__.py @@ -0,0 +1 @@ +from .instrument_driver import InstrumentDriver diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index dcd61fd5..11c0cabf 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from ...general.item_attribute import ItemAttribute from ..property_settings import ( RangePropertySettings, ValuesPropertySettings, @@ -7,10 +6,9 @@ DictPropertySettings) import numpy as np import re -from abc import ABC -class AbstractDriver(ItemAttribute, ABC): +class AbstractDriver(object): ''' Abstract driver class which creates class attributes based on a settings dictionary @@ -113,8 +111,8 @@ def get_instrument_property(self, obj, settings_obj, debug=False): def set_instrument_property(self, obj, settings_obj, new_value): ''' - Generator function for settings dictionary with 'values_list' item - Check that new_value is in settings['values_list'], if not, rejects command + Generator function for settings dictionary with 'values' item + Check that new_value is in settings['values'], if not, rejects command Parameters ---------- @@ -215,7 +213,7 @@ def validate_property_settings(self, settings_dict): # Check that settings_dict has a property type key(s) i = 0 - property_types = ['range', 'values_list', 'indexed_values', 'dict_values'] + property_types = ['range', 'values', 'indexed_values', 'dict_values'] for prop in property_types: if prop in settings_keys: @@ -237,8 +235,8 @@ def validate_property_settings(self, settings_dict): assert 'return_type' in settings_keys, f'{name} requires a "return_type" setting' assert settings_dict['range'][1] > settings_dict['range'][0], f'{name} has bad "range" settings, range[0] < range[1]' property_class = RangePropertySettings - elif 'values_list' in settings_keys: - assert isinstance(settings_dict['values_list'], list), f'{name} "values" setting must be a list' + elif 'values' in settings_keys: + assert isinstance(settings_dict['values'], list), f'{name} "values" setting must be a list' property_class = ValuesPropertySettings elif 'indexed_values' in settings_keys: assert isinstance(settings_dict['indexed_values'], list), f'{name} "indexed_values" setting must be a list' diff --git a/pyscan/drivers/instrument_driver/instrument_driver.py b/pyscan/drivers/instrument_driver/instrument_driver.py index 514fc901..57f22eff 100644 --- a/pyscan/drivers/instrument_driver/instrument_driver.py +++ b/pyscan/drivers/instrument_driver/instrument_driver.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from .abstract_driver import AbstractDriver -from .new_instrument import new_instrument +from ..new_instrument import new_instrument class InstrumentDriver(AbstractDriver): diff --git a/pyscan/drivers/property_settings/__init__.py b/pyscan/drivers/property_settings/__init__.py index 8e8ba332..f704e5af 100644 --- a/pyscan/drivers/property_settings/__init__.py +++ b/pyscan/drivers/property_settings/__init__.py @@ -1,4 +1,4 @@ -from .dict_values_property_settings import DictPropertySettings -from .indexed_values_property_settings import IndexedPropertySettings +from .dict_property_settings import DictPropertySettings +from .indexed_property_settings import IndexedPropertySettings from .range_property_settings import RangePropertySettings from .values_property_settings import ValuesPropertySettings diff --git a/pyscan/drivers/property_settings/abstract_property_settings.py b/pyscan/drivers/property_settings/abstract_property_settings.py index b7a30acf..fe7d2c66 100644 --- a/pyscan/drivers/property_settings/abstract_property_settings.py +++ b/pyscan/drivers/property_settings/abstract_property_settings.py @@ -1,17 +1,15 @@ from ...general.item_attribute import ItemAttribute -from abc import ABC -class AbstractPropertySettings(ItemAttribute, ABC): +class AbstractPropertySettings(ItemAttribute): - def __init__(self, device, settings_dict): - - self.device = device + def __init__(self, settings_dict): + self.name = settings_dict['name'] self._name = '_' + self.name - for key, value in settings_dict: - self.key = value + for key, value in settings_dict.items(): + setattr(self, key, value) def validate_set_value(self, new_value): ''' diff --git a/pyscan/drivers/property_settings/dict_property_settings.py b/pyscan/drivers/property_settings/dict_property_settings.py new file mode 100644 index 00000000..46aa26d1 --- /dev/null +++ b/pyscan/drivers/property_settings/dict_property_settings.py @@ -0,0 +1,52 @@ +from .abstract_property_settings import AbstractPropertySettings +from collections import OrderedDict + + +class DictValueException(Exception): + + def __init__(self, prop, dict_values, dict_key_types, value): + + msg = f'{prop} = {value} invalid input\n' + msg += f'Valid inputs for dict_values property {prop} are:\n' + for key, value in zip(list(dict_values.keys()), list(dict_key_types.values())): + msg += f'{key},\n' + + super().__init__(msg) + + +class DictPropertySettings(AbstractPropertySettings): + + def __init__(self, settings_dict): + super().__init__(settings_dict) + + self.dict_values = OrderedDict(settings_dict['dict_values']) + + self.key_type_dict = {} + self.str_values_dict = {} + + for key, value in self.dict_values.items(): + self.key_type_dict[key] = type(key) + self.str_values_dict[key] = str(value) + + def validate_set_value(self, new_value): + if new_value in self.dict_values.keys(): + return True + else: + raise DictValueException(self.name, self.dict_values, self.key_type_dict, new_value) + + def format_write_value(self, new_value): + + return self.dict_values[new_value] + + def format_query_return(self, ret): + + key = self.find_first_key(ret) + + key_type = self.key_type_dict[key] + + return key_type(key) + + def find_first_key(self, ret): + for key, val in self.dict_values.items(): + if str(val) == str(ret): + return key diff --git a/pyscan/drivers/property_settings/indexed_property_settings.py b/pyscan/drivers/property_settings/indexed_property_settings.py index 438f8060..d588c054 100644 --- a/pyscan/drivers/property_settings/indexed_property_settings.py +++ b/pyscan/drivers/property_settings/indexed_property_settings.py @@ -1,32 +1,39 @@ -from itemattribute import ItemAttribute +from .abstract_property_settings import AbstractPropertySettings class IndexedValueException(Exception): - def __init__(self, prop, indexed_values, value): + def __init__(self, prop, indexed_values, types, value): msg = f'{prop} = {value} invalid input\n' msg += f'Valid inputs for indexed value property {prop} are:' - msg += ',\n'.join(indexed_values) + msg += ',\n'.join([value + " " + str(type) for value, type in zip(indexed_values, types)]) super().__init__(msg) -class IndexedPropertySettings(ItemAttribute): +class IndexedPropertySettings(AbstractPropertySettings): - def __init__(self, device, settings_dict): - super().__init__(device, settings_dict) + def __init__(self, settings_dict): + super().__init__(settings_dict) + + self.types = [] + self.value_strings = [] + + for val in self.indexed_values: + self.types.append(type(val)) + self.value_strings.append(str(val)) def validate_set_value(self, new_value): - if new_value in self.index_values: + if new_value in self.indexed_values: return True else: - raise IndexedValueException(self.name, self.range, new_value) + raise IndexedValueException(self.name, self.value_strings, self.types, new_value) def format_write_value(self, new_value): - return self.indexed_values.index(new_value) def format_query_return(self, ret): + return_type = self.types[int(ret)] - return self.indexed_values[int(ret)] + return return_type(self.indexed_values[int(ret)]) diff --git a/pyscan/drivers/property_settings/range_property_settings.py b/pyscan/drivers/property_settings/range_property_settings.py index bb286ed1..6e2227e5 100644 --- a/pyscan/drivers/property_settings/range_property_settings.py +++ b/pyscan/drivers/property_settings/range_property_settings.py @@ -1,4 +1,4 @@ -from itemattribute import ItemAttribute +from .abstract_property_settings import AbstractPropertySettings class RangeException(Exception): @@ -11,10 +11,10 @@ def __init__(self, prop, range, value): super().__init__(msg) -class RangePropertySettings(ItemAttribute): +class RangePropertySettings(AbstractPropertySettings): - def __init__(self, device, settings_dict): - super().__init__(device, settings_dict) + def __init__(self, settings_dict): + super().__init__(settings_dict) def validate_set_value(self, new_value): if self.range[0] <= new_value <= self.range[1]: diff --git a/pyscan/drivers/property_settings/values_property_settings.py b/pyscan/drivers/property_settings/values_property_settings.py index dc186e02..17d35cc7 100644 --- a/pyscan/drivers/property_settings/values_property_settings.py +++ b/pyscan/drivers/property_settings/values_property_settings.py @@ -1,31 +1,50 @@ -from itemattribute import ItemAttribute +from .abstract_property_settings import AbstractPropertySettings class ValueException(Exception): - def __init__(self, prop, values, value): + def __init__(self, prop, values, types, value): msg = f'{prop} = {value} invalid input\n' - msg += f'Valid inputs for values property {prop} are:' - msg += ',\n'.join(values) + msg += f'Valid inputs for values property {prop} are:\n' + msg += ',\n'.join([value + " " + str(type) for value, type in zip(values, types)]) super().__init__(msg) -class ValuesPropertySettings(ItemAttribute): +class ValuesPropertySettings(AbstractPropertySettings): - def __init__(self, device, settings_dict): - super().__init__(device, settings_dict) + def __init__(self, settings_dict): + + super().__init__(settings_dict) + + self.types = [] + self.value_strings = [] + + for val in self.values: + self.types.append(type(val)) + self.value_strings.append(str(val)) def validate_set_value(self, new_value): if new_value in self.values: return True else: - raise ValueException(self.name, self.range, new_value) + raise ValueException(self.name, self.value_strings, self.types, new_value) def format_write_value(self, new_value): return new_value def format_query_return(self, ret): - return ret + try: + # Check if the return is directly in the values list + index = self.values.index(ret) + except ValueError: + # If not, check if the return is in the value_strings list + index = self.value_strings.index(ret) + except: + raise Exception('Returned value {} {} not found in values or value_strings'.format(ret, type(ret))) + + return_type = self.types[index] + + return return_type(ret) diff --git a/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt b/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt index fe58e494..4e976046 100644 --- a/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt @@ -1 +1 @@ -Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.8.2 at 2024-10-04 14:47:53 +Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.8.3 at 2024-10-24 09:30:29 diff --git a/pyscan/drivers/testing/driver_test_logs/test_voltage_test_log.txt b/pyscan/drivers/testing/driver_test_logs/test_voltage_test_log.txt index 58a48814..77e94e1b 100644 --- a/pyscan/drivers/testing/driver_test_logs/test_voltage_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/test_voltage_test_log.txt @@ -1 +1 @@ -Passed with test_voltage version v0.1.0 tested on pyscan version v0.8.2 at 2024-10-04 14:47:53 +Passed with test_voltage version v1.0.0 tested on pyscan version v0.8.3 at 2024-10-24 09:30:29 diff --git a/pyscan/general/pyscan_json_encoder.py b/pyscan/general/pyscan_json_encoder.py index f84d9f26..546f7485 100644 --- a/pyscan/general/pyscan_json_encoder.py +++ b/pyscan/general/pyscan_json_encoder.py @@ -1,6 +1,6 @@ import json import numpy as np -from pyscan.general.item_attribute import ItemAttribute +from ..general.item_attribute import ItemAttribute from ..drivers.instrument_driver import InstrumentDriver from pyvisa.resources import ( # FirewireInstrument, @@ -47,49 +47,34 @@ def default(self, obj, debug=False): if type(obj) is type: return obj.__name__ elif isinstance(obj, (InstrumentDriver, ItemAttribute)): - if debug is True: - print(f"obj {obj} was instance of InstrumentDriver and or ItemAttribute.") return obj.__dict__ elif isinstance(obj, (range, tuple)): - if debug is True: - print(f"obj {obj} was instance of {type(obj)}.") return list(obj) # Handle numpy integers elif isinstance(obj, np.integer): - if debug is True: - print(f"Object {obj} is a numpy integer, converting to int.") return int(obj) # Handle numpy floating values elif isinstance(obj, np.floating): - if debug is True: - print(f"Object {obj} is a numpy floating value, converting to float.") return float(obj) # Handle numpy arrays elif isinstance(obj, np.ndarray): - if debug is True: - print(f"Object {obj} is a numpy array, converting to list.") return obj.tolist() elif callable(obj): - if debug is True: - print(f"obj {obj} is a function, returning source code.") - return inspect.getsource(obj) # Talk with Andy about this and perhaps implementing in load_expt? - elif isinstance(obj, (WindowsPath, Path)): # This covers both WindowsPath and PosixPath - if debug is True: - print(f"obj {obj} is a Path or WindowsPath, returning string of the path.") + return inspect.getsource(obj) + elif isinstance(obj, (WindowsPath, Path)): return str(obj) elif type(obj) is type(iter(range(1))): return list(obj) elif isinstance( obj, - ( - # FirewireInstrument, - GPIBInstrument, - # PXIInstrument, - SerialInstrument, - TCPIPInstrument, - USBInstrument, - # VXIInstrument, - ), + (# FirewireInstrument, + GPIBInstrument, + # PXIInstrument, + SerialInstrument, + TCPIPInstrument, + USBInstrument, + # VXIInstrument) + ) ): if debug is True: print(f"obj {obj} is a pyvisa instrument, returning resource name.") diff --git a/pyscan/measurement/abstract_experiment.py b/pyscan/measurement/abstract_experiment.py index a9f4fdc9..dca3bb9f 100644 --- a/pyscan/measurement/abstract_experiment.py +++ b/pyscan/measurement/abstract_experiment.py @@ -211,7 +211,8 @@ def save_point(self): f[key][self.runinfo.indicies, ...] = self[key][self.runinfo.indicies, ...] def save_row(self): - '''Saves full scan0 of data at once. Does not return anything. + ''' + Saves full scan0 of data at once. Does not return anything. ''' save_path = self.runinfo.data_path / '{}.hdf5'.format(self.runinfo.long_name) @@ -229,8 +230,8 @@ def save_row(self): f[key][:, self.runinfo.line_indicies, ...] = self[key][self.runinfo.line_indicies, ...] def save_metadata(self): - '''Formats and saves metadata from self.runinfo and self.devices. Does not return anything. - + ''' + Formats and saves metadata from self.runinfo and self.devices. Does not return anything. ''' save_path = self.runinfo.data_path / '{}.hdf5'.format(self.runinfo.long_name) save_name = str(save_path.absolute()) diff --git a/test/drivers/test_property_settings/test_values_property_setttings.py b/test/drivers/test_property_settings/test_values_property_setttings.py index ede184aa..e1148a4e 100644 --- a/test/drivers/test_property_settings/test_values_property_setttings.py +++ b/test/drivers/test_property_settings/test_values_property_setttings.py @@ -3,7 +3,7 @@ @pytest.fixture def settings(): - return ValuesPropertySettings({'name': 'test', 'values_list': [1, 2, 3, 'a', 'b', 'c']}) + return ValuesPropertySettings({'name': 'test', 'values': [1, 2, 3, 'a', 'b', 'c']}) def test_validate_set_value_valid(settings): diff --git a/test/drivers/test_test_instrument_driver.py b/test/drivers/test_test_instrument_driver.py deleted file mode 100644 index 3bf552a5..00000000 --- a/test/drivers/test_test_instrument_driver.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest -import math -import string -from collections import OrderedDict -from pyscan.drivers.testing.test_instrument_driver import TestInstrumentDriver -from pyscan.drivers.testing.auto_test_driver import test_driver - -# #################### still need to add error flags for this file... -# ##################### test more thouroughly with multiple instances to make sure -# ######## this works and doesn't bug out like it did with multiple testvoltage instances -# ######### on 2_29_24 - - -def test_testinstrumentdriver(): - test_instrument = TestInstrumentDriver() - - # check that the initialized state has the expected attributes - def check_has_attributes(device, attributes): - for a in attributes: - assert hasattr(test_instrument, a), "test device does not have {} attribute when initialized".format(a) - - attributes = ['instrument', 'debug', '_float_values_settings', '_str_values_settings', - '_range_settings', '_indexed_values_settings', '_dict_values_settings', '_float_values', - '_str_values', '_range', '_indexed_values', "_dict_values", "_version", "black_list_for_testing"] - check_has_attributes(test_instrument, attributes) - - # check that the initialized attributes have the expected values - def check_attribute_values(device, attributes, ev): - for i in range(len(ev)): - err_string = "test device {} attribute does not equal {}".format(device[attributes[i]], ev[i]) - assert (device[attributes[i]] == ev[i]), err_string - - floatvs = {'name': 'float_values', 'write_string': 'FLOAT_VALUES {}', 'query_string': 'FLOAT_VALUES?', - 'values': [2, 2.2339340249, 89.129398], 'return_type': float} - strvs = {'name': 'str_values', 'write_string': 'STR_VALUES {}', 'query_string': 'STR_VALUES?', - 'values': ['2', 'x', 'False', '(1, 10)', "['1', '10']"], 'return_type': str} - rgs = {'name': 'range', 'write_string': 'RANGE {}', 'query_string': 'RANGE?', - 'range': [0, 10], 'return_type': float} - idxvs = {'name': 'indexed_values', 'write_string': 'INDEXED_VALUES {}', 'query_string': 'INDEXED_VALUES?', - 'indexed_values': ['A', 'B', 'C', 'D', 196, 2.0, '101001'], 'return_type': str} - dicts = {'name': 'dict_values', 'write_string': 'DICT_VALUES {}', 'query_string': 'DICT_VALUES?', - 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0}, 'return_type': str} - expected_values = ['instrument#123', False, floatvs, strvs, rgs, idxvs, dicts, 2, '2', 0, 'A', 'off'] - check_attribute_values(test_instrument, attributes, expected_values) - - # check the set_values_property behavior - def check_values_property(key): - name = test_instrument[key]['name'] - test_val = 0 - for i in range(-10, 10000): - if test_val not in test_instrument[key]['values']: - with pytest.raises(Exception): - test_instrument[name] = test_val - test_val += .1 - - with pytest.raises(Exception): - test_instrument[name] = 'not ok' - - for val in test_instrument[key]['values']: - test_instrument[name] = val - assert test_instrument[name] == val, "{} not equal {}".format(test_instrument[name], val) - assert test_instrument["_{}".format(name)] == val - - # check the set_range_property behavior for a range item - def check_range_property(key): - min_range = min(test_instrument[key]['range']) - max_range = max(test_instrument[key]['range']) - name = test_instrument[key]['name'] - with pytest.raises(Exception): - test_instrument[name] = min_range - .001 - with pytest.raises(Exception): - test_instrument[name] = min_range - 1 - with pytest.raises(Exception): - test_instrument[name] = max_range + .001 - with pytest.raises(Exception): - test_instrument[name] = max_range + 1 - - # set fixed number of steps to divide the overall range by for step size for actual drivers - step = 1 - if abs(test_instrument[key]['range'][0] - test_instrument[key]['range'][0]) > 10000: - step = math.ceil(abs(test_instrument[key]['range'][0] - test_instrument[key]['range'][0]) / 1000) - - for r in range(test_instrument[key]['range'][0], test_instrument[key]['range'][0], step): - test_instrument[name] = r - assert test_instrument[name] == r - assert test_instrument['_{}'.format(name)] == r - - # check the set_indexed_values_property behavior - def check_indexed_property(key): - # check a random string rather than a for loop - name = test_instrument[key]['name'] - for letter in string.ascii_letters: - if letter not in test_instrument[key]['indexed_values']: - with pytest.raises(Exception): - test_instrument[name] = letter - - step = 0 - for i in range(0, 1000): - if i not in test_instrument[key]['indexed_values']: - with pytest.raises(Exception): - test_instrument[name] = step - step += .1 - - with pytest.raises(Exception): - test_instrument[name] = True - with pytest.raises(Exception): - test_instrument[name] = [1, 5] - with pytest.raises(Exception): - test_instrument[name] = {'key1': 'bad boy', 'key2': 'badder girl'} - - for idx, iv in enumerate(test_instrument._indexed_values_settings['indexed_values']): - test_instrument[name] = iv - assert test_instrument["_{}".format(name)] == iv - assert test_instrument.query('INDEXED_VALUES?') == str(idx) - - # check the set_dict_values_property behavior - def check_dict_property(key): - # key must be string or number, set property to include lists, arrays, arbritray values of diversity - name = test_instrument[key]['name'] - ord_dict = OrderedDict(test_instrument[key]['dict_values']) - for k in test_instrument[key]['dict_values']: - test_instrument[name] = k - assert test_instrument["_{}".format(name)] == test_instrument.find_first_key(ord_dict, ord_dict[k]) - assert test_instrument.query('DICT_VALUES?') == str(test_instrument[key]['dict_values'][k]) - - for letter in string.ascii_letters: - if letter not in test_instrument[key]['dict_values']: - with pytest.raises(Exception): - test_instrument[name] = letter - - step = 0 - for i in range(0, 1000): - if i not in test_instrument[key]['dict_values']: - with pytest.raises(Exception): - test_instrument[name] = step - step += .1 - - with pytest.raises(Exception): - test_instrument[name] = True - - # implements above checks for all attributes by type - def check_properties(test_instrument, num_val_props=2, num_range_props=1, - num_idx_vals_props=1, num_dict_vals_props=1, total_att=15): - # iterate over all attributes to test accordingly using predefined functions - values_counter, range_counter, idx_vals_counter, dict_vals_counter = 0, 1, 1, 1 - values_idx, range_idx, idx_vals_idx, dict_vals_idx = [], [], [], [] - for key in test_instrument.__dict__.keys(): - try: - keys = test_instrument[key].keys() - if ('values' in keys) and ('indexed_' not in keys) and ('dict_' not in keys): - values_counter += 1 - values_idx.append(values_counter) - check_values_property(key) - except: - values_counter += 1 - try: - if 'range' in test_instrument[key].keys(): - range_counter += 1 - range_idx.append(range_counter) - check_range_property(key) - except: - range_counter += 1 - try: - if 'indexed_values' in test_instrument[key].keys(): - idx_vals_counter += 1 - idx_vals_idx.append(idx_vals_counter) - check_indexed_property(key) - except: - idx_vals_counter += 1 - try: - if 'dict_values' in test_instrument[key].keys(): - dict_vals_counter += 1 - dict_vals_idx.append(dict_vals_counter) - check_dict_property(key) - except: - dict_vals_counter += 1 - - mid_string = 'properties found and tested out of' - print("{} range {} {} total attributes.".format(len(range_idx), mid_string, range_counter)) - print("{} values {} {} total attributes.".format(len(values_idx), mid_string, values_counter)) - print("{} indexed values {} {} total attributes.".format(len(idx_vals_idx), mid_string, idx_vals_counter)) - print("{} dict values {} {} total attributes.".format(len(dict_vals_idx), mid_string, dict_vals_counter)) - - assert num_val_props == len(values_idx) - assert num_range_props == len(range_idx) - assert num_idx_vals_props == len(idx_vals_idx) - assert num_dict_vals_props == len(dict_vals_idx) - assert range_counter == values_counter == idx_vals_counter == dict_vals_counter == total_att - - check_properties(test_instrument) - - test_driver(test_instrument) From 281ce7515808078269282016994c424e7a82a07c Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 28 Oct 2024 11:45:18 -0600 Subject: [PATCH 07/29] ignoring .vscode --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bc6aec1e..d33a7b9d 100644 --- a/.gitignore +++ b/.gitignore @@ -119,4 +119,5 @@ dmypy.json backup .backup *.backup -test.ipynb \ No newline at end of file +test.ipynb +.vscode \ No newline at end of file From 6d8f12cf258823ab47c4bdbc1854b957dd55d9d9 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 28 Oct 2024 14:14:55 -0600 Subject: [PATCH 08/29] Autotesting instrument driver works --- .../instrument_driver/abstract_driver.py | 4 +- .../abstract_property_settings.py | 3 +- pyscan/drivers/testing/auto_test_driver.py | 605 ------------------ .../testing/auto_test_driver_properties.py | 389 +++++++++++ pyscan/drivers/testing/check_initial_state.py | 21 + .../test_instrument_driver_test_log.txt | 2 +- .../drivers/testing/test_instrument_driver.py | 37 +- pyscan/drivers/testing/test_properties.py | 212 ++++++ pyscan/drivers/testing/test_voltage.py | 16 +- test/drivers/test_test_voltage.py | 68 -- test/measurement/test_abstract_experiment.py | 130 ++-- 11 files changed, 711 insertions(+), 776 deletions(-) delete mode 100644 pyscan/drivers/testing/auto_test_driver.py create mode 100644 pyscan/drivers/testing/auto_test_driver_properties.py create mode 100644 pyscan/drivers/testing/check_initial_state.py create mode 100644 pyscan/drivers/testing/test_properties.py diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 11c0cabf..4e7b3323 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +from ...general.item_attribute import ItemAttribute from ..property_settings import ( RangePropertySettings, ValuesPropertySettings, @@ -8,7 +8,7 @@ import re -class AbstractDriver(object): +class AbstractDriver(ItemAttribute): ''' Abstract driver class which creates class attributes based on a settings dictionary diff --git a/pyscan/drivers/property_settings/abstract_property_settings.py b/pyscan/drivers/property_settings/abstract_property_settings.py index fe7d2c66..44156fd0 100644 --- a/pyscan/drivers/property_settings/abstract_property_settings.py +++ b/pyscan/drivers/property_settings/abstract_property_settings.py @@ -1,7 +1,6 @@ -from ...general.item_attribute import ItemAttribute -class AbstractPropertySettings(ItemAttribute): +class AbstractPropertySettings(object): def __init__(self, settings_dict): diff --git a/pyscan/drivers/testing/auto_test_driver.py b/pyscan/drivers/testing/auto_test_driver.py deleted file mode 100644 index 817874c4..00000000 --- a/pyscan/drivers/testing/auto_test_driver.py +++ /dev/null @@ -1,605 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest -import math -from collections import OrderedDict -import typing -from pyscan.drivers.testing.test_instrument_driver import TestInstrumentDriver -from pyscan.general.get_pyscan_version import get_pyscan_version -import os -from datetime import datetime -import re -import pprint - -''' -WARNING! -If used without first creating a proper blacklist of _properties that cannot be safely -changed to any value in their range of options based on the driver settings may -cause instruments to self destruct, or lead to significant injury and even DEATH. -Depending on the instrument and how it's set up this may or may not be a relevant issue. -If there are any settings that could be changed to risk harming some or the instrument -blacklist them before proceeding by adding a self.black_list_for_testing property at the end -of the instrument driver's __init__. -ONLY RUN THIS TEST UNIT IF YOU ARE CERTAIN ALL SUCH ATTRIBUTES HAVE BEEN PROPERLY BLACKLISTED! -''' - -# not incluing booleans since they can be interpreted ambiguously as ints. Should it? -BAD_INPUTS = [-19812938238312948, -1.11123444859, 3.2222111234, 985767665954, 890992238.2345, - 'not ok', 'bad value', - [1, 2412, 19], [1, 191, 13, -5.3], - {'Alfred': "Batman's uncle", 'or': 'imposter'}] - - -missing_prop_str = "device did not have {} property, check it's in drivers initialize_properties or update_properties." -prop_err_str1 = ("attempted to set {} property {} to {} but when queried returned {}." - + "\n This is often a sign of interdependent properties that are not suitable for this auto testing." - + "Check for interdependence and consider blacklisting.") -prop_err_str2 = ("set {} property {} to {} but _{} returned {}." - + "\n This is often a sign of interdependent properties that are not suitable for this auto testing." - + "Check for interdependence and consider blacklisting.") - - -# This function is critical step to ensuring safety when testing drivers with actual instruments -def validate_blacklist(test_instrument): - settings = [] - settings_names = [] - total_settings = 0 - for attribute_name in test_instrument.__dict__.keys(): - if "_settings" in attribute_name: - settings.append(attribute_name) - _name = "_{}".format(test_instrument[attribute_name]['name']) - settings_names.append(_name) - total_settings += 1 - - if hasattr(test_instrument, 'black_list_for_testing'): - for blacklisted in test_instrument.black_list_for_testing: - err_msg = "WARNING, blacklisted attribute could not be validated and does not match a driver property" - assert blacklisted in settings_names, err_msg - count = 0 - for i in test_instrument.black_list_for_testing: - if i == blacklisted: - count += 1 - if count > 1: - assert False, "There appear to be duplicates in your blacklist." - else: - err_msg = str("Warning, driver needs black_list_for_testing attribute, please double check if there are \n" - + "any attributes that need to be blacklisted for safety purposes, as non-blacklisted settings \n" - + "will be iterated through the entirety of their range and could cause significant injury or \n" - + "even death to nearby users and permanently damage instruments if used improperly. \n" - + "If no properties are to be blacklisted for testing, please set the black_list_for_testing \n" - "attribute to an empty list as acknowledgment of this warning to continue.") - assert False, err_msg - - -def save_initial_state(device): - saved_settings = [] - # print(device.__dict__.keys()) - for attribute_name in device.__dict__.keys(): - if "_settings" in attribute_name: - '''try: - name = "_{}".format(device[attribute_name]['name']) - val = device[name] - except (Exception):''' - name = "{}".format(device[attribute_name]['name']) - val = device[name] - if 'ranges' in device[attribute_name].keys(): - val = tuple(val) - saved_settings.append((name, val)) - # print(device.__dict__.keys()) - - return saved_settings - - -def restore_initial_state(device, saved_settings): - restored_settings = [] - for setting in saved_settings: - setter = setting[0] - _name = "_{}".format(setter) - val = setting[1] - - if _name in device.black_list_for_testing: - err_msg = f"WARNING! BLACKLISTED PROPERTY WAS SOMEHOW CHANGED. Was {val}, now {device[setter]}\n" - assert val == device[setter], err_msg - restored_settings.append((setter, device[setter])) - continue - elif 'read_only' in device["_{}_settings".format(setter)].keys(): - continue - elif 'ranges' in device["_{}_settings".format(setter)].keys(): - val = device["_{}_settings".format(setter)]['return_type'](val) - - try: - device[setter] = val - except Exception: - try: - device[setter] = device["_{}_settings".format(setter)]['return_type'](val) - except Exception: - assert False, ("setter is: {} saved setting is: {} val is: {}" - .format(setter, setting, val)) - query_result = device[setter] - if 'ranges' in device["_{}_settings".format(setter)].keys(): - query_result = tuple(query_result) - restored_settings.append((setter, query_result)) - - return restored_settings - - -def reset_device_properties(device): - settings = [] - for attribute_name in device.__dict__.keys(): - if "_settings" in attribute_name: - settings.append(attribute_name) - - blacklisted = [] - for name in settings: - keys = device[name].keys() - var_name = name.replace('_settings', '') - if var_name in device.black_list_for_testing: - blacklisted.append((var_name, device[var_name])) - continue - elif 'read_only' in keys: - continue - elif ('values_list' in keys): - device[var_name] = device[name]['values_list'][0] - elif 'range' in keys: - device[var_name] = device[name]['range'][0] - elif 'ranges' in keys: - # write to reset val later - pass - elif 'indexed_values' in keys: - device[var_name] = device[name]['indexed_values'][0] - elif 'dict_values' in keys: - for key in device[name]['dict_values'].keys(): - device[var_name] = key - break - else: - assert False, "no valid type present in setting: {}. Must be one of {}.".format( - name, ['values_list', 'range', 'indexed_values', 'dict_values', 'read_only']) - - if len(blacklisted) > 0: - print("Blacklisted settings that will not be tested or changed are: ") - pprint.pprint(blacklisted) - - -# check that the initialized state has the expected attributes -def check_has_attributes(device, attributes): - for a in attributes: - assert hasattr(device, a), "test device does not have {} attribute when initialized".format(a) - - -# check that the initialized attributes have the expected values -def check_attribute_values(device, attributes, ev): - for i in range(len(ev)): - err_string = "test device {} attribute does not equal {}".format(device[attributes[i]], ev[i]) - assert (device[attributes[i]] == ev[i]), err_string - - -# designed for testing read only properties of any type -def check_read_only_property(device, key): - name = device[key]['name'] - settings = device['_' + name + '_settings'] - return_type = device[key]['return_type'] - - # I'm not sure that this will work. It might be that only the underscore property can be used to access - # the property value. - assert type(device[name]) is return_type, "read_only property {} returned type {} not {}".format( - name, type(device[name]), return_type) - assert type(device["_{}".format(name)]) is return_type, "read_only _property {} returned type {} not {}".format( - name, type(device["_{}".format(name)]), return_type) - - assert 'write_string' not in settings, "read_only property {} has write_string {}".format( - name, settings['write_string']) - - # I'm not sure that this will fail. If not, it should be that the original value remains the same no matter what - # you try to set it to. - for val in BAD_INPUTS: - with pytest.raises(Exception): - device[name] = val - - -# check the set_values_property behavior -def check_values_property(device, key): - name = device[key]['name'] - - # reset value to baseline for consistency between tests - try: - device[name] = device[key]['values_list][0] - except Exception: - assert False, missing_prop_str.format(name) - - for val in BAD_INPUTS: - if val not in device[key]['values_list]: - with pytest.raises(Exception): - device[name] = val - - for val in device[key]['values_list]: - device[name] = val - # ################ consider within a range here since may not return perfect value. - assert device[name] == val, \ - prop_err_str1.format('values_list, name, str(val) + str(type(val)), str(device[name]) + str(type(device[name]))) - assert device["_{}".format(name)] == val, prop_err_str2.format('values_list, name, val, - "_{}".format(name), device["_{}".format(name)]) - - # reset value to baseline for consistency between tests - device[name] = device[key]['values_list][0] - - -# check the set_range_property behavior for a range item -def check_range_property(device, key): - min_range = min(device[key]['range']) - max_range = max(device[key]['range']) - name = device[key]['name'] - - # reset range to baseline for consistency between tests - try: - device[name] = device[key]['range'][0] - except Exception: - assert False, missing_prop_str.format(name) - - with pytest.raises(Exception): - device[name] = min_range - .001 - with pytest.raises(Exception): - device[name] = min_range - 1 - with pytest.raises(Exception): - device[name] = max_range + .001 - with pytest.raises(Exception): - device[name] = max_range + 1 - - # set fixed number of steps to divide the overall range by for step size for actual drivers - # ################# change these number of steps to follow ranges. - step = 1 - if abs(device[key]['range'][0] - device[key]['range'][0]) > 10000: - step = math.ceil(abs(device[key]['range'][0] - device[key]['range'][0]) / 1000) - - for r in range(int(device[key]['range'][0]), int(device[key]['range'][0]), step): - device[name] = r - assert device[name] == r, prop_err_str1.format('range', name, r, device[name]) - assert device['_{}'.format(name)] == r, prop_err_str2.format('range', name, r, - "_{}".format(name), device["_{}".format(name)]) - - # reset range to baseline for consistency between tests - device[name] = device[key]['range'][0] - - -# check the set_indexed_values_property behavior -def check_indexed_property(device, key): - # check a random string rather than a for loop - name = device[key]['name'] - - # reset value to baseline for consistency between tests - try: - device[name] = device[key]['indexed_values'][0] - except Exception: - assert False, missing_prop_str.format(name) - - for item in BAD_INPUTS: - if item not in device[key]['indexed_values']: - with pytest.raises(Exception): - device[name] = item - - for idx, iv in enumerate(device[key]['indexed_values']): - # print(str(iv), str(idx), name, device[name]) - device[name] = iv - assert device[name] == iv, prop_err_str1.format('indexed', name, iv, device[name]) - assert device["_{}".format(name)] == iv, prop_err_str2.format('indexed', name, iv, - "_{}".format(name), device["_{}".format(name)]) - # print("underscore property is: ", device["_{}".format(name)]) - - # reset value to baseline for consistency between tests - device[name] = device[key]['indexed_values'][0] - - -# check the set_dict_values_property behavior -def check_dict_property(device, key): - # key must be string or number, set property to include lists, arrays, arbritray values of diversity - name = device[key]['name'] - ord_dict = OrderedDict(device[key]['dict_values']) - - # reset the dict value to the first value for consistency between tests - for k in ord_dict: - try: - device[name] = k - except Exception: - assert False, missing_prop_str.format(name) - break - - for k in device[key]['dict_values']: - # print(k) - device[name] = k - err_str1 = ("{} key return not properly formatted. Did not return first key {}, instead returned {}").format( - name, device[key].find_first_key(ord_dict[k]), device[name]) - assert device[name] == device[key].find_first_key(ord_dict[k]), err_str1 - err_str2 = ("_{} key return not properly formatted. Did not return first key {}, instead returned {}").format( - name, device[key].find_first_key(ord_dict[k]), device[name]) - assert device["_{}".format(name)] == device[key].find_first_key(ord_dict[k]), err_str2 - - for bad_input in BAD_INPUTS: - if isinstance(bad_input, typing.Hashable): - if bad_input not in device[key]['dict_values']: - with pytest.raises(Exception): - device[name] = bad_input - - # reset the dict value to the first value for consistency between tests - for k in ord_dict: - device[name] = k - break - - -# implements above checks for all attributes by type -def check_properties(test_instrument, verbose=True): - # This is a critical step to ensuring safety when testing drivers with actual instruments - validate_blacklist(test_instrument) - - # iterate over all attributes to test accordingly using predefined functions - values_counter, range_counter, idx_vals_counter, dict_vals_counter = 0, 0, 0, 0 - instrument_name = test_instrument.__class__.__name__ - # values_idx, range_idx, idx_vals_idx, dict_vals_idx = [], [], [], [] - saved_settings = save_initial_state(test_instrument) - if verbose: - print("Initial state for the {} was: ".format(instrument_name)) - pprint.pprint(saved_settings) - print("\n") - - reset_device_properties(test_instrument) - reset_settings = save_initial_state(test_instrument) - if verbose: - print("Reset state for the {} was: ".format(instrument_name)) - pprint.pprint(reset_settings) - print("\n") - - print("\nBeginning tests for: ", test_instrument.__class__.__name__, " version ", test_instrument._version) - - settings = [] - total_settings = 0 - for attribute_name in test_instrument.__dict__.keys(): - if "_settings" in attribute_name: - settings.append(attribute_name) - total_settings += 1 - write_only_settings = [] - - for name in settings: - # if hasattr(test_instrument, 'black_list_for_testing'): - base_name = name.replace('_settings', '') - # base_name = test_instrument[name]['name'] - if base_name in test_instrument['black_list_for_testing']: - continue - keys = test_instrument[name].keys() - if 'read_only' in keys: - check_read_only_property(test_instrument, name) - elif 'write_only' in keys: - write_only_settings.append(name) - continue - elif ('values_list in keys) and ('indexed_' not in keys) and ('dict_' not in keys): - values_counter += 1 - # values_idx.append(values_counter) - check_values_property(test_instrument, name) - elif 'range' in keys: - range_counter += 1 - # range_idx.append(range_counter) - check_range_property(test_instrument, name) - elif 'indexed_values' in keys: - idx_vals_counter += 1 - # idx_vals_idx.append(idx_vals_counter) - check_indexed_property(test_instrument, name) - elif 'dict_values' in keys: - dict_vals_counter += 1 - # dict_vals_idx.append(dict_vals_counter) - check_dict_property(test_instrument, name) - else: - assert False, "no valid type present in setting: {}. Must be one of {}.".format( - name, ['values_list, 'range', 'indexed_values', 'dict_values']) - print(name) - restored_settings = restore_initial_state(test_instrument, saved_settings) - - diff = set(restored_settings) ^ set(saved_settings) - - mid_string = 'properties found and tested out of' - print("\n{} range {} {} total settings found.".format(range_counter, mid_string, total_settings)) - print("{} values {} {} total settings found.".format(values_counter, mid_string, total_settings)) - print("{} indexed values {} {} total settings found.".format(idx_vals_counter, mid_string, total_settings)) - print("{} dict values {} {} total settings found.".format(dict_vals_counter, mid_string, total_settings)) - try: - print("{} blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)" - .format(len(test_instrument.black_list_for_testing))) - except Exception: - pass - if len(write_only_settings) > 0: - print("{} write only settings could not be auto tested... custom test cases recommended for this.".format( - len(write_only_settings))) - print("skipped write only settings include: {}".format(write_only_settings)) - total_tested = range_counter + values_counter + idx_vals_counter + dict_vals_counter - print("{} properties tested out of {} total settings.".format(total_tested, total_settings)) - - # if isinstance(test_instrument, TestInstrumentDriver): - # assert values_counter == range_counter == idx_vals_counter == dict_vals_counter == 1 - # print("Drivers test unit seems to be working as expected.") - - if verbose: - print("\nSettings restored to: ") - pprint.pprint(restored_settings) - - if (len(diff) > 0): - print("\nRestored settings are different for the following: ", diff) - print("\n") - - assert hasattr(test_instrument, '_version'), "The instrument had no attribute _version" - # print("The (previous) instrument version was: ", test_instrument._version) - - -def write_log(device, exception=None, save_multiple_lines=False): - try: - driver_file_name = str(type(device)).split("'")[1].split(".")[-2] - except Exception: - if exception is None: - err_string = "The tests were passed but...\n" - err_string = err_string + "failed to log test history. Driver class file path not as expected." - else: - err_string = "Tests failed with exception: \n{}\n".format(exception) - err_string = err_string + "failed to log test history. Driver class file path not as expected." - assert False, err_string - - if exception is None: - pre_string = "The tests were passed but...\n" - new_line = "Passed with {} version v{} tested on pyscan version {} at {}".format( - driver_file_name, device._version, get_pyscan_version(), datetime.now().strftime("%Y-%m-%d %H:%M:%S")) - elif exception is not None: - pre_string = "Tests failed with exception: \n{}\n".format(exception) - new_line = "Failed with {} version v{} tested on pyscan version {} at {}".format( - driver_file_name, device._version, get_pyscan_version(), datetime.now().strftime("%Y-%m-%d %H:%M:%S")) - - driver_file_name = driver_file_name + '_test_log.txt' - base_dir = os.path.dirname(os.path.abspath(__file__)) - directory = os.path.join(base_dir, '../testing/driver_test_logs/') - driver_test_logs_file_names = os.listdir(directory) - path = os.path.join(directory, driver_file_name) - - if driver_file_name in driver_test_logs_file_names: - if save_multiple_lines: - with open(path, 'r') as test_log: - existing_log = test_log.read() - - with open(path, 'w') as test_log: - test_log.write(new_line + '\n') - if save_multiple_lines: - test_log.write(existing_log) - - else: - print("No test log file detected for this driver. Creating a new one.") - - with open(path, 'w') as test_log: - test_log.write(new_line) - - try: - with open(path, 'r') as test_log: - test_log.read() - print("The new test log for this driver is: ", new_line) - except Exception: - err_string = pre_string + "Test log seemed to save but could not be accessed." - err_string = err_string + "Please ensure test records are saving properly." - assert False, err_string - - -# parses the instruments doc string and checks for each attribute -def check_attribute_doc_strings(test_instrument): - settings = [] - total_settings = 0 - for attribute_name in test_instrument.__dict__.keys(): - if "_settings" in attribute_name: - settings.append(attribute_name) - total_settings += 1 - - for setting in settings: - # if hasattr(test_instrument, 'black_list_for_testing'): - name = test_instrument[setting]['name'] - try: - doc_string = test_instrument.get_property_docstring(name) - except Exception: - assert False, "Doc string could not be found or not properly formatted for {}".format(name) - - splits = doc_string.split('\n') - assert name in splits[0], "attribute name not found on first line of doc_string for {}".format(name) - assert len(splits) > 1, "doc string for {} found as only 1 line".format(name) - assert [len(splits[1]) > 3], "doc string's second line is not long enough for {}".format(name) - - -def extract_attributes_from_docstring(doc_string): - # Assuming attributes are listed with 4 leading spaces and followed by a colon - attributes = [] - in_attributes_section = False - - for line in doc_string.split('\n'): - # Check if we've reached the Methods section - if 'Methods' in line: - break # Stop processing if we've reached Methods - - # Check for the start of Attributes section - if 'Attributes' in line: - in_attributes_section = True - continue # Go to the next line to read attributes - - # If we are in the Attributes section, extract attributes - if in_attributes_section: - if line.strip() == '': - continue # Skip empty lines - # Match lines that start with 4 spaces and contain a word followed by a colon - match = re.match(r'^\s{4}(\w+)\s*:', line) - if match: - attributes.append(match.group(1)) - - return attributes - - -def extract_methods_from_docstring(doc_string): - # Assuming methods are listed under 'Methods' section - methods = [] - in_methods_section = False - - for line in doc_string.split('\n'): - if 'Methods' in line: - in_methods_section = True - elif in_methods_section: - if line.strip() == '': - break - match = re.match(r'\s*(\w+)\s*\(', line) - if match: - methods.append(match.group(1)) - - return methods - - -# parses and checks the instruments doc string for proper formatting -def check_doc_strings(test_instrument): - print("Checking driver doc string.") - assert test_instrument.__doc__, "No doc string found for this driver." - doc_string = test_instrument.__doc__ - try: - lines = doc_string.split('\n') - except Exception: - assert False, "doc string found but is only one line" - - post_str = " not properly formatted or in doc string." - - assert ' Parameters' in lines, "Input parameters" + post_str - - assert ' Attributes\n ----------\n (Properties)\n' in doc_string, "Attributes" + post_str - - following_lines = lines[lines.index(' Parameters'):] - for line in following_lines: - assert line.startswith(' ') or line == '', "Improper indentation of line {}".format(repr(line)) - - check_attribute_doc_strings(test_instrument) - - # Extract attributes and methods from the docstring - attributes = extract_attributes_from_docstring(doc_string) - methods = extract_methods_from_docstring(doc_string) - - # Check that each attribute and method in the docstring exists in the test_instrument - for attribute in attributes: - assert hasattr(test_instrument, attribute), f"Attribute '{attribute}' listed in docstring but not the driver." - - for method in methods: - assert hasattr(test_instrument, method), f"Method '{method}' listed in docstring but not the driver." - - # write formatting test cases here. - - -def test_driver(device=TestInstrumentDriver(), skip_log=False, expected_attributes=None, expected_values=None, - verbose=True): - if expected_attributes is not None: - check_has_attributes(device, expected_attributes) - - if expected_values is not None: - check_attribute_values(device, expected_attributes, expected_values) - - check_properties(device, verbose) - print( - f"\033[92m Property implementation tests passed, instrument: {device.__class__.__name__} looks ready to go. \033[0m") - - check_doc_strings(device) - print("\033[92m Docstring tests passed and looking good. \033[0m") - - # Note, based on this execution order - # the driver log can pass the driver for functional tests success - # before ensuring doc string is properly formatted - if skip_log is False: - write_log(device) - - print(f"\033[1;32m {device.__class__.__name__} test results logged. \033[0m") diff --git a/pyscan/drivers/testing/auto_test_driver_properties.py b/pyscan/drivers/testing/auto_test_driver_properties.py new file mode 100644 index 00000000..a13eaee4 --- /dev/null +++ b/pyscan/drivers/testing/auto_test_driver_properties.py @@ -0,0 +1,389 @@ +from ...general.get_pyscan_version import get_pyscan_version +from .test_properties import ( + test_values_property, + test_range_property, + test_indexed_property, + test_dict_values_property, + test_read_only_property) +from ..property_settings import ( + ValuesPropertySettings, RangePropertySettings, IndexedPropertySettings, + DictPropertySettings) +from .check_initial_state import check_initial_state +import os +from datetime import datetime +import re +import pprint + +''' +WARNING! +If used without first creating a proper blacklist of _properties that cannot be safely +changed to any value in their range of options based on the driver settings may +cause instruments to self destruct, or lead to significant injury and even DEATH. +Depending on the instrument and how it's set up this may or may not be a relevant issue. +If there are any settings that could be changed to risk harming some or the instrument +blacklist them before proceeding by adding a self.black_list_for_testing property at the end +of the instrument driver's __init__. +ONLY RUN THIS TEST UNIT IF YOU ARE CERTAIN ALL SUCH ATTRIBUTES HAVE BEEN PROPERLY BLACKLISTED! +''' + +# not incluing booleans since they can be interpreted ambiguously as ints. Should it? +BAD_INPUTS = [-19812938238312948, -1.11123444859, 3.2222111234, 985767665954, 890992238.2345, + 'not ok', 'bad value', + [1, 2412, 19], [1, 191, 13, -5.3], + {'Alfred': "Batman's uncle", 'or': 'imposter'}] + + +prop_err_str1 = ("attempted to set {} property {} to {} but when queried returned {}." + + "\n This is often a sign of interdependent properties that are not suitable for this auto testing." + + "test for interdependence and consider blacklisting.") +prop_err_str2 = ("set {} property {} to {} but _{} returned {}." + + "\n This is often a sign of interdependent properties that are not suitable for this auto testing." + + "test for interdependence and consider blacklisting.") + + +def auto_test_driver_properties(device, detailed_dependence=False, skip_log=False, verbose=True): + ''' + Automatically tests driver properties and documnetation + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + detailed_dependenced : bool, optional + Tests the interdependence between properties and reports the results, by default False + skip_log : bool, optional + Skip the logging of the test results, by default False + verbose : bool, optional + Print the results of the tests, by default True + ''' + + test_properties(device, detailed_dependence, verbose) + + print( + f"\033[92m Property implementation tests passed, instrument: {device.__class__.__name__}. \033[0m") + + test_doc_string(device) + print("\033[92m Docstring tests passed. \033[0m") + + if skip_log is False: + write_log(device) + + print(f"\033[1;32m {device.__class__.__name__} test results logged. \033[0m") + + +def test_properties(device, detailed_dependence, verbose=False): + ''' + Automatically finds all of the PropertySettings properties of device and tests that they work + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + verbose : bool, optional + Print the results of the tests, by default False + ''' + + assert hasattr(device, '_version'), \ + "The instrument had no attribute _version, assign the driver a _version attribute" + + property_names = device.get_pyscan_properties() + + initial_state = get_initial_state(device, property_names) + + property_names = validate_blacklist(device, property_names) + + values_counter, range_counter, indexed_values_counter, dict_values_counter = 0, 0, 0, 0 + + instrument_name = device.__class__.__name__ + + if verbose: + print("Initial state for the {} was: ".format(instrument_name)) + pprint.pprint(initial_state) + print("\n") + + print("\nBeginning tests for: ", device.__class__.__name__, " version ", device._version) + + for property_name in property_names: + settings_name = f"_{property_name}_settings" + settings = device[settings_name] + + if property_name in device['black_list_for_testing']: + continue + + if hasattr(settings, 'read_only'): + test_read_only_property(device, property_name) + + if hasattr(settings, 'write_only'): + continue + + if isinstance(settings, ValuesPropertySettings): + values_counter += 1 + test_values_property(device, property_name, detailed_dependence, initial_state) + elif isinstance(settings, RangePropertySettings): + range_counter += 1 + test_range_property(device, property_name, detailed_dependence, initial_state) + elif isinstance(settings, IndexedPropertySettings): + indexed_values_counter += 1 + test_indexed_property(device, property_name, detailed_dependence, initial_state) + elif isinstance(settings, DictPropertySettings): + dict_values_counter += 1 + test_dict_values_property(device, property_name, detailed_dependence, initial_state) + else: + raise KeyError("No valid type present in setting: {}. Must be one of {}.".format( + property_name, ['values', 'range', 'indexed_values', 'dict_values'])) + + device[property_name] = initial_state[property_name] + + print(property_name) + + check_initial_state(device, property_name, initial_state) + + for key, value in initial_state.items(): + device[key] = value + + n_properties = len(property_names) + + mid_string = 'properties found and tested out of' + print("\n{} range {} {} total settings found.".format(range_counter, mid_string, n_properties)) + print("{} values {} {} total settings found.".format(values_counter, mid_string, n_properties)) + print("{} indexed values {} {} total settings found.".format(indexed_values_counter, mid_string, n_properties)) + print("{} dict values {} {} total settings found.".format(dict_values_counter, mid_string, n_properties)) + + print( + "{} blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)".format( + len(device.black_list_for_testing))) + + total_tested = range_counter + values_counter + indexed_values_counter + dict_values_counter + print("{} properties tested out of {} total settings.".format(total_tested, n_properties)) + + if verbose: + print("\nSettings restored to: ") + pprint.pprint(initial_state) + + +def validate_blacklist(device, property_names): + ''' + Validates that the black_list_for_testing attribute is present in the device and that all blacklisted properties + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + property_names : list + List of automatically found pyscan property names + + Returns + ------- + property_names : list + Property names having removed the blacklisted properties + ''' + + assert hasattr(device, 'black_list_for_testing'), \ + "Driver needs black_list_for_testing attribute, assign empty list of no blacklisted properties requied" + + for black_listed_property in device.black_list_for_testing: + if black_listed_property in property_names: + property_names.remove(black_listed_property) + + return property_names + + +def get_initial_state(device, property_names): + ''' + Gets the intial state of all properties + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + property_names : list + List of automatically found pyscan property names + + Returns + ------- + dict + key, values of property names, inital value pairs + ''' + + initial_state = {} + + for property_name in property_names: + initial_state[property_name] = device[property_name] + + return initial_state + + +def test_doc_string(device): + ''' + Tests the formatting of the docstring for the driver + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + ''' + + print("Testing driver doc string.") + assert device.__doc__, "No doc string found for this driver." + doc_string = device.__doc__ + + try: + lines = doc_string.split('\n') + except Exception: + assert False, "doc string found but is only one line" + + post_str = " not properly formatted or in doc string." + assert ' Parameters' in lines, "Input parameters" + post_str + assert ' Attributes\n ----------\n (Properties)\n' in doc_string, "Attributes" + post_str + + following_lines = lines[lines.index(' Parameters'):] + + for line in following_lines: + assert line.startswith(' ') or line == '', "Improper indentation of line {}".format(repr(line)) + + test_attribute_doc_string(device) + + test_attributes_from_docstring(device, doc_string) + test_methods_from_docstring(device, doc_string) + + +def test_attribute_doc_string(device): + ''' + Test the docstring for properties + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + ''' + property_names = device.get_pyscan_properties() + + for property_name in property_names: + + doc_string = device.get_property_docstring(property_name) + + lines = doc_string.split('\n') + assert property_name in lines[0], \ + "attribute property_name not found on first line of doc_string for {}".format(property_name) + assert len(lines) > 1, "doc string for {} found as only 1 line".format(property_name) + assert [len(lines[1]) > 3], "doc string's second line is not long enough for {}".format(property_name) + + +def test_attributes_from_docstring(device, doc_string): + ''' + Test that attributes listed in the docstring are also present in the driver + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + doc_string : str + The docstring of the driver + ''' + + attributes = [] + in_attributes_section = False + + for line in doc_string.split('\n'): + # test if we've reached the Methods section + if 'Methods' in line: + break # Stop processing if we've reached Methods + + # test for the start of Attributes section + if 'Attributes' in line: + in_attributes_section = True + continue # Go to the next line to read attributes + + # If we are in the Attributes section, extract attributes + if in_attributes_section: + if line.strip() == '': + continue # Skip empty lines + # Match lines that start with 4 spaces and contain a word followed by a colon + match = re.match(r'^\s{4}(\w+)\s*:', line) + if match: + attributes.append(match.group(1)) + + for attribute in attributes: + assert hasattr(device, attribute), f"Attribute '{attribute}' listed in docstring but not the driver." + + +def test_methods_from_docstring(device, doc_string): + ''' + Test that methods listed in the docstring are also present in the driver + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + doc_string : str + The docstring of the driver + ''' + + methods = [] + in_methods_section = False + + for line in doc_string.split('\n'): + if 'Methods' in line: + in_methods_section = True + elif in_methods_section: + if line.strip() == '': + break + match = re.match(r'\s*(\w+)\s*\(', line) + if match: + methods.append(match.group(1)) + + for method in methods: + assert hasattr(device, method), f"Method '{method}' listed in docstring but not the driver." + + +def write_log(device, exception=None, save_multiple_lines=False): + try: + driver_file_name = str(type(device)).split("'")[1].split(".")[-2] + except Exception: + if exception is None: + err_string = "The tests were passed but...\n" + err_string = err_string + "failed to log test history. Driver class file path not as expected." + else: + err_string = "Tests failed with exception: \n{}\n".format(exception) + err_string = err_string + "failed to log test history. Driver class file path not as expected." + assert False, err_string + + if exception is None: + pre_string = "The tests were passed but...\n" + new_line = "Passed with {} version v{} tested on pyscan version {} at {}".format( + driver_file_name, device._version, get_pyscan_version(), datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + elif exception is not None: + pre_string = "Tests failed with exception: \n{}\n".format(exception) + new_line = "Failed with {} version v{} tested on pyscan version {} at {}".format( + driver_file_name, device._version, get_pyscan_version(), datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + + driver_file_name = driver_file_name + '_test_log.txt' + base_dir = os.path.dirname(os.path.abspath(__file__)) + directory = os.path.join(base_dir, '../testing/driver_test_logs/') + driver_test_logs_file_names = os.listdir(directory) + path = os.path.join(directory, driver_file_name) + + if driver_file_name in driver_test_logs_file_names: + if save_multiple_lines: + with open(path, 'r') as test_log: + existing_log = test_log.read() + + with open(path, 'w') as test_log: + test_log.write(new_line + '\n') + if save_multiple_lines: + test_log.write(existing_log) + + else: + print("No test log file detected for this driver. Creating a new one.") + + with open(path, 'w') as test_log: + test_log.write(new_line) + + try: + with open(path, 'r') as test_log: + test_log.read() + print("The new test log for this driver is: ", new_line) + except Exception: + err_string = pre_string + "Test log seemed to save but could not be accessed." + err_string = err_string + "Please ensure test records are saving properly." + assert False, err_string diff --git a/pyscan/drivers/testing/check_initial_state.py b/pyscan/drivers/testing/check_initial_state.py new file mode 100644 index 00000000..154880c3 --- /dev/null +++ b/pyscan/drivers/testing/check_initial_state.py @@ -0,0 +1,21 @@ +def check_initial_state(device, property_name, initial_state): + ''' + Check if the properties have changed from their intial state after a property has been tested + + Parameters + ---------- + device : subclass of pyscan.drivers.AbstractDriver + Instance of a driver subclassed from AbstractDriver + property_name : str + Name of the property that was just tested or changed + initial_state : dict + key, value pairs of property names and their initial values + ''' + + for key, value in initial_state.items(): + + new_value = device[key] + + if new_value != value: + print(f'Warning, changing {property_name} changed {key} from {value} to {new_value}') + device[key] = value diff --git a/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt b/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt index 4e976046..774669d3 100644 --- a/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt @@ -1 +1 @@ -Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.8.3 at 2024-10-24 09:30:29 +Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.8.3 at 2024-10-28 14:07:06 diff --git a/pyscan/drivers/testing/test_instrument_driver.py b/pyscan/drivers/testing/test_instrument_driver.py index a38bff0a..19c7585e 100644 --- a/pyscan/drivers/testing/test_instrument_driver.py +++ b/pyscan/drivers/testing/test_instrument_driver.py @@ -7,8 +7,8 @@ class TestInstrumentDriver(AbstractDriver): Parameters ---------- - instrument : mock - Optional parameter. + instrument : str (None) + A string that holds the place of an instrument to be connected to. Attributes ---------- @@ -39,7 +39,7 @@ def __init__(self, debug=False, instrument=None, *arg, **kwarg): self._float_value = 2.0 self._str_value = '2' self._bool_value = False - self._range = 0 + self._range = 0.111111 self._indexed_value = 'A' self._dict_value = 'off' self._version = "0.1.0" @@ -56,10 +56,10 @@ def query_property(self, settings_obj): val = self._str_value elif string == 'BOOL_VALUES?': val = self._bool_value - elif string == 'RANGE?': - val = self._range + elif string in 'RANGE?': + val = "{:.3f}".format(self._range) elif string == 'INDEXED_VALUES?': - val = self._indexed_value_settings['indexed_values'].index(self._indexed_value) + val = self._indexed_value_settings.indexed_values.index(self._indexed_value) elif string == 'DICT_VALUES?': for key, val in settings_obj.dict_values.items(): if str(key) == str(self._dict_value): @@ -77,7 +77,9 @@ def write_property(self, settings_obj, new_value): if 'BOOL_VALUES' in string: return string.strip('BOOL_VALUES ') elif 'RANGE' in string: - return string.strip('RANGE ') + ret = string.strip('RANGE ') + ret = float("{:.3f}".format(float(ret))) + return ret elif 'INDEXED_VALUES' in string: return string.strip('INDEXED_VALUES ') elif 'DICT_VALUES' in string: @@ -89,19 +91,19 @@ def initialize_properties(self): 'name': 'float_value', 'write_string': 'FLOAT_VALUES {}', 'query_string': 'FLOAT_VALUES?', - 'values_list': [2.0, 2.2339340249, 89.129398]}) + 'values': [2.0, 2.2339340249, 89.129398]}) self.add_device_property({ 'name': 'str_value', 'write_string': 'STR_VALUES {}', 'query_string': 'STR_VALUES?', - 'values_list': ['2', 'x', 'False']}) + 'values': ['2', 'x', 'False']}) self.add_device_property({ 'name': 'bool_value', 'write_string': 'BOOL_VALUES {}', 'query_string': 'BOOL_VALUES?', - 'values_list': [True, False]}) + 'values': [True, False]}) self.add_device_property({ 'name': 'range', @@ -122,13 +124,6 @@ def initialize_properties(self): 'query_string': 'DICT_VALUES?', 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0, 1: 1, 0: 0}}) - with pytest.raises(Exception): - self.add_device_property({ - 'name': 'bad_values', - 'write_string': 'DICT_VALUES {}', - 'query_string': 'DICT_VALUES?', - 'invalid_key_name': {'on': 1, 'off': 0, '1': 1, '0': 0, 0: 0, 1: 1}, - 'return_type': str}) def update_properties(self): self.float_value @@ -183,17 +178,17 @@ def query_property(self, settings_obj): elif string == 'RANGE?': return str(self._range) elif string == 'INDEXED_VALUES?': - idx = self._indexed_value_settings['indexed_values'].index(self._indexed_value) + idx = self._indexed_value_settings.indexed_values.index(self._indexed_value) return str(idx) elif string == 'DICT_VALUES?': - val = self._dict_values_settings['dict_values'][self._dict_value] + val = self._dict_values_settings.dict_values[self._dict_value] return str(val) # we are not currently testing for this in test voltage... doesn't seem particularly useful to do so def write_property(self, settings_obj, new_value): string = settings_obj.object.write_string.format(new_value) - if 'values_list' in string: + if 'values' in string: return string.strip('VALUES ') elif 'RANGE' in string: return string.strip('RANGE ') @@ -208,7 +203,7 @@ def initialize_properties(self): 'name': 'value', 'write_string': 'VALUES {}', 'query_string': 'VALUES?', - 'values_list': [2, 'x', False, (1, 10), ['1', '10']], + 'values': [2, 'x', False, (1, 10), ['1', '10']], 'return_type': str }) diff --git a/pyscan/drivers/testing/test_properties.py b/pyscan/drivers/testing/test_properties.py new file mode 100644 index 00000000..21c47594 --- /dev/null +++ b/pyscan/drivers/testing/test_properties.py @@ -0,0 +1,212 @@ +# test the set_values_property behavior +import pytest +from math import floor, ceil +import typing +from pyscan.general.d_range import drange +from .check_initial_state import check_initial_state + + +missing_prop_str = "device did not have {} property, test it's in drivers initialize_properties or update_properties." +BAD_INPUTS = [-19812938238312948, -1.11123444859, 3.2222111234, 985767665954, 890992238.2345, + 'not ok', 'bad value', + [1, 2412, 19], [1, 191, 13, -5.3], + {'Alfred': "Batman's uncle", 'or': 'imposter'}] +prop_err_str1 = ("attempted to set {} property {} to {} but when queried returned {}." + + "\n This is often a sign of interdependent properties that are not suitable for this auto testing." + + "test for interdependence and consider blacklisting.") +prop_err_str2 = ("set {} property {} to {} but _{} returned {}." + + "\n This is often a sign of interdependent properties that are not suitable for this auto testing." + + "test for interdependence and consider blacklisting.") + + +def test_read_only_property(device, property_name): + ''' + Tests a read-only property of Range, Values, IndexedValues, or Dict type + + Parameters + ---------- + device : Subclass of pyscan.drivers.AbstractDriver + The device to test + property_name : str + The name of the property to test + detailed_dependence : bool + If True, check if changing this property changes other properties + initial_state : dict + key, value pairs of property names and their initial values + ''' + + assert device[property_name], 'Could not read property {}'.format(property_name) + + +def test_values_property(device, property_name, detailed_dependence, initial_state): + ''' + Tests a values property + + Parameters + ---------- + device : Subclass of pyscan.drivers.AbstractDriver + The device to test + property_name : str + The name of the property to test + detailed_dependence : bool + If True, check if changing this property changes other properties + initial_state : dict + key, value pairs of property names and their initial values + ''' + + settings_name = f'_{property_name}_settings' + settings = device[settings_name] + + for value in settings.values: + device[property_name] = value + assert device[property_name] == value, prop_err_str1.format( + 'values', property_name, str(value) + str(type(value)), + str(device[property_name]) + str(type(device[property_name]))) + assert device[property_name] == value, prop_err_str2.format( + 'values', property_name, value, + property_name, property_name) + + if detailed_dependence: + check_initial_state(device, property_name, initial_state) + + for value in BAD_INPUTS: + if value not in settings.values: + with pytest.raises(Exception): + device[property_name] = value + + +def decimal_places(x): + return len(str(x).split('.')[1]) + + +def decimal_floor(x, n): + val = float('{:.{prec}f}'.format(x, prec=n)) + if val > x: + return val - 10**(-n) + else: + return val + + +def decimal_ceil(x, n): + val = float('{:.{prec}f}'.format(x, prec=n)) + if val < x: + return val + 10**(-n) + else: + return val + + +def test_range_property(device, property_name, detailed_dependence, initial_state): + ''' + Tests a range property of an instrument + + Parameters + ---------- + device : Subclass of pyscan.drivers.AbstractDriver + The device to test + property_name : str + The name of the property to test + detailed_dependence : bool + If True, check if changing this property changes other properties + initial_state : dict + key, value pairs of property names and their initial values + ''' + + settings_name = f'_{property_name}_settings' + settings = device[settings_name] + + min_range = min(settings.range) + max_range = max(settings.range) + + with pytest.raises(Exception): + device[property_name] = min_range - .001 + with pytest.raises(Exception): + device[property_name] = min_range - 1 + with pytest.raises(Exception): + device[property_name] = max_range + .001 + with pytest.raises(Exception): + device[property_name] = max_range + 1 + + step = abs(settings.range[1] - settings.range[0]) / 9 + + for value in drange(int(settings.range[0]), step, int(settings.range[1])): + device[property_name] = value + + new_value = device[property_name] + + n = decimal_places(new_value) + + low = decimal_floor(value, n) + high = decimal_ceil(value, n) + + assert low <= value <= high, prop_err_str1.format( + 'indexed', property_name, value, device[property_name]) + + +def test_indexed_property(device, property_name, detailed_dependence, initial_state): + ''' + Tests an indexed property of an instrument + + Parameters + ---------- + device : Subclass of pyscan.drivers.AbstractDriver + The device to test + property_name : str + The name of the property to test + detailed_dependence : bool + If True, check if changing this property changes other properties + initial_state : dict + key, value pairs of property names and their initial values + ''' + + settings_name = f'_{property_name}_settings' + settings = device[settings_name] + + for i, value in enumerate(settings.indexed_values): + device[property_name] = value + + assert device[property_name] == value, prop_err_str1.format( + 'indexed', property_name, value, device[property_name]) + assert device["_{}".format(property_name)] == value, prop_err_str2.format( + 'indexed', property_name, value, "_{}".format(property_name), device["_{}".format(property_name)]) + + for value in BAD_INPUTS: + if value not in settings.indexed_values: + with pytest.raises(Exception): + device[property_name] = value + + +def test_dict_values_property(device, property_name, detailed_dependence, initial_state): + ''' + Tests an dict values property of an instrument + + Parameters + ---------- + device : Subclass of pyscan.drivers.AbstractDriver + The device to test + property_name : str + The name of the property to test + detailed_dependence : bool + If True, check if changing this property changes other properties + initial_state : dict + key, value pairs of property names and their initial values + ''' + + settings_name = f'_{property_name}_settings' + settings = device[settings_name] + + for key, value in settings.dict_values.items(): + device[property_name] = value + + err_str1 = ("{} key return not properly formatted. Did not return first key {}, instead returned {}").format( + property_name, settings.dict_values[key], device[property_name]) + assert device[property_name] == settings.find_first_key(settings.dict_values[key]), err_str1 + + err_str2 = ("_{} key return not properly formatted. Did not return first key {}, instead returned {}").format( + property_name, settings.dict_values[key], device[property_name]) + assert device["_{}".format(property_name)] == settings.find_first_key(settings.dict_values[key]), err_str2 + + for bad_input in BAD_INPUTS: + if isinstance(bad_input, typing.Hashable): + if bad_input not in settings.dict_values: + with pytest.raises(Exception): + device[property_name] = bad_input diff --git a/pyscan/drivers/testing/test_voltage.py b/pyscan/drivers/testing/test_voltage.py index ac3b0683..ae024ed8 100644 --- a/pyscan/drivers/testing/test_voltage.py +++ b/pyscan/drivers/testing/test_voltage.py @@ -48,11 +48,7 @@ def query_property(self, settings_obj): elif string == 'POW?': return str(self._power) elif string == 'OUTP?': - if self._output_state == 'off': - return '0' - if self._output_state == 'on': - return '1' - return str(self._output_state) + return self._output_state_settings.indexed_values.index(self._output_state) def write_property(self, settings_obj, new_value): @@ -64,7 +60,7 @@ def initialize_properties(self): 'name': 'voltage', 'write_string': 'VOLT {}', 'query_string': 'VOLT?', - 'range': [0, 10], + 'range': [0.0, 10.0], 'return_type': float }) @@ -72,14 +68,10 @@ def initialize_properties(self): 'name': 'power', 'write_string': 'POW {}', 'query_string': 'POW?', - 'values_list': [1, 10], - 'return_type': int - }) + 'values': [1, 10]}) self.add_device_property({ 'name': 'output_state', 'write_string': 'OUTP {}', 'query_string': 'OUTP?', - 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0}, - 'return_type': str - }) + 'indexed_values': ['off', 'on']}) diff --git a/test/drivers/test_test_voltage.py b/test/drivers/test_test_voltage.py index 51eccdba..ac212662 100644 --- a/test/drivers/test_test_voltage.py +++ b/test/drivers/test_test_voltage.py @@ -4,7 +4,6 @@ from pyscan.drivers.testing.test_voltage import TestVoltage from pyscan.drivers.testing.auto_test_driver import test_driver -import pytest def test_test_voltage(): @@ -19,71 +18,4 @@ def test_test_voltage(): # set up v1 as representative for testing v1 = TestVoltage() - # ########## add 2 more test voltages for testing... - - # test voltage attribute - assert hasattr(v1, 'voltage'), "TestVoltage missing voltage attribute" - assert isinstance(v1.voltage, float), "TestVoltage voltage attribute is not a float" - assert v1.voltage == 0.0, "TestVoltage voltage not initialized to 0.0" - assert v1._voltage == 0.0, "TestVoltage _voltage does not return 0.0" - with pytest.raises(Exception): - v1.voltage = -1, "TestVoltage voltage can be negative" - with pytest.raises(Exception): - v1.voltage = 11, "TestVoltage voltage can be more than 10" - with pytest.raises(Exception): - v1.voltage = 'bad', "TestVoltage can be a string" - assert callable(v1.query), "TestVoltage query is not a callable function" - assert v1.query('VOLT?') == '0.0', "TestVoltage query of 'Volt?' does not return string of voltage" - v1.voltage = 1 - v1.voltage = 5 - v1.voltage = 10 - - # test power attribute initialization - assert hasattr(v1, 'power'), "TestVoltage missing power attribute" - assert isinstance(v1.power, int), "TestVoltage power is not an int" - assert v1.power == 1, "TestVoltage power is not initialized as 1" - assert v1.query('POW?') == '1', "TestVoltage query of 'POW?' does not return string of power" - with pytest.raises(Exception): - v1.power = 0, "TestVoltage power can be set to 0" - with pytest.raises(Exception): - v1.power = 2, "TestVoltage power can be set to 2" - with pytest.raises(Exception): - v1.power = 9, "TestVoltage power can be set to 9" - with pytest.raises(Exception): - v1.power = 11, "TestVoltage power can be set to 11" - with pytest.raises(Exception): - v1.power = 'superpower', "TestVoltage power can be set to a string" - v1.power = 10 - - # test output state attribute initialization - assert hasattr(v1, 'output_state'), "TestVoltage missing output_state attribute" - assert isinstance(v1._output_state, str), "TestVoltage _output_state is not initially a string" - assert v1._output_state == 'off', "TestVoltage _output_state does not initially return expected output state" - assert v1.query('OUTP?') == '0', "TestVoltage query of 'OUTP?' does not return expected result" - with pytest.raises(Exception): - v1.output_state = {'orkitty ork ork': 1}, "TestVoltage output_state can be set to invalid dict" - with pytest.raises(Exception): - v1.output_state = 'orkity ork ork', "TestVoltage output_state can be set to invalid string" - with pytest.raises(Exception): - v1.output_state = -1, "TestVoltage output_state can be set to negative number" - with pytest.raises(Exception): - v1.output_state = 2, "TestVoltage output_state can be set to invalid integer" - with pytest.raises(Exception): - v1.output_state = 1, "TestVoltage output_state can be set to an integer" - with pytest.raises(Exception): - v1.output_state = '5', "TestVoltage output_state can be set to a non dictionary key string" - v1.output_state = '1' - assert v1.query('OUTP?') == '1', "TestVoltage query of 'OUTP?' does not return expected result" - assert v1._output_state == 'on', "TestVoltage _output_state does not get machine value after '1' key user input" - assert v1.output_state == 'on', "TestVoltage output_state does not get machine value after 'on' key user input" - v1.output_state = '0' - assert v1._output_state == 'off', "TestVoltage _output_state does not get machine value after 'off' key user input" - assert v1.output_state == 'off', "TestVoltage output_state does not get machine value after 'off' key user input" - v1.output_state = 'on' - assert v1._output_state == 'on', "TestVoltage _output_state does not get machine value after 'on' key user input" - assert v1.output_state == 'on', "TestVoltage output_state does not get machine value after 'on' key user input" - v1.output_state = 'off' - assert v1._output_state == 'off', "TestVoltage _output_state does not get machine value after 'off' key user input" - assert v1.output_state == 'off', "TestVoltage output_state does not get machine value after 'off' key user input" - test_driver(v1) diff --git a/test/measurement/test_abstract_experiment.py b/test/measurement/test_abstract_experiment.py index 185584a7..7e578fe8 100644 --- a/test/measurement/test_abstract_experiment.py +++ b/test/measurement/test_abstract_experiment.py @@ -59,7 +59,7 @@ def test_abstract_experiment(): None """ - def test_ms_diff_inputs(data_dir=None, measure_function=measure_point, allocate='preallocate'): + def test_expt_diff_inputs(data_dir=None, measure_function=measure_point, allocate='preallocate'): devices = ps.ItemAttribute() devices.v1 = ps.TestVoltage() devices.v2 = ps.TestVoltage() @@ -77,91 +77,91 @@ def test_ms_diff_inputs(data_dir=None, measure_function=measure_point, allocate= runinfo.measure_function = measure_function - ms = AbstractExperiment(runinfo, devices, data_dir) + expt = AbstractExperiment(runinfo, devices, data_dir) # testing meta sweep's init - assert hasattr(ms, 'runinfo'), "Meta Sweep runinfo not set up" - assert ms.runinfo == runinfo, "Meta Sweep runinfo not set up properly" + assert hasattr(expt, 'runinfo'), "Meta Sweep runinfo not set up" + assert expt.runinfo == runinfo, "Meta Sweep runinfo not set up properly" - assert hasattr(ms, 'devices'), "Meta Sweep devices not set up" - assert ms.devices == devices, "Meta Sweep devices not set up properly" + assert hasattr(expt, 'devices'), "Meta Sweep devices not set up" + assert expt.devices == devices, "Meta Sweep devices not set up properly" - assert hasattr(ms.runinfo, 'data_path'), "Meta Sweep data path not set up" + assert hasattr(expt.runinfo, 'data_path'), "Meta Sweep data path not set up" # testing meta sweep's setup data dir method - assert callable(ms.setup_data_dir) - ms.setup_data_dir(data_dir) + assert callable(expt.setup_data_dir) + expt.setup_data_dir(data_dir) if data_dir is None: - assert ms.runinfo.data_path == Path('./backup'), "Meta Sweep data path not set up properly" + assert expt.runinfo.data_path == Path('./backup'), "Meta Sweep data path not set up properly" else: # ################ This is what the program is doing... is this what we want? ############### - assert ms.runinfo.data_path == Path(Path(data_dir)), "Meta Sweep data path not set up properly" - assert ms.runinfo.data_path.is_dir() + assert expt.runinfo.data_path == Path(Path(data_dir)), "Meta Sweep data path not set up properly" + assert expt.runinfo.data_path.is_dir() # testing meta sweep's check runinfo method - assert callable(ms.check_runinfo) - ms.check_runinfo() - assert ms.check_runinfo() == 1 + assert callable(expt.check_runinfo) + expt.check_runinfo() + assert expt.check_runinfo() == 1 - assert hasattr(ms.runinfo, 'long_name'), "Meta Sweep runinfo long name is not initialized by check_runinfo()" - assert isinstance(ms.runinfo.long_name, str), "Meta Sweep runinfo long name is not initialized as a string" - # check that the long name is formatted with values for YYYYMMDDTHHMMSS, and optionally a - followed by digits. - assert re.match(r'^\d{8}T\d{6}(-\d+)?$', ms.runinfo.long_name), "runinfo long_name is not properly formatted" + assert hasattr(expt.runinfo, 'long_name'), "Meta Sweep runinfo long name is not initialized by check_runinfo()" + assert isinstance(expt.runinfo.long_name, str), "Meta Sweep runinfo long name is not initialized as a string" + # check that the long name is formatted with values for YYYYMMDDTHHMexptS, and optionally a - followed by digits. + assert re.match(r'^\d{8}T\d{6}(-\d+)?$', expt.runinfo.long_name), "runinfo long_name is not properly formatted" - assert hasattr(ms.runinfo, 'short_name'), "Meta Sweep runinfo long name is not initialized by check_runinfo()" - assert isinstance(ms.runinfo.short_name, str), "Meta Sweep runinfo short name is not initialized as a string" + assert hasattr(expt.runinfo, 'short_name'), "Meta Sweep runinfo long name is not initialized by check_runinfo()" + assert isinstance(expt.runinfo.short_name, str), "Meta Sweep runinfo short name is not initialized as a string" # setting file name for loading later if data_dir is None: - file_name = './backup/' + ms.runinfo.long_name + file_name = './backup/' + expt.runinfo.long_name else: - file_name = data_dir + '/' + ms.runinfo.long_name + file_name = data_dir + '/' + expt.runinfo.long_name # ############### testing meta sweeps preallocate method here? Or will we be changing to dynamic allocation? - data = ms.runinfo.measure_function(ms) - if np.all(np.array(ms.runinfo.indicies) == 0): + data = expt.runinfo.measure_function(expt) + if np.all(np.array(expt.runinfo.indicies) == 0): for key, value in data.items(): - ms.runinfo.measured.append(key) + expt.runinfo.measured.append(key) if allocate == 'preallocate': - ms.preallocate(data) + expt.preallocate(data) elif allocate == 'preallocate_line': - ms.preallocate_line(data) + expt.preallocate_line(data) else: assert False, "allocate input variable for test not acceptable" # testing meta sweep's check runinfo method with bad scan inputs bad_runinfo = ps.RunInfo() bad_runinfo.scan0 = ps.PropertyScan({'v8': ps.drange(0, 0.1, 0.1)}, 'voltage') - bad_ms = AbstractExperiment(bad_runinfo, devices, data_dir) + bad_expt = AbstractExperiment(bad_runinfo, devices, data_dir) with pytest.raises(Exception): - bad_ms.check_runinfo(), "Metasweep's check runinfo did not ensure validation of devices and properties" + bad_expt.check_runinfo(), "Metasweep's check runinfo did not ensure validation of devices and properties" # testing meta sweep's check runinfo method with more than 1 repeat scan bad_runinfo2 = ps.RunInfo() bad_runinfo2.scan0 = ps.PropertyScan({'v1': ps.drange(0, 0.1, 0.1)}, 'voltage') bad_runinfo2.scan1 = ps.RepeatScan(3) bad_runinfo2.scan2 = ps.RepeatScan(3) - bad_ms2 = AbstractExperiment(bad_runinfo, devices, data_dir) + bad_expt2 = AbstractExperiment(bad_runinfo, devices, data_dir) with pytest.raises(Exception): - bad_ms2.check_runinfo(), "Metasweep's check runinfo did not flag runinfo with more than one repeat scan" + bad_expt2.check_runinfo(), "Metasweep's check runinfo did not flag runinfo with more than one repeat scan" # testing meta sweep's get time method *placeholder* - assert callable(ms.get_time) + assert callable(expt.get_time) # ############# The following saves don't seem to be saving any data to the file, not sure why... # testing meta sweep's save point method - assert callable(ms.save_point) - ms.save_point() + assert callable(expt.save_point) + expt.save_point() # testing meta sweep's save row method - assert callable(ms.save_row) - ms.save_row() + assert callable(expt.save_row) + expt.save_row() # testing meta sweep's save meta data method - assert callable(ms.save_metadata) - ms.save_metadata() + assert callable(expt.save_metadata) + expt.save_metadata() # now loading the experiment to check the information was saved properly temp = ps.load_experiment(file_name) @@ -196,7 +196,7 @@ def test_ms_diff_inputs(data_dir=None, measure_function=measure_point, allocate= assert temp.x3.shape == (2, 5, 5) check_3D_array(temp.x3) - assert len(temp.__dict__.keys()) == 5 + len(ms.runinfo.measured) + assert len(temp.__dict__.keys()) == 5 + len(expt.runinfo.measured) # check that the meta data was saved and loaded with expected attributes assert hasattr(temp, 'runinfo'), "runinfo was not saved/could not be loaded from meta data to temp" @@ -238,53 +238,53 @@ def test_ms_diff_inputs(data_dir=None, measure_function=measure_point, allocate= assert list(temp.devices.__dict__.keys()) == ['v1', 'v2', 'v3'], "save meta data issue saving runinfo.devices" # testing meta sweep's start thread method - assert callable(ms.start_thread), "meta sweep's start thread method not callable" - assert not hasattr(ms.runinfo, 'running'), "meta sweep runinfo has running attribute before expected" - ms.start_thread() + assert callable(expt.start_thread), "meta sweep's start thread method not callable" + assert not hasattr(expt.runinfo, 'running'), "meta sweep runinfo has running attribute before expected" + expt.start_thread() # try to affirm thread is running/ran here... threading only showed 1 thread running before and after - assert hasattr(ms.runinfo, 'running'), "meta sweep runinfo does not have running attribute after start thread" - assert ms.runinfo.running is True, "meta sweep's start thread method did not set runinfo running to true" + assert hasattr(expt.runinfo, 'running'), "meta sweep runinfo does not have running attribute after start thread" + assert expt.runinfo.running is True, "meta sweep's start thread method did not set runinfo running to true" # testing meta sweep's stop method - assert callable(ms.stop), "meta sweep's stop method not callable" - assert not hasattr(ms.runinfo, 'complete'), "meta sweep runinfo has complete attribute before expected" + assert callable(expt.stop), "meta sweep's stop method not callable" + assert not hasattr(expt.runinfo, 'complete'), "meta sweep runinfo has complete attribute before expected" buffer = StringIO() sys.stdout = buffer - ms.stop() - assert hasattr(ms.runinfo, 'complete'), "meta sweep runinfo does not have complete attribute after stop()" - assert ms.runinfo.running is False, "meta sweep's start thread method did not set runinfo running to false" - assert ms.runinfo.complete == 'stopped', "meta sweep's stop method did not set runinfo complete to stopped" + expt.stop() + assert hasattr(expt.runinfo, 'complete'), "meta sweep runinfo does not have complete attribute after stop()" + assert expt.runinfo.running is False, "meta sweep's start thread method did not set runinfo running to false" + assert expt.runinfo.complete == 'stopped', "meta sweep's stop method did not set runinfo complete to stopped" print_output = buffer.getvalue() sys.stdout = sys.__stdout__ assert print_output.strip() == 'Stopping Experiment', "meta sweep's stop method does not print confirmation" # test meta sweep's run method *placeholder* - assert callable(ms.run), "meta sweep's run method not callable" + assert callable(expt.run), "meta sweep's run method not callable" # test meta sweep's setup runinfo method *placeholder* - assert callable(ms.setup_runinfo), "meta sweep's setup runinfo method not callable" + assert callable(expt.setup_runinfo), "meta sweep's setup runinfo method not callable" # test meta sweep's setup instruments method *placeholder* - assert callable(ms.setup_instruments), "meta sweep's setup instruments method not callable" + assert callable(expt.setup_instruments), "meta sweep's setup instruments method not callable" # test meta sweep's default trigger method - assert callable(ms.default_trigger_function), "meta sweep's default trigger method not callable" + assert callable(expt.default_trigger_function), "meta sweep's default trigger method not callable" with pytest.raises(Exception): - ms.default_trigger_function() + expt.default_trigger_function() - ms.devices.trigger = ps.ItemAttribute() - ms.devices.trigger.trigger = empty_function - ms.default_trigger_function() + expt.devices.trigger = ps.ItemAttribute() + expt.devices.trigger.trigger = empty_function + expt.default_trigger_function() if data_dir is None: shutil.rmtree('./backup') else: shutil.rmtree(data_dir) - test_ms_diff_inputs() - test_ms_diff_inputs(data_dir='./backeep') - test_ms_diff_inputs(data_dir='./backup', allocate='preallocate_line') - test_ms_diff_inputs(data_dir=None, measure_function=measure_up_to_3D) - test_ms_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D) - test_ms_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D, allocate='preallocate_line') + test_expt_diff_inputs() + test_expt_diff_inputs(data_dir='./backeep') + test_expt_diff_inputs(data_dir='./backup', allocate='preallocate_line') + test_expt_diff_inputs(data_dir=None, measure_function=measure_up_to_3D) + test_expt_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D) + test_expt_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D, allocate='preallocate_line') From ac18061999ce445ea45c9ce5b70b860b83e756dc Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 28 Oct 2024 14:25:19 -0600 Subject: [PATCH 09/29] Full pytest suite now works --- .../testing/auto_test_driver_properties.py | 4 ++++ .../test_instrument_driver_test_log.txt | 2 +- .../drivers/testing/test_instrument_driver.py | 4 ++-- pyscan/general/pyscan_json_encoder.py | 3 ++- test/drivers/test_drivers_test_unit.py | 12 +++++------ test/drivers/test_test_voltage.py | 21 ------------------- 6 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 test/drivers/test_test_voltage.py diff --git a/pyscan/drivers/testing/auto_test_driver_properties.py b/pyscan/drivers/testing/auto_test_driver_properties.py index a13eaee4..8856d58d 100644 --- a/pyscan/drivers/testing/auto_test_driver_properties.py +++ b/pyscan/drivers/testing/auto_test_driver_properties.py @@ -182,8 +182,12 @@ def validate_blacklist(device, property_names): "Driver needs black_list_for_testing attribute, assign empty list of no blacklisted properties requied" for black_listed_property in device.black_list_for_testing: + print(black_listed_property) if black_listed_property in property_names: property_names.remove(black_listed_property) + else: + raise AttributeError( + 'black_list_for_testing property {} not found in driver properties'.format(black_listed_property)) return property_names diff --git a/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt b/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt index 774669d3..7926e31f 100644 --- a/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt @@ -1 +1 @@ -Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.8.3 at 2024-10-28 14:07:06 +Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.8.3 at 2024-10-28 14:19:00 diff --git a/pyscan/drivers/testing/test_instrument_driver.py b/pyscan/drivers/testing/test_instrument_driver.py index 19c7585e..83f5ab71 100644 --- a/pyscan/drivers/testing/test_instrument_driver.py +++ b/pyscan/drivers/testing/test_instrument_driver.py @@ -45,7 +45,7 @@ def __init__(self, debug=False, instrument=None, *arg, **kwarg): self._version = "0.1.0" # self.update_properties() - self.black_list_for_testing = ['_str_value'] + self.black_list_for_testing = ['str_value'] def query_property(self, settings_obj): @@ -169,7 +169,7 @@ def __init__(self, debug=False, instrument=None, *arg, **kwarg): self._version = "0.1.0" self.update_properties() - self.black_list_for_testing = ['_nonexistent_property_name'] + self.black_list_for_testing = ['nonexistent_property_name'] def query_property(self, settings_obj): string = settings_obj.query_string diff --git a/pyscan/general/pyscan_json_encoder.py b/pyscan/general/pyscan_json_encoder.py index 546f7485..a6d4b43f 100644 --- a/pyscan/general/pyscan_json_encoder.py +++ b/pyscan/general/pyscan_json_encoder.py @@ -2,6 +2,7 @@ import numpy as np from ..general.item_attribute import ItemAttribute from ..drivers.instrument_driver import InstrumentDriver +from ..drivers.property_settings.abstract_property_settings import AbstractPropertySettings from pyvisa.resources import ( # FirewireInstrument, GPIBInstrument, @@ -46,7 +47,7 @@ def default(self, obj, debug=False): if type(obj) is type: return obj.__name__ - elif isinstance(obj, (InstrumentDriver, ItemAttribute)): + elif isinstance(obj, (InstrumentDriver, ItemAttribute, AbstractPropertySettings)): return obj.__dict__ elif isinstance(obj, (range, tuple)): return list(obj) diff --git a/test/drivers/test_drivers_test_unit.py b/test/drivers/test_drivers_test_unit.py index c06746fd..a7d90b17 100644 --- a/test/drivers/test_drivers_test_unit.py +++ b/test/drivers/test_drivers_test_unit.py @@ -1,30 +1,30 @@ from pyscan.drivers.testing.test_instrument_driver import TestInstrumentDriver from pyscan.drivers.testing.test_voltage import TestVoltage -from pyscan.drivers.testing.auto_test_driver import test_driver +from pyscan.drivers.testing.auto_test_driver_properties import auto_test_driver_properties import pytest def test_drivers_test_unit(): test_instrument = TestInstrumentDriver() - test_driver(test_instrument, skip_log=True) + auto_test_driver_properties(test_instrument, skip_log=True) # This NEEDS to fail and is critical to safety bad_driver_entry = TestInstrumentDriver() # sets bad value to be included in blacklist. Mimics a mispelling or other accident. bad_driver_entry.black_list_for_testing = ['_nonexistent_property'] - with pytest.raises(Exception): - test_driver(bad_driver_entry, skip_log=True) + with pytest.raises(AttributeError): + auto_test_driver_properties(bad_driver_entry, skip_log=True) # This NEEDS to fail and is critical to safety bad_driver_entry2 = TestInstrumentDriver() # sets bad value to be included in blacklist. Mimics a mispelling or other accident. bad_driver_entry2.black_list_for_testing = ['_values', '_values'] with pytest.raises(Exception): - test_driver(bad_driver_entry2, skip_log=True) + auto_test_driver_properties(bad_driver_entry2, skip_log=True) # Include additional tests to more thoroughly ensure driver test unit flags all blacklist discrepancies. test_voltage = TestVoltage() # query functions for test voltage have been bypassed to decouple dependencies so it remains modifiable - test_driver(test_voltage, skip_log=True) + auto_test_driver_properties(test_voltage, skip_log=True) diff --git a/test/drivers/test_test_voltage.py b/test/drivers/test_test_voltage.py deleted file mode 100644 index ac212662..00000000 --- a/test/drivers/test_test_voltage.py +++ /dev/null @@ -1,21 +0,0 @@ -''' -Pytest functions to test the Runinfo class -''' - -from pyscan.drivers.testing.test_voltage import TestVoltage -from pyscan.drivers.testing.auto_test_driver import test_driver - - -def test_test_voltage(): - """ - Testing TestVoltage class - - Returns - -------- - None - """ - - # set up v1 as representative for testing - v1 = TestVoltage() - - test_driver(v1) From ec6b1864ca4188f555f558f5424c5467da5fa5fe Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 28 Oct 2024 14:36:44 -0600 Subject: [PATCH 10/29] re-fixed driver imports --- pyscan/drivers/__init__.py | 27 ------------------- .../test_instrument_driver_test_log.txt | 2 +- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/pyscan/drivers/__init__.py b/pyscan/drivers/__init__.py index bb46d7f4..dfc90874 100755 --- a/pyscan/drivers/__init__.py +++ b/pyscan/drivers/__init__.py @@ -3,33 +3,6 @@ # Objects from .instrument_driver import InstrumentDriver -# Instrument Drivers -from .agilent33500 import Agilent33500 -from .agilent34410 import Agilent34410 -from .agilentdso900series import AgilentDSO900Series -from .agilent8267d import AgilentE8267D -from .agilent8275n import Agilent8275N -from .americanmagnetics430 import AmericanMagnetics430 -from .blueforslog import BlueForsLog -from .bkprecision9130b import BKPrecision9130B -from .hp34401a import HP34401A -from .keithley2260b import Keithley2260B -from .keithley2400 import Keithley2400 -from .kepcoBOP import KepcoBOP -from .oxfordips120 import OxfordIPS120 -from .pulselaser import PulseLaser -from .stanford396 import Stanford396 -from .stanford400 import Stanford400 -from .stanford470 import Stanford470 -from .stanford620 import Stanford620 -from .stanford830 import Stanford830 -from .stanford860 import Stanford860 -from .stanford900 import Stanford900 -from .stanford928 import Stanford928 -from .tpi1002a import TPI1002A -from .yokogawags200 import YokogawaGS200 -from .actonsp2300 import ActonSP2300 - # Brand collections from .agilent import * from .american_magnetics import * diff --git a/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt b/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt index 7926e31f..cc325fcc 100644 --- a/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/test_instrument_driver_test_log.txt @@ -1 +1 @@ -Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.8.3 at 2024-10-28 14:19:00 +Passed with test_instrument_driver version v0.1.0 tested on pyscan version v0.8.4 at 2024-10-28 14:29:39 From 9a2e15872ff09edbc43c304b3991c38fa5d187a9 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 28 Oct 2024 14:43:31 -0600 Subject: [PATCH 11/29] changed ms to expt in test_abstract_experiment --- test/measurement/test_abstract_experiment.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/measurement/test_abstract_experiment.py b/test/measurement/test_abstract_experiment.py index 67e06daf..f11ffa49 100644 --- a/test/measurement/test_abstract_experiment.py +++ b/test/measurement/test_abstract_experiment.py @@ -85,7 +85,6 @@ def test_expt_diff_inputs(data_dir=None, measure_function=measure_point, allocat if data_dir is None: assert expt.runinfo.data_path == Path('./backup'), "Meta Sweep data path not set up properly" else: - # ################ This is what the program is doing... is this what we want? ############### assert expt.runinfo.data_path == Path(Path(data_dir)), "Meta Sweep data path not set up properly" assert expt.runinfo.data_path.is_dir() @@ -101,6 +100,7 @@ def test_expt_diff_inputs(data_dir=None, measure_function=measure_point, allocat assert hasattr(expt.runinfo, 'short_name'), "Meta Sweep runinfo long name is not initialized by check_runinfo()" assert isinstance(expt.runinfo.short_name, str), "Meta Sweep runinfo short name is not initialized as a string" + assert expt.runinfo.short_name == expt.runinfo.long_name[8:], "Meta Sweep short name is not the correct value" # setting file name for loading later if data_dir is None: @@ -111,8 +111,6 @@ def test_expt_diff_inputs(data_dir=None, measure_function=measure_point, allocat # ############### testing meta sweeps preallocate method here? Or will we be changing to dynamic allocation? data = expt.runinfo.measure_function(expt) if np.all(np.array(expt.runinfo.indicies) == 0): - for key, value in data.items(): - expt.runinfo.measured.append(key) if allocate == 'preallocate': expt.preallocate(data) elif allocate == 'preallocate_line': @@ -144,7 +142,7 @@ def test_expt_diff_inputs(data_dir=None, measure_function=measure_point, allocat # ############# The following saves don't seem to be saving any data to the file, not sure why... # testing meta sweep's save point method assert callable(expt.save_point) - expt.save_point() + expt.save_point(data) # testing meta sweep's save row method assert callable(expt.save_row) @@ -273,7 +271,13 @@ def test_expt_diff_inputs(data_dir=None, measure_function=measure_point, allocat test_expt_diff_inputs() test_expt_diff_inputs(data_dir='./backeep') - test_expt_diff_inputs(data_dir='./backup', allocate='preallocate_line') + with pytest.raises(Exception): + # experiments that use preallocate_line such as fast galvo and fast stage behave differenty + # in a way where without refactoring this will not/should not pass. + test_expt_diff_inputs(data_dir='./backup', allocate='preallocate_line') test_expt_diff_inputs(data_dir=None, measure_function=measure_up_to_3D) test_expt_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D) - test_expt_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D, allocate='preallocate_line') + with pytest.raises(Exception): + # This should not work with preallocate_line as is, + # because it doesn't factor data dimension into it's preallocation + test_expt_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D, allocate='preallocate_line') From 1bdf9df91316bb7753b4ffd7c7afabd81741c80a Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 28 Oct 2024 16:47:45 -0600 Subject: [PATCH 12/29] Fixed read only by adding ReadOnlyPropertySetting and requiring read_only attribute in PropertySettings --- .../instrument_driver/abstract_driver.py | 26 ++++++++++----- pyscan/drivers/property_settings/__init__.py | 1 + .../dict_property_settings.py | 2 ++ .../indexed_property_settings.py | 7 +++- .../range_property_settings.py | 3 ++ .../read_only_property_settings.py | 32 +++++++++++++++++++ .../values_property_settings.py | 2 ++ .../testing/auto_test_driver_properties.py | 27 ++++++++++------ .../drivers/testing/test_instrument_driver.py | 14 +++++++- 9 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 pyscan/drivers/property_settings/read_only_property_settings.py diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 4e7b3323..06aade3b 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -3,7 +3,9 @@ RangePropertySettings, ValuesPropertySettings, IndexedPropertySettings, - DictPropertySettings) + DictPropertySettings, + ReadOnlyPropertySetting) +from ..property_settings.read_only_property_settings import ReadOnlyException import numpy as np import re @@ -128,9 +130,13 @@ def set_instrument_property(self, obj, settings_obj, new_value): None ''' - settings_obj.validate_set_value(new_value) - self.write_property(settings_obj, new_value) - setattr(self, settings_obj._name, new_value) + if not settings_obj.read_only: + settings_obj.validate_set_value(new_value) + self.write_property(settings_obj, new_value) + setattr(self, settings_obj._name, new_value) + else: + print(settings_obj, new_value) + raise ReadOnlyException(settings_obj.name) def update_properties(self): properties = self.get_pyscan_properties() @@ -227,9 +233,16 @@ def validate_property_settings(self, settings_dict): valid = np.sum([other in settings_keys for other in other_required_key]) assert valid, \ f'{name} Invalid settings dictionary, if read_only, you must also have "return_type" or "indexed_values"' + assert valid <= 1, \ + f'{name} Invalid settings dictionary, if read_only, you must also have only "return_type" or "indexed_values"' # Check that the type value is correct - if 'range' in settings_keys: + if 'indexed_values' in settings_keys: + assert isinstance(settings_dict['indexed_values'], list), f'{name} "indexed_values" setting must be a list' + property_class = IndexedPropertySettings + elif 'read_only' in settings_keys: + property_class = ReadOnlyPropertySetting + elif 'range' in settings_keys: assert len(settings_dict['range']) == 2, f'{name} "range" setting must be a list of lenght 2' assert isinstance(settings_dict['range'], list), f'{name} "range" property setting must be a list' assert 'return_type' in settings_keys, f'{name} requires a "return_type" setting' @@ -238,9 +251,6 @@ def validate_property_settings(self, settings_dict): elif 'values' in settings_keys: assert isinstance(settings_dict['values'], list), f'{name} "values" setting must be a list' property_class = ValuesPropertySettings - elif 'indexed_values' in settings_keys: - assert isinstance(settings_dict['indexed_values'], list), f'{name} "indexed_values" setting must be a list' - property_class = IndexedPropertySettings elif 'dict_values' in settings_keys: assert isinstance(settings_dict['dict_values'], dict), f'{name} "dict_values" setting must be a dict' property_class = DictPropertySettings diff --git a/pyscan/drivers/property_settings/__init__.py b/pyscan/drivers/property_settings/__init__.py index f704e5af..af705cd6 100644 --- a/pyscan/drivers/property_settings/__init__.py +++ b/pyscan/drivers/property_settings/__init__.py @@ -2,3 +2,4 @@ from .indexed_property_settings import IndexedPropertySettings from .range_property_settings import RangePropertySettings from .values_property_settings import ValuesPropertySettings +from .read_only_property_settings import ReadOnlyPropertySetting diff --git a/pyscan/drivers/property_settings/dict_property_settings.py b/pyscan/drivers/property_settings/dict_property_settings.py index 46aa26d1..19240bc2 100644 --- a/pyscan/drivers/property_settings/dict_property_settings.py +++ b/pyscan/drivers/property_settings/dict_property_settings.py @@ -24,6 +24,8 @@ def __init__(self, settings_dict): self.key_type_dict = {} self.str_values_dict = {} + self.read_only = False + for key, value in self.dict_values.items(): self.key_type_dict[key] = type(key) self.str_values_dict[key] = str(value) diff --git a/pyscan/drivers/property_settings/indexed_property_settings.py b/pyscan/drivers/property_settings/indexed_property_settings.py index d588c054..ef21e5eb 100644 --- a/pyscan/drivers/property_settings/indexed_property_settings.py +++ b/pyscan/drivers/property_settings/indexed_property_settings.py @@ -1,4 +1,5 @@ from .abstract_property_settings import AbstractPropertySettings +from .read_only_property_settings import ReadOnlyException class IndexedValueException(Exception): @@ -20,12 +21,16 @@ def __init__(self, settings_dict): self.types = [] self.value_strings = [] + self.read_only = hasattr(self, 'read_only') + for val in self.indexed_values: self.types.append(type(val)) self.value_strings.append(str(val)) def validate_set_value(self, new_value): - if new_value in self.indexed_values: + if self.read_only: + raise ReadOnlyException(self.name) + elif new_value in self.indexed_values: return True else: raise IndexedValueException(self.name, self.value_strings, self.types, new_value) diff --git a/pyscan/drivers/property_settings/range_property_settings.py b/pyscan/drivers/property_settings/range_property_settings.py index 6e2227e5..726b0ae7 100644 --- a/pyscan/drivers/property_settings/range_property_settings.py +++ b/pyscan/drivers/property_settings/range_property_settings.py @@ -14,8 +14,11 @@ def __init__(self, prop, range, value): class RangePropertySettings(AbstractPropertySettings): def __init__(self, settings_dict): + super().__init__(settings_dict) + self.read_only = False + def validate_set_value(self, new_value): if self.range[0] <= new_value <= self.range[1]: return True diff --git a/pyscan/drivers/property_settings/read_only_property_settings.py b/pyscan/drivers/property_settings/read_only_property_settings.py new file mode 100644 index 00000000..383ec28d --- /dev/null +++ b/pyscan/drivers/property_settings/read_only_property_settings.py @@ -0,0 +1,32 @@ +from .abstract_property_settings import AbstractPropertySettings + + +class ReadOnlyException(Exception): + + def __init__(self, prop): + + msg = f"{prop} is a read only property and can't be set" + + super().__init__(msg) + + +class ReadOnlyPropertySetting(AbstractPropertySettings): + + def __init__(self, settings_dict): + + super().__init__(settings_dict) + + self.write_string = None + + self.types = [] + self.value_strings = [] + + def validate_set_value(self, new_value): + ReadOnlyException(self.name) + + def format_write_value(self, new_value): + ReadOnlyException(self.name) + + def format_query_return(self, ret): + + return self.return_type(ret) diff --git a/pyscan/drivers/property_settings/values_property_settings.py b/pyscan/drivers/property_settings/values_property_settings.py index 17d35cc7..45f513f1 100644 --- a/pyscan/drivers/property_settings/values_property_settings.py +++ b/pyscan/drivers/property_settings/values_property_settings.py @@ -21,6 +21,8 @@ def __init__(self, settings_dict): self.types = [] self.value_strings = [] + self.read_only = False + for val in self.values: self.types.append(type(val)) self.value_strings.append(str(val)) diff --git a/pyscan/drivers/testing/auto_test_driver_properties.py b/pyscan/drivers/testing/auto_test_driver_properties.py index 8856d58d..3fdd8314 100644 --- a/pyscan/drivers/testing/auto_test_driver_properties.py +++ b/pyscan/drivers/testing/auto_test_driver_properties.py @@ -6,8 +6,11 @@ test_dict_values_property, test_read_only_property) from ..property_settings import ( - ValuesPropertySettings, RangePropertySettings, IndexedPropertySettings, - DictPropertySettings) + ValuesPropertySettings, + RangePropertySettings, + IndexedPropertySettings, + DictPropertySettings, + ReadOnlyPropertySetting) from .check_initial_state import check_initial_state import os from datetime import datetime @@ -93,7 +96,7 @@ def test_properties(device, detailed_dependence, verbose=False): property_names = validate_blacklist(device, property_names) values_counter, range_counter, indexed_values_counter, dict_values_counter = 0, 0, 0, 0 - + read_only_counter = 0 instrument_name = device.__class__.__name__ if verbose: @@ -110,9 +113,10 @@ def test_properties(device, detailed_dependence, verbose=False): if property_name in device['black_list_for_testing']: continue - if hasattr(settings, 'read_only'): + if settings.read_only: test_read_only_property(device, property_name) - + read_only_counter += 1 + continue if hasattr(settings, 'write_only'): continue @@ -128,18 +132,23 @@ def test_properties(device, detailed_dependence, verbose=False): elif isinstance(settings, DictPropertySettings): dict_values_counter += 1 test_dict_values_property(device, property_name, detailed_dependence, initial_state) + elif isinstance(settings, ReadOnlyPropertySetting): + read_only_counter += 1 + test_read_only_property(device, property_name) else: raise KeyError("No valid type present in setting: {}. Must be one of {}.".format( - property_name, ['values', 'range', 'indexed_values', 'dict_values'])) - - device[property_name] = initial_state[property_name] + property_name, ['values', 'range', 'indexed_values', 'dict_values', 'read_only'])) + if not settings.read_only: + device[property_name] = initial_state[property_name] print(property_name) check_initial_state(device, property_name, initial_state) for key, value in initial_state.items(): - device[key] = value + settings = getattr(device, f'_{key}_settings') + if not settings.read_only: + device[key] = value n_properties = len(property_names) diff --git a/pyscan/drivers/testing/test_instrument_driver.py b/pyscan/drivers/testing/test_instrument_driver.py index 83f5ab71..3b03b06a 100644 --- a/pyscan/drivers/testing/test_instrument_driver.py +++ b/pyscan/drivers/testing/test_instrument_driver.py @@ -13,6 +13,8 @@ class TestInstrumentDriver(AbstractDriver): Attributes ---------- (Properties) + id : str + The instrument id, read-only float_value : float for testing float values property str_value : str @@ -36,6 +38,7 @@ def __init__(self, debug=False, instrument=None, *arg, **kwarg): self.instrument = 'instrument#123' self.debug = debug + self._id = 'instrument#123' self._float_value = 2.0 self._str_value = '2' self._bool_value = False @@ -64,7 +67,9 @@ def query_property(self, settings_obj): for key, val in settings_obj.dict_values.items(): if str(key) == str(self._dict_value): break - val + elif string == '*IDN?': + val = self._id + return val def write_property(self, settings_obj, new_value): @@ -87,6 +92,13 @@ def write_property(self, settings_obj, new_value): def initialize_properties(self): + self.add_device_property({ + 'name': 'id', + 'query_string': '*IDN?', + 'read_only': True, + 'return_type': str + }) + self.add_device_property({ 'name': 'float_value', 'write_string': 'FLOAT_VALUES {}', From 5350b4c96df478f368c815e36837ee06e1c2aac5 Mon Sep 17 00:00:00 2001 From: cint-transport Date: Tue, 29 Oct 2024 09:31:39 -0600 Subject: [PATCH 13/29] fix(drivers): fixed stanford830 and keithley 2260b to comply with new PropertySettings --- .../keithley2260b_test_notebook.ipynb | 2 +- .../srs830_test_notebook.ipynb | 160 ++++++++---------- .../instrument_driver/abstract_driver.py | 5 +- .../instrument_driver/instrument_driver.py | 8 +- pyscan/drivers/keithley/keithley2260b.py | 24 ++- .../dict_property_settings.py | 1 - pyscan/drivers/stanford/stanford830.py | 19 +-- .../keithley2260b_test_log.txt | 2 +- .../driver_test_logs/stanford830_test_log.txt | 2 +- pyscan/drivers/testing/test_properties.py | 3 +- 10 files changed, 96 insertions(+), 130 deletions(-) diff --git a/drivers_test_notebooks/keithley2260b_test_notebook.ipynb b/drivers_test_notebooks/keithley2260b_test_notebook.ipynb index 25ae7e0e..6f905327 100644 --- a/drivers_test_notebooks/keithley2260b_test_notebook.ipynb +++ b/drivers_test_notebooks/keithley2260b_test_notebook.ipynb @@ -31,7 +31,7 @@ "%autoreload 2\n", "\n", "import pyscan as ps\n", - "from pyscan.drivers.testing.auto_test_driver import test_driver\n", + "from pyscan.drivers.testing.auto_test_driver_properties import auto_test_driver_properties\n", "from pyvisa import ResourceManager, VisaIOError" ] }, diff --git a/drivers_test_notebooks/srs830_test_notebook.ipynb b/drivers_test_notebooks/srs830_test_notebook.ipynb index 9e5d8940..bb6c2157 100644 --- a/drivers_test_notebooks/srs830_test_notebook.ipynb +++ b/drivers_test_notebooks/srs830_test_notebook.ipynb @@ -6,30 +6,13 @@ "metadata": { "metadata": {} }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Could not load Keysight SD1\n", - "Could not load Keysight SD1\n", - "pylablib not found, AttocubeANC350 not loaded\n", - "Basler Camera software not found, BaserCamera not loaded\n", - "Helios Camera not installed\n", - "msl not installed, Thorlabs BSC203 driver not loaded\n", - "seabreeze module not found, Ocean Optics not imported\n", - "Thorlabs Kinesis not found, ThorlabsBSC203 not loaded\n", - "Thorlabs Kinesis not found, ThorlabsBPC303 not loaded\n", - "Thorlabs Kinesis not found, ThorlabsMFF101 not loaded\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", "\n", "import pyscan as ps\n", - "from pyscan.drivers.testing.auto_test_driver import test_driver\n", + "from pyscan.drivers.testing.auto_test_driver_properties import auto_test_driver_properties\n", "from pyvisa import ResourceManager, VisaIOError\n", "import pytest\n", "from time import sleep" @@ -70,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 17, "metadata": { "metadata": {} }, @@ -79,62 +62,55 @@ "name": "stdout", "output_type": "stream", "text": [ - "Blacklisted settings that will not be tested or changed are: \n", - "[('_amplitude', 0.028),\n", - " ('_input_configuration', 'A-B'),\n", - " ('_sensitivity', 0.005),\n", - " ('_time_constant', 1)]\n", + "input_configuration\n", + "time_constant\n", + "amplitude\n", + "sensitivity\n", "\n", "Beginning tests for: Stanford830 version 1.0.3\n", - "_id_settings\n", - "_phase_settings\n", - "_reference_source_settings\n", - "_frequency_settings\n", - "_reference_slope_settings\n", - "_harmonic_settings\n", - "_input_ground_settings\n", - "_input_coupling_settings\n", - "_input_line_filter_settings\n", - "_reserve_mode_settings\n", - "_filter_slope_settings\n", - "_synchronous_filter_settings\n", - "_display1_output_source_settings\n", - "_display2_output_source_settings\n", - "_auxiliary_voltage1_settings\n", - "_auxiliary_voltage2_settings\n", - "_auxiliary_voltage3_settings\n", - "_auxiliary_voltage4_settings\n", - "_sample_rate_settings\n", - "_end_buffer_mode_settings\n", - "_trigger_mode_settings\n", - "_buffer_points_settings\n", + "phase\n", + "reference_source\n", + "frequency\n", + "reference_slope\n", + "harmonic\n", + "input_ground\n", + "input_coupling\n", + "input_line_filter\n", + "reserve_mode\n", + "filter_slope\n", + "synchronous_filter\n", + "display1_output_source\n", + "display2_output_source\n", + "auxiliary_voltage1\n", + "auxiliary_voltage2\n", + "auxiliary_voltage3\n", + "auxiliary_voltage4\n", + "sample_rate\n", + "end_buffer_mode\n", + "trigger_mode\n", "\n", - "7 range properties found and tested out of 26 total settings found.\n", - "0 values properties found and tested out of 26 total settings found.\n", - "11 indexed values properties found and tested out of 26 total settings found.\n", - "2 dict values properties found and tested out of 26 total settings found.\n", + "7 range properties found and tested out of 22 total settings found.\n", + "0 values properties found and tested out of 22 total settings found.\n", + "13 indexed values properties found and tested out of 22 total settings found.\n", + "0 dict values properties found and tested out of 22 total settings found.\n", "4 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", - "20 properties tested out of 26 total settings.\n", - "\n", - "Restored settings are different for the following: {('id', 'Stanford_Research_Systems,SR830,s/n86813,ver1.07 '), ('buffer_points', 159)}\n", - "\n", - "\n", - "\u001b[92m Property implementation tests passed, instrument: Stanford830 looks ready to go. \u001b[0m\n", - "Checking driver doc string.\n", - "\u001b[92m Docstring tests passed and looking good. \u001b[0m\n", - "The new test log for this driver is: Passed with stanford830 version v1.0.3 tested on pyscan version v0.8.0 at 2024-10-03 08:29:18\n", + "20 properties tested out of 22 total settings.\n", + "\u001b[92m Property implementation tests passed, instrument: Stanford830. \u001b[0m\n", + "Testing driver doc string.\n", + "\u001b[92m Docstring tests passed. \u001b[0m\n", + "The new test log for this driver is: Passed with stanford830 version v1.0.3 tested on pyscan version v0.8.4 at 2024-10-29 09:11:35\n", "\u001b[1;32m Stanford830 test results logged. \u001b[0m\n" ] } ], "source": [ "srs830 = ps.Stanford830(res)\n", - "test_driver(srs830, verbose=False)" + "auto_test_driver_properties(srs830, verbose=False)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 18, "metadata": { "metadata": {} }, @@ -190,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 19, "metadata": { "metadata": {} }, @@ -216,16 +192,16 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-8.9169e-05 -9.53682e-07\n", - "0.000385286 -7.15259e-07\n", - "-4.76841e-07 -2.3842e-07\n" + "-0.000105858 -7.15259e-07\n", + "0.000421764 4.76841e-07\n", + "3.88625e-05 0.0\n" ] } ], @@ -264,7 +240,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -272,9 +248,9 @@ "output_type": "stream", "text": [ "0.002\n", - "0.00233333\n", + "0.001\n", "0.00166667\n", - "0.00133333\n" + "0.000666667\n" ] } ], @@ -289,17 +265,17 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-8.86922e-05\n", - "0.000385048\n", - "-2.3842e-07\n", - "5899.178441489818\n" + "-0.000106097\n", + "0.000421764\n", + "-4.76841e-07\n", + "5962.089260533679\n" ] } ], @@ -314,15 +290,15 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-8.86922e-05\n", - "0.000385286\n" + "-0.000105858\n", + "0.000421526\n" ] } ], @@ -335,23 +311,23 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[-8.86922e-05, 0.000385286]\n", - "[-8.9169e-05, -2.3842e-07]\n", - "[-8.89306e-05, 5900.55354135963]\n", - "[-8.89306e-05, 0.000666667]\n", - "[-8.9169e-05, 0.00233333]\n", - "[-8.89306e-05, -0.0156667]\n", - "[-8.9169e-05, 0.00133333]\n", - "[-8.89306e-05, 1000.0]\n", - "[-8.89306e-05, -8.89306e-05]\n", - "[-8.9169e-05, 0.000385286]\n" + "[-0.000105858, 0.000421287]\n", + "[-0.000105858, -2.3842e-07]\n", + "[-0.000105858, 5963.46436040349]\n", + "[-0.000105858, 0.002]\n", + "[-0.000105858, 0.004]\n", + "[-0.000105858, 0.00433333]\n", + "[-0.000105858, 0.00433333]\n", + "[-0.000106097, 1000.0]\n", + "[-0.000106097, -0.000106097]\n", + "[-0.000105858, 0.000421287]\n" ] } ], @@ -372,7 +348,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -393,7 +369,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -422,7 +398,7 @@ ], "metadata": { "kernelspec": { - "display_name": "rsbrostenv", + "display_name": ".venv", "language": "python", "name": "python3" }, diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 06aade3b..986cb77a 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -132,7 +132,8 @@ def set_instrument_property(self, obj, settings_obj, new_value): if not settings_obj.read_only: settings_obj.validate_set_value(new_value) - self.write_property(settings_obj, new_value) + value = settings_obj.format_write_value(new_value) + self.write_property(settings_obj, value) setattr(self, settings_obj._name, new_value) else: print(settings_obj, new_value) @@ -143,7 +144,7 @@ def update_properties(self): for prop in properties: settings = self['_{}_settings'.format(prop)] - if 'write_only' not in settings: + if not hasattr(settings, 'write_only'): self[prop] def get_pyscan_properties(self): diff --git a/pyscan/drivers/instrument_driver/instrument_driver.py b/pyscan/drivers/instrument_driver/instrument_driver.py index 57f22eff..fec23261 100644 --- a/pyscan/drivers/instrument_driver/instrument_driver.py +++ b/pyscan/drivers/instrument_driver/instrument_driver.py @@ -80,7 +80,7 @@ def query_property(self, settings_obj): value = self.instrument.query(settings_obj.query_string) - return self.format_query_return(self, value) + return value def write(self, string): ''' @@ -114,11 +114,7 @@ def write_property(self, settings_obj, new_value): None ''' - value = settings_obj.format_write_value(new_value) - - self.instrument.write(settings_obj.write_string.format(value)) - - setattr(self, settings_obj._name, new_value) + self.instrument.write(settings_obj.write_string.format(new_value)) def read(self): ''' diff --git a/pyscan/drivers/keithley/keithley2260b.py b/pyscan/drivers/keithley/keithley2260b.py index 474fad30..93574832 100644 --- a/pyscan/drivers/keithley/keithley2260b.py +++ b/pyscan/drivers/keithley/keithley2260b.py @@ -84,9 +84,11 @@ def __init__(self, instrument, debug=False): super().__init__(instrument) + self.instrument.read_termination = '\n' + self.debug = False - self._version = "0.1.2" + self._version = "0.1.3" # Get current limits self.max_current = float(self.query('CURR? MAX').strip('\n')) @@ -124,7 +126,7 @@ def __init__(self, instrument, debug=False): self.max_voltage_falling_slew_rate = float(self.query('VOLT:SLEW:FALL? MAX').strip('\n')) self.min_voltage_falling_slew_rate = float(self.query('VOLT:SLEW:FALL? MIN').strip('\n')) - self.black_list_for_testing = ['_current', "_voltage"] + self.black_list_for_testing = ['current', "voltage"] self.initialize_properties() self.update_properties() @@ -150,22 +152,19 @@ def initialize_properties(self): 'name': 'output_mode', 'write_string': 'OUTP:MODE {}', 'query_string': 'OUTP:MODE?', - 'indexed_values': ['CVHS', 'CCHS', 'CVLS', 'CCLS'], - 'return_type': int}) + 'indexed_values': ['CVHS', 'CCHS', 'CVLS', 'CCLS']}) self.add_device_property({ 'name': 'output', 'write_string': 'OUTP {}', 'query_string': 'OUTP?', - 'dict_values': {'off': 0, 'on': 1, '0': 0, '1': 1, 0: 0, 1: 1}, - 'return_type': int}) + 'indexed_values': ['off', 'on']}) self.add_device_property({ 'name': 'output_trigger_state', 'write_string': 'OUTP:TRIG {}', 'query_string': 'OUTP:TRIG?', - 'dict_values': {'off': 0, 'on': 1, '0': 0, '1': 1, 0: 0, 1: 1}, - 'return_type': int}) + 'indexed_values': ['off', 'on']}) # SENS:AVER:COUN properties @@ -210,8 +209,7 @@ def initialize_properties(self): 'name': 'current_protection_state', 'write_string': 'CURR:PROT:STAT {}', 'query_string': 'CURR:PROT:STAT?', - 'dict_values': {'off': 0, 'on': 1, '0': 0, '1': 1, 0: 0, 1: 1}, - 'return_type': float}) + 'indexed_values': ['off', 'on']}) self.add_device_property({ 'name': 'current_rising_slew_rate', @@ -286,15 +284,13 @@ def initialize_properties(self): 'name': 'transient_trigger_source', 'write_string': 'TRIG:TRAN:SOUR {}', 'query_string': 'TRIG:TRAN:SOUR?', - 'values': ['BUS', 'IMM'], - 'return_type': str}) + 'values': ['BUS', 'IMM']}) self.add_device_property({ 'name': 'output_trigger_source', 'write_string': 'TRIG:OUTP:SOUR {}', 'query_string': 'TRIG:OUTP:SOUR?', - 'values': ['BUS', 'IMM'], - 'return_type': str}) + 'values': ['BUS', 'IMM']}) def measure_current(self): diff --git a/pyscan/drivers/property_settings/dict_property_settings.py b/pyscan/drivers/property_settings/dict_property_settings.py index 19240bc2..8b3af3b7 100644 --- a/pyscan/drivers/property_settings/dict_property_settings.py +++ b/pyscan/drivers/property_settings/dict_property_settings.py @@ -43,7 +43,6 @@ def format_write_value(self, new_value): def format_query_return(self, ret): key = self.find_first_key(ret) - key_type = self.key_type_dict[key] return key_type(key) diff --git a/pyscan/drivers/stanford/stanford830.py b/pyscan/drivers/stanford/stanford830.py index 527afd24..e1401d77 100644 --- a/pyscan/drivers/stanford/stanford830.py +++ b/pyscan/drivers/stanford/stanford830.py @@ -136,13 +136,13 @@ def __init__(self, instrument): super().__init__(instrument) self.debug = False - self._version = "1.0.3" + self._version = "1.0.4" self.black_list_for_testing = [ - '_input_configuration', - "_time_constant", - "_amplitude", - "_sensitivity"] + 'input_configuration', + "time_constant", + "amplitude", + "sensitivity"] self.initialize_properties() self.update_properties() @@ -168,8 +168,7 @@ def initialize_properties(self): 'name': 'reference_source', 'write_string': 'FMOD {}', 'query_string': 'FMOD?', - 'indexed_values': ['external', 'internal'], - 'return_type': int}) + 'indexed_values': ['external', 'internal']}) self.add_device_property({ 'name': 'frequency', @@ -278,8 +277,7 @@ def initialize_properties(self): 'name': 'synchronous_filter', 'write_string': 'SYNC {}', 'query_string': 'SYNC?', - 'dict_values': {'off': 0, 'on': 1, '0': 0, '1': 1, 0: 0, 1: 1}, - 'return_type': int}) + 'indexed_values': ['off', 'on']}) # Display and Output Properties: @@ -351,8 +349,7 @@ def initialize_properties(self): 'name': 'trigger_mode', 'write_string': 'TSTR {}', 'query_string': 'TSTR?', - 'dict_values': {'off': 0, 'on': 1, '0': 0, '1': 1, 0: 0, 1: 1}, - 'return_type': int}) + 'indexed_values': ['off', 'on']}) self.add_device_property({ 'name': 'buffer_points', diff --git a/pyscan/drivers/testing/driver_test_logs/keithley2260b_test_log.txt b/pyscan/drivers/testing/driver_test_logs/keithley2260b_test_log.txt index 39cd1aa2..874fff4f 100644 --- a/pyscan/drivers/testing/driver_test_logs/keithley2260b_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/keithley2260b_test_log.txt @@ -1 +1 @@ -Passed with keithley2260b version v0.1.1 tested on pyscan version v0.5.0 at 2024-06-11 12:27:11 \ No newline at end of file +Passed with keithley2260b version v0.1.3 tested on pyscan version v0.8.4 at 2024-10-29 09:29:56 diff --git a/pyscan/drivers/testing/driver_test_logs/stanford830_test_log.txt b/pyscan/drivers/testing/driver_test_logs/stanford830_test_log.txt index b579f033..0ace3082 100644 --- a/pyscan/drivers/testing/driver_test_logs/stanford830_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/stanford830_test_log.txt @@ -1 +1 @@ -Passed with stanford830 version v1.0.3 tested on pyscan version v0.8.0 at 2024-10-03 08:29:18 +Passed with stanford830 version v1.0.3 tested on pyscan version v0.8.4 at 2024-10-29 09:11:35 diff --git a/pyscan/drivers/testing/test_properties.py b/pyscan/drivers/testing/test_properties.py index 21c47594..deb7b6ae 100644 --- a/pyscan/drivers/testing/test_properties.py +++ b/pyscan/drivers/testing/test_properties.py @@ -128,7 +128,8 @@ def test_range_property(device, property_name, detailed_dependence, initial_stat step = abs(settings.range[1] - settings.range[0]) / 9 - for value in drange(int(settings.range[0]), step, int(settings.range[1])): + for value in drange(settings.range[0], step, settings.range[1]): + device[property_name] = value new_value = device[property_name] From b08095ba3733deb5bb051d672d3145357907a9be Mon Sep 17 00:00:00 2001 From: cint-transport Date: Tue, 29 Oct 2024 09:42:04 -0600 Subject: [PATCH 14/29] test(test_station): updated testing notebooks and created real_experiment test --- .github/workflows/test_station.yml | 6 +- .../demo_test_notebook.ipynb | 345 ------------------ drivers_test_notebooks/keithley2260b.ipynb | 340 +++++++++++++++++ .../keithley2260b_test_notebook.ipynb | 298 --------------- drivers_test_notebooks/real_experiment.ipynb | 100 +++++ ..._test_notebook.ipynb => stanford830.ipynb} | 0 6 files changed, 445 insertions(+), 644 deletions(-) delete mode 100644 drivers_test_notebooks/demo_test_notebook.ipynb create mode 100644 drivers_test_notebooks/keithley2260b.ipynb delete mode 100644 drivers_test_notebooks/keithley2260b_test_notebook.ipynb create mode 100644 drivers_test_notebooks/real_experiment.ipynb rename drivers_test_notebooks/{srs830_test_notebook.ipynb => stanford830.ipynb} (100%) diff --git a/.github/workflows/test_station.yml b/.github/workflows/test_station.yml index 46317f72..71a68bc2 100644 --- a/.github/workflows/test_station.yml +++ b/.github/workflows/test_station.yml @@ -26,7 +26,11 @@ jobs: - name: Test notebooks run: | - python -m pytest --nbmake ./drivers_test_notebooks/srs830_test_notebook.ipynb + python -m pytest --nbmake ./drivers_test_notebooks/stanford830.ipynb + python -m pytest --nbmake ./drivers_test_notebooks/keithley2260b.ipynb + python -m pytest --nbmake ./drivers_test_notebooks/real_experiment.ipynb + + # - name: Archive test results # uses: actions/upload-artifact@v4 # with: diff --git a/drivers_test_notebooks/demo_test_notebook.ipynb b/drivers_test_notebooks/demo_test_notebook.ipynb deleted file mode 100644 index 73ca87cb..00000000 --- a/drivers_test_notebooks/demo_test_notebook.ipynb +++ /dev/null @@ -1,345 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "\n", - "import pyscan as ps\n", - "from pyscan.drivers.testing.auto_test_driver import test_driver" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "from pyvisa import ResourceManager, VisaIOError" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "GPIB0::8::INSTR Stanford_Research_Systems,SR830,s/n86813,ver1.07 \n", - "\n" - ] - } - ], - "source": [ - "rm = ResourceManager()\n", - "\n", - "rs = rm.list_resources()\n", - "# print(rs)\n", - "for r in rs:\n", - " res = rm.open_resource(r)\n", - " try: \n", - " name = res.query('*IDN?')\n", - " if 'SR830' in name:\n", - " print(r, name)\n", - " break\n", - " except VisaIOError:\n", - " pass\n", - " res.close()" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Stanford_Research_Systems,SR830,s/n86813,ver1.07 \\n'" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res.query('*IDN?')" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [], - "source": [ - "# Jasmine suggests that this may hide a bug in certain drivers where reassigning an instrument connection variable\n", - "# will not close the previous connection. This may be an issue we need to test for so that reconnecting is reliable.\n", - "# Drivers with this issue may need an additional clause \"__close__\" to resolve reconnecting issues.\n", - "try:\n", - " del srs830\n", - "except: \n", - " pass\n", - "\n", - "srs830 = ps.Stanford830(res)" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.1.0\n" - ] - } - ], - "source": [ - "print(srs830._version)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "dict_keys(['instrument', '_driver_class', 'debug', '_version', 'black_list_for_testing', '_phase_settings', '_reference_source_settings', '_frequency_settings', '_reference_slope_settings', '_harmonic_settings', '_amplitude_settings', '_input_configuration_settings', '_input_ground_settings', '_input_coupling_settings', '_input_line_filter_settings', '_sensitivity_settings', '_reserve_mode_settings', '_time_constant_settings', '_filter_slope_settings', '_synchronous_filter_settings', '_display1_output_source_settings', '_display2_output_source_settings', '_auxillary_voltage1_settings', '_auxillary_voltage2_settings', '_auxillary_voltage3_settings', '_auxillary_voltage4_settings', '_sample_rate_settings', '_end_buffer_mode_settings', '_trigger_mode_settings', '_local_remote_control_settings', '_gpib_overrided_state_settings', '_power_on_status_clear_settings', '_phase', '_reference_source', '_frequency', '_reference_slope', '_harmonic', '_amplitude', '_input_configuration', '_input_ground', '_input_coupling', '_input_line_filter', '_sensitivity', '_reserve_mode', '_time_constant', '_filter_slope', '_synchronous_filter', '_display1_output_source', '_display2_output_source', '_auxillary_voltage1', '_auxillary_voltage2', '_auxillary_voltage3', '_auxillary_voltage4', '_sample_rate', '_end_buffer_mode', '_trigger_mode', '_local_remote_control', '_gpib_overrided_state', '_power_on_status_clear'])\n", - "Initial state for the Stanford830 was: [('phase', 180.0), ('reference_source', 'external'), ('frequency', 1000.0), ('reference_slope', 'sine zero'), ('harmonic', 1), ('amplitude', 0.984), ('input_configuration', 'A-B'), ('input_ground', 'AC'), ('input_coupling', 'AC'), ('input_line_filter', 'none'), ('sensitivity', 0.01), ('reserve_mode', 'high'), ('time_constant', 30), ('filter_slope', 6), ('synchronous_filter', 'off'), ('display1_output_source', 'x'), ('display2_output_source', 'y'), ('auxillary_voltage1', -10.495), ('auxillary_voltage2', -10.5), ('auxillary_voltage3', -10.5), ('auxillary_voltage4', -10.5), ('sample_rate', 0.25), ('end_buffer_mode', 'one shot'), ('trigger_mode', 'on'), ('local_remote_control', 'remote'), ('gpib_overrided_state', 'on'), ('power_on_status_clear', 'on')]\n", - "These blacklisted settings and their corresponding values were not reset: [('_amplitude', 0.984), ('_input_configuration', 'A-B'), ('_time_constant', 30), ('_power_on_status_clear', 'on')]\n", - "dict_keys(['instrument', '_driver_class', 'debug', '_version', 'black_list_for_testing', '_phase_settings', '_reference_source_settings', '_frequency_settings', '_reference_slope_settings', '_harmonic_settings', '_amplitude_settings', '_input_configuration_settings', '_input_ground_settings', '_input_coupling_settings', '_input_line_filter_settings', '_sensitivity_settings', '_reserve_mode_settings', '_time_constant_settings', '_filter_slope_settings', '_synchronous_filter_settings', '_display1_output_source_settings', '_display2_output_source_settings', '_auxillary_voltage1_settings', '_auxillary_voltage2_settings', '_auxillary_voltage3_settings', '_auxillary_voltage4_settings', '_sample_rate_settings', '_end_buffer_mode_settings', '_trigger_mode_settings', '_local_remote_control_settings', '_gpib_overrided_state_settings', '_power_on_status_clear_settings', '_phase', '_reference_source', '_frequency', '_reference_slope', '_harmonic', '_amplitude', '_input_configuration', '_input_ground', '_input_coupling', '_input_line_filter', '_sensitivity', '_reserve_mode', '_time_constant', '_filter_slope', '_synchronous_filter', '_display1_output_source', '_display2_output_source', '_auxillary_voltage1', '_auxillary_voltage2', '_auxillary_voltage3', '_auxillary_voltage4', '_sample_rate', '_end_buffer_mode', '_trigger_mode', '_local_remote_control', '_gpib_overrided_state', '_power_on_status_clear'])\n", - "Reset state for the Stanford830 was: [('phase', 180.0), ('reference_source', 'external'), ('frequency', 1000.0), ('reference_slope', 'sine zero'), ('harmonic', 1), ('amplitude', 0.984), ('input_configuration', 'A-B'), ('input_ground', 'AC'), ('input_coupling', 'AC'), ('input_line_filter', 'none'), ('sensitivity', 0.01), ('reserve_mode', 'high'), ('time_constant', 30), ('filter_slope', 6), ('synchronous_filter', 'off'), ('display1_output_source', 'x'), ('display2_output_source', 'y'), ('auxillary_voltage1', -10.495), ('auxillary_voltage2', -10.5), ('auxillary_voltage3', -10.5), ('auxillary_voltage4', -10.5), ('sample_rate', 0.25), ('end_buffer_mode', 'one shot'), ('trigger_mode', 'on'), ('local_remote_control', 'remote'), ('gpib_overrided_state', 'on'), ('power_on_status_clear', 'on')]\n", - "Beginning tests for: Stanford830\n", - "7 range properties found and tested out of 27 total settings found.\n", - "0 values properties found and tested out of 27 total settings found.\n", - "13 indexed values properties found and tested out of 27 total settings found.\n", - "3 dict values properties found and tested out of 27 total settings found.\n", - "4 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", - "23 properties tested out of 27 total settings.\n", - "Settings restored to: [('phase', 180.0), ('reference_source', 'external'), ('frequency', 1000.0), ('reference_slope', 'sine zero'), ('harmonic', 1), ('amplitude', 0.984), ('input_configuration', 'A-B'), ('input_ground', 'AC'), ('input_coupling', 'AC'), ('input_line_filter', 'none'), ('sensitivity', 0.01), ('reserve_mode', 'high'), ('time_constant', 30), ('filter_slope', 6), ('synchronous_filter', 'off'), ('display1_output_source', 'x'), ('display2_output_source', 'y'), ('auxillary_voltage1', -10.495), ('auxillary_voltage2', -10.5), ('auxillary_voltage3', -10.5), ('auxillary_voltage4', -10.5), ('sample_rate', 0.25), ('end_buffer_mode', 'one shot'), ('trigger_mode', 'on'), ('local_remote_control', 'remote'), ('gpib_overrided_state', 'on'), ('power_on_status_clear', 'on')]\n", - "The previous instrument version was: 0.1.0\n", - "The new test log for this driver is: Passed with stanford830 version v0.1.0 tested on pyscan version v0.3.0 at 2024-05-28 12:35:11\n", - "\u001b[1;32mTests passed, instrument Stanford830 looks ready to go.\u001b[0m\n" - ] - } - ], - "source": [ - "'''\n", - "If a device is failing test cases for a particular property try the following solutions first:\n", - "\n", - "1. Make sure there are no typos, abnormalities, or other mismatches in formatting in the add_device_property section\n", - "for the given property.\n", - "\n", - "2. If the instrument's documentation notes that property has dependencies, create a black_list_for_testing attribute\n", - "of type list in the driver init and include the .\n", - "For the Stanford830 it looks like this: self.black_list_for_testing = ['_input_configuration', \"_time_constant\"]\n", - "\n", - "3. Make sure that all the properties being added to the device are also updated\n", - "For the Standford830 it operates like this: \n", - " def __init__(self, instrument)\n", - " self.initialize_properties()\n", - " self.update_properties()\n", - "\n", - " def update_properties(self):\n", - " self.phase\n", - " self.*all other added properties*\n", - "\n", - "If properties are added but not updated in this way it will not pass the drivers test unit.\n", - "\n", - "These are the most common problems I encountered when testing for the Stanford830. \n", - "'''\n", - "\n", - "test_driver(srs830)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1;31mType:\u001b[0m property\n", - "\u001b[1;31mString form:\u001b[0m \n", - "\u001b[1;31mDocstring:\u001b[0m \n", - "input_configuration : int\n", - "Indexed_values: ['A', 'A-B', 'Ie6', 'Ie8']. Returns int." - ] - } - ], - "source": [ - "srs830.input_configuration?" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ASRL3::INSTR Keithley Instruments Inc.,Model 2260B-80-27,1403238,01.72.20150702\n", - "\n" - ] - } - ], - "source": [ - "rm = ResourceManager()\n", - "\n", - "rs = rm.list_resources()\n", - "# print(rs)\n", - "for r in rs:\n", - " res = rm.open_resource(r)\n", - " try: \n", - " name = res.query('*IDN?')\n", - " if 'Keithley Instruments Inc.,Model 2260B' in name:\n", - " print(r, name)\n", - " break\n", - " except VisaIOError:\n", - " pass\n", - " res.close()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ASRL3::INSTR Keithley Instruments Inc.,Model 2260B-80-27,1403238,01.72.20150702\n", - "\n" - ] - } - ], - "source": [ - "try:\n", - " del keithley\n", - "except: \n", - " pass\n", - "keithley = ps.Keithley2260B(res)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " del keithley\n", - "except: \n", - " pass\n", - "keithley = ps.Keithley2260B(res)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.1.0\n" - ] - } - ], - "source": [ - "print(keithley.version)" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "dict_keys(['instrument', '_driver_class', 'debug', '_version', 'max_current', 'min_current', 'max_current_trigger_ampliutde', 'min_current_trigger_ampliutde', 'max_over_current_level', 'min_over_current_level', 'max_current_rising_slew_rate', 'min_current_rising_slew_rate', 'max_current_falling_slew_rate', 'min_current_falling_slew_rate', 'max_resistance', 'min_resistance', 'max_voltage', 'min_voltage', 'max_voltage_trigger_ampliutde', 'min_voltage_trigger_ampliutde', 'max_over_voltage_level', 'min_over_voltage_level', 'max_voltage_rising_slew_rate', 'min_voltage_rising_slew_rate', 'max_voltage_falling_slew_rate', 'min_voltage_falling_slew_rate', 'black_list_for_testing', '_output_on_delay_settings', '_output_off_delay_settings', '_output_mode_settings', '_output_settings', '_output_trigger_state_settings', '_smoothing_settings', '_current_settings', '_curret_trigger_amplitude_settings', '_over_current_level_settings', '_current_protection_state_settings', '_current_rising_slew_rate_settings', '_current_falling_slew_rate_settings', '_resistance_settings', '_voltage_settings', '_voltage_trigger_amplitude_settings', '_over_voltage_level_settings', '_voltage_rising_slew_rate_settings', '_voltage_falling_slew_rate_settings', '_transient_trigger_source_settings', '_output_trigger_source_settings', '_output_on_delay', '_output_off_delay', '_output_mode', '_output', '_output_trigger_state', '_smoothing', '_current', '_curret_trigger_amplitude', '_over_current_level', '_current_protection_state', '_current_rising_slew_rate', '_current_falling_slew_rate', '_resistance', '_voltage', '_voltage_trigger_amplitude', '_over_voltage_level', '_voltage_rising_slew_rate', '_voltage_falling_slew_rate', '_transient_trigger_source', '_output_trigger_source'])\n", - "Initial state for the Keithley2260B was: [('output_on_delay', 0.0), ('output_off_delay', 0.0), ('output_mode', 'CVHS'), ('output', 'off'), ('output_trigger_state', 'off'), ('smoothing', 'low'), ('current', 0.0), ('curret_trigger_amplitude', 0.0), ('over_current_level', 29.7), ('current_protection_state', 'off'), ('current_rising_slew_rate', 0.01), ('current_falling_slew_rate', 0.01), ('resistance', 0.01), ('voltage', 0.0), ('voltage_trigger_amplitude', 0.0), ('over_voltage_level', 8.0), ('voltage_rising_slew_rate', 0.1), ('voltage_falling_slew_rate', 0.1), ('transient_trigger_source', 'BUS'), ('output_trigger_source', 'BUS')]\n", - "These blacklisted settings and their corresponding values were not reset: [('_current', 0.0), ('_voltage', 0.0)]\n", - "dict_keys(['instrument', '_driver_class', 'debug', '_version', 'max_current', 'min_current', 'max_current_trigger_ampliutde', 'min_current_trigger_ampliutde', 'max_over_current_level', 'min_over_current_level', 'max_current_rising_slew_rate', 'min_current_rising_slew_rate', 'max_current_falling_slew_rate', 'min_current_falling_slew_rate', 'max_resistance', 'min_resistance', 'max_voltage', 'min_voltage', 'max_voltage_trigger_ampliutde', 'min_voltage_trigger_ampliutde', 'max_over_voltage_level', 'min_over_voltage_level', 'max_voltage_rising_slew_rate', 'min_voltage_rising_slew_rate', 'max_voltage_falling_slew_rate', 'min_voltage_falling_slew_rate', 'black_list_for_testing', '_output_on_delay_settings', '_output_off_delay_settings', '_output_mode_settings', '_output_settings', '_output_trigger_state_settings', '_smoothing_settings', '_current_settings', '_curret_trigger_amplitude_settings', '_over_current_level_settings', '_current_protection_state_settings', '_current_rising_slew_rate_settings', '_current_falling_slew_rate_settings', '_resistance_settings', '_voltage_settings', '_voltage_trigger_amplitude_settings', '_over_voltage_level_settings', '_voltage_rising_slew_rate_settings', '_voltage_falling_slew_rate_settings', '_transient_trigger_source_settings', '_output_trigger_source_settings', '_output_on_delay', '_output_off_delay', '_output_mode', '_output', '_output_trigger_state', '_smoothing', '_current', '_curret_trigger_amplitude', '_over_current_level', '_current_protection_state', '_current_rising_slew_rate', '_current_falling_slew_rate', '_resistance', '_voltage', '_voltage_trigger_amplitude', '_over_voltage_level', '_voltage_rising_slew_rate', '_voltage_falling_slew_rate', '_transient_trigger_source', '_output_trigger_source'])\n", - "Reset state for the Keithley2260B was: [('output_on_delay', 0.0), ('output_off_delay', 0.0), ('output_mode', 'CVHS'), ('output', 'off'), ('output_trigger_state', 'off'), ('smoothing', 'low'), ('current', 0.0), ('curret_trigger_amplitude', 0.0), ('over_current_level', 29.7), ('current_protection_state', 'off'), ('current_rising_slew_rate', 0.01), ('current_falling_slew_rate', 0.01), ('resistance', 0.01), ('voltage', 0.0), ('voltage_trigger_amplitude', 0.0), ('over_voltage_level', 8.0), ('voltage_rising_slew_rate', 0.1), ('voltage_falling_slew_rate', 0.1), ('transient_trigger_source', 'BUS'), ('output_trigger_source', 'BUS')]\n", - "Beginning tests for: Keithley2260B\n", - "11 range properties found and tested out of 20 total settings found.\n", - "2 values properties found and tested out of 20 total settings found.\n", - "2 indexed values properties found and tested out of 20 total settings found.\n", - "3 dict values properties found and tested out of 20 total settings found.\n", - "2 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", - "18 properties tested out of 20 total settings.\n", - "Settings restored to: [('output_on_delay', 0.0), ('output_off_delay', 0.0), ('output_mode', 'CVHS'), ('output', 'off'), ('output_trigger_state', 'off'), ('smoothing', 'low'), ('current', 0.0), ('curret_trigger_amplitude', 0.0), ('over_current_level', 29.7), ('current_protection_state', 'off'), ('current_rising_slew_rate', 0.01), ('current_falling_slew_rate', 0.01), ('resistance', 0.01), ('voltage', 0.0), ('voltage_trigger_amplitude', 0.0), ('over_voltage_level', 8.0), ('voltage_rising_slew_rate', 0.1), ('voltage_falling_slew_rate', 0.1), ('transient_trigger_source', 'BUS'), ('output_trigger_source', 'BUS')]\n", - "The previous instrument version was: 0.1.0\n", - "The last tested date could not be found. Check the driver_versions json for this driver.\n", - "The new test log for this driver is: Passed with keithley2260b version v0.1.0 tested on pyscan version v0.3.0 at 2024-05-24 13:50:29\n", - "\u001b[1;32mTests passed, instrument Keithley2260B looks ready to go.\u001b[0m\n" - ] - } - ], - "source": [ - "test_driver(keithley)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# need to add __close__ ability to drivers to close connections after use and test for this." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "rsbrostenv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/drivers_test_notebooks/keithley2260b.ipynb b/drivers_test_notebooks/keithley2260b.ipynb new file mode 100644 index 00000000..063a6610 --- /dev/null +++ b/drivers_test_notebooks/keithley2260b.ipynb @@ -0,0 +1,340 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "metadata": {} + }, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import pyscan as ps\n", + "from pyscan.drivers.testing.auto_test_driver_properties import auto_test_driver_properties\n", + "from pyvisa import ResourceManager, VisaIOError" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "metadata": {} + }, + "source": [ + "# Connect device" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "metadata": {} + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ASRL3::INSTR Keithley Instruments Inc.,Model 2260B-80-27,1403238,01.72.20150702\n", + "\n" + ] + } + ], + "source": [ + "rm = ResourceManager()\n", + "\n", + "rs = rm.list_resources()\n", + "# print(rs)\n", + "for r in rs:\n", + " res = rm.open_resource(r)\n", + " try: \n", + " name = res.query('*IDN?')\n", + " if 'Keithley Instruments Inc.,Model 2260B' in name:\n", + " print(r, name)\n", + " break\n", + " except VisaIOError:\n", + " pass\n", + " res.close()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "isource = ps.Keithley2260B(isource)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test Properties" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "metadata": {} + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "current\n", + "voltage\n", + "Initial state for the Keithley2260B was: \n", + "{'current': 0.0,\n", + " 'current_falling_slew_rate': 0.01,\n", + " 'current_protection_state': 'off',\n", + " 'current_rising_slew_rate': 0.01,\n", + " 'current_trigger_amplitude': 0.0,\n", + " 'output': 'off',\n", + " 'output_mode': 'CVHS',\n", + " 'output_off_delay': 0.0,\n", + " 'output_on_delay': 0.0,\n", + " 'output_trigger_source': 'BUS',\n", + " 'output_trigger_state': 'off',\n", + " 'over_current_level': 29.7,\n", + " 'over_voltage_level': 8.0,\n", + " 'resistance': 0.01,\n", + " 'transient_trigger_source': 'BUS',\n", + " 'voltage': 0.0,\n", + " 'voltage_falling_slew_rate': 0.1,\n", + " 'voltage_rising_slew_rate': 0.1,\n", + " 'voltage_trigger_amplitude': 0.0}\n", + "\n", + "\n", + "\n", + "Beginning tests for: Keithley2260B version 0.1.3\n", + "output_on_delay\n", + "output_off_delay\n", + "output_mode\n", + "output\n", + "output_trigger_state\n", + "current_trigger_amplitude\n", + "over_current_level\n", + "Warning, changing over_current_level changed current_protection_state from off to on\n", + "current_protection_state\n", + "current_rising_slew_rate\n", + "current_falling_slew_rate\n", + "resistance\n", + "voltage_trigger_amplitude\n", + "over_voltage_level\n", + "voltage_rising_slew_rate\n", + "voltage_falling_slew_rate\n", + "transient_trigger_source\n", + "output_trigger_source\n", + "\n", + "11 range properties found and tested out of 17 total settings found.\n", + "2 values properties found and tested out of 17 total settings found.\n", + "4 indexed values properties found and tested out of 17 total settings found.\n", + "0 dict values properties found and tested out of 17 total settings found.\n", + "2 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", + "17 properties tested out of 17 total settings.\n", + "\n", + "Settings restored to: \n", + "{'current': 0.0,\n", + " 'current_falling_slew_rate': 0.01,\n", + " 'current_protection_state': 'off',\n", + " 'current_rising_slew_rate': 0.01,\n", + " 'current_trigger_amplitude': 0.0,\n", + " 'output': 'off',\n", + " 'output_mode': 'CVHS',\n", + " 'output_off_delay': 0.0,\n", + " 'output_on_delay': 0.0,\n", + " 'output_trigger_source': 'BUS',\n", + " 'output_trigger_state': 'off',\n", + " 'over_current_level': 29.7,\n", + " 'over_voltage_level': 8.0,\n", + " 'resistance': 0.01,\n", + " 'transient_trigger_source': 'BUS',\n", + " 'voltage': 0.0,\n", + " 'voltage_falling_slew_rate': 0.1,\n", + " 'voltage_rising_slew_rate': 0.1,\n", + " 'voltage_trigger_amplitude': 0.0}\n", + "\u001b[92m Property implementation tests passed, instrument: Keithley2260B. \u001b[0m\n", + "Testing driver doc string.\n", + "\u001b[92m Docstring tests passed. \u001b[0m\n", + "The new test log for this driver is: Passed with keithley2260b version v0.1.3 tested on pyscan version v0.8.4 at 2024-10-29 09:29:56\n", + "\u001b[1;32m Keithley2260B test results logged. \u001b[0m\n" + ] + } + ], + "source": [ + "auto_test_driver_properties(isource)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "metadata": {} + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isource.measure_current()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "metadata": {} + }, + "outputs": [], + "source": [ + "# voltage is blacklisted for safety, just try 0 voltage\n", + "\n", + "isource.voltage\n", + "isource.voltage = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "metadata": {} + }, + "outputs": [], + "source": [ + "# current is blacklisted for safety, just try 0\n", + "\n", + "isource.current\n", + "isource.current = 0" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "metadata": {} + }, + "source": [ + "# Test methods" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "metadata": {} + }, + "outputs": [ + { + "data": { + "text/plain": [ + "-0.003" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isource.measure_voltage()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "metadata": {} + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isource.measure_power()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "metadata": {} + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isource.measure_current()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Close device" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "metadata": {} + }, + "outputs": [], + "source": [ + "try:\n", + " del isource\n", + "except: \n", + " pass" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/drivers_test_notebooks/keithley2260b_test_notebook.ipynb b/drivers_test_notebooks/keithley2260b_test_notebook.ipynb deleted file mode 100644 index 6f905327..00000000 --- a/drivers_test_notebooks/keithley2260b_test_notebook.ipynb +++ /dev/null @@ -1,298 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Could not load Keysight SD1\n", - "Could not load Keysight SD1\n", - "pylablib not found, AttocubeANC350 not loaded\n", - "Basler Camera software not found, BaserCamera not loaded\n", - "Helios Camera not installed\n", - "msl not installed, Thorlabs BSC203 driver not loaded\n", - "seabreeze module not found, Ocean Optics not imported\n", - "Failed to load spinapi library.\n", - "spinapi is not installed, PulseBlaster driver not loaded.\n", - "Thorlabs Kinesis not found, ThorlabsBSC203 not loaded\n", - "Thorlabs Kinesis not found, ThorlabsBPC303 not loaded\n", - "Thorlabs Kinesis not found, ThorlabsMFF101 not loaded\n" - ] - } - ], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "\n", - "import pyscan as ps\n", - "from pyscan.drivers.testing.auto_test_driver_properties import auto_test_driver_properties\n", - "from pyvisa import ResourceManager, VisaIOError" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "metadata": {} - }, - "source": [ - "# Connect device" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ASRL3::INSTR Keithley Instruments Inc.,Model 2260B-80-27,1403238,01.72.20150702\n", - "\n" - ] - } - ], - "source": [ - "rm = ResourceManager()\n", - "\n", - "rs = rm.list_resources()\n", - "# print(rs)\n", - "for r in rs:\n", - " res = rm.open_resource(r)\n", - " try: \n", - " name = res.query('*IDN?')\n", - " if 'Keithley Instruments Inc.,Model 2260B' in name:\n", - " print(r, name)\n", - " break\n", - " except VisaIOError:\n", - " pass\n", - " res.close()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "metadata": {} - }, - "outputs": [], - "source": [ - "isource = ps.Keithley2260B(res)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Test Properties" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "dict_keys(['instrument', 'debug', 'max_current', 'min_current', 'max_current_trigger_ampliutde', 'min_current_trigger_ampliutde', 'max_over_current_level', 'min_over_current_level', 'max_current_rising_slew_rate', 'min_current_rising_slew_rate', 'max_current_falling_slew_rate', 'min_current_falling_slew_rate', 'max_resistance', 'min_resistance', 'max_voltage', 'min_voltage', 'max_voltage_trigger_ampliutde', 'min_voltage_trigger_ampliutde', 'max_over_voltage_level', 'min_over_voltage_level', 'max_voltage_rising_slew_rate', 'min_voltage_rising_slew_rate', 'max_voltage_falling_slew_rate', 'min_voltage_falling_slew_rate', 'black_list_for_testing', '_output_on_delay_settings', '_output_off_delay_settings', '_output_mode_settings', '_output_settings', '_output_trigger_state_settings', '_smoothing_settings', '_current_settings', '_curret_trigger_amplitude_settings', '_over_current_level_settings', '_current_protection_state_settings', '_current_rising_slew_rate_settings', '_current_falling_slew_rate_settings', '_resistance_settings', '_voltage_settings', '_voltage_trigger_amplitude_settings', '_over_voltage_level_settings', '_voltage_rising_slew_rate_settings', '_voltage_falling_slew_rate_settings', '_transient_trigger_source_settings', '_output_trigger_source_settings', '_output_on_delay', '_output_off_delay', '_output_mode', '_output', '_output_trigger_state', '_smoothing', '_current', '_curret_trigger_amplitude', '_over_current_level', '_current_protection_state', '_current_rising_slew_rate', '_current_falling_slew_rate', '_resistance', '_voltage', '_voltage_trigger_amplitude', '_over_voltage_level', '_voltage_rising_slew_rate', '_voltage_falling_slew_rate', '_transient_trigger_source', '_output_trigger_source'])\n", - "Initial state for the Keithley2260B was: [('output_on_delay', 0.0), ('output_off_delay', 0.0), ('output_mode', 'CVHS'), ('output', 'off'), ('output_trigger_state', 'off'), ('smoothing', 'low'), ('current', 0.0), ('curret_trigger_amplitude', 0.0), ('over_current_level', 29.7), ('current_protection_state', 'off'), ('current_rising_slew_rate', 0.01), ('current_falling_slew_rate', 0.01), ('resistance', 0.01), ('voltage', 0.0), ('voltage_trigger_amplitude', 0.0), ('over_voltage_level', 8.0), ('voltage_rising_slew_rate', 0.1), ('voltage_falling_slew_rate', 0.1), ('transient_trigger_source', 'BUS'), ('output_trigger_source', 'BUS')]\n", - "These blacklisted settings and their corresponding values were not reset: [('_current', 0.0), ('_voltage', 0.0)]\n", - "dict_keys(['instrument', 'debug', 'max_current', 'min_current', 'max_current_trigger_ampliutde', 'min_current_trigger_ampliutde', 'max_over_current_level', 'min_over_current_level', 'max_current_rising_slew_rate', 'min_current_rising_slew_rate', 'max_current_falling_slew_rate', 'min_current_falling_slew_rate', 'max_resistance', 'min_resistance', 'max_voltage', 'min_voltage', 'max_voltage_trigger_ampliutde', 'min_voltage_trigger_ampliutde', 'max_over_voltage_level', 'min_over_voltage_level', 'max_voltage_rising_slew_rate', 'min_voltage_rising_slew_rate', 'max_voltage_falling_slew_rate', 'min_voltage_falling_slew_rate', 'black_list_for_testing', '_output_on_delay_settings', '_output_off_delay_settings', '_output_mode_settings', '_output_settings', '_output_trigger_state_settings', '_smoothing_settings', '_current_settings', '_curret_trigger_amplitude_settings', '_over_current_level_settings', '_current_protection_state_settings', '_current_rising_slew_rate_settings', '_current_falling_slew_rate_settings', '_resistance_settings', '_voltage_settings', '_voltage_trigger_amplitude_settings', '_over_voltage_level_settings', '_voltage_rising_slew_rate_settings', '_voltage_falling_slew_rate_settings', '_transient_trigger_source_settings', '_output_trigger_source_settings', '_output_on_delay', '_output_off_delay', '_output_mode', '_output', '_output_trigger_state', '_smoothing', '_current', '_curret_trigger_amplitude', '_over_current_level', '_current_protection_state', '_current_rising_slew_rate', '_current_falling_slew_rate', '_resistance', '_voltage', '_voltage_trigger_amplitude', '_over_voltage_level', '_voltage_rising_slew_rate', '_voltage_falling_slew_rate', '_transient_trigger_source', '_output_trigger_source'])\n", - "Reset state for the Keithley2260B was: [('output_on_delay', 0.0), ('output_off_delay', 0.0), ('output_mode', 'CVHS'), ('output', 'off'), ('output_trigger_state', 'off'), ('smoothing', 'low'), ('current', 0.0), ('curret_trigger_amplitude', 0.0), ('over_current_level', 29.7), ('current_protection_state', 'off'), ('current_rising_slew_rate', 0.01), ('current_falling_slew_rate', 0.01), ('resistance', 0.01), ('voltage', 0.0), ('voltage_trigger_amplitude', 0.0), ('over_voltage_level', 8.0), ('voltage_rising_slew_rate', 0.1), ('voltage_falling_slew_rate', 0.1), ('transient_trigger_source', 'BUS'), ('output_trigger_source', 'BUS')]\n", - "Beginning tests for: Keithley2260B\n", - "11 range properties found and tested out of 20 total settings found.\n", - "2 values properties found and tested out of 20 total settings found.\n", - "2 indexed values properties found and tested out of 20 total settings found.\n", - "3 dict values properties found and tested out of 20 total settings found.\n", - "2 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", - "18 properties tested out of 20 total settings.\n", - "Settings restored to: [('output_on_delay', 0.0), ('output_off_delay', 0.0), ('output_mode', 'CVHS'), ('output', 'off'), ('output_trigger_state', 'off'), ('smoothing', 'low'), ('current', 0.0), ('curret_trigger_amplitude', 0.0), ('over_current_level', 29.7), ('current_protection_state', 'off'), ('current_rising_slew_rate', 0.01), ('current_falling_slew_rate', 0.01), ('resistance', 0.01), ('voltage', 0.0), ('voltage_trigger_amplitude', 0.0), ('over_voltage_level', 8.0), ('voltage_rising_slew_rate', 0.1), ('voltage_falling_slew_rate', 0.1), ('transient_trigger_source', 'BUS'), ('output_trigger_source', 'BUS')]\n", - "Tests passed, instrument Keithley2260B should be ready to go.\n" - ] - } - ], - "source": [ - "test_driver(isource)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0.0" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "isource.measure_current()" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "metadata": {} - }, - "outputs": [], - "source": [ - "# voltage is blacklisted for safety, just try 0 voltage\n", - "\n", - "isource.voltage\n", - "isource.voltage = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "metadata": {} - }, - "outputs": [], - "source": [ - "# current is blacklisted for safety, just try 0\n", - "\n", - "isource.current\n", - "isource.current = 0" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "metadata": {} - }, - "source": [ - "# Test methods" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "data": { - "text/plain": [ - "-0.004" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "isource.measure_voltage()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0.0" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "isource.measure_power()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0.0" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "isource.measure_current()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Close device" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "metadata": {} - }, - "outputs": [], - "source": [ - "try:\n", - " del isource\n", - "except: \n", - " pass" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/drivers_test_notebooks/real_experiment.ipynb b/drivers_test_notebooks/real_experiment.ipynb new file mode 100644 index 00000000..1fe2e4f3 --- /dev/null +++ b/drivers_test_notebooks/real_experiment.ipynb @@ -0,0 +1,100 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import pyscan as ps\n", + "import numpy as np\n", + "from pyvisa import ResourceManager, VisaIOError" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "devices = ps.ItemAttribute()\n", + "\n", + "\n", + "rm = ResourceManager()\n", + "\n", + "rs = rm.list_resources()\n", + "# print(rs)\n", + "for r in rs:\n", + " res = rm.open_resource(r)\n", + " try: \n", + " name = res.query('*IDN?')\n", + " if 'SR830' in name:\n", + " devices.lockin = ps.Stanford830(res)\n", + " elif 'Keithley Instruments Inc.,Model 2260B' in name:\n", + " devices.isource = ps.Keithley2260B(res)\n", + " except VisaIOError:\n", + " pass\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def measure_function(expt):\n", + "\n", + " d = ps.ItemAttribute()\n", + " \n", + " d.f = expt.devices.lockin.frequency\n", + "\n", + " return d\n", + "\n", + "\n", + "runinfo = ps.RunInfo()\n", + "\n", + "f0 = devices.lockin.frequency\n", + "\n", + "runinfo.measure_function = measure_function\n", + "runinfo.scan0 = ps.PropertyScan({'lockin': np.logspace(1, 3)}, prop='frequency')\n", + "\n", + "expt = ps.Experiment(runinfo, devices)\n", + "expt.run()\n", + "\n", + "devices.lockin.frequency = f0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/drivers_test_notebooks/srs830_test_notebook.ipynb b/drivers_test_notebooks/stanford830.ipynb similarity index 100% rename from drivers_test_notebooks/srs830_test_notebook.ipynb rename to drivers_test_notebooks/stanford830.ipynb From 8f2334b1762c3a73b6ca44f40f7f061979f94d62 Mon Sep 17 00:00:00 2001 From: cint-transport Date: Tue, 29 Oct 2024 09:48:33 -0600 Subject: [PATCH 15/29] fix(): missed real_experiment save --- drivers_test_notebooks/real_experiment.ipynb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers_test_notebooks/real_experiment.ipynb b/drivers_test_notebooks/real_experiment.ipynb index 1fe2e4f3..5cb3af8b 100644 --- a/drivers_test_notebooks/real_experiment.ipynb +++ b/drivers_test_notebooks/real_experiment.ipynb @@ -66,13 +66,6 @@ "\n", "devices.lockin.frequency = f0" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From d6b2e7f79096174e1829cb2e35fd3cba498c1fb2 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Tue, 29 Oct 2024 13:47:50 -0600 Subject: [PATCH 16/29] syntax: fixed flake8 errors --- .../instrument_driver/abstract_driver.py | 18 +++++++++++------- .../testing/auto_test_driver_properties.py | 2 +- .../drivers/testing/test_instrument_driver.py | 1 - pyscan/drivers/testing/test_properties.py | 3 +-- pyscan/general/pyscan_json_encoder.py | 16 ++++++++-------- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 986cb77a..65f91834 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -227,15 +227,18 @@ def validate_property_settings(self, settings_dict): i += 1 if 'read_only' not in settings_keys: - assert (i <= 1) and (i > 0), \ - f'{name} invalid settings, must have a single type indicator "values", "indexed_values", "range", or "dict_values"' + assert (i <= 1) and (i > 0), ( + f'{name} invalid settings, must have a single type indicator "values",' + + 'indexed_values", "range", or "dict_values"') else: other_required_key = ['return_type', 'indexed_values'] valid = np.sum([other in settings_keys for other in other_required_key]) - assert valid, \ - f'{name} Invalid settings dictionary, if read_only, you must also have "return_type" or "indexed_values"' - assert valid <= 1, \ - f'{name} Invalid settings dictionary, if read_only, you must also have only "return_type" or "indexed_values"' + assert valid, ( + f'{name} Invalid settings dictionary, if read_only,' + + ' you must also have "return_type" or "indexed_values"') + assert valid <= 1, ( + f'{name} Invalid settings dictionary, if read_only,' + + 'you must also have only "return_type" or "indexed_values"') # Check that the type value is correct if 'indexed_values' in settings_keys: @@ -247,7 +250,8 @@ def validate_property_settings(self, settings_dict): assert len(settings_dict['range']) == 2, f'{name} "range" setting must be a list of lenght 2' assert isinstance(settings_dict['range'], list), f'{name} "range" property setting must be a list' assert 'return_type' in settings_keys, f'{name} requires a "return_type" setting' - assert settings_dict['range'][1] > settings_dict['range'][0], f'{name} has bad "range" settings, range[0] < range[1]' + assert settings_dict['range'][1] > settings_dict['range'][0], ( + f'{name} has bad "range" settings, range[0] < range[1]') property_class = RangePropertySettings elif 'values' in settings_keys: assert isinstance(settings_dict['values'], list), f'{name} "values" setting must be a list' diff --git a/pyscan/drivers/testing/auto_test_driver_properties.py b/pyscan/drivers/testing/auto_test_driver_properties.py index 3fdd8314..a0320f7b 100644 --- a/pyscan/drivers/testing/auto_test_driver_properties.py +++ b/pyscan/drivers/testing/auto_test_driver_properties.py @@ -159,7 +159,7 @@ def test_properties(device, detailed_dependence, verbose=False): print("{} dict values {} {} total settings found.".format(dict_values_counter, mid_string, n_properties)) print( - "{} blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)".format( + "{} blacklisted settings not testing".format( len(device.black_list_for_testing))) total_tested = range_counter + values_counter + indexed_values_counter + dict_values_counter diff --git a/pyscan/drivers/testing/test_instrument_driver.py b/pyscan/drivers/testing/test_instrument_driver.py index 3b03b06a..bf4b3564 100644 --- a/pyscan/drivers/testing/test_instrument_driver.py +++ b/pyscan/drivers/testing/test_instrument_driver.py @@ -136,7 +136,6 @@ def initialize_properties(self): 'query_string': 'DICT_VALUES?', 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0, 1: 1, 0: 0}}) - def update_properties(self): self.float_value self.str_value diff --git a/pyscan/drivers/testing/test_properties.py b/pyscan/drivers/testing/test_properties.py index deb7b6ae..297cbdb6 100644 --- a/pyscan/drivers/testing/test_properties.py +++ b/pyscan/drivers/testing/test_properties.py @@ -1,6 +1,5 @@ # test the set_values_property behavior import pytest -from math import floor, ceil import typing from pyscan.general.d_range import drange from .check_initial_state import check_initial_state @@ -164,7 +163,7 @@ def test_indexed_property(device, property_name, detailed_dependence, initial_st for i, value in enumerate(settings.indexed_values): device[property_name] = value - + assert device[property_name] == value, prop_err_str1.format( 'indexed', property_name, value, device[property_name]) assert device["_{}".format(property_name)] == value, prop_err_str2.format( diff --git a/pyscan/general/pyscan_json_encoder.py b/pyscan/general/pyscan_json_encoder.py index a6d4b43f..b512214f 100644 --- a/pyscan/general/pyscan_json_encoder.py +++ b/pyscan/general/pyscan_json_encoder.py @@ -67,14 +67,14 @@ def default(self, obj, debug=False): elif type(obj) is type(iter(range(1))): return list(obj) elif isinstance( - obj, - (# FirewireInstrument, - GPIBInstrument, - # PXIInstrument, - SerialInstrument, - TCPIPInstrument, - USBInstrument, - # VXIInstrument) + obj, ( + GPIBInstrument, + # FirewireInstrument, + # PXIInstrument, + SerialInstrument, + TCPIPInstrument, + USBInstrument, + # VXIInstrument, ) ): if debug is True: From a4718e94f34e8217c33886e236074e20b4b312d3 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Wed, 30 Oct 2024 10:39:22 -0600 Subject: [PATCH 17/29] reworked class based properties to actually have class based properties --- .../instrument_driver/abstract_driver.py | 92 ++++----------- .../instrument_driver/properties/__init__.py | 5 + .../abstract_instrument_property.py | 106 ++++++++++++++++++ .../properties/dict_values_property.py} | 46 ++++---- .../properties/indexed_property.py} | 40 ++++--- .../properties/range_property.py | 36 ++++++ .../properties/read_only_exception.py | 7 ++ .../properties/read_only_property.py | 27 +++++ .../properties/values_property.py} | 54 ++++----- pyscan/drivers/property_settings/__init__.py | 5 - .../abstract_property_settings.py | 29 ----- .../range_property_settings.py | 33 ------ .../read_only_property_settings.py | 32 ------ .../testing/auto_test_driver_properties.py | 10 +- pyscan/general/pyscan_json_encoder.py | 3 +- .../test_dict_values_settings.py | 51 --------- .../test_indexed_values_settings.py | 44 -------- .../test_range_property_settings.py | 46 -------- .../test_values_property_setttings.py | 33 ------ 19 files changed, 282 insertions(+), 417 deletions(-) create mode 100644 pyscan/drivers/instrument_driver/properties/__init__.py create mode 100644 pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py rename pyscan/drivers/{property_settings/dict_property_settings.py => instrument_driver/properties/dict_values_property.py} (54%) rename pyscan/drivers/{property_settings/indexed_property_settings.py => instrument_driver/properties/indexed_property.py} (51%) create mode 100644 pyscan/drivers/instrument_driver/properties/range_property.py create mode 100644 pyscan/drivers/instrument_driver/properties/read_only_exception.py create mode 100644 pyscan/drivers/instrument_driver/properties/read_only_property.py rename pyscan/drivers/{property_settings/values_property_settings.py => instrument_driver/properties/values_property.py} (55%) delete mode 100644 pyscan/drivers/property_settings/__init__.py delete mode 100644 pyscan/drivers/property_settings/abstract_property_settings.py delete mode 100644 pyscan/drivers/property_settings/range_property_settings.py delete mode 100644 pyscan/drivers/property_settings/read_only_property_settings.py delete mode 100644 test/drivers/test_property_settings/test_dict_values_settings.py delete mode 100644 test/drivers/test_property_settings/test_indexed_values_settings.py delete mode 100644 test/drivers/test_property_settings/test_range_property_settings.py delete mode 100644 test/drivers/test_property_settings/test_values_property_setttings.py diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 65f91834..60cea08f 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -1,11 +1,10 @@ from ...general.item_attribute import ItemAttribute -from ..property_settings import ( - RangePropertySettings, - ValuesPropertySettings, - IndexedPropertySettings, - DictPropertySettings, - ReadOnlyPropertySetting) -from ..property_settings.read_only_property_settings import ReadOnlyException +from .properties import ( + DictProperty, DictPropertySettings, + IndexedProperty, IndexedPropertySettings, + RangeProperty, RangePropertySettings, + ReadOnlyProperty, ReadOnlyPropertySetting, + ValuesProperty, ValuesPropertySettings) import numpy as np import re @@ -71,74 +70,21 @@ def add_device_property(self, settings_dict): name = settings_dict['name'] settings_name = '_' + name + '_settings' - property_class = self.validate_property_settings(settings_dict) + property_class, settings_class = self.validate_property_settings(settings_dict) self.validate_subclass_settings(settings_dict) # Make property settings attribute - settings_obj = property_class(settings_dict) + settings_obj = settings_class(settings_dict) setattr(self, settings_name, settings_obj) # Make self.name property - property_definition = property( - fget=lambda obj: self.get_instrument_property(obj, settings_obj), - fset=lambda obj, new_value: self.set_instrument_property(obj, settings_obj, new_value), - doc=self.get_property_docstring(name)) - setattr(self.__class__, name, property_definition) + setattr(self.__class__, name, property_class()) + vars(self.__class__)[name].name = name + vars(self.__class__)[name]._name = f'_{name}' + vars(self.__class__)[name].settings_name = f'_{name}_settings' + vars(self.__class__)[name].__doc__ = self.get_property_docstring(name) setattr(self, settings_obj._name, None) - def get_instrument_property(self, obj, settings_obj, debug=False): - ''' - Generator function for a query function of the instrument - that sends the query string and formats the return based on - settings['return_type'] - - Parameters - obj : - parent object - settings : dict - settings dictionary - debug : bool - returns query string instead of querying instrument - - Returns - ------- - value formatted to setting's ['return_type'] - ''' - value = self.query_property(settings_obj) - value = settings_obj.format_query_return(value) - - setattr(self, settings_obj._name, value) - - return value - - def set_instrument_property(self, obj, settings_obj, new_value): - ''' - Generator function for settings dictionary with 'values' item - Check that new_value is in settings['values'], if not, rejects command - - Parameters - ---------- - obj : - parent class object - settings : PropetySettings subclass - RangeSettings, ValuesSettings, IndexedValuesSettings, or DictValuesSettings - new_value : - new_value to be formatted and sent to instrument - - Returns - ------- - None - ''' - - if not settings_obj.read_only: - settings_obj.validate_set_value(new_value) - value = settings_obj.format_write_value(new_value) - self.write_property(settings_obj, value) - setattr(self, settings_obj._name, new_value) - else: - print(settings_obj, new_value) - raise ReadOnlyException(settings_obj.name) - def update_properties(self): properties = self.get_pyscan_properties() @@ -243,24 +189,24 @@ def validate_property_settings(self, settings_dict): # Check that the type value is correct if 'indexed_values' in settings_keys: assert isinstance(settings_dict['indexed_values'], list), f'{name} "indexed_values" setting must be a list' - property_class = IndexedPropertySettings + property_class, settings_class = IndexedProperty, IndexedPropertySettings elif 'read_only' in settings_keys: - property_class = ReadOnlyPropertySetting + property_class, settings_class = ReadOnlyProperty, ReadOnlyPropertySetting elif 'range' in settings_keys: assert len(settings_dict['range']) == 2, f'{name} "range" setting must be a list of lenght 2' assert isinstance(settings_dict['range'], list), f'{name} "range" property setting must be a list' assert 'return_type' in settings_keys, f'{name} requires a "return_type" setting' assert settings_dict['range'][1] > settings_dict['range'][0], ( f'{name} has bad "range" settings, range[0] < range[1]') - property_class = RangePropertySettings + property_class, settings_class = RangeProperty, RangePropertySettings elif 'values' in settings_keys: assert isinstance(settings_dict['values'], list), f'{name} "values" setting must be a list' - property_class = ValuesPropertySettings + property_class, settings_class = ValuesProperty, ValuesPropertySettings elif 'dict_values' in settings_keys: assert isinstance(settings_dict['dict_values'], dict), f'{name} "dict_values" setting must be a dict' - property_class = DictPropertySettings + property_class, settings_class = DictProperty, DictPropertySettings - return property_class + return property_class, settings_class def validate_subclass_settings(self, settings_dict): ''' diff --git a/pyscan/drivers/instrument_driver/properties/__init__.py b/pyscan/drivers/instrument_driver/properties/__init__.py new file mode 100644 index 00000000..4d0532ec --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/__init__.py @@ -0,0 +1,5 @@ +from .dict_values_property import DictProperty, DictPropertySettings +from .indexed_property import IndexedProperty, IndexedPropertySettings +from .range_property import RangeProperty, RangePropertySettings +from .read_only_property import ReadOnlyProperty, ReadOnlyPropertySetting +from .values_property import ValuesProperty, ValuesPropertySettings diff --git a/pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py b/pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py new file mode 100644 index 00000000..62143561 --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py @@ -0,0 +1,106 @@ +from ....general.item_attribute import ItemAttribute +from .read_only_exception import ReadOnlyException + + +class AbstractInstrumentProperty: + + def __set_name__(self, owner, name): + + self.name = name + self._name = '_' + name + self.settings_name = f'_{name}_settings' + + def __get__(self, obj, objtype=None): + ''' + Generator function for a query function of the instrument + that sends the query string and formats the return based on + settings['return_type'] + + Parameters + obj : + Instance of object containing ths property + + Returns + ------- + value formatted to setting's ['return_type'] + ''' + settings_obj = getattr(obj, self.settings_name) + + value = obj.query_property(settings_obj) + value = self.format_query_return(value, settings_obj) + + setattr(obj, self._name, value) + + return value + + def __set__(self, obj, new_value): + ''' + Sets the instrument property + + Parameters + ---------- + obj : + parent class object + new_value : + new_value to be formatted and sent to instrument + + Returns + ------- + None + ''' + settings_obj = getattr(obj, self.settings_name) + + if not settings_obj.read_only: + self.validate_set_value(new_value, settings_obj) + value = self.format_write_value(new_value, settings_obj) + obj.write_property(settings_obj, value) + setattr(obj, self._name, new_value) + else: + raise ReadOnlyException(self.name) + + def validate_set_value(self, new_value, settings_obj): + ''' + Abstract method that validates the input value for the instrument + ''' + pass + + def format_write_value(self, new_value, settings_obj): + ''' + Abstract method that formats the input value for the instrument + ''' + pass + + def format_query_return(self, returned_value, settings_obj): + ''' + Abstract method that formats the return value from the instrument + ''' + pass + + +class AbstractPropertySettings(ItemAttribute): + + def __init__(self, settings_dict): + + self.name = settings_dict['name'] + self._name = '_' + self.name + + for key, value in settings_dict.items(): + setattr(self, key, value) + + def validate_set_value(self, new_value): + ''' + Abstract method that validates the input value for the instrument + ''' + pass + + def format_write_value(self, new_value): + ''' + Abstract method that formats the input value for the instrument + ''' + pass + + def format_query_return(self, ret): + ''' + Abstract method that formats the return value from the instrument + ''' + pass diff --git a/pyscan/drivers/property_settings/dict_property_settings.py b/pyscan/drivers/instrument_driver/properties/dict_values_property.py similarity index 54% rename from pyscan/drivers/property_settings/dict_property_settings.py rename to pyscan/drivers/instrument_driver/properties/dict_values_property.py index 8b3af3b7..667ea6c0 100644 --- a/pyscan/drivers/property_settings/dict_property_settings.py +++ b/pyscan/drivers/instrument_driver/properties/dict_values_property.py @@ -1,4 +1,4 @@ -from .abstract_property_settings import AbstractPropertySettings +from .abstract_instrument_property import AbstractInstrumentProperty, AbstractPropertySettings from collections import OrderedDict @@ -14,6 +14,27 @@ def __init__(self, prop, dict_values, dict_key_types, value): super().__init__(msg) +class DictProperty(AbstractInstrumentProperty): + + def validate_set_value(self, new_value, settings_obj): + if new_value in settings_obj.dict_values.keys(): + return True + else: + raise DictValueException( + self.name, settings_obj.dict_values, settings_obj.key_type_dict, new_value) + + def format_write_value(self, new_value, settings_obj): + + return settings_obj.dict_values[new_value] + + def format_query_return(self, return_value, settings_obj): + + key = settings_obj.find_first_key(return_value) + key_type = settings_obj.key_type_dict[key] + + return key_type(key) + + class DictPropertySettings(AbstractPropertySettings): def __init__(self, settings_dict): @@ -26,28 +47,11 @@ def __init__(self, settings_dict): self.read_only = False - for key, value in self.dict_values.items(): + for key, value in settings_dict['dict_values'].items(): self.key_type_dict[key] = type(key) self.str_values_dict[key] = str(value) - def validate_set_value(self, new_value): - if new_value in self.dict_values.keys(): - return True - else: - raise DictValueException(self.name, self.dict_values, self.key_type_dict, new_value) - - def format_write_value(self, new_value): - - return self.dict_values[new_value] - - def format_query_return(self, ret): - - key = self.find_first_key(ret) - key_type = self.key_type_dict[key] - - return key_type(key) - - def find_first_key(self, ret): + def find_first_key(self, return_value): for key, val in self.dict_values.items(): - if str(val) == str(ret): + if str(val) == str(return_value): return key diff --git a/pyscan/drivers/property_settings/indexed_property_settings.py b/pyscan/drivers/instrument_driver/properties/indexed_property.py similarity index 51% rename from pyscan/drivers/property_settings/indexed_property_settings.py rename to pyscan/drivers/instrument_driver/properties/indexed_property.py index ef21e5eb..476d8c0b 100644 --- a/pyscan/drivers/property_settings/indexed_property_settings.py +++ b/pyscan/drivers/instrument_driver/properties/indexed_property.py @@ -1,5 +1,5 @@ -from .abstract_property_settings import AbstractPropertySettings -from .read_only_property_settings import ReadOnlyException +from .abstract_instrument_property import AbstractInstrumentProperty, AbstractPropertySettings +from .read_only_property import ReadOnlyException class IndexedValueException(Exception): @@ -13,6 +13,26 @@ def __init__(self, prop, indexed_values, types, value): super().__init__(msg) +class IndexedProperty(AbstractInstrumentProperty): + + def validate_set_value(self, new_value, settings_obj): + if settings_obj.read_only: + raise ReadOnlyException(self.name) + elif new_value in settings_obj.indexed_values: + return True + else: + raise IndexedValueException( + self.name, settings_obj.value_strings, settings_obj.types, new_value) + + def format_write_value(self, new_value, settings_obj): + return settings_obj.indexed_values.index(new_value) + + def format_query_return(self, return_value, settings_obj): + return_type = settings_obj.types[int(return_value)] + + return return_type(settings_obj.indexed_values[int(return_value)]) + + class IndexedPropertySettings(AbstractPropertySettings): def __init__(self, settings_dict): @@ -26,19 +46,3 @@ def __init__(self, settings_dict): for val in self.indexed_values: self.types.append(type(val)) self.value_strings.append(str(val)) - - def validate_set_value(self, new_value): - if self.read_only: - raise ReadOnlyException(self.name) - elif new_value in self.indexed_values: - return True - else: - raise IndexedValueException(self.name, self.value_strings, self.types, new_value) - - def format_write_value(self, new_value): - return self.indexed_values.index(new_value) - - def format_query_return(self, ret): - return_type = self.types[int(ret)] - - return return_type(self.indexed_values[int(ret)]) diff --git a/pyscan/drivers/instrument_driver/properties/range_property.py b/pyscan/drivers/instrument_driver/properties/range_property.py new file mode 100644 index 00000000..83151e76 --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/range_property.py @@ -0,0 +1,36 @@ +from .abstract_instrument_property import AbstractInstrumentProperty, AbstractPropertySettings + + +class RangeException(Exception): + + def __init__(self, prop, range, value): + + msg = f'{prop} = {value} out of range\n' + msg += f'Valid range for {prop}: {range[0]} <= new_value <= {range[1]}' + + super().__init__(msg) + + +class RangeProperty(AbstractInstrumentProperty): + + def validate_set_value(self, new_value, settings_obj): + if settings_obj.range[0] <= new_value <= settings_obj.range[1]: + return True + else: + raise RangeException(self.name, settings_obj.range, new_value) + + def format_write_value(self, new_value, settings_obj): + return new_value + + def format_query_return(self, return_value, settings_obj): + + return float(return_value) + + +class RangePropertySettings(AbstractPropertySettings): + + def __init__(self, settings_dict): + + super().__init__(settings_dict) + + self.read_only = False diff --git a/pyscan/drivers/instrument_driver/properties/read_only_exception.py b/pyscan/drivers/instrument_driver/properties/read_only_exception.py new file mode 100644 index 00000000..384ea5c9 --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/read_only_exception.py @@ -0,0 +1,7 @@ +class ReadOnlyException(Exception): + + def __init__(self, prop): + + msg = f"{prop} is a read only property and can't be set" + + super().__init__(msg) diff --git a/pyscan/drivers/instrument_driver/properties/read_only_property.py b/pyscan/drivers/instrument_driver/properties/read_only_property.py new file mode 100644 index 00000000..ed34a036 --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/read_only_property.py @@ -0,0 +1,27 @@ +from .abstract_instrument_property import AbstractInstrumentProperty, AbstractPropertySettings +from .read_only_exception import ReadOnlyException + + +class ReadOnlyProperty(AbstractInstrumentProperty): + + def validate_set_value(self, new_value, settings_obj): + ReadOnlyException(self.name) + + def format_write_value(self, new_value, settings_obj): + ReadOnlyException(self.name) + + def format_query_return(self, return_value, settings_obj): + + return settings_obj.return_type(return_value) + + +class ReadOnlyPropertySetting(AbstractPropertySettings): + + def __init__(self, settings_dict): + + super().__init__(settings_dict) + + self.write_string = None + + self.types = [] + self.value_strings = [] diff --git a/pyscan/drivers/property_settings/values_property_settings.py b/pyscan/drivers/instrument_driver/properties/values_property.py similarity index 55% rename from pyscan/drivers/property_settings/values_property_settings.py rename to pyscan/drivers/instrument_driver/properties/values_property.py index 45f513f1..3ac4ca83 100644 --- a/pyscan/drivers/property_settings/values_property_settings.py +++ b/pyscan/drivers/instrument_driver/properties/values_property.py @@ -1,4 +1,4 @@ -from .abstract_property_settings import AbstractPropertySettings +from .abstract_instrument_property import AbstractInstrumentProperty, AbstractPropertySettings class ValueException(Exception): @@ -12,41 +12,45 @@ def __init__(self, prop, values, types, value): super().__init__(msg) -class ValuesPropertySettings(AbstractPropertySettings): - - def __init__(self, settings_dict): - - super().__init__(settings_dict) - - self.types = [] - self.value_strings = [] +class ValuesProperty(AbstractInstrumentProperty): - self.read_only = False - - for val in self.values: - self.types.append(type(val)) - self.value_strings.append(str(val)) - - def validate_set_value(self, new_value): - if new_value in self.values: + def validate_set_value(self, new_value, settings_obj): + if new_value in settings_obj.values: return True else: - raise ValueException(self.name, self.value_strings, self.types, new_value) + raise ValueException(self.name, settings_obj.value_strings, settings_obj.types, new_value) - def format_write_value(self, new_value): + def format_write_value(self, new_value, settings_object): return new_value - def format_query_return(self, ret): + def format_query_return(self, returned_value, settings_obj): try: # Check if the return is directly in the values list - index = self.values.index(ret) + index = settings_obj.values.index(returned_value) except ValueError: # If not, check if the return is in the value_strings list - index = self.value_strings.index(ret) + index = settings_obj.value_strings.index(returned_value) except: - raise Exception('Returned value {} {} not found in values or value_strings'.format(ret, type(ret))) + raise Exception('Returned value {} {} not found in values or value_strings'.format( + returned_value, type(returned_value))) + + return_type = settings_obj.types[index] - return_type = self.types[index] + return return_type(returned_value) - return return_type(ret) + +class ValuesPropertySettings(AbstractPropertySettings): + + def __init__(self, settings_dict): + + super().__init__(settings_dict) + + self.types = [] + self.value_strings = [] + + self.read_only = False + + for val in self.values: + self.types.append(type(val)) + self.value_strings.append(str(val)) diff --git a/pyscan/drivers/property_settings/__init__.py b/pyscan/drivers/property_settings/__init__.py deleted file mode 100644 index af705cd6..00000000 --- a/pyscan/drivers/property_settings/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .dict_property_settings import DictPropertySettings -from .indexed_property_settings import IndexedPropertySettings -from .range_property_settings import RangePropertySettings -from .values_property_settings import ValuesPropertySettings -from .read_only_property_settings import ReadOnlyPropertySetting diff --git a/pyscan/drivers/property_settings/abstract_property_settings.py b/pyscan/drivers/property_settings/abstract_property_settings.py deleted file mode 100644 index 44156fd0..00000000 --- a/pyscan/drivers/property_settings/abstract_property_settings.py +++ /dev/null @@ -1,29 +0,0 @@ - - -class AbstractPropertySettings(object): - - def __init__(self, settings_dict): - - self.name = settings_dict['name'] - self._name = '_' + self.name - - for key, value in settings_dict.items(): - setattr(self, key, value) - - def validate_set_value(self, new_value): - ''' - Abstract method that validates the input value for the instrument - ''' - pass - - def format_write_value(self, new_value): - ''' - Abstract method that formats the input value for the instrument - ''' - pass - - def format_query_return(self, ret): - ''' - Abstract method that formats the return value from the instrument - ''' - pass diff --git a/pyscan/drivers/property_settings/range_property_settings.py b/pyscan/drivers/property_settings/range_property_settings.py deleted file mode 100644 index 726b0ae7..00000000 --- a/pyscan/drivers/property_settings/range_property_settings.py +++ /dev/null @@ -1,33 +0,0 @@ -from .abstract_property_settings import AbstractPropertySettings - - -class RangeException(Exception): - - def __init__(self, prop, range, value): - - msg = f'{prop} = {value} out of range\n' - msg += f'Valid range for {prop}: {range[0]} <= new_value <= {range[1]}' - - super().__init__(msg) - - -class RangePropertySettings(AbstractPropertySettings): - - def __init__(self, settings_dict): - - super().__init__(settings_dict) - - self.read_only = False - - def validate_set_value(self, new_value): - if self.range[0] <= new_value <= self.range[1]: - return True - else: - raise RangeException(self.name, self.range, new_value) - - def format_write_value(self, new_value): - return new_value - - def format_query_return(self, ret): - - return float(ret) diff --git a/pyscan/drivers/property_settings/read_only_property_settings.py b/pyscan/drivers/property_settings/read_only_property_settings.py deleted file mode 100644 index 383ec28d..00000000 --- a/pyscan/drivers/property_settings/read_only_property_settings.py +++ /dev/null @@ -1,32 +0,0 @@ -from .abstract_property_settings import AbstractPropertySettings - - -class ReadOnlyException(Exception): - - def __init__(self, prop): - - msg = f"{prop} is a read only property and can't be set" - - super().__init__(msg) - - -class ReadOnlyPropertySetting(AbstractPropertySettings): - - def __init__(self, settings_dict): - - super().__init__(settings_dict) - - self.write_string = None - - self.types = [] - self.value_strings = [] - - def validate_set_value(self, new_value): - ReadOnlyException(self.name) - - def format_write_value(self, new_value): - ReadOnlyException(self.name) - - def format_query_return(self, ret): - - return self.return_type(ret) diff --git a/pyscan/drivers/testing/auto_test_driver_properties.py b/pyscan/drivers/testing/auto_test_driver_properties.py index a0320f7b..17fbf505 100644 --- a/pyscan/drivers/testing/auto_test_driver_properties.py +++ b/pyscan/drivers/testing/auto_test_driver_properties.py @@ -5,12 +5,12 @@ test_indexed_property, test_dict_values_property, test_read_only_property) -from ..property_settings import ( - ValuesPropertySettings, - RangePropertySettings, - IndexedPropertySettings, +from ..instrument_driver.properties import ( DictPropertySettings, - ReadOnlyPropertySetting) + IndexedPropertySettings, + RangePropertySettings, + ReadOnlyPropertySetting, + ValuesPropertySettings) from .check_initial_state import check_initial_state import os from datetime import datetime diff --git a/pyscan/general/pyscan_json_encoder.py b/pyscan/general/pyscan_json_encoder.py index b512214f..b6220d27 100644 --- a/pyscan/general/pyscan_json_encoder.py +++ b/pyscan/general/pyscan_json_encoder.py @@ -2,7 +2,6 @@ import numpy as np from ..general.item_attribute import ItemAttribute from ..drivers.instrument_driver import InstrumentDriver -from ..drivers.property_settings.abstract_property_settings import AbstractPropertySettings from pyvisa.resources import ( # FirewireInstrument, GPIBInstrument, @@ -47,7 +46,7 @@ def default(self, obj, debug=False): if type(obj) is type: return obj.__name__ - elif isinstance(obj, (InstrumentDriver, ItemAttribute, AbstractPropertySettings)): + elif isinstance(obj, (InstrumentDriver, ItemAttribute)): return obj.__dict__ elif isinstance(obj, (range, tuple)): return list(obj) diff --git a/test/drivers/test_property_settings/test_dict_values_settings.py b/test/drivers/test_property_settings/test_dict_values_settings.py deleted file mode 100644 index c6b9937a..00000000 --- a/test/drivers/test_property_settings/test_dict_values_settings.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest -from pyscan.drivers.property_settings.dict_property_settings import DictPropertySettings, DictValueException - - -@pytest.fixture -def settings(): - return DictPropertySettings({ - 'name': 'test', - 'dict_values': { - 1: 'one', - 2: 'two', - 'three': 'three', - 'four': 'two', # Repeated value - 5: 'five', - 'six': 'six', - 7: 'seven', - 'eight': 'six' # Repeated value - } - }) - - -def test_validate_set_value_valid_key(settings): - assert settings.validate_set_value(1) is True - assert settings.validate_set_value('three') is True - - -def test_validate_set_value_invalid_key(settings): - with pytest.raises(DictValueException): - settings.validate_set_value('invalid_key') - - -def test_format_write_value(settings): - assert settings.format_write_value(1) == 'one' - assert settings.format_write_value('three') == 'three' - - -def test_format_query_return(settings): - assert settings.format_query_return('one') == 1 - assert settings.format_query_return('three') == 'three' - - -def test_format_query_return_invalid(settings): - with pytest.raises(KeyError): - settings.format_query_return('invalid_value') - - -def test_find_first_key(settings): - assert settings.find_first_key('one') == 1 - assert settings.find_first_key('three') == 'three' - assert settings.find_first_key('two') in [2, 'four'] # Adjusted for repeated value - assert settings.find_first_key('six') in ['six', 'eight'] # Adjusted for repeated value \ No newline at end of file diff --git a/test/drivers/test_property_settings/test_indexed_values_settings.py b/test/drivers/test_property_settings/test_indexed_values_settings.py deleted file mode 100644 index b1da2c07..00000000 --- a/test/drivers/test_property_settings/test_indexed_values_settings.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from pyscan.drivers.property_settings.indexed_property_settings import IndexedPropertySettings, IndexedValueException - - -@pytest.fixture -def settings(): - return IndexedPropertySettings({ - 'name': 'test', - 'indexed_values': [0, 1, 2, 3, 4, 'a', 'b', 'c', 'd', True, False]}) - - -def test_validate_set_value_within_index(settings): - assert settings.validate_set_value(2) is True - - -def test_validate_set_value_below_index(settings): - with pytest.raises(IndexedValueException): - settings.validate_set_value(-1) - - -def test_validate_set_value_above_index(settings): - with pytest.raises(IndexedValueException): - settings.validate_set_value(5) - - -def test_format_write_value(settings): - assert settings.format_write_value(3) == 3 - - -def test_format_query_return(settings): - assert settings.format_query_return('2') == 2 - - -def test_validate_set_value_edge_case_lower(settings): - assert settings.validate_set_value(0) is True - - -def test_validate_set_value_edge_case_upper(settings): - assert settings.validate_set_value(4) is True - - -def test_format_query_return_invalid(settings): - with pytest.raises(ValueError): - settings.format_query_return('invalid') diff --git a/test/drivers/test_property_settings/test_range_property_settings.py b/test/drivers/test_property_settings/test_range_property_settings.py deleted file mode 100644 index 0d802c73..00000000 --- a/test/drivers/test_property_settings/test_range_property_settings.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest -from pyscan.drivers.property_settings.range_property_settings import RangePropertySettings, RangeException - - -@pytest.fixture -def settings(): - return RangePropertySettings({'name': 'test', 'range': (0, 10)}) - - -def test_validate_set_value_within_range(settings): - assert settings.validate_set_value(5) is True - - -def test_validate_set_value_below_range(settings): - with pytest.raises(RangeException): - settings.validate_set_value(-1) - - -def test_validate_set_value_above_range(settings): - with pytest.raises(RangeException): - settings.validate_set_value(11) - - -def test_format_write_value(settings): - assert settings.format_write_value(5) == 5 - - -def test_format_query_return(settings): - assert settings.format_query_return('5.5') == 5.5 - - -def test_validate_set_value_edge_case_lower(settings): - assert settings.validate_set_value(0) is True - - -def test_validate_set_value_edge_case_upper(settings): - assert settings.validate_set_value(10) is True - - -def test_format_query_return_integer(settings): - assert settings.format_query_return('5') == 5.0 - - -def test_format_query_return_invalid(settings): - with pytest.raises(ValueError): - settings.format_query_return('invalid') diff --git a/test/drivers/test_property_settings/test_values_property_setttings.py b/test/drivers/test_property_settings/test_values_property_setttings.py deleted file mode 100644 index e1148a4e..00000000 --- a/test/drivers/test_property_settings/test_values_property_setttings.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest -from pyscan.drivers.property_settings.values_property_settings import ValuesPropertySettings, ValueException - -@pytest.fixture -def settings(): - return ValuesPropertySettings({'name': 'test', 'values': [1, 2, 3, 'a', 'b', 'c']}) - - -def test_validate_set_value_valid(settings): - assert settings.validate_set_value(1) is True - assert settings.validate_set_value('a') is True - - -def test_validate_set_value_invalid(settings): - with pytest.raises(ValueException): - settings.validate_set_value(4) - with pytest.raises(ValueException): - settings.validate_set_value('d') - - -def test_format_write_value(settings): - assert settings.format_write_value(1) == 1 - assert settings.format_write_value('a') == 'a' - - -def test_format_query_return_valid(settings): - assert settings.format_query_return('1') == 1 - assert settings.format_query_return('a') == 'a' - - -def test_format_query_return_invalid(settings): - with pytest.raises(ValueError): - settings.format_query_return('d') \ No newline at end of file From 1b115c3da54a4a21f9c58e11df1d39deccaa0b04 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Wed, 30 Oct 2024 10:44:48 -0600 Subject: [PATCH 18/29] test(driver): fixed stanford860 driver for new class based properties --- .../{stanford860_test_notebook.ipynb => stanford860.ipynb} | 4 ++-- pyscan/drivers/__init__.py | 6 +----- pyscan/drivers/instrument_driver/__init__.py | 1 + pyscan/drivers/instrument_driver/instrument_driver.py | 2 +- pyscan/drivers/{ => instrument_driver}/new_instrument.py | 0 pyscan/drivers/properties | 0 6 files changed, 5 insertions(+), 8 deletions(-) rename drivers_test_notebooks/{stanford860_test_notebook.ipynb => stanford860.ipynb} (98%) rename pyscan/drivers/{ => instrument_driver}/new_instrument.py (100%) delete mode 100644 pyscan/drivers/properties diff --git a/drivers_test_notebooks/stanford860_test_notebook.ipynb b/drivers_test_notebooks/stanford860.ipynb similarity index 98% rename from drivers_test_notebooks/stanford860_test_notebook.ipynb rename to drivers_test_notebooks/stanford860.ipynb index e351514a..d630ce76 100644 --- a/drivers_test_notebooks/stanford860_test_notebook.ipynb +++ b/drivers_test_notebooks/stanford860.ipynb @@ -21,7 +21,7 @@ "%autoreload 2\n", "\n", "import pyscan as ps\n", - "from pyscan.drivers.testing.auto_test_driver import test_driver\n", + "from pyscan.drivers.testing.auto_test_driver_properties import auto_test_driver_properties\n", "from pyvisa import ResourceManager, VisaIOError\n", "import pytest\n", "from time import sleep" @@ -148,7 +148,7 @@ ], "source": [ "srs860 = ps.Stanford860(res)\n", - "test_driver(srs860, verbose=False)\n" + "auto_test_driver_properties(srs860, verbose=False)\n" ] }, { diff --git a/pyscan/drivers/__init__.py b/pyscan/drivers/__init__.py index dfc90874..acb63afc 100755 --- a/pyscan/drivers/__init__.py +++ b/pyscan/drivers/__init__.py @@ -1,7 +1,7 @@ import os # Objects -from .instrument_driver import InstrumentDriver +from .instrument_driver import InstrumentDriver, new_instrument # Brand collections from .agilent import * @@ -28,10 +28,6 @@ from .thorlabs import * from .spin_core import * - -# Methods -from .new_instrument import new_instrument - # Test Devices from .testing.test_voltage import TestVoltage from .testing.test_instrument_driver import TestInstrumentDriver diff --git a/pyscan/drivers/instrument_driver/__init__.py b/pyscan/drivers/instrument_driver/__init__.py index 1481bed4..08db02bc 100644 --- a/pyscan/drivers/instrument_driver/__init__.py +++ b/pyscan/drivers/instrument_driver/__init__.py @@ -1 +1,2 @@ from .instrument_driver import InstrumentDriver +from .new_instrument import new_instrument \ No newline at end of file diff --git a/pyscan/drivers/instrument_driver/instrument_driver.py b/pyscan/drivers/instrument_driver/instrument_driver.py index fec23261..015529b3 100644 --- a/pyscan/drivers/instrument_driver/instrument_driver.py +++ b/pyscan/drivers/instrument_driver/instrument_driver.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from .abstract_driver import AbstractDriver -from ..new_instrument import new_instrument +from .new_instrument import new_instrument class InstrumentDriver(AbstractDriver): diff --git a/pyscan/drivers/new_instrument.py b/pyscan/drivers/instrument_driver/new_instrument.py similarity index 100% rename from pyscan/drivers/new_instrument.py rename to pyscan/drivers/instrument_driver/new_instrument.py diff --git a/pyscan/drivers/properties b/pyscan/drivers/properties deleted file mode 100644 index e69de29b..00000000 From 543f971489a9211a0b03947f910a84ef992dccef Mon Sep 17 00:00:00 2001 From: i-am-mounce <146007668+i-am-mounce@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:46:47 -0600 Subject: [PATCH 19/29] Update __init__.py --- pyscan/drivers/instrument_driver/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscan/drivers/instrument_driver/__init__.py b/pyscan/drivers/instrument_driver/__init__.py index 08db02bc..52051953 100644 --- a/pyscan/drivers/instrument_driver/__init__.py +++ b/pyscan/drivers/instrument_driver/__init__.py @@ -1,2 +1,2 @@ from .instrument_driver import InstrumentDriver -from .new_instrument import new_instrument \ No newline at end of file +from .new_instrument import new_instrument From f36af4b5807874c6c0fd62c1d68a4b81e44b0c7f Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Thu, 31 Oct 2024 13:54:52 -0600 Subject: [PATCH 20/29] test: targeted only the ./test folder for pytest --- .github/workflows/complete.yml | 2 +- pyscan/drivers/testing/test_properties.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/complete.yml b/.github/workflows/complete.yml index 6f550814..c6f279d3 100644 --- a/.github/workflows/complete.yml +++ b/.github/workflows/complete.yml @@ -66,7 +66,7 @@ jobs: - name: Test with pytest run: | - pytest + pytest ./test - name: Test notebooks run: | diff --git a/pyscan/drivers/testing/test_properties.py b/pyscan/drivers/testing/test_properties.py index 297cbdb6..3f3b2cde 100644 --- a/pyscan/drivers/testing/test_properties.py +++ b/pyscan/drivers/testing/test_properties.py @@ -4,6 +4,8 @@ from pyscan.general.d_range import drange from .check_initial_state import check_initial_state +__test__ = False + missing_prop_str = "device did not have {} property, test it's in drivers initialize_properties or update_properties." BAD_INPUTS = [-19812938238312948, -1.11123444859, 3.2222111234, 985767665954, 890992238.2345, From f723504bc09b1b912a0d4b281a4bb905eecf7264 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Mon, 4 Nov 2024 11:35:57 -0700 Subject: [PATCH 21/29] fix(driver): doc string if not found returns blank. If multiple, takes first --- pyscan/drivers/instrument_driver.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pyscan/drivers/instrument_driver.py b/pyscan/drivers/instrument_driver.py index 15bddddd..2b038ffc 100644 --- a/pyscan/drivers/instrument_driver.py +++ b/pyscan/drivers/instrument_driver.py @@ -433,13 +433,22 @@ def get_property_docstring(self, prop_name): doc = self.__doc__.split('\n') - r = re.compile(" {} :".format(prop_name)) - match = list(filter(r.match, doc)) + r = " {} :".format(prop_name) - assert len(match) > 0, "No matches for {} documentation".format(prop_name) - assert len(match) == 1, "Too many matches for {} documentation".format(prop_name) - match = match[0] + def find_match(str): + if r in str: + return True + else: + return False + + match = list(filter(find_match, doc)) + assert not len(match) > 1, "Too many matches for {} documentation".format(prop_name) + + if len(match) == 0: + match = '' + else: + match = match[0] for i, string in enumerate(doc): if string == match: break From 1a1cc077e08611e383a3ae50bca41213be94c587 Mon Sep 17 00:00:00 2001 From: cint-transport Date: Mon, 4 Nov 2024 11:37:56 -0700 Subject: [PATCH 22/29] Start of newly formatted tlk drivers --- drivers_test_notebooks/thorlabsbpc303.ipynb | 227 +++++++++++++++ .../instrument_driver/abstract_driver.py | 28 +- .../instrument_driver/instrument_driver.py | 8 +- pyscan/drivers/thorlabs/__init__.py | 11 +- pyscan/drivers/thorlabs/check_for_error.py | 18 ++ .../thorlabs/thorlabs_kinesis_driver.py | 38 +++ pyscan/drivers/thorlabs/thorlabsbpc303.py | 274 ++++++++++++------ 7 files changed, 503 insertions(+), 101 deletions(-) create mode 100644 drivers_test_notebooks/thorlabsbpc303.ipynb create mode 100644 pyscan/drivers/thorlabs/check_for_error.py create mode 100644 pyscan/drivers/thorlabs/thorlabs_kinesis_driver.py diff --git a/drivers_test_notebooks/thorlabsbpc303.ipynb b/drivers_test_notebooks/thorlabsbpc303.ipynb new file mode 100644 index 00000000..192c7151 --- /dev/null +++ b/drivers_test_notebooks/thorlabsbpc303.ipynb @@ -0,0 +1,227 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import pyscan as ps\n", + "from time import sleep\n", + "from pyscan_tlk import benchtoppiezo as bp" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "try: \n", + " del piezo\n", + "except:\n", + " pass\n", + "\n", + "piezo = ps.ThorlabsBPC303(71000001, simulate=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(196609, 393216)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert piezo.number_channels == 3\n", + "assert piezo.max_channel_count == 3\n", + "piezo.firmware_version, piezo.software_version" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'undefined'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "piezo.x_control_mode = 'undefined'\n", + "\n", + "sleep(0.1)\n", + "\n", + "piezo.x_control_mode" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'ThorlabsBPC303' object has no attribute 'bp'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[5], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mpiezo\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbp\u001b[49m\u001b[38;5;241m.\u001b[39mPBC_Tol\n", + "\u001b[1;31mAttributeError\u001b[0m: 'ThorlabsBPC303' object has no attribute 'bp'" + ] + } + ], + "source": [ + "conversion = max_travel * 0.1 / (2e15-1)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1639.344262295082" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "200000/122" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "750" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "piezo.instrument.PBC_Trav (piezo.serial_number, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0011444266422522317" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "75/(2**16-1)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "19.98848" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "122 / 200000 * 2**15" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "32767" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2**15-1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 60cea08f..adcae6f0 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -35,7 +35,7 @@ class AbstractDriver(ItemAttribute): get_property_docstring(prop_name) ''' - def __init__(self, instrument, debug=False): + def __init__(self): pass def query_property(self, settings): @@ -71,7 +71,7 @@ def add_device_property(self, settings_dict): settings_name = '_' + name + '_settings' property_class, settings_class = self.validate_property_settings(settings_dict) - self.validate_subclass_settings(settings_dict) + settings_class = self.validate_subclass_settings(settings_class) # Make property settings attribute settings_obj = settings_class(settings_dict) @@ -126,12 +126,22 @@ def get_property_docstring(self, prop_name): doc = self.__doc__.split('\n') - r = re.compile(".*{} :".format(prop_name)) - match = list(filter(r.match, doc)) + r = " {} :".format(prop_name) - assert len(match) > 0, "No matches for {} documentation".format(prop_name) - assert len(match) == 1, "Too many matches for {} documentation".format(prop_name) - match = match[0] + def find_match(str): + if r in str: + return True + else: + return False + + match = list(filter(find_match, doc)) + + assert not len(match) > 1, "Too many matches for {} documentation".format(prop_name) + + if len(match) == 0: + match = '' + else: + match = match[0] for i, string in enumerate(doc): if string == match: @@ -208,10 +218,10 @@ def validate_property_settings(self, settings_dict): return property_class, settings_class - def validate_subclass_settings(self, settings_dict): + def validate_subclass_settings(self, settings_obj): ''' Abstract method to be overloaded which checks the validity of input settings beyond range, values, indexed_values, dict_values, read-only, write-only, etc. ''' - pass + return settings_obj diff --git a/pyscan/drivers/instrument_driver/instrument_driver.py b/pyscan/drivers/instrument_driver/instrument_driver.py index 015529b3..0d01198c 100644 --- a/pyscan/drivers/instrument_driver/instrument_driver.py +++ b/pyscan/drivers/instrument_driver/instrument_driver.py @@ -129,19 +129,19 @@ def read(self): return self.instrument.read() - def validate_subclass_settings(self, settings_dict): + def validate_subclass_settings(self, settings_obj): ''' For ScPIPropertySettings, ensures that write_string, query_string, read_only, and write_only are configured properly Parameters ---------- - settings_dict : dict + settings_obj : instance of subclass of AbstractPropertySetttings Dictionary of settings that generate a pyscan device property object ''' - settings_keys = list(settings_dict.keys()) + settings_keys = list(settings_obj.keys()) if 'read_only' in settings_keys: assert 'write_string' not in settings_keys, \ @@ -158,3 +158,5 @@ def validate_subclass_settings(self, settings_dict): f'{self.name} is missing a "query_string" key' assert 'write_string' in settings_keys, \ f'{self.name} is missing a "write_string" key' + + return settings_obj diff --git a/pyscan/drivers/thorlabs/__init__.py b/pyscan/drivers/thorlabs/__init__.py index b4b74857..c528716d 100644 --- a/pyscan/drivers/thorlabs/__init__.py +++ b/pyscan/drivers/thorlabs/__init__.py @@ -2,14 +2,15 @@ import importlib.util import sys -name = 'thorlabs_kinesis' +name = 'pyscan_tlk' +import pyscan_tlk as tlk if name in sys.modules: from .thorlabsbpc303 import ThorlabsBPC303 - from .thorlabsbsc203 import ThorlabsBSC203 - from .thorlabsmff101 import ThorlabsMFF101 + # from .thorlabsbsc203 import ThorlabsBSC203 + # from .thorlabsmff101 import ThorlabsMFF101 else: from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBPC303 - from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBSC203 - from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsMFF101 + # from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBSC203 + # from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsMFF101 diff --git a/pyscan/drivers/thorlabs/check_for_error.py b/pyscan/drivers/thorlabs/check_for_error.py new file mode 100644 index 00000000..56e3009b --- /dev/null +++ b/pyscan/drivers/thorlabs/check_for_error.py @@ -0,0 +1,18 @@ +from pyscan_tlk.definitions.kinesisexception import KinesisException + + +def check_return_for_error(ret): + + if ret != 0: + raise KinesisException(ret) + + +def check_for_error(func, *arg, **kwarg): + + def new_function(*arg, **kwarg): + + ret = func(*arg, **kwarg) + + check_for_error(ret) + + return new_function diff --git a/pyscan/drivers/thorlabs/thorlabs_kinesis_driver.py b/pyscan/drivers/thorlabs/thorlabs_kinesis_driver.py new file mode 100644 index 00000000..6d423778 --- /dev/null +++ b/pyscan/drivers/thorlabs/thorlabs_kinesis_driver.py @@ -0,0 +1,38 @@ +from ..instrument_driver.abstract_driver import AbstractDriver +from .check_for_error import check_return_for_error +from ctypes import c_char_p + + +class ThorlabsKinesisDriver(AbstractDriver): + + def __init__(self, serial_number, debug=False): + self.serial_number = c_char_p(bytes(str(serial_number), "utf-8")) + + def query_property(self, settings_obj): + + if 'channel' in settings_obj: + ret = settings_obj.query_function(self.serial_number, settings_obj.channel) + else: + ret = settings_obj.query_function(self.serial_number) + + return ret + + def write_property(self, settings_obj, new_value): + + if 'channel' in settings_obj: + ret = settings_obj.write_function(self.serial_number, settings_obj.channel, new_value) + else: + ret = settings_obj.write_function(self.serial_number, new_value) + + if settings_obj.check_for_error: + check_return_for_error(ret) + return ret + + def validate_subclass_settings(self, settings_obj): + + keys = settings_obj.__dict__.keys() + + if 'check_for_error' not in keys: + settings_obj.check_for_error = False + + return settings_obj diff --git a/pyscan/drivers/thorlabs/thorlabsbpc303.py b/pyscan/drivers/thorlabs/thorlabsbpc303.py index 6c80f14c..5a7f4fee 100644 --- a/pyscan/drivers/thorlabs/thorlabsbpc303.py +++ b/pyscan/drivers/thorlabs/thorlabsbpc303.py @@ -1,73 +1,224 @@ # -*- coding: utf-8 -*- -from ...general.item_attribute import ItemAttribute -from thorlabs_kinesis import benchtop_piezo as bp -from ctypes import c_char_p, c_ushort, c_ulong, c_short +from .thorlabs_kinesis_driver import ThorlabsKinesisDriver +from .check_for_error import check_for_error +from pyscan_tlk import benchtoppiezo as bp +from ctypes import c_ushort, c_ulong, c_short from time import sleep +from math import floor c_word = c_ushort c_dword = c_ulong -class ThorlabsBPC303(ItemAttribute): +class ThorlabsBPC303(ThorlabsKinesisDriver): ''' Driver for ThorLabs BPC303 - 3-Channel 150 V Benchtop Piezo Controller with USB Parameters ---------- - serial : str - Serial number string. Defaults to "71872242". + serial_number : int + Serial number integer + + Attributes + ---------- + (Properties) + number_channels : int + Queries the number of channels available on the instrument + max_channel_count : int + Queries the maximum number of channels on the instrument + firmware_version : int + Queries the firmware version + software_version : int + Queries the software version + + x_polling_duration : int + Queries the polling duration in ms for channel x + x_maximum_travel : int + Queries the maximum travel for channel x + x_status_bits : int + Queries the status bits for channel x + x_maximum_voltage : int + Queries the maximum voltage for channel x + x_control_mode : str + Sets/queries the control mode for x channel to + 'undefined', 'open loop', 'closed loop', 'open loop smoothed', 'closed loop smoothed' + x : float + Sets/queries the x position in um + + + + y_polling_duration : int + Queries the polling duration in ms for channel y + y_maximum_travel : int + Queries the maximum travel for channel y + y_status_bits : int + Queries the status bits for channel y + y_control_mode : str + Sets/queries the control mode for y channel to + 'undefined', 'open loop', 'closed loop', 'open loop smoothed', 'closed loop smoothed' + y_maximum_voltage : int + Queries the maximum voltage for channel y + y : float + Sets/queries the y position in um + + z_polling_duration : int + Queries the polling duration in ms for channel z + z_maximum_travel : int + Queries the maximum travel for channel z + z_status_bits : int + Queries the status bits for channel z + z_control_mode : str + Sets/queries the control mode for z channel to + 'undefined', 'open loop', 'closed loop', 'open loop smoothed', 'closed loop smoothed' + z_maximum_voltage : int + Queries the maximum voltage for channel z + z : float + Sets/queries the z position in um + + + Methods + ------- + ''' - def __init__(self, serial="71872242"): - self._version = "0.1.0" - self.serial = c_char_p(bytes(serial, "utf-8")) - if self.build_device_list() != 0: - assert 0, 'Could not build device list' - if self.open() != 0: - print(self.open()) - sleep(1.5) - for i in range(1, 4): - sleep(0.25) - try: - self.load_channel_settings(c_short(i)) - except: - pass - sleep(0.25) + def __init__(self, serial_number, simulate=False): + + super().__init__(serial_number) + + self._version = "1.0.0" + + self.build_device_list() + if simulate: + self.initialize_simulations() + self.open() + + self.instrument = bp + for i in range(1, 4): + self.start_polling_channel(i) + + self.initialize_device_properties() + + def initialize_device_properties(self): + + self.add_device_property({ + 'name': 'number_channels', + 'return_type': int, + 'read_only': True, + 'query_function': bp.PBC_GetNumChannels}) + + self.add_device_property({ + 'name': 'max_channel_count', + 'return_type': int, + 'read_only': True, + 'query_function': bp.PBC_MaxChannelCount}) + + self.add_device_property({ + 'name': 'firmware_version', + 'return_type': int, + 'read_only': True, + 'query_function': bp.PBC_GetFirmwareVersion}) + + self.add_device_property({ + 'name': 'software_version', + 'return_type': int, + 'read_only': True, + 'query_function': bp.PBC_GetSoftwareVersion}) + + for i, channel in zip([1, 2, 3], ['x', 'y', 'z']): + + self.add_device_property({ + 'name': f'{channel}_polling_duration', + 'read_only': True, + 'return_type': int, + 'query_function': bp.PBC_PollingDuration, + 'channel': i}) + + self.add_device_property({ + 'name': f'{channel}_maximum_travel', + 'read_only': True, + 'return_type': int, + 'query_function': bp.PBC_GetMaximumTravel, + 'channel': i}) + + self.add_device_property({ + 'name': f'{channel}_status_bits', + 'read_only': True, + 'return_type': int, + 'query_function': bp.PBC_GetStatusBits, + 'channel': i}) + + self.add_device_property({ + 'name': f'{channel}_maximum_voltage', + 'read_only': True, + 'return_type': int, + 'query_function': bp.PBC_GetMaxOutputVoltage, + 'channel': i}) + + self.add_device_property({ + 'name': f'{channel}_control_mode', + 'indexed_values': [ + 'undefined', + 'open loop', + 'closed loop', + 'open loop smoothed', + 'closed loop smoothed'], + 'query_function': bp.PBC_GetPositionControlMode, + 'write_function': bp.PBC_SetPositionControlMode, + 'channel': i}) + + self.add_device_property({ + 'name': f'{channel}', + 'range': [0, 20000], + 'query_function': bp.PBC_GetPosition, + 'write_function': bp.PBC_SetPosition, + 'channel': i, + 'return_type': int}) + + @check_for_error def build_device_list(self): return bp.TLI_BuildDeviceList() + def initialize_simulations(self): + bp.TLI_InitializeSimulations() + + @check_for_error def zero_channel(self, channel): - bp.PBC_SetZero(self.serial, channel) + bp.PBC_SetZero(self.serial_number, channel) - def set_channel_closed_loop(self, channel): - bp.PBC_SetPositionControlMode(self.serial, channel, 2) + def zero_x(self): + self.zero_channel(1) - def set_channel_open_loop(self, channel): - bp.PBC_SetPositionControlMode(self.serial, channel, 1) + def zero_y(self): + self.zero_channel(2) + def zero_z(self): + self.zero_channel(3) + + def zero_xyz(self): + for i in range(1, 4): + self.zero_channel(i) + + @check_for_error def open(self): - return bp.PBC_Open(self.serial) + return bp.PBC_Open(self.serial_number) def close(self): - bp.PBC_Close(self.serial) - - def get_number_channels(self): - return bp.PBC_GetNumChannels(self.serial) + bp.PBC_Close(self.serial_number) def load_channel_settings(self, channel): - bp.PBC_LoadSettings(self.serial, channel) + bp.PBC_LoadSettings(self.serial_number, channel) def get_channel_position(self, channel): - pos = bp.PBC_GetPosition(self.serial, c_short(channel)) + pos = bp.PBC_GetPosition(self.serial_number, c_short(channel)) pos = self.to_real_units(pos) return pos def to_device_units(self, val, pva=0): """pva is 0 [position], 1 [velocity], or 2 [acceleration]""" conversions = [122 / 200000] - return int(round(val / conversions[pva])) + return int(floor(val / conversions[pva])) def to_real_units(self, val, pva=0): """pva is 0 [position], 1 [velocity], or 2 [acceleration]""" @@ -76,58 +227,13 @@ def to_real_units(self, val, pva=0): def move_channel_to(self, channel, location, wait=True): index = self.to_device_units(location, 0) - bp.PBC_SetPosition(self.serial, channel, index) - # if wait: - # bp.PBC_RequestPosition(self.serial, channel) - # sleep(0.05) - # pos = bp.PBC_GetPosition(self.serial, channel) - # while index != pos: - # bp.PBC_RequestPosition(self.serial, channel) - # sleep(0.05) - # pos = bp.PBC_GetPosition(self.serial, channel) + bp.PBC_SetPosition(self.serial_number, channel, index) + + def start_polling_channel(self, channel, dt=50): + bp.PBC_StartPolling(self.serial_number, channel, dt) def stop_polling_channel(self, channel): - bp.PBC_StopPolling(self.serial, channel) - - @property - def x(self): - self._x = self.get_channel_position(1) - return self._x - - @x.setter - def x(self, value): - self.move_channel_to(1, value) - self._x = value - - @property - def y(self): - self._y = self.get_channel_position(2) - return self._y - - @y.setter - def y(self, value): - self.move_channel_to(2, value) - self._y = value - - @property - def z(self): - self._z = self.get_channel_position(3) - return self._z - - @z.setter - def z(self, value): - self.move_channel_to(3, value) - self._z = value - - @property - def xyz(self): - self._xyz = [self.get_channel_position(i) for i in range(1, 4)] - return self._xyz - - @xyz.setter - def xyz(self, posns): - for i, posn in zip(range(1, 4), posns): - self.move_channel_to(i, posn) + bp.PBC_StopPolling(self.serial_number, channel) def __del__(self): [self.stop_polling_channel(q) for q in range(1, 4)] From bde9275329b652f6a8b71045f143504849e356bf Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Tue, 5 Nov 2024 10:15:10 -0700 Subject: [PATCH 23/29] Revert "Start of newly formatted tlk drivers" This reverts commit 1a1cc077e08611e383a3ae50bca41213be94c587. --- drivers_test_notebooks/thorlabsbpc303.ipynb | 227 --------------- .../instrument_driver/abstract_driver.py | 28 +- .../instrument_driver/instrument_driver.py | 8 +- pyscan/drivers/thorlabs/__init__.py | 10 +- pyscan/drivers/thorlabs/check_for_error.py | 18 -- .../thorlabs/thorlabs_kinesis_driver.py | 38 --- pyscan/drivers/thorlabs/thorlabsbpc303.py | 274 ++++++------------ 7 files changed, 101 insertions(+), 502 deletions(-) delete mode 100644 drivers_test_notebooks/thorlabsbpc303.ipynb delete mode 100644 pyscan/drivers/thorlabs/check_for_error.py delete mode 100644 pyscan/drivers/thorlabs/thorlabs_kinesis_driver.py diff --git a/drivers_test_notebooks/thorlabsbpc303.ipynb b/drivers_test_notebooks/thorlabsbpc303.ipynb deleted file mode 100644 index 192c7151..00000000 --- a/drivers_test_notebooks/thorlabsbpc303.ipynb +++ /dev/null @@ -1,227 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "\n", - "import pyscan as ps\n", - "from time import sleep\n", - "from pyscan_tlk import benchtoppiezo as bp" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "try: \n", - " del piezo\n", - "except:\n", - " pass\n", - "\n", - "piezo = ps.ThorlabsBPC303(71000001, simulate=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(196609, 393216)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert piezo.number_channels == 3\n", - "assert piezo.max_channel_count == 3\n", - "piezo.firmware_version, piezo.software_version" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'undefined'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "piezo.x_control_mode = 'undefined'\n", - "\n", - "sleep(0.1)\n", - "\n", - "piezo.x_control_mode" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "'ThorlabsBPC303' object has no attribute 'bp'", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[5], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mpiezo\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbp\u001b[49m\u001b[38;5;241m.\u001b[39mPBC_Tol\n", - "\u001b[1;31mAttributeError\u001b[0m: 'ThorlabsBPC303' object has no attribute 'bp'" - ] - } - ], - "source": [ - "conversion = max_travel * 0.1 / (2e15-1)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1639.344262295082" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "200000/122" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "750" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "piezo.instrument.PBC_Trav (piezo.serial_number, 1)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.0011444266422522317" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "75/(2**16-1)" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "19.98848" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "122 / 200000 * 2**15" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "32767" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "2**15-1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index adcae6f0..60cea08f 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -35,7 +35,7 @@ class AbstractDriver(ItemAttribute): get_property_docstring(prop_name) ''' - def __init__(self): + def __init__(self, instrument, debug=False): pass def query_property(self, settings): @@ -71,7 +71,7 @@ def add_device_property(self, settings_dict): settings_name = '_' + name + '_settings' property_class, settings_class = self.validate_property_settings(settings_dict) - settings_class = self.validate_subclass_settings(settings_class) + self.validate_subclass_settings(settings_dict) # Make property settings attribute settings_obj = settings_class(settings_dict) @@ -126,22 +126,12 @@ def get_property_docstring(self, prop_name): doc = self.__doc__.split('\n') - r = " {} :".format(prop_name) + r = re.compile(".*{} :".format(prop_name)) + match = list(filter(r.match, doc)) - def find_match(str): - if r in str: - return True - else: - return False - - match = list(filter(find_match, doc)) - - assert not len(match) > 1, "Too many matches for {} documentation".format(prop_name) - - if len(match) == 0: - match = '' - else: - match = match[0] + assert len(match) > 0, "No matches for {} documentation".format(prop_name) + assert len(match) == 1, "Too many matches for {} documentation".format(prop_name) + match = match[0] for i, string in enumerate(doc): if string == match: @@ -218,10 +208,10 @@ def validate_property_settings(self, settings_dict): return property_class, settings_class - def validate_subclass_settings(self, settings_obj): + def validate_subclass_settings(self, settings_dict): ''' Abstract method to be overloaded which checks the validity of input settings beyond range, values, indexed_values, dict_values, read-only, write-only, etc. ''' - return settings_obj + pass diff --git a/pyscan/drivers/instrument_driver/instrument_driver.py b/pyscan/drivers/instrument_driver/instrument_driver.py index 0d01198c..015529b3 100644 --- a/pyscan/drivers/instrument_driver/instrument_driver.py +++ b/pyscan/drivers/instrument_driver/instrument_driver.py @@ -129,19 +129,19 @@ def read(self): return self.instrument.read() - def validate_subclass_settings(self, settings_obj): + def validate_subclass_settings(self, settings_dict): ''' For ScPIPropertySettings, ensures that write_string, query_string, read_only, and write_only are configured properly Parameters ---------- - settings_obj : instance of subclass of AbstractPropertySetttings + settings_dict : dict Dictionary of settings that generate a pyscan device property object ''' - settings_keys = list(settings_obj.keys()) + settings_keys = list(settings_dict.keys()) if 'read_only' in settings_keys: assert 'write_string' not in settings_keys, \ @@ -158,5 +158,3 @@ def validate_subclass_settings(self, settings_obj): f'{self.name} is missing a "query_string" key' assert 'write_string' in settings_keys, \ f'{self.name} is missing a "write_string" key' - - return settings_obj diff --git a/pyscan/drivers/thorlabs/__init__.py b/pyscan/drivers/thorlabs/__init__.py index 6e8a6df1..3f08b669 100644 --- a/pyscan/drivers/thorlabs/__init__.py +++ b/pyscan/drivers/thorlabs/__init__.py @@ -4,9 +4,9 @@ try: import pyscan_tlk from .thorlabsbpc303 import ThorlabsBPC303 - from .thorlabsbsc203 import ThorlabsBSC203 - from .thorlabsmff101 import ThorlabsMFF101 -except ModuleNotFoundError: + # from .thorlabsbsc203 import ThorlabsBSC203 + # from .thorlabsmff101 import ThorlabsMFF101 +else: from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBPC303 - # from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBSC203 - # from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsMFF101 + from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBSC203 + from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsMFF101 diff --git a/pyscan/drivers/thorlabs/check_for_error.py b/pyscan/drivers/thorlabs/check_for_error.py deleted file mode 100644 index 56e3009b..00000000 --- a/pyscan/drivers/thorlabs/check_for_error.py +++ /dev/null @@ -1,18 +0,0 @@ -from pyscan_tlk.definitions.kinesisexception import KinesisException - - -def check_return_for_error(ret): - - if ret != 0: - raise KinesisException(ret) - - -def check_for_error(func, *arg, **kwarg): - - def new_function(*arg, **kwarg): - - ret = func(*arg, **kwarg) - - check_for_error(ret) - - return new_function diff --git a/pyscan/drivers/thorlabs/thorlabs_kinesis_driver.py b/pyscan/drivers/thorlabs/thorlabs_kinesis_driver.py deleted file mode 100644 index 6d423778..00000000 --- a/pyscan/drivers/thorlabs/thorlabs_kinesis_driver.py +++ /dev/null @@ -1,38 +0,0 @@ -from ..instrument_driver.abstract_driver import AbstractDriver -from .check_for_error import check_return_for_error -from ctypes import c_char_p - - -class ThorlabsKinesisDriver(AbstractDriver): - - def __init__(self, serial_number, debug=False): - self.serial_number = c_char_p(bytes(str(serial_number), "utf-8")) - - def query_property(self, settings_obj): - - if 'channel' in settings_obj: - ret = settings_obj.query_function(self.serial_number, settings_obj.channel) - else: - ret = settings_obj.query_function(self.serial_number) - - return ret - - def write_property(self, settings_obj, new_value): - - if 'channel' in settings_obj: - ret = settings_obj.write_function(self.serial_number, settings_obj.channel, new_value) - else: - ret = settings_obj.write_function(self.serial_number, new_value) - - if settings_obj.check_for_error: - check_return_for_error(ret) - return ret - - def validate_subclass_settings(self, settings_obj): - - keys = settings_obj.__dict__.keys() - - if 'check_for_error' not in keys: - settings_obj.check_for_error = False - - return settings_obj diff --git a/pyscan/drivers/thorlabs/thorlabsbpc303.py b/pyscan/drivers/thorlabs/thorlabsbpc303.py index 5a7f4fee..6c80f14c 100644 --- a/pyscan/drivers/thorlabs/thorlabsbpc303.py +++ b/pyscan/drivers/thorlabs/thorlabsbpc303.py @@ -1,224 +1,73 @@ # -*- coding: utf-8 -*- -from .thorlabs_kinesis_driver import ThorlabsKinesisDriver -from .check_for_error import check_for_error -from pyscan_tlk import benchtoppiezo as bp -from ctypes import c_ushort, c_ulong, c_short +from ...general.item_attribute import ItemAttribute +from thorlabs_kinesis import benchtop_piezo as bp +from ctypes import c_char_p, c_ushort, c_ulong, c_short from time import sleep -from math import floor c_word = c_ushort c_dword = c_ulong -class ThorlabsBPC303(ThorlabsKinesisDriver): +class ThorlabsBPC303(ItemAttribute): ''' Driver for ThorLabs BPC303 - 3-Channel 150 V Benchtop Piezo Controller with USB Parameters ---------- - serial_number : int - Serial number integer - - Attributes - ---------- - (Properties) - number_channels : int - Queries the number of channels available on the instrument - max_channel_count : int - Queries the maximum number of channels on the instrument - firmware_version : int - Queries the firmware version - software_version : int - Queries the software version - - x_polling_duration : int - Queries the polling duration in ms for channel x - x_maximum_travel : int - Queries the maximum travel for channel x - x_status_bits : int - Queries the status bits for channel x - x_maximum_voltage : int - Queries the maximum voltage for channel x - x_control_mode : str - Sets/queries the control mode for x channel to - 'undefined', 'open loop', 'closed loop', 'open loop smoothed', 'closed loop smoothed' - x : float - Sets/queries the x position in um - - - - y_polling_duration : int - Queries the polling duration in ms for channel y - y_maximum_travel : int - Queries the maximum travel for channel y - y_status_bits : int - Queries the status bits for channel y - y_control_mode : str - Sets/queries the control mode for y channel to - 'undefined', 'open loop', 'closed loop', 'open loop smoothed', 'closed loop smoothed' - y_maximum_voltage : int - Queries the maximum voltage for channel y - y : float - Sets/queries the y position in um - - z_polling_duration : int - Queries the polling duration in ms for channel z - z_maximum_travel : int - Queries the maximum travel for channel z - z_status_bits : int - Queries the status bits for channel z - z_control_mode : str - Sets/queries the control mode for z channel to - 'undefined', 'open loop', 'closed loop', 'open loop smoothed', 'closed loop smoothed' - z_maximum_voltage : int - Queries the maximum voltage for channel z - z : float - Sets/queries the z position in um - - - Methods - ------- - + serial : str + Serial number string. Defaults to "71872242". ''' - def __init__(self, serial_number, simulate=False): - - super().__init__(serial_number) - - self._version = "1.0.0" - - self.build_device_list() - if simulate: - self.initialize_simulations() - self.open() - - self.instrument = bp - + def __init__(self, serial="71872242"): + self._version = "0.1.0" + self.serial = c_char_p(bytes(serial, "utf-8")) + if self.build_device_list() != 0: + assert 0, 'Could not build device list' + if self.open() != 0: + print(self.open()) + sleep(1.5) for i in range(1, 4): - self.start_polling_channel(i) - - self.initialize_device_properties() - - def initialize_device_properties(self): - - self.add_device_property({ - 'name': 'number_channels', - 'return_type': int, - 'read_only': True, - 'query_function': bp.PBC_GetNumChannels}) - - self.add_device_property({ - 'name': 'max_channel_count', - 'return_type': int, - 'read_only': True, - 'query_function': bp.PBC_MaxChannelCount}) - - self.add_device_property({ - 'name': 'firmware_version', - 'return_type': int, - 'read_only': True, - 'query_function': bp.PBC_GetFirmwareVersion}) - - self.add_device_property({ - 'name': 'software_version', - 'return_type': int, - 'read_only': True, - 'query_function': bp.PBC_GetSoftwareVersion}) - - for i, channel in zip([1, 2, 3], ['x', 'y', 'z']): - - self.add_device_property({ - 'name': f'{channel}_polling_duration', - 'read_only': True, - 'return_type': int, - 'query_function': bp.PBC_PollingDuration, - 'channel': i}) - - self.add_device_property({ - 'name': f'{channel}_maximum_travel', - 'read_only': True, - 'return_type': int, - 'query_function': bp.PBC_GetMaximumTravel, - 'channel': i}) - - self.add_device_property({ - 'name': f'{channel}_status_bits', - 'read_only': True, - 'return_type': int, - 'query_function': bp.PBC_GetStatusBits, - 'channel': i}) - - self.add_device_property({ - 'name': f'{channel}_maximum_voltage', - 'read_only': True, - 'return_type': int, - 'query_function': bp.PBC_GetMaxOutputVoltage, - 'channel': i}) - - self.add_device_property({ - 'name': f'{channel}_control_mode', - 'indexed_values': [ - 'undefined', - 'open loop', - 'closed loop', - 'open loop smoothed', - 'closed loop smoothed'], - 'query_function': bp.PBC_GetPositionControlMode, - 'write_function': bp.PBC_SetPositionControlMode, - 'channel': i}) - - self.add_device_property({ - 'name': f'{channel}', - 'range': [0, 20000], - 'query_function': bp.PBC_GetPosition, - 'write_function': bp.PBC_SetPosition, - 'channel': i, - 'return_type': int}) - - @check_for_error + sleep(0.25) + try: + self.load_channel_settings(c_short(i)) + except: + pass + sleep(0.25) + def build_device_list(self): return bp.TLI_BuildDeviceList() - def initialize_simulations(self): - bp.TLI_InitializeSimulations() - - @check_for_error def zero_channel(self, channel): - bp.PBC_SetZero(self.serial_number, channel) + bp.PBC_SetZero(self.serial, channel) - def zero_x(self): - self.zero_channel(1) + def set_channel_closed_loop(self, channel): + bp.PBC_SetPositionControlMode(self.serial, channel, 2) - def zero_y(self): - self.zero_channel(2) + def set_channel_open_loop(self, channel): + bp.PBC_SetPositionControlMode(self.serial, channel, 1) - def zero_z(self): - self.zero_channel(3) - - def zero_xyz(self): - for i in range(1, 4): - self.zero_channel(i) - - @check_for_error def open(self): - return bp.PBC_Open(self.serial_number) + return bp.PBC_Open(self.serial) def close(self): - bp.PBC_Close(self.serial_number) + bp.PBC_Close(self.serial) + + def get_number_channels(self): + return bp.PBC_GetNumChannels(self.serial) def load_channel_settings(self, channel): - bp.PBC_LoadSettings(self.serial_number, channel) + bp.PBC_LoadSettings(self.serial, channel) def get_channel_position(self, channel): - pos = bp.PBC_GetPosition(self.serial_number, c_short(channel)) + pos = bp.PBC_GetPosition(self.serial, c_short(channel)) pos = self.to_real_units(pos) return pos def to_device_units(self, val, pva=0): """pva is 0 [position], 1 [velocity], or 2 [acceleration]""" conversions = [122 / 200000] - return int(floor(val / conversions[pva])) + return int(round(val / conversions[pva])) def to_real_units(self, val, pva=0): """pva is 0 [position], 1 [velocity], or 2 [acceleration]""" @@ -227,13 +76,58 @@ def to_real_units(self, val, pva=0): def move_channel_to(self, channel, location, wait=True): index = self.to_device_units(location, 0) - bp.PBC_SetPosition(self.serial_number, channel, index) - - def start_polling_channel(self, channel, dt=50): - bp.PBC_StartPolling(self.serial_number, channel, dt) + bp.PBC_SetPosition(self.serial, channel, index) + # if wait: + # bp.PBC_RequestPosition(self.serial, channel) + # sleep(0.05) + # pos = bp.PBC_GetPosition(self.serial, channel) + # while index != pos: + # bp.PBC_RequestPosition(self.serial, channel) + # sleep(0.05) + # pos = bp.PBC_GetPosition(self.serial, channel) def stop_polling_channel(self, channel): - bp.PBC_StopPolling(self.serial_number, channel) + bp.PBC_StopPolling(self.serial, channel) + + @property + def x(self): + self._x = self.get_channel_position(1) + return self._x + + @x.setter + def x(self, value): + self.move_channel_to(1, value) + self._x = value + + @property + def y(self): + self._y = self.get_channel_position(2) + return self._y + + @y.setter + def y(self, value): + self.move_channel_to(2, value) + self._y = value + + @property + def z(self): + self._z = self.get_channel_position(3) + return self._z + + @z.setter + def z(self, value): + self.move_channel_to(3, value) + self._z = value + + @property + def xyz(self): + self._xyz = [self.get_channel_position(i) for i in range(1, 4)] + return self._xyz + + @xyz.setter + def xyz(self, posns): + for i, posn in zip(range(1, 4), posns): + self.move_channel_to(i, posn) def __del__(self): [self.stop_polling_channel(q) for q in range(1, 4)] From c25e54c8a712bedcdd29a5d8970456e277542bea Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Tue, 5 Nov 2024 10:16:30 -0700 Subject: [PATCH 24/29] revert(): accidentally added thorlabs changes --- pyscan/drivers/thorlabs/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyscan/drivers/thorlabs/__init__.py b/pyscan/drivers/thorlabs/__init__.py index 3f08b669..38f38df0 100644 --- a/pyscan/drivers/thorlabs/__init__.py +++ b/pyscan/drivers/thorlabs/__init__.py @@ -1,12 +1,12 @@ from .thorlabsitc4001 import ThorLabsITC4001 -try: - import pyscan_tlk - from .thorlabsbpc303 import ThorlabsBPC303 - # from .thorlabsbsc203 import ThorlabsBSC203 - # from .thorlabsmff101 import ThorlabsMFF101 -else: - from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBPC303 - from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBSC203 - from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsMFF101 +# try: +# import pyscan_tlk +# from .thorlabsbpc303 import ThorlabsBPC303 +# from .thorlabsbsc203 import ThorlabsBSC203 +# from .thorlabsmff101 import ThorlabsMFF101 +# except ModuleNotFoundError: +# from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBPC303 +# from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBSC203 +# from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsMFF101 From d5d4312939d278046c9b0a6d61ac11151ba692e9 Mon Sep 17 00:00:00 2001 From: "amounce@sandia.gov" Date: Tue, 5 Nov 2024 10:20:03 -0700 Subject: [PATCH 25/29] Changed thorlabs import back --- pyscan/drivers/thorlabs/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyscan/drivers/thorlabs/__init__.py b/pyscan/drivers/thorlabs/__init__.py index 38f38df0..fa602c66 100644 --- a/pyscan/drivers/thorlabs/__init__.py +++ b/pyscan/drivers/thorlabs/__init__.py @@ -1,12 +1,12 @@ from .thorlabsitc4001 import ThorLabsITC4001 -# try: -# import pyscan_tlk -# from .thorlabsbpc303 import ThorlabsBPC303 -# from .thorlabsbsc203 import ThorlabsBSC203 -# from .thorlabsmff101 import ThorlabsMFF101 -# except ModuleNotFoundError: -# from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBPC303 -# from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBSC203 -# from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsMFF101 +try: + import thorlabs_kinesis + from .thorlabsbpc303 import ThorlabsBPC303 + from .thorlabsbsc203 import ThorlabsBSC203 + from .thorlabsmff101 import ThorlabsMFF101 +except ModuleNotFoundError: + from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBPC303 + from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsBSC203 + from .thorlabs_exceptions import ThorlabsKinesisImportException as ThorlabsMFF101 From c08891273a63e2b833f716cf2de2a0452e15d3d0 Mon Sep 17 00:00:00 2001 From: cint-transport Date: Tue, 5 Nov 2024 10:54:58 -0700 Subject: [PATCH 26/29] fix(driver): fixing sr860 for new property based drivers --- drivers_test_notebooks/keithley2260b.ipynb | 2 +- pyscan/drivers/stanford/stanford860.py | 3 +-- .../drivers/testing/driver_test_logs/stanford830_test_log.txt | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers_test_notebooks/keithley2260b.ipynb b/drivers_test_notebooks/keithley2260b.ipynb index 063a6610..056210c7 100644 --- a/drivers_test_notebooks/keithley2260b.ipynb +++ b/drivers_test_notebooks/keithley2260b.ipynb @@ -64,7 +64,7 @@ "metadata": {}, "outputs": [], "source": [ - "isource = ps.Keithley2260B(isource)" + "isource = ps.Keithley2260B(res)" ] }, { diff --git a/pyscan/drivers/stanford/stanford860.py b/pyscan/drivers/stanford/stanford860.py index 6059656e..358a8777 100644 --- a/pyscan/drivers/stanford/stanford860.py +++ b/pyscan/drivers/stanford/stanford860.py @@ -200,8 +200,7 @@ def initialize_properties(self): 'name': 'timebase_state', 'query_string': 'TBSTAT?', 'indexed_values': ['internal', 'external'], - 'read_only': True, - 'return_type': str}) + 'read_only': True}) self.add_device_property({ 'name': 'phase', diff --git a/pyscan/drivers/testing/driver_test_logs/stanford830_test_log.txt b/pyscan/drivers/testing/driver_test_logs/stanford830_test_log.txt index 0ace3082..e0684c36 100644 --- a/pyscan/drivers/testing/driver_test_logs/stanford830_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/stanford830_test_log.txt @@ -1 +1 @@ -Passed with stanford830 version v1.0.3 tested on pyscan version v0.8.4 at 2024-10-29 09:11:35 +Passed with stanford830 version v1.0.4 tested on pyscan version v0.8.5 at 2024-11-05 10:49:19 From 7fb975ae40d367697419226dfb430aae0399b6cc Mon Sep 17 00:00:00 2001 From: cint-transport Date: Tue, 5 Nov 2024 11:25:16 -0700 Subject: [PATCH 27/29] test(): fixed autotesting for stanford and keithley --- drivers_test_notebooks/stanford860.ipynb | 221 +++++++++--------- .../instrument_driver/abstract_driver.py | 20 +- pyscan/drivers/stanford/stanford860.py | 14 +- .../testing/auto_test_driver_properties.py | 12 +- pyscan/drivers/testing/check_initial_state.py | 13 +- .../keithley2260b_test_log.txt | 2 +- .../driver_test_logs/stanford860_test_log.txt | 2 +- pyscan/drivers/testing/test_properties.py | 6 +- 8 files changed, 151 insertions(+), 139 deletions(-) diff --git a/drivers_test_notebooks/stanford860.ipynb b/drivers_test_notebooks/stanford860.ipynb index d630ce76..385414bf 100644 --- a/drivers_test_notebooks/stanford860.ipynb +++ b/drivers_test_notebooks/stanford860.ipynb @@ -2,20 +2,11 @@ "cells": [ { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "metadata": {} }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -29,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 2, "metadata": { "metadata": {} }, @@ -62,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 3, "metadata": { "metadata": {} }, @@ -71,99 +62,93 @@ "name": "stdout", "output_type": "stream", "text": [ - "Blacklisted settings that will not be tested or changed are: \n", - "[('_amplitude', 0.0),\n", - " ('_voltage_offset', 0.0),\n", - " ('_auxiliary_output_voltage_1', 0.0),\n", - " ('_auxiliary_output_voltage_2', 0.0),\n", - " ('_auxiliary_output_voltage_3', 0.0),\n", - " ('_auxiliary_output_voltage_4', 0.0)]\n", + "amplitude\n", + "voltage_offset\n", + "auxiliary_output_voltage_1\n", + "auxiliary_output_voltage_2\n", + "auxiliary_output_voltage_3\n", + "auxiliary_output_voltage_4\n", "\n", "Beginning tests for: Stanford860 version 0.2.0\n", - "_id_settings\n", - "_timebase_mode_settings\n", - "_timebase_state_settings\n", - "_phase_settings\n", - "_frequency_settings\n", - "_internal_frequency_settings\n", - "_external_frequency_settings\n", - "_detection_frequency_settings\n", - "_harmonic_settings\n", - "_dual_harmonic_settings\n", - "_chopper_blade_slots_settings\n", - "_chopper_blade_phase_settings\n", - "_dc_out_reference_settings\n", - "_reference_source_settings\n", - "_reference_trigger_mode_settings\n", - "_reference_impedance_settings\n", - "_signal_input_settings\n", - "_voltage_input_mode_settings\n", - "_voltage_input_coupling_settings\n", - "_voltage_input_shield_settings\n", - "_voltage_input_range_settings\n", - "_current_input_impedance_settings\n", - "_signal_strength_settings\n", - "_voltage_sensitivity_settings\n", - "_time_constant_settings\n", - "_filter_slope_settings\n", - "_synchronous_filter_settings\n", - "_advanced_filter_settings\n", - "_equivalent_bandwidth_settings\n", - "_channel_1_output_settings\n", - "_channel_2_output_settings\n", - "_x_expand_settings\n", - "_y_expand_settings\n", - "_r_expand_settings\n", - "_x_offset_state_settings\n", - "_y_offset_state_settings\n", - "_r_offset_state_settings\n", - "_x_offset_percent_settings\n", - "_y_offset_percent_settings\n", - "_r_offset_percent_settings\n", - "_x_ratio_state_settings\n", - "_y_ratio_state_settings\n", - "_r_ratio_state_settings\n", - "_auxiliary_input_voltage_1_settings\n", - "_auxiliary_input_voltage_2_settings\n", - "_auxiliary_input_voltage_3_settings\n", - "_auxiliary_input_voltage_4_settings\n", - "\n", - "8 range properties found and tested out of 53 total settings found.\n", - "0 values properties found and tested out of 53 total settings found.\n", - "28 indexed values properties found and tested out of 53 total settings found.\n", - "0 dict values properties found and tested out of 53 total settings found.\n", - "6 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", - "36 properties tested out of 53 total settings.\n", - "\n", - "Restored settings are different for the following: {('timebase_state', 'external'), ('equivalent_bandwidth', 2.5), ('detection_frequency', 100000.0), ('id', 'Stanford_Research_Systems,SR860,005709,1.55'), ('auxiliary_input_voltage_1', -0.00053000450134), ('external_frequency', 0.00063607783522), ('auxiliary_input_voltage_2', -0.0003297328949), ('internal_frequency', 100000.0), ('auxiliary_input_voltage_3', 0.00010013580322), ('auxiliary_input_voltage_4', -0.00013303756714), ('signal_strength', 0)}\n", + "timebase_mode\n", + "phase\n", + "frequency\n", + "harmonic\n", + "dual_harmonic\n", + "chopper_blade_slots\n", + "chopper_blade_phase\n", + "dc_out_reference\n", + "reference_source\n", + "reference_trigger_mode\n", + "reference_impedance\n", + "signal_input\n", + "Warning, changing signal_input changed signal_strength from 0 to 4\n", + "voltage_input_mode\n", + "voltage_input_coupling\n", + "voltage_input_shield\n", + "voltage_input_range\n", + "Warning, changing voltage_input_range changed signal_strength from 0 to 2\n", + "current_input_impedance\n", + "voltage_sensitivity\n", + "time_constant\n", + "filter_slope\n", + "synchronous_filter\n", + "advanced_filter\n", + "channel_1_output\n", + "channel_2_output\n", + "x_expand\n", + "y_expand\n", + "r_expand\n", + "x_offset_state\n", + "y_offset_state\n", + "r_offset_state\n", + "x_offset_percent\n", + "y_offset_percent\n", + "r_offset_percent\n", + "x_ratio_state\n", + "y_ratio_state\n", + "r_ratio_state\n", + "{'id': 'Stanford_Research_Systems,SR860,005709,1.55\\n', 'timebase_mode': 'auto', 'timebase_state': 'external', 'phase': 0.0, 'frequency': 100000.0, 'internal_frequency': 100000.0, 'external_frequency': 0.012201439589, 'detection_frequency': 100000.0, 'harmonic': 1.0, 'dual_harmonic': 1.0, 'chopper_blade_slots': 6, 'chopper_blade_phase': 0.0, 'amplitude': 0.0, 'voltage_offset': 0.0, 'dc_out_reference': 'common', 'reference_source': 'interna', 'reference_trigger_mode': 'sine', 'reference_impedance': 50, 'signal_input': 'voltage', 'voltage_input_mode': 'A', 'voltage_input_coupling': 'ac', 'voltage_input_shield': 'float', 'voltage_input_range': 1, 'current_input_impedance': 1, 'signal_strength': 0, 'voltage_sensitivity': 1, 'time_constant': 0.1, 'filter_slope': 6, 'synchronous_filter': 'off', 'advanced_filter': 'on', 'equivalent_bandwidth': 2.5, 'channel_1_output': 'x', 'channel_2_output': 'y', 'x_expand': 'off', 'y_expand': 'off', 'r_expand': 'off', 'x_offset_state': 'on', 'y_offset_state': 'off', 'r_offset_state': 'off', 'x_offset_percent': 0.0, 'y_offset_percent': 0.0, 'r_offset_percent': 0.0, 'x_ratio_state': 'off', 'y_ratio_state': 'off', 'r_ratio_state': 'off', 'auxiliary_input_voltage_1': -0.00052499771118, 'auxiliary_input_voltage_2': -0.00030255317688, 'auxiliary_input_voltage_3': 0.00036263465881, 'auxiliary_input_voltage_4': -2.0742416382e-05, 'auxiliary_output_voltage_1': 0.0, 'auxiliary_output_voltage_2': 0.0, 'auxiliary_output_voltage_3': 0.0, 'auxiliary_output_voltage_4': 0.0}\n", "\n", - "\n", - "\u001b[92m Property implementation tests passed, instrument: Stanford860 looks ready to go. \u001b[0m\n", - "Checking driver doc string.\n", - "\u001b[92m Docstring tests passed and looking good. \u001b[0m\n", - "The new test log for this driver is: Passed with stanford860 version v0.2.0 tested on pyscan version v0.8.3 at 2024-10-16 09:42:36\n", + "8 range properties found and tested out of 47 total settings found.\n", + "0 values properties found and tested out of 47 total settings found.\n", + "28 indexed values properties found and tested out of 47 total settings found.\n", + "0 dict values properties found and tested out of 47 total settings found.\n", + "6 blacklisted settings not testing\n", + "36 properties tested out of 47 total settings.\n", + "\u001b[92m Property implementation tests passed, instrument: Stanford860. \u001b[0m\n", + "Testing driver doc string.\n", + "\u001b[92m Docstring tests passed. \u001b[0m\n", + "The new test log for this driver is: Passed with stanford860 version v0.2.0 tested on pyscan version v0.8.5 at 2024-11-05 11:21:50\n", "\u001b[1;32m Stanford860 test results logged. \u001b[0m\n" ] } ], "source": [ "srs860 = ps.Stanford860(res)\n", - "auto_test_driver_properties(srs860, verbose=False)\n" + "\n", + "initial_state_ignore_list = [\n", + " 'auxiliary_input_voltage_1',\n", + " 'auxiliary_input_voltage_2',\n", + " 'auxiliary_input_voltage_3',\n", + " 'auxiliary_input_voltage_4',\n", + " 'external_frequency']\n", + "auto_test_driver_properties(srs860, initial_state_ignore_list=initial_state_ignore_list, verbose=False)\n" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-1.8110673849e-08\n", - "6.7703318507e-08\n", - "7.488257836e-08\n", - "118.55735016\n" + "-2.3728938814e-08\n", + "-1.6870865593e-08\n", + "2.3174122177e-08\n", + "29.996479034\n" ] } ], @@ -176,23 +161,23 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "x -8.4704034009e-08\n", - "y 1.2525850934e-07\n", - "r 1.401335652e-07\n", - "theta 121.91644287\n", - "aux_in_1 -0.00083541870117\n", - "aux_in_2 -0.00025177001953\n", - "aux_in_3 0.00017166137695\n", - "aux_in_4 2.2888183594e-05\n", - "x_noise 1.7659951448\n", - "y_noise nan\n", + "x 7.5871639638e-08\n", + "y 8.1871050384e-08\n", + "r 9.9473901116e-08\n", + "theta 50.645298004\n", + "aux_in_1 -0.00066375732422\n", + "aux_in_2 -0.00033187866211\n", + "aux_in_3 0.00026321411133\n", + "aux_in_4 -0.00017166137695\n", + "x_noise 0.62450659275\n", + "y_noise 2.8721762646e-05\n", "aux_out_1 0.0\n", "aux_out_2 0.0\n", "phaseamplitude 0.0\n", @@ -215,43 +200,45 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[7.6801391913e-08, 7.6801391913e-08]\n", - "[8.1036887423e-08, 9.5599574479e-08]\n", - "[1.0447769938e-07, -0.03803052542171321]\n", - "[1.293406342e-07, 116.66421509]\n", - "[1.3341853844e-07, -0.00034332275391]\n", - "[1.4005352966e-07, 0.0]\n", - "[1.4855240238e-07, -0.00012588500977]\n", - "[1.5229676364e-07, 1.7659951448]\n", - "[1.4999460518e-07, nan]\n", - "[1.4387092051e-07, 0.0]\n", - "[1.3570021906e-07, 0.0]\n", - "[1.3867341409e-07, 0.0]\n", - "[1.3666570453e-07, 0.0]\n", - "[1.430487373e-07, 0.0]\n", - "[1.3871361659e-07, 100000.0]\n", - "[1.535691041e-07, 0.0058028604835]\n" + "[-3.7532835506e-08, -3.7532835506e-08]\n", + "[-1.5960377908e-08, 4.7653941238e-08]\n", + "[-2.2005781641e-09, -0.039341922849830825]\n", + "[1.0529091377e-08, 23.590711594]\n", + "[1.9856116751e-08, -0.00019454956055]\n", + "[1.6336024089e-08, 0.00027465820312]\n", + "[8.8861176195e-09, 0.00013732910156]\n", + "[-6.583346368e-11, 0.62450659275]\n", + "[-1.4619821798e-08, 2.8663413104e-05]\n", + "[-2.5099382128e-08, 0.0]\n", + "[-3.6038706241e-08, 0.0]\n", + "[-4.4508222885e-08, 0.0]\n", + "[-5.2974641562e-08, 0.0]\n", + "[-4.8823149967e-08, 0.0]\n", + "[-4.3956823959e-08, 100000.0]\n", + "[-3.3374426778e-08, 0.022692745551]\n" ] }, { "data": { "text/plain": [ - "[1.5602878989e-07, 1.5685968435e-07, -0.03803052542171321]" + "[5.8716067564e-09, 5.9340075076e-09, -0.041309018992293715]" ] }, - "execution_count": 14, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "# Test snap\n", + "\n", "parameters = [\n", " \"x\", \"y\", \"theta\", \"r\", \"aux_in_1\", \"aux_in_2\", \"aux_in_3\", \"aux_in_4\",\n", " \"x_noise\", \"y_noise\", \"aux_out_1\", \"aux_out_2\", \"phase\"\n", diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 60cea08f..244af8da 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -126,12 +126,22 @@ def get_property_docstring(self, prop_name): doc = self.__doc__.split('\n') - r = re.compile(".*{} :".format(prop_name)) - match = list(filter(r.match, doc)) + r = " {} :".format(prop_name) - assert len(match) > 0, "No matches for {} documentation".format(prop_name) - assert len(match) == 1, "Too many matches for {} documentation".format(prop_name) - match = match[0] + def find_match(str): + if r in str: + return True + else: + return False + + match = list(filter(find_match, doc)) + + assert not len(match) > 1, "Too many matches for {} documentation".format(prop_name) + + if len(match) == 0: + match = '' + else: + match = match[0] for i, string in enumerate(doc): if string == match: diff --git a/pyscan/drivers/stanford/stanford860.py b/pyscan/drivers/stanford/stanford860.py index 358a8777..e847f79d 100644 --- a/pyscan/drivers/stanford/stanford860.py +++ b/pyscan/drivers/stanford/stanford860.py @@ -168,12 +168,12 @@ def __init__(self, instrument): self._version = "0.2.0" self.black_list_for_testing = [ - '_amplitude', - '_voltage_offset', - '_auxiliary_output_voltage_1', - '_auxiliary_output_voltage_2', - '_auxiliary_output_voltage_3', - '_auxiliary_output_voltage_4'] + 'amplitude', + 'voltage_offset', + 'auxiliary_output_voltage_1', + 'auxiliary_output_voltage_2', + 'auxiliary_output_voltage_3', + 'auxiliary_output_voltage_4'] self.initialize_properties() self.update_properties() @@ -266,7 +266,7 @@ def initialize_properties(self): 'name': 'amplitude', 'write_string': 'SLVL {}', 'query_string': 'SLVL?', - 'range': [1e-9, 2], + 'range': [0, 2], 'return_type': float}) self.add_device_property({ diff --git a/pyscan/drivers/testing/auto_test_driver_properties.py b/pyscan/drivers/testing/auto_test_driver_properties.py index 17fbf505..c2eaa149 100644 --- a/pyscan/drivers/testing/auto_test_driver_properties.py +++ b/pyscan/drivers/testing/auto_test_driver_properties.py @@ -44,7 +44,9 @@ + "test for interdependence and consider blacklisting.") -def auto_test_driver_properties(device, detailed_dependence=False, skip_log=False, verbose=True): +def auto_test_driver_properties( + device, detailed_dependence=False, + initial_state_ignore_list=[], skip_log=False, verbose=True): ''' Automatically tests driver properties and documnetation @@ -54,13 +56,15 @@ def auto_test_driver_properties(device, detailed_dependence=False, skip_log=Fals Instance of a driver subclassed from AbstractDriver detailed_dependenced : bool, optional Tests the interdependence between properties and reports the results, by default False + initial_state_ignore_list : list[str] + List of strings of properties to ignore for checking changes skip_log : bool, optional Skip the logging of the test results, by default False verbose : bool, optional Print the results of the tests, by default True ''' - test_properties(device, detailed_dependence, verbose) + test_properties(device, detailed_dependence, initial_state_ignore_list, verbose) print( f"\033[92m Property implementation tests passed, instrument: {device.__class__.__name__}. \033[0m") @@ -74,7 +78,7 @@ def auto_test_driver_properties(device, detailed_dependence=False, skip_log=Fals print(f"\033[1;32m {device.__class__.__name__} test results logged. \033[0m") -def test_properties(device, detailed_dependence, verbose=False): +def test_properties(device, detailed_dependence, initial_state_ignore_list, verbose=False): ''' Automatically finds all of the PropertySettings properties of device and tests that they work @@ -143,7 +147,7 @@ def test_properties(device, detailed_dependence, verbose=False): print(property_name) - check_initial_state(device, property_name, initial_state) + check_initial_state(device, property_name, initial_state, initial_state_ignore_list) for key, value in initial_state.items(): settings = getattr(device, f'_{key}_settings') diff --git a/pyscan/drivers/testing/check_initial_state.py b/pyscan/drivers/testing/check_initial_state.py index 154880c3..fbceab02 100644 --- a/pyscan/drivers/testing/check_initial_state.py +++ b/pyscan/drivers/testing/check_initial_state.py @@ -1,4 +1,7 @@ -def check_initial_state(device, property_name, initial_state): +from ..instrument_driver.properties.read_only_exception import ReadOnlyException + + +def check_initial_state(device, property_name, initial_state, initial_state_ignore_list): ''' Check if the properties have changed from their intial state after a property has been tested @@ -14,8 +17,14 @@ def check_initial_state(device, property_name, initial_state): for key, value in initial_state.items(): + if key in initial_state_ignore_list: + continue + new_value = device[key] if new_value != value: print(f'Warning, changing {property_name} changed {key} from {value} to {new_value}') - device[key] = value + try: + device[key] = value + except ReadOnlyException: + pass diff --git a/pyscan/drivers/testing/driver_test_logs/keithley2260b_test_log.txt b/pyscan/drivers/testing/driver_test_logs/keithley2260b_test_log.txt index 874fff4f..25e46b2a 100644 --- a/pyscan/drivers/testing/driver_test_logs/keithley2260b_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/keithley2260b_test_log.txt @@ -1 +1 @@ -Passed with keithley2260b version v0.1.3 tested on pyscan version v0.8.4 at 2024-10-29 09:29:56 +Passed with keithley2260b version v0.1.3 tested on pyscan version v0.8.5 at 2024-11-05 11:24:08 diff --git a/pyscan/drivers/testing/driver_test_logs/stanford860_test_log.txt b/pyscan/drivers/testing/driver_test_logs/stanford860_test_log.txt index 5c3308f2..4de49470 100644 --- a/pyscan/drivers/testing/driver_test_logs/stanford860_test_log.txt +++ b/pyscan/drivers/testing/driver_test_logs/stanford860_test_log.txt @@ -1 +1 @@ -Passed with stanford860 version v0.2.0 tested on pyscan version v0.8.3 at 2024-10-16 09:42:36 +Passed with stanford860 version v0.2.0 tested on pyscan version v0.8.5 at 2024-11-05 11:21:50 diff --git a/pyscan/drivers/testing/test_properties.py b/pyscan/drivers/testing/test_properties.py index 3f3b2cde..61026941 100644 --- a/pyscan/drivers/testing/test_properties.py +++ b/pyscan/drivers/testing/test_properties.py @@ -35,8 +35,10 @@ def test_read_only_property(device, property_name): initial_state : dict key, value pairs of property names and their initial values ''' - - assert device[property_name], 'Could not read property {}'.format(property_name) + try: + device[property_name] + except Exception: + raise Exception(f'Could not read, read-only property {property_name}') def test_values_property(device, property_name, detailed_dependence, initial_state): From 5c45047c48bd90bd55b6b272f2f7c209085bae82 Mon Sep 17 00:00:00 2001 From: cint-transport Date: Tue, 5 Nov 2024 11:28:12 -0700 Subject: [PATCH 28/29] git status --- drivers_test_notebooks/keithley2260b.ipynb | 30 +++++----- drivers_test_notebooks/stanford830.ipynb | 70 +++++++++++----------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/drivers_test_notebooks/keithley2260b.ipynb b/drivers_test_notebooks/keithley2260b.ipynb index 056210c7..d1c19411 100644 --- a/drivers_test_notebooks/keithley2260b.ipynb +++ b/drivers_test_notebooks/keithley2260b.ipynb @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 4, "metadata": { "metadata": {} }, @@ -134,7 +134,7 @@ "2 values properties found and tested out of 17 total settings found.\n", "4 indexed values properties found and tested out of 17 total settings found.\n", "0 dict values properties found and tested out of 17 total settings found.\n", - "2 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", + "2 blacklisted settings not testing\n", "17 properties tested out of 17 total settings.\n", "\n", "Settings restored to: \n", @@ -160,7 +160,7 @@ "\u001b[92m Property implementation tests passed, instrument: Keithley2260B. \u001b[0m\n", "Testing driver doc string.\n", "\u001b[92m Docstring tests passed. \u001b[0m\n", - "The new test log for this driver is: Passed with keithley2260b version v0.1.3 tested on pyscan version v0.8.4 at 2024-10-29 09:29:56\n", + "The new test log for this driver is: Passed with keithley2260b version v0.1.3 tested on pyscan version v0.8.5 at 2024-11-05 11:24:08\n", "\u001b[1;32m Keithley2260B test results logged. \u001b[0m\n" ] } @@ -171,7 +171,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 5, "metadata": { "metadata": {} }, @@ -182,7 +182,7 @@ "0.0" ] }, - "execution_count": 14, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -193,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 6, "metadata": { "metadata": {} }, @@ -207,7 +207,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 7, "metadata": { "metadata": {} }, @@ -230,7 +230,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 8, "metadata": { "metadata": {} }, @@ -241,7 +241,7 @@ "-0.003" ] }, - "execution_count": 17, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -252,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 9, "metadata": { "metadata": {} }, @@ -263,7 +263,7 @@ "0.0" ] }, - "execution_count": 18, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -274,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 10, "metadata": { "metadata": {} }, @@ -285,7 +285,7 @@ "0.0" ] }, - "execution_count": 19, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -303,7 +303,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 11, "metadata": { "metadata": {} }, diff --git a/drivers_test_notebooks/stanford830.ipynb b/drivers_test_notebooks/stanford830.ipynb index bb6c2157..7cf4c1d4 100644 --- a/drivers_test_notebooks/stanford830.ipynb +++ b/drivers_test_notebooks/stanford830.ipynb @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 3, "metadata": { "metadata": {} }, @@ -67,7 +67,7 @@ "amplitude\n", "sensitivity\n", "\n", - "Beginning tests for: Stanford830 version 1.0.3\n", + "Beginning tests for: Stanford830 version 1.0.4\n", "phase\n", "reference_source\n", "frequency\n", @@ -93,12 +93,12 @@ "0 values properties found and tested out of 22 total settings found.\n", "13 indexed values properties found and tested out of 22 total settings found.\n", "0 dict values properties found and tested out of 22 total settings found.\n", - "4 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", + "4 blacklisted settings not testing\n", "20 properties tested out of 22 total settings.\n", "\u001b[92m Property implementation tests passed, instrument: Stanford830. \u001b[0m\n", "Testing driver doc string.\n", "\u001b[92m Docstring tests passed. \u001b[0m\n", - "The new test log for this driver is: Passed with stanford830 version v1.0.3 tested on pyscan version v0.8.4 at 2024-10-29 09:11:35\n", + "The new test log for this driver is: Passed with stanford830 version v1.0.4 tested on pyscan version v0.8.5 at 2024-11-05 10:49:19\n", "\u001b[1;32m Stanford830 test results logged. \u001b[0m\n" ] } @@ -110,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 4, "metadata": { "metadata": {} }, @@ -166,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 5, "metadata": { "metadata": {} }, @@ -192,16 +192,16 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-0.000105858 -7.15259e-07\n", - "0.000421764 4.76841e-07\n", - "3.88625e-05 0.0\n" + "-0.000105381 0.0\n", + "0.000421526 2.3842e-07\n", + "-4.76841e-07 -2.3842e-07\n" ] } ], @@ -240,17 +240,17 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.002\n", - "0.001\n", - "0.00166667\n", - "0.000666667\n" + "0.00366667\n", + "0.00233333\n", + "-0.00666667\n", + "0.00266667\n" ] } ], @@ -265,17 +265,17 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-0.000106097\n", - "0.000421764\n", - "-4.76841e-07\n", - "5962.089260533679\n" + "-0.000106335\n", + "0.000421287\n", + "-7.15259e-07\n", + "5969.308534850187\n" ] } ], @@ -290,15 +290,15 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-0.000105858\n", - "0.000421526\n" + "-0.000106335\n", + "0.000421287\n" ] } ], @@ -311,23 +311,23 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[-0.000105858, 0.000421287]\n", - "[-0.000105858, -2.3842e-07]\n", - "[-0.000105858, 5963.46436040349]\n", - "[-0.000105858, 0.002]\n", - "[-0.000105858, 0.004]\n", - "[-0.000105858, 0.00433333]\n", - "[-0.000105858, 0.00433333]\n", - "[-0.000106097, 1000.0]\n", + "[-0.000106335, 0.000421049]\n", + "[-0.000106335, -7.15259e-07]\n", + "[-0.000106335, 5966.902110078017]\n", + "[-0.000106335, 0.002]\n", + "[-0.000106335, 0.001]\n", + "[-0.000106335, 0.003]\n", + "[-0.000106335, 0.00133333]\n", + "[-0.000106335, 1000.0]\n", "[-0.000106097, -0.000106097]\n", - "[-0.000105858, 0.000421287]\n" + "[-0.000106335, 0.000421526]\n" ] } ], @@ -348,7 +348,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -369,7 +369,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 12, "metadata": {}, "outputs": [ { From 54eba3b517bf989929a7e07b061205ead0e051c3 Mon Sep 17 00:00:00 2001 From: i-am-mounce Date: Tue, 7 Jan 2025 10:22:53 -0700 Subject: [PATCH 29/29] fix(references): fixed references to itemattribute and get_pyscan_version --- pyscan/drivers/instrument_driver/abstract_driver.py | 2 +- .../properties/abstract_instrument_property.py | 2 +- pyscan/drivers/testing/auto_test_driver_properties.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py index 244af8da..18832554 100644 --- a/pyscan/drivers/instrument_driver/abstract_driver.py +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -1,4 +1,4 @@ -from ...general.item_attribute import ItemAttribute +from itemattribute import ItemAttribute from .properties import ( DictProperty, DictPropertySettings, IndexedProperty, IndexedPropertySettings, diff --git a/pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py b/pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py index 62143561..80b7cec2 100644 --- a/pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py +++ b/pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py @@ -1,4 +1,4 @@ -from ....general.item_attribute import ItemAttribute +from itemattribute import ItemAttribute from .read_only_exception import ReadOnlyException diff --git a/pyscan/drivers/testing/auto_test_driver_properties.py b/pyscan/drivers/testing/auto_test_driver_properties.py index c2eaa149..1b08c33d 100644 --- a/pyscan/drivers/testing/auto_test_driver_properties.py +++ b/pyscan/drivers/testing/auto_test_driver_properties.py @@ -1,4 +1,4 @@ -from ...general.get_pyscan_version import get_pyscan_version +from pyscan.measurement.get_pyscan_version import get_pyscan_version from .test_properties import ( test_values_property, test_range_property,