diff --git a/.github/workflows/complete.yml b/.github/workflows/complete.yml index 14f3843d..c63e76d8 100644 --- a/.github/workflows/complete.yml +++ b/.github/workflows/complete.yml @@ -67,7 +67,7 @@ jobs: - name: Test with pytest run: | - pytest + pytest ./test - name: Test notebooks run: | diff --git a/.github/workflows/test_station.yml b/.github/workflows/test_station.yml index aa7fc899..951cdac8 100644 --- a/.github/workflows/test_station.yml +++ b/.github/workflows/test_station.yml @@ -27,7 +27,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/.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 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..d1c19411 --- /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": 3, + "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": [ + "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\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.5 at 2024-11-05 11:24:08\n", + "\u001b[1;32m Keithley2260B test results logged. \u001b[0m\n" + ] + } + ], + "source": [ + "auto_test_driver_properties(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.003" + ] + }, + "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": ".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 25ae7e0e..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 import test_driver\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..5cb3af8b --- /dev/null +++ b/drivers_test_notebooks/real_experiment.ipynb @@ -0,0 +1,93 @@ +{ + "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" + ] + } + ], + "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 70% rename from drivers_test_notebooks/srs830_test_notebook.ipynb rename to drivers_test_notebooks/stanford830.ipynb index 9e5d8940..7cf4c1d4 100644 --- a/drivers_test_notebooks/srs830_test_notebook.ipynb +++ b/drivers_test_notebooks/stanford830.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" @@ -79,57 +62,50 @@ "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", - "\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", - "\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", - "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", + "input_configuration\n", + "time_constant\n", + "amplitude\n", + "sensitivity\n", "\n", + "Beginning tests for: Stanford830 version 1.0.4\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", - "\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", + "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\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.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" ] } ], "source": [ "srs830 = ps.Stanford830(res)\n", - "test_driver(srs830, verbose=False)" + "auto_test_driver_properties(srs830, verbose=False)" ] }, { @@ -223,8 +199,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "-8.9169e-05 -9.53682e-07\n", - "0.000385286 -7.15259e-07\n", + "-0.000105381 0.0\n", + "0.000421526 2.3842e-07\n", "-4.76841e-07 -2.3842e-07\n" ] } @@ -271,10 +247,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.002\n", + "0.00366667\n", "0.00233333\n", - "0.00166667\n", - "0.00133333\n" + "-0.00666667\n", + "0.00266667\n" ] } ], @@ -296,10 +272,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "-8.86922e-05\n", - "0.000385048\n", - "-2.3842e-07\n", - "5899.178441489818\n" + "-0.000106335\n", + "0.000421287\n", + "-7.15259e-07\n", + "5969.308534850187\n" ] } ], @@ -321,8 +297,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "-8.86922e-05\n", - "0.000385286\n" + "-0.000106335\n", + "0.000421287\n" ] } ], @@ -342,16 +318,16 @@ "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.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.000106335, 0.000421526]\n" ] } ], @@ -422,7 +398,7 @@ ], "metadata": { "kernelspec": { - "display_name": "rsbrostenv", + "display_name": ".venv", "language": "python", "name": "python3" }, diff --git a/drivers_test_notebooks/stanford860.ipynb b/drivers_test_notebooks/stanford860.ipynb new file mode 100644 index 00000000..385414bf --- /dev/null +++ b/drivers_test_notebooks/stanford860.ipynb @@ -0,0 +1,275 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "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\n", + "import pytest\n", + "from time import sleep" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "metadata": {} + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "GPIB0::4::INSTR Stanford_Research_Systems,SR860,005709,1.55\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 'SR860' 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": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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", + "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", + "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", + "\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": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-2.3728938814e-08\n", + "-1.6870865593e-08\n", + "2.3174122177e-08\n", + "29.996479034\n" + ] + } + ], + "source": [ + "# Test read channels\n", + "\n", + "for channel in list(range(1, 5)):\n", + " print(srs860.read_channel(channel))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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", + "dc_offset 0.0\n", + "internal_frequency 0.0\n", + "external_frequency 100000.0\n" + ] + } + ], + "source": [ + "# Test read \n", + "parameters = [\n", + " \"x\", \"y\", \"r\", \"theta\", \"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", + " \"amplitude\", \"dc_offset\", \"internal_frequency\", \"external_frequency\"]\n", + "\n", + "for param in parameters:\n", + " print(param, srs860.read(param))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[-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": [ + "[5.8716067564e-09, 5.9340075076e-09, -0.041309018992293715]" + ] + }, + "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", + " \"amplitude\", \"dc_offset\", \"internal_frequency\", \"external_frequency\"]\n", + "\n", + "for param in parameters:\n", + " print(srs860.snap('x', param))\n", + "\n", + "srs860.snap('x', 'y', 'theta')\n" + ] + } + ], + "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/stanford860_test_notebook.ipynb b/drivers_test_notebooks/stanford860_test_notebook.ipynb deleted file mode 100644 index e351514a..00000000 --- a/drivers_test_notebooks/stanford860_test_notebook.ipynb +++ /dev/null @@ -1,288 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "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\n", - "from pyvisa import ResourceManager, VisaIOError\n", - "import pytest\n", - "from time import sleep" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "GPIB0::4::INSTR Stanford_Research_Systems,SR860,005709,1.55\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 'SR860' in name:\n", - " print(r, name)\n", - " break\n", - " except VisaIOError:\n", - " pass\n", - " res.close()" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "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", - "\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", - "\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", - "\u001b[1;32m Stanford860 test results logged. \u001b[0m\n" - ] - } - ], - "source": [ - "srs860 = ps.Stanford860(res)\n", - "test_driver(srs860, verbose=False)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-1.8110673849e-08\n", - "6.7703318507e-08\n", - "7.488257836e-08\n", - "118.55735016\n" - ] - } - ], - "source": [ - "# Test read channels\n", - "\n", - "for channel in list(range(1, 5)):\n", - " print(srs860.read_channel(channel))" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "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", - "aux_out_1 0.0\n", - "aux_out_2 0.0\n", - "phaseamplitude 0.0\n", - "dc_offset 0.0\n", - "internal_frequency 0.0\n", - "external_frequency 100000.0\n" - ] - } - ], - "source": [ - "# Test read \n", - "parameters = [\n", - " \"x\", \"y\", \"r\", \"theta\", \"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", - " \"amplitude\", \"dc_offset\", \"internal_frequency\", \"external_frequency\"]\n", - "\n", - "for param in parameters:\n", - " print(param, srs860.read(param))" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "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" - ] - }, - { - "data": { - "text/plain": [ - "[1.5602878989e-07, 1.5685968435e-07, -0.03803052542171321]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "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", - " \"amplitude\", \"dc_offset\", \"internal_frequency\", \"external_frequency\"]\n", - "\n", - "for param in parameters:\n", - " print(srs860.snap('x', param))\n", - "\n", - "srs860.snap('x', 'y', 'theta')\n" - ] - } - ], - "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/__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/agilent/agilentdso900series.py b/pyscan/drivers/agilent/agilentdso900series.py index c99cc327..ef80a5bf 100644 --- a/pyscan/drivers/agilent/agilentdso900series.py +++ b/pyscan/drivers/agilent/agilentdso900series.py @@ -83,14 +83,15 @@ 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': [ + 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({ diff --git a/pyscan/drivers/heliotis/helios_sdk.py b/pyscan/drivers/heliotis/helios_sdk.py index d30b3be3..9f897534 100644 --- a/pyscan/drivers/heliotis/helios_sdk.py +++ b/pyscan/drivers/heliotis/helios_sdk.py @@ -69,7 +69,7 @@ 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 + Generator function for settings dictionary with 'values item Check that new_value is in settings['values'], if not, rejects command Args: diff --git a/pyscan/drivers/instrument_driver.py b/pyscan/drivers/instrument_driver.py index 7cb94af5..ed9fa441 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 diff --git a/pyscan/drivers/instrument_driver/__init__.py b/pyscan/drivers/instrument_driver/__init__.py new file mode 100644 index 00000000..52051953 --- /dev/null +++ b/pyscan/drivers/instrument_driver/__init__.py @@ -0,0 +1,2 @@ +from .instrument_driver import InstrumentDriver +from .new_instrument import new_instrument diff --git a/pyscan/drivers/instrument_driver/abstract_driver.py b/pyscan/drivers/instrument_driver/abstract_driver.py new file mode 100644 index 00000000..18832554 --- /dev/null +++ b/pyscan/drivers/instrument_driver/abstract_driver.py @@ -0,0 +1,227 @@ +from itemattribute import ItemAttribute +from .properties import ( + DictProperty, DictPropertySettings, + IndexedProperty, IndexedPropertySettings, + RangeProperty, RangePropertySettings, + ReadOnlyProperty, ReadOnlyPropertySetting, + ValuesProperty, ValuesPropertySettings) +import numpy as np +import re + + +class AbstractDriver(ItemAttribute): + ''' + 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, settings_class = self.validate_property_settings(settings_dict) + self.validate_subclass_settings(settings_dict) + + # Make property settings attribute + settings_obj = settings_class(settings_dict) + setattr(self, settings_name, settings_obj) + + # Make self.name property + 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 update_properties(self): + properties = self.get_pyscan_properties() + + for prop in properties: + settings = self['_{}_settings'.format(prop)] + if not hasattr(settings, 'write_only'): + 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 = " {} :".format(prop_name) + + 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 + + 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"') + 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: + assert isinstance(settings_dict['indexed_values'], list), f'{name} "indexed_values" setting must be a list' + property_class, settings_class = IndexedProperty, IndexedPropertySettings + elif 'read_only' in settings_keys: + 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, 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, 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, settings_class = DictProperty, DictPropertySettings + + return property_class, settings_class + + 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. + ''' + + 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..015529b3 --- /dev/null +++ b/pyscan/drivers/instrument_driver/instrument_driver.py @@ -0,0 +1,160 @@ +# -*- 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 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 + ''' + + self.instrument.write(settings_obj.write_string.format(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/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/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..80b7cec2 --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/abstract_instrument_property.py @@ -0,0 +1,106 @@ +from itemattribute 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/instrument_driver/properties/dict_values_property.py b/pyscan/drivers/instrument_driver/properties/dict_values_property.py new file mode 100644 index 00000000..667ea6c0 --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/dict_values_property.py @@ -0,0 +1,57 @@ +from .abstract_instrument_property import AbstractInstrumentProperty, 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 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): + super().__init__(settings_dict) + + self.dict_values = OrderedDict(settings_dict['dict_values']) + + self.key_type_dict = {} + self.str_values_dict = {} + + self.read_only = False + + for key, value in settings_dict['dict_values'].items(): + self.key_type_dict[key] = type(key) + self.str_values_dict[key] = str(value) + + def find_first_key(self, return_value): + for key, val in self.dict_values.items(): + if str(val) == str(return_value): + return key diff --git a/pyscan/drivers/instrument_driver/properties/indexed_property.py b/pyscan/drivers/instrument_driver/properties/indexed_property.py new file mode 100644 index 00000000..476d8c0b --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/indexed_property.py @@ -0,0 +1,48 @@ +from .abstract_instrument_property import AbstractInstrumentProperty, AbstractPropertySettings +from .read_only_property import ReadOnlyException + + +class IndexedValueException(Exception): + + 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([value + " " + str(type) for value, type in zip(indexed_values, types)]) + + 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): + super().__init__(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)) 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/instrument_driver/properties/values_property.py b/pyscan/drivers/instrument_driver/properties/values_property.py new file mode 100644 index 00000000..3ac4ca83 --- /dev/null +++ b/pyscan/drivers/instrument_driver/properties/values_property.py @@ -0,0 +1,56 @@ +from .abstract_instrument_property import AbstractInstrumentProperty, AbstractPropertySettings + + +class ValueException(Exception): + + def __init__(self, prop, values, types, value): + + msg = f'{prop} = {value} invalid input\n' + 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 ValuesProperty(AbstractInstrumentProperty): + + def validate_set_value(self, new_value, settings_obj): + if new_value in settings_obj.values: + return True + else: + raise ValueException(self.name, settings_obj.value_strings, settings_obj.types, new_value) + + def format_write_value(self, new_value, settings_object): + return new_value + + def format_query_return(self, returned_value, settings_obj): + + try: + # Check if the return is directly in the values list + index = settings_obj.values.index(returned_value) + except ValueError: + # If not, check if the return is in the value_strings list + index = settings_obj.value_strings.index(returned_value) + except: + raise Exception('Returned value {} {} not found in values or value_strings'.format( + returned_value, type(returned_value))) + + return_type = settings_obj.types[index] + + return return_type(returned_value) + + +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/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/princeton_instruments/pylonsdk.py b/pyscan/drivers/princeton_instruments/pylonsdk.py index ca188e93..eeeee7a6 100644 --- a/pyscan/drivers/princeton_instruments/pylonsdk.py +++ b/pyscan/drivers/princeton_instruments/pylonsdk.py @@ -58,7 +58,7 @@ 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 + Generator function for settings dictionary with 'values item Check that new_value is in settings['values'], if not, rejects command Args: diff --git a/pyscan/drivers/stanford/stanford396.py b/pyscan/drivers/stanford/stanford396.py index 9ebb32c4..fbb833af 100755 --- a/pyscan/drivers/stanford/stanford396.py +++ b/pyscan/drivers/stanford/stanford396.py @@ -121,8 +121,9 @@ def initialize_properties(self): 'name': 'modulation_type', 'write_string': 'TYPE {}', 'query_string': 'TYPE?', - 'values': ['am', 'fm', 'phim', 'sweep', 'pulse', - 'blank', 'qam', 'cpm', 'vbs'], + 'values': [ + 'am', 'fm', 'phim', 'sweep', 'pulse', + 'blank', 'qam', 'cpm', 'vbs'], 'return_type': int}) self.add_device_property({ diff --git a/pyscan/drivers/stanford/stanford830.py b/pyscan/drivers/stanford/stanford830.py index 38ba6ff1..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', @@ -831,7 +828,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'abs][:-2] # sample_rate = available_rates[ # np.bisect_left(available_rates, 1 / wait_dict[slope])] diff --git a/pyscan/drivers/stanford/stanford860.py b/pyscan/drivers/stanford/stanford860.py index 6059656e..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() @@ -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', @@ -267,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 new file mode 100644 index 00000000..1b08c33d --- /dev/null +++ b/pyscan/drivers/testing/auto_test_driver_properties.py @@ -0,0 +1,406 @@ +from pyscan.measurement.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 ..instrument_driver.properties import ( + DictPropertySettings, + IndexedPropertySettings, + RangePropertySettings, + ReadOnlyPropertySetting, + ValuesPropertySettings) +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, + initial_state_ignore_list=[], 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 + 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, initial_state_ignore_list, 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, initial_state_ignore_list, 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 + read_only_counter = 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 settings.read_only: + test_read_only_property(device, property_name) + read_only_counter += 1 + continue + 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) + 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', '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, initial_state_ignore_list) + + for key, value in initial_state.items(): + settings = getattr(device, f'_{key}_settings') + if not settings.read_only: + 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".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: + 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 + + +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..fbceab02 --- /dev/null +++ b/pyscan/drivers/testing/check_initial_state.py @@ -0,0 +1,30 @@ +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 + + 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(): + + 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}') + 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 39cd1aa2..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.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.5 at 2024-11-05 11:24:08 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..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.0 at 2024-10-03 08:29:18 +Passed with stanford830 version v1.0.4 tested on pyscan version v0.8.5 at 2024-11-05 10:49:19 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_instrument_driver.py b/pyscan/drivers/testing/test_instrument_driver.py index d3deac10..bf4b3564 100644 --- a/pyscan/drivers/testing/test_instrument_driver.py +++ b/pyscan/drivers/testing/test_instrument_driver.py @@ -1,28 +1,31 @@ -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 ---------- - instrument : mock - Optional parameter. + instrument : str (None) + A string that holds the place of an instrument to be connected to. Attributes ---------- (Properties) - float_values : float + id : str + The instrument id, read-only + 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 +38,43 @@ 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._range = 0 - self._indexed_values = 'A' - self._dict_values = 'off' + self._id = 'instrument#123' + self._float_value = 2.0 + self._str_value = '2' + self._bool_value = False + self._range = 0.111111 + 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(self, string): + def query_property(self, settings_obj): + + 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) - elif string == 'RANGE?': - return str(self._range) + val = self._bool_value + elif string in 'RANGE?': + val = "{:.3f}".format(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 + elif string == '*IDN?': + val = self._id + + return val - def write(self, string): + def write_property(self, settings_obj, new_value): + + string = settings_obj.write_string.format(new_value) if 'FLOAT_VALUES' in string: return string.strip('FLOAT_VALUES ') if 'STR_VALUES' in string: @@ -70,7 +82,9 @@ def write(self, string): 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: @@ -79,89 +93,74 @@ def write(self, string): def initialize_properties(self): self.add_device_property({ - 'name': 'float_values', + 'name': 'id', + 'query_string': '*IDN?', + 'read_only': True, + 'return_type': str + }) + + self.add_device_property({ + 'name': 'float_value', 'write_string': 'FLOAT_VALUES {}', 'query_string': 'FLOAT_VALUES?', - 'values': [2, 2.2339340249, 89.129398], - 'return_type': float - }) + 'values': [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': ['2', 'x', 'False']}) + + self.add_device_property({ + 'name': 'bool_value', + 'write_string': 'BOOL_VALUES {}', + 'query_string': 'BOOL_VALUES?', + 'values': [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 - }) - 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") + 'dict_values': {'on': 1, 'off': 0, '1': 1, '0': 0, 1: 1, 0: 0}}) 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 +173,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'] + 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' in string: return string.strip('VALUES ') elif 'RANGE' in string: return string.strip('RANGE ') @@ -209,7 +211,7 @@ 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']], diff --git a/pyscan/drivers/testing/test_properties.py b/pyscan/drivers/testing/test_properties.py new file mode 100644 index 00000000..61026941 --- /dev/null +++ b/pyscan/drivers/testing/test_properties.py @@ -0,0 +1,216 @@ +# test the set_values_property behavior +import pytest +import typing +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, + '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 + ''' + 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): + ''' + 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(settings.range[0], step, 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 f487c66a..ae024ed8 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 ..instrument_driver.abstract_driver import AbstractDriver -class TestVoltage(InstrumentDriver): +class TestVoltage(AbstractDriver): ''' 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,30 +36,23 @@ 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): + 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' - # 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 ') + return self._output_state_settings.indexed_values.index(self._output_state) + + def write_property(self, settings_obj, new_value): + + return str(new_value) def initialize_properties(self): @@ -67,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 }) @@ -75,18 +68,10 @@ def initialize_properties(self): 'name': 'power', 'write_string': 'POW {}', 'query_string': 'POW?', - 'values': [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 - }) - - @property - def version(self): - return self._version + 'indexed_values': ['off', 'on']}) diff --git a/pyscan/drivers/thorlabs/__init__.py b/pyscan/drivers/thorlabs/__init__.py index 91f68091..fa602c66 100644 --- a/pyscan/drivers/thorlabs/__init__.py +++ b/pyscan/drivers/thorlabs/__init__.py @@ -1,9 +1,8 @@ from .thorlabsitc4001 import ThorLabsITC4001 -import importlib.util -import sys + try: - import pyscan_tlk + import thorlabs_kinesis from .thorlabsbpc303 import ThorlabsBPC303 from .thorlabsbsc203 import ThorlabsBSC203 from .thorlabsmff101 import ThorlabsMFF101 diff --git a/pyscan/measurement/abstract_experiment.py b/pyscan/measurement/abstract_experiment.py index 34e42413..b09151b5 100644 --- a/pyscan/measurement/abstract_experiment.py +++ b/pyscan/measurement/abstract_experiment.py @@ -407,7 +407,8 @@ def save_point(self, data): 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) @@ -425,8 +426,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_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_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) diff --git a/test/drivers/test_test_voltage.py b/test/drivers/test_test_voltage.py deleted file mode 100644 index 51eccdba..00000000 --- a/test/drivers/test_test_voltage.py +++ /dev/null @@ -1,89 +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 -import pytest - - -def test_test_voltage(): - """ - Testing TestVoltage class - - Returns - -------- - None - """ - - # 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 7452daf4..f11ffa49 100644 --- a/test/measurement/test_abstract_experiment.py +++ b/test/measurement/test_abstract_experiment.py @@ -50,7 +50,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() @@ -68,89 +68,89 @@ 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: - 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 ms.runinfo.short_name == ms.runinfo.long_name[8:], "Meta Sweep short name is not the correct value" + 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: - 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): 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(data) + assert callable(expt.save_point) + expt.save_point(data) # 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) @@ -183,7 +183,7 @@ def test_ms_diff_inputs(data_dir=None, measure_function=measure_point, allocate= assert temp.x3.shape == (2, 5, 5) assert data.x3 in 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" @@ -225,59 +225,59 @@ 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_expt_diff_inputs() + test_expt_diff_inputs(data_dir='./backeep') 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_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_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) 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_ms_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D, allocate='preallocate_line') + test_expt_diff_inputs(data_dir='./backup', measure_function=measure_up_to_3D, allocate='preallocate_line')