diff --git a/drivers_test_notebooks/srs830_test_notebook.ipynb b/drivers_test_notebooks/srs830_test_notebook.ipynb index 2ccac567..9e5d8940 100644 --- a/drivers_test_notebooks/srs830_test_notebook.ipynb +++ b/drivers_test_notebooks/srs830_test_notebook.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "metadata": {} }, @@ -11,8 +11,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" + "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" ] } ], @@ -22,31 +30,24 @@ "\n", "import pyscan as ps\n", "from pyscan.drivers.testing.auto_test_driver import test_driver\n", - "from pyvisa import ResourceManager, VisaIOError" + "from pyvisa import ResourceManager, VisaIOError\n", + "import pytest\n", + "from time import sleep" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "metadata": {} }, "outputs": [ { - "ename": "VisaIOError", - "evalue": "VI_ERROR_RSRC_BUSY (-1073807246): The resource is valid, but VISA cannot currently access it.", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mVisaIOError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[2], line 6\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[38;5;66;03m# print(rs)\u001b[39;00m\n\u001b[0;32m 5\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m r \u001b[38;5;129;01min\u001b[39;00m rs:\n\u001b[1;32m----> 6\u001b[0m res \u001b[38;5;241m=\u001b[39m rm\u001b[38;5;241m.\u001b[39mopen_resource(r)\n\u001b[0;32m 7\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m: \n\u001b[0;32m 8\u001b[0m name \u001b[38;5;241m=\u001b[39m res\u001b[38;5;241m.\u001b[39mquery(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m*IDN?\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", - "File \u001b[1;32m~\\AppData\\Roaming\\Python\\Python311\\site-packages\\pyvisa\\highlevel.py:3292\u001b[0m, in \u001b[0;36mResourceManager.open_resource\u001b[1;34m(self, resource_name, access_mode, open_timeout, resource_pyclass, **kwargs)\u001b[0m\n\u001b[0;32m 3286\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m present:\n\u001b[0;32m 3287\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[0;32m 3288\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m%r\u001b[39;00m\u001b[38;5;124m is not a valid attribute for type \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 3289\u001b[0m \u001b[38;5;241m%\u001b[39m (key, res\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m)\n\u001b[0;32m 3290\u001b[0m )\n\u001b[1;32m-> 3292\u001b[0m res\u001b[38;5;241m.\u001b[39mopen(access_mode, open_timeout)\n\u001b[0;32m 3294\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m key, value \u001b[38;5;129;01min\u001b[39;00m kwargs\u001b[38;5;241m.\u001b[39mitems():\n\u001b[0;32m 3295\u001b[0m \u001b[38;5;28msetattr\u001b[39m(res, key, value)\n", - "File \u001b[1;32m~\\AppData\\Roaming\\Python\\Python311\\site-packages\\pyvisa\\resources\\resource.py:281\u001b[0m, in \u001b[0;36mResource.open\u001b[1;34m(self, access_mode, open_timeout)\u001b[0m\n\u001b[0;32m 277\u001b[0m logger\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m - opening ...\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_resource_name, extra\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_logging_extra)\n\u001b[0;32m 278\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_resource_manager\u001b[38;5;241m.\u001b[39mignore_warning(\n\u001b[0;32m 279\u001b[0m constants\u001b[38;5;241m.\u001b[39mStatusCode\u001b[38;5;241m.\u001b[39msuccess_device_not_present\n\u001b[0;32m 280\u001b[0m ):\n\u001b[1;32m--> 281\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msession, status \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_resource_manager\u001b[38;5;241m.\u001b[39mopen_bare_resource(\n\u001b[0;32m 282\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_resource_name, access_mode, open_timeout\n\u001b[0;32m 283\u001b[0m )\n\u001b[0;32m 285\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m status \u001b[38;5;241m==\u001b[39m constants\u001b[38;5;241m.\u001b[39mStatusCode\u001b[38;5;241m.\u001b[39msuccess_device_not_present:\n\u001b[0;32m 286\u001b[0m \u001b[38;5;66;03m# The device was not ready when we opened the session.\u001b[39;00m\n\u001b[0;32m 287\u001b[0m \u001b[38;5;66;03m# Now it gets five seconds more to become ready.\u001b[39;00m\n\u001b[0;32m 288\u001b[0m \u001b[38;5;66;03m# Every 0.1 seconds we probe it with viClear.\u001b[39;00m\n\u001b[0;32m 289\u001b[0m start_time \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime()\n", - "File \u001b[1;32m~\\AppData\\Roaming\\Python\\Python311\\site-packages\\pyvisa\\highlevel.py:3217\u001b[0m, in \u001b[0;36mResourceManager.open_bare_resource\u001b[1;34m(self, resource_name, access_mode, open_timeout)\u001b[0m\n\u001b[0;32m 3188\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mopen_bare_resource\u001b[39m(\n\u001b[0;32m 3189\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[0;32m 3190\u001b[0m resource_name: \u001b[38;5;28mstr\u001b[39m,\n\u001b[0;32m 3191\u001b[0m access_mode: constants\u001b[38;5;241m.\u001b[39mAccessModes \u001b[38;5;241m=\u001b[39m constants\u001b[38;5;241m.\u001b[39mAccessModes\u001b[38;5;241m.\u001b[39mno_lock,\n\u001b[0;32m 3192\u001b[0m open_timeout: \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m=\u001b[39m constants\u001b[38;5;241m.\u001b[39mVI_TMO_IMMEDIATE,\n\u001b[0;32m 3193\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tuple[VISASession, StatusCode]:\n\u001b[0;32m 3194\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Open the specified resource without wrapping into a class.\u001b[39;00m\n\u001b[0;32m 3195\u001b[0m \n\u001b[0;32m 3196\u001b[0m \u001b[38;5;124;03m Parameters\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 3215\u001b[0m \n\u001b[0;32m 3216\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[1;32m-> 3217\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvisalib\u001b[38;5;241m.\u001b[39mopen(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msession, resource_name, access_mode, open_timeout)\n", - "File \u001b[1;32m~\\AppData\\Roaming\\Python\\Python311\\site-packages\\pyvisa\\ctwrapper\\functions.py:1850\u001b[0m, in \u001b[0;36mopen\u001b[1;34m(library, session, resource_name, access_mode, open_timeout)\u001b[0m\n\u001b[0;32m 1846\u001b[0m out_session \u001b[38;5;241m=\u001b[39m ViSession()\n\u001b[0;32m 1848\u001b[0m \u001b[38;5;66;03m# [ViSession, ViRsrc, ViAccessMode, ViUInt32, ViPSession]\u001b[39;00m\n\u001b[0;32m 1849\u001b[0m \u001b[38;5;66;03m# ViRsrc converts from (str, unicode, bytes) to bytes\u001b[39;00m\n\u001b[1;32m-> 1850\u001b[0m ret \u001b[38;5;241m=\u001b[39m library\u001b[38;5;241m.\u001b[39mviOpen(\n\u001b[0;32m 1851\u001b[0m session, resource_name, access_mode, open_timeout, byref(out_session)\n\u001b[0;32m 1852\u001b[0m )\n\u001b[0;32m 1853\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m out_session\u001b[38;5;241m.\u001b[39mvalue, ret\n", - "File \u001b[1;32m~\\AppData\\Roaming\\Python\\Python311\\site-packages\\pyvisa\\ctwrapper\\highlevel.py:226\u001b[0m, in \u001b[0;36mIVIVisaLibrary._return_handler\u001b[1;34m(self, ret_value, func, arguments)\u001b[0m\n\u001b[0;32m 223\u001b[0m \u001b[38;5;66;03m# Set session back to a safe value\u001b[39;00m\n\u001b[0;32m 224\u001b[0m session \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m--> 226\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandle_return_value(session, ret_value)\n", - "File \u001b[1;32m~\\AppData\\Roaming\\Python\\Python311\\site-packages\\pyvisa\\highlevel.py:251\u001b[0m, in \u001b[0;36mVisaLibraryBase.handle_return_value\u001b[1;34m(self, session, status_code)\u001b[0m\n\u001b[0;32m 248\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_last_status_in_session[session] \u001b[38;5;241m=\u001b[39m rv\n\u001b[0;32m 250\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[1;32m--> 251\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m errors\u001b[38;5;241m.\u001b[39mVisaIOError(rv)\n\u001b[0;32m 253\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m rv \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39missue_warning_on:\n\u001b[0;32m 254\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m session \u001b[38;5;129;01mand\u001b[39;00m rv \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_ignore_warning_in_session[session]:\n", - "\u001b[1;31mVisaIOError\u001b[0m: VI_ERROR_RSRC_BUSY (-1073807246): The resource is valid, but VISA cannot currently access it." + "name": "stdout", + "output_type": "stream", + "text": [ + "GPIB0::8::INSTR Stanford_Research_Systems,SR830,s/n86813,ver1.07 \n", + "\n" ] } ], @@ -69,29 +70,7 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "metadata": {} - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'Stanford_Research_Systems,SR830,s/n86813,ver1.07 \\n'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "name" - ] - }, - { - "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": { "metadata": {} }, @@ -100,30 +79,62 @@ "name": "stdout", "output_type": "stream", "text": [ - "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'), ('sample_rate', 0.25), ('end_buffer_mode', 'one shot'), ('trigger_mode', 'on')]\n", - "These blacklisted settings and their corresponding values were not reset: [('_input_configuration', 'A-B'), ('_time_constant', 30), ('_sample_rate', 0.25)]\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'), ('sample_rate', 0.25), ('end_buffer_mode', 'one shot'), ('trigger_mode', 'on')]\n", - "Beginning tests for: Stanford830\n", - "4 range properties found and tested out of 18 total settings found.\n", - "0 values properties found and tested out of 18 total settings found.\n", - "9 indexed values properties found and tested out of 18 total settings found.\n", - "2 dict values properties found and tested out of 18 total settings found.\n", - "3 blacklisted settings not testing (likely due to interdependencies not suitable for automated testing)\n", - "15 properties tested out of 18 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'), ('sample_rate', 0.25), ('end_buffer_mode', 'one shot'), ('trigger_mode', 'on')]\n", - "Tests passed, instrument Stanford830 should be ready to go.\n" + "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", + "\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", + "\u001b[1;32m Stanford830 test results logged. \u001b[0m\n" ] } ], "source": [ "srs830 = ps.Stanford830(res)\n", - "\n", - "test_driver(srs830)" + "test_driver(srs830, verbose=False)" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 4, "metadata": { "metadata": {} }, @@ -132,39 +143,54 @@ "name": "stdout", "output_type": "stream", "text": [ - "('r', 'aux1')\n", - "('x', 'none')\n", "Sucessfully failed setting channel 1 to y\n", - "('theta', 'aux3')\n", - "('y', 'none')\n", - "Sucessfully failed setting channel 2 to x\n" + "Sucessfully failed setting channel 2 to x\n", + "Successfully tested get and set display\n" ] } ], "source": [ "# Test get and set display\n", "\n", - "srs830.set_display(1, 'r', 'aux1')\n", - "print(srs830.get_display(1))\n", + "srs830.set_display(1, 'r', 'aux1') # number, source, ratio\n", + "source, ratio = srs830.get_display(1)\n", + "assert source == 'r'\n", + "assert ratio == 'aux1'\n", + "\n", + "\n", "srs830.set_display(1, 'x', 'none')\n", - "print(srs830.get_display(1))\n", - "try:\n", + "source, ratio = srs830.get_display(1)\n", + "assert source == 'x'\n", + "assert ratio == 'none'\n", + "\n", + "\n", + "with pytest.raises(AssertionError):\n", " srs830.set_display(1, 'y', 'none')\n", - "except AssertionError:\n", - " print('Sucessfully failed setting channel 1 to y')\n", + "print('Sucessfully failed setting channel 1 to y')\n", + " \n", + " \n", "srs830.set_display(2, 'theta', 'aux3')\n", - "print(srs830.get_display(2))\n", + "source, ratio = srs830.get_display(2)\n", + "assert source == 'theta'\n", + "assert ratio == 'aux3'\n", + "\n", + "\n", "srs830.set_display(2, 'y', 'none')\n", - "print(srs830.get_display(2))\n", - "try:\n", + "source, ratio = srs830.get_display(2)\n", + "assert source == 'y'\n", + "assert ratio == 'none'\n", + "\n", + "\n", + "with pytest.raises(AssertionError):\n", " srs830.set_display(2, 'x', 'none')\n", - "except AssertionError:\n", - " print('Sucessfully failed setting channel 2 to x')" + "print('Sucessfully failed setting channel 2 to x')\n", + "\n", + "print(\"Successfully tested get and set display\")\n" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 5, "metadata": { "metadata": {} }, @@ -173,8 +199,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "(100.0, 1)\n", - "(0.0, 0)\n" + "['100', '1']\n", + "['0', '0']\n" ] } ], @@ -183,34 +209,35 @@ "\n", "srs830.set_channel_offset_expand(1, 100, 10)\n", "print(srs830.get_channel_offset_expand(1))\n", + "\n", "srs830.set_channel_offset_expand(1, 0, 1)\n", "print(srs830.get_channel_offset_expand(1))\n" ] }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.00384523 6.10356e-05\n", - "-0.0144044 -0.000183106\n", - "0.0 0.0\n" + "-8.9169e-05 -9.53682e-07\n", + "0.000385286 -7.15259e-07\n", + "-4.76841e-07 -2.3842e-07\n" ] } ], "source": [ "# Test auto offset\n", - "# First number should be some value, second number should be close to 0\n", + "# v0 should be some value, v_offset should be close to 0\n", "srs830.set_channel_offset_expand(1, 0, 1)\n", "v0 = srs830.read('x')\n", "srs830.auto_offset('x')\n", "v_offset = srs830.read('x')\n", "print(v0, v_offset)\n", - "srs830.set_channel_offset_expand(1, 0, 1)\n", + "assert abs(v_offset - 0) < 0.0001\n", "\n", "\n", "srs830.set_channel_offset_expand(2, 0, 1)\n", @@ -218,31 +245,36 @@ "srs830.auto_offset('y')\n", "v_offset = srs830.read('y')\n", "print(v0, v_offset)\n", - "srs830.set_channel_offset_expand(2, 0, 1)\n", + "assert abs(v_offset - 0) < 0.0001\n", "\n", "srs830.set_channel_offset_expand(1, 0, 1)\n", "v0 = srs830.read('r')\n", "srs830.auto_offset('r')\n", "v_offset = srs830.read('r')\n", "print(v0, v_offset)\n", - "srs830.set_display(1, 'r', 'none')\n", + "assert abs(v_offset - 0) < 0.0001\n", + "\n", + "\n", "srs830.set_channel_offset_expand(1, 0, 1)\n", - "srs830.set_display(1, 'x', 'none')\n" + "srs830.set_display(1, 'x', 'none')\n", + "\n", + "srs830.set_channel_offset_expand(2, 0, 1)\n", + "srs830.set_display(2, 'y', 'none')\n" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.00366667\n", - "0.00266667\n", - "0.00433333\n", - "0.00266667\n" + "0.002\n", + "0.00233333\n", + "0.00166667\n", + "0.00133333\n" ] } ], @@ -250,22 +282,24 @@ "# Test read aux inputs\n", "# shoudl show some random small value\n", "for i in range(1, 5):\n", - " print(srs830.read_aux_input(i))" + " aux_input = srs830.read_aux_input(i)\n", + " print(aux_input)\n", + " assert abs(aux_input - 0) < 0.1" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.00384523\n", - "-0.0144044\n", - "0.0\n", - "-74.97\n" + "-8.86922e-05\n", + "0.000385048\n", + "-2.3842e-07\n", + "5899.178441489818\n" ] } ], @@ -280,15 +314,15 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.00384523\n", - "-0.0144044\n" + "-8.86922e-05\n", + "0.000385286\n" ] } ], @@ -301,24 +335,23 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[0.00384523, -0.0144044]\n", - "[0.00384523, 0.0]\n", - "[0.00384523, -74.97]\n", - "[0.00384523, 0.000666667]\n", - "[0.00384523, 0.004]\n", - "[0.00384523, -0.00633333]\n", - "[0.00384523, 0.000666667]\n", - "[0.00384523, 1000.0]\n", - "[0.00384523, 0.00384523]\n", - "[0.00384523, -0.0144044]\n", - "Couldnt snap 7 entries\n" + "[-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" ] } ], @@ -331,48 +364,44 @@ "\n", "srs830.snap('x', 'y', 'r', 'theta', 'frequency', 'display1')\n", "\n", - "try:\n", + "\n", + "with pytest.raises(AssertionError):\n", " srs830.snap('x', 'y', 'r', 'theta', 'frequency', 'display1', 'display2')\n", - "except:\n", - " print('Couldnt snap 7 entries')" + " # snap only accepts 6 entries" ] }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 11, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "'Stanford_Research_Systems,SR830,s/n86813,ver1.07 '" - ] - }, - "execution_count": 67, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Stanford_Research_Systems,SR830,s/n86813,ver1.07 \n" + ] } ], "source": [ "# test get ID\n", "\n", - "srs830.get_identificaiton_string()" + "id = srs830.get_identificaiton_string()\n", + "assert 'Stanford_Research_Systems,SR830' in id\n", + "print(id)" ] }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 12, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "5" - ] - }, - "execution_count": 95, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "159\n" + ] } ], "source": [ @@ -380,65 +409,20 @@ "\n", "srs830.buffer_points\n", "srs830.wait_for_trigger()\n", - "srs830.set_buffer_mode(0.25)\n", + "srs830.set_buffer_mode(8)\n", "srs830.wait_for_trigger()\n", "# srs830.start()\n", "srs830.trigger()\n", "sleep(20)\n", - "srs830.buffer_points\n" - ] - }, - { - "cell_type": "code", - "execution_count": 91, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0\\n'" - ] - }, - "execution_count": 91, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "srs830.query('SRAT?')" - ] - }, - { - "cell_type": "code", - "execution_count": 92, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.0625" - ] - }, - "execution_count": 92, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "srs830.sample_rate" + "srs830.buffer_points\n", + "srs830.pause()\n", + "print(srs830.buffer_points)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "base", + "display_name": "rsbrostenv", "language": "python", "name": "python3" }, @@ -452,7 +436,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.4" } }, "nbformat": 4, diff --git a/pyscan/drivers/stanford830.py b/pyscan/drivers/stanford830.py index d30775d9..cde1a890 100644 --- a/pyscan/drivers/stanford830.py +++ b/pyscan/drivers/stanford830.py @@ -16,27 +16,35 @@ class Stanford830(InstrumentDriver): Attributes ---------- (Properties) + id : read-only + Gets the id string of the device. phase : float - Range: [-180, 180] - reference_source : int - Indexed_values: ['external', 'internal']. Returns float. + Gets/sets the phase of the device. Range: [-180, 180] + reference_source : str + Get/sets the reference source. Indexed_values: ['external', 'internal']. frequency : float - Range: [0.001, 102000] + Gets/sets/ the frequency. Range: [0.001, 102000] reference_slope : int - Indexed_values: ['sine zero', 'ttl rising', 'ttl falling']. Returns float. + Gets/sets the slope of the frequency refeference. + Indexed_values: ['sine zero', 'ttl rising', 'ttl falling']. harmonic : int - Range: [1, 19999] + Gets/sets the demodulation harmonic. Range: [1, 19999] amplitude : float - Range: [0.004, 5.0] - input_configuration : int - Indexed_values: ['A', 'A-B', 'Ie6', 'Ie8']. Returns int. - input_ground : int + Gets/sets the output amplitude. Range: [0.004, 5.0] + input_configuration : str + Gets/sets the analog input mode. + Indexed_values: ['A', 'A-B', 'Ie6', 'Ie8']. + input_ground : str + Get/sets the input ground. Indexed_values: ['AC', 'DC'] - input_coupling : int + input_coupling : str + Gets/sets the input coupling. Indexed_values: ['AC', 'DC'] - input_line_filter : int + input_line_filter : str + Gets/sets the line filer of the input. Indexed_values: ['none', 'line', '2xline', 'both'] - sensitivity : int + sensitivity : float + Gets/sets the sensitivy (max input). Indexed_values: [2e-9, 5e-9, 10e-9, 20e-9, 50e-5, 100e-9, 200e-9, 500e-9, 1e-6, 2e-6, 5e-6, @@ -46,32 +54,81 @@ class Stanford830(InstrumentDriver): 10e-3, 20e-3, 50e-3, 100e-3, 200e-3, 500e-3, 1] - reserve_mode : int + reserve_mode : str + Gets/sets the dynamic reserver Indexed_values: ['high', 'normal', 'low'] - time_constant : int + time_constant : float + Gets/sets the time constant in seconds Indexed_values: [10e-6, 30e-6, 100e-6, 300e-6, 1e-3, 3e-3, 10e-3, 30e-3, 100e-3, 300e-3, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000] filter_slope : int + Gets/sets the slope of the filter in dB/oct Indexed_values: [6, 12, 18, 24] - synchronous_filter : int + synchronous_filter : str + Gets/sets the synchronous filter state Indexed_values: ['off', 'on'] - sample_rate : int + display1_output_source : str + Gets/sets the displayed source on display 1 + Indexed values ['x', 'display'] + display2_output_source : str + Gets/sets the displayed source on display 2 + Indexed values['y', 'display'] + auxiliary_voltage1 : float + Gets/sets the channel 1 auxiliary output voltage + range - [-10.5, 10.500] + auxiliary_voltage2 : float + Gets/sets the channel 2 auxiliary output voltage + range - [-10.5, 10.500] + auxiliary_voltage3 : float + Gets/sets the channel 3 auxiliary output voltage + range - [-10.5, 10.500] + auxiliary_voltage4 : float + Gets/sets the channel 4 auxiliary output voltage + range - [-10.5, 10.500] + sample_rate : float + Get/sets the sample rate in Hz Indexed_values: [0.0625, .125, .250, .5, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 'trigger'] - end_buffer_mode : int + end_buffer_mode : str + Gets/sets the end mode for th data buffer Indexed_values: ['one shot', 'loop'] trigger_mode : int + Gets/sets the trigger mode state Indexed_values': ['off', 'on'] + buffer_points : read-only + Gets the number of points stored in the buffer. Methods ------- + get_display(display_number) + set_display(display_number, source, ratio) + get_channel_offset_expand(channel) + set_channel_offset_expand(channel, offset, expand) + read_aux_input(index) + auto_gain() + auto_reserve() + auto_phase() + auto_offset() + trigger() + start() + pause() + reset() + read(source) + read_display(display) + snap(*args) + read_ascii_buffer() + read_binary_buffer() + reset_to_default_settings() + clear_status_bytes() + set_buffer_mode(sample_rate) + wait_for_trigger() snap_xy() - Returns array of current read x and read y values. + ''' def __init__(self, instrument): @@ -79,9 +136,13 @@ def __init__(self, instrument): super().__init__(instrument) self.debug = False - self._version = "1.0.0" + self._version = "1.0.3" - self.black_list_for_testing = ['_input_configuration', "_time_constant", "_amplitude", "_power_on_status_clear"] + self.black_list_for_testing = [ + '_input_configuration', + "_time_constant", + "_amplitude", + "_sensitivity"] self.initialize_properties() self.update_properties() @@ -90,6 +151,12 @@ def initialize_properties(self): # Reference and Phase properties + self.add_device_property({ + 'name': 'id', + 'query_string': '*IDN?', + 'read_only': True, + 'return_type': str}) + self.add_device_property({ 'name': 'phase', 'write_string': 'PHAS {}', @@ -230,31 +297,31 @@ def initialize_properties(self): 'indexed_values': ['y', 'display'], 'return_type': int}) - # Auxillary Input/Ouput Properties + # auxiliary Input/Ouput Properties self.add_device_property({ - 'name': 'auxillary_voltage1', + 'name': 'auxiliary_voltage1', 'write_string': 'AUXV 1, {}', 'query_string': 'AUXV? 1', 'range': [-10.5, 10.500], 'return_type': float}) self.add_device_property({ - 'name': 'auxillary_voltage2', + 'name': 'auxiliary_voltage2', 'write_string': 'AUXV 2, {}', 'query_string': 'AUXV? 2', 'range': [-10.5, 10.500], 'return_type': float}) self.add_device_property({ - 'name': 'auxillary_voltage3', + 'name': 'auxiliary_voltage3', 'write_string': 'AUXV 3, {}', 'query_string': 'AUXV? 3', 'range': [-10.5, 10.500], 'return_type': float}) self.add_device_property({ - 'name': 'auxillary_voltage4', + 'name': 'auxiliary_voltage4', 'write_string': 'AUXV 4, {}', 'query_string': 'AUXV? 4', 'range': [-10.5, 10.500], @@ -287,27 +354,10 @@ def initialize_properties(self): 'dict_values': {'off': 0, 'on': 1, '0': 0, '1': 1, 0: 0, 1: 1}, 'return_type': int}) - # Interface Properties - - self.add_device_property({ - 'name': 'local_remote_control', - 'write_string': 'LOCL {}', - 'query_string': 'LOCL?', - 'indexed_values': ['local', 'remote', 'local lockout'], - 'return_type': int}) - self.add_device_property({ - 'name': 'gpib_overrided_state', - 'write_string': 'OVRM {}', - 'query_string': 'OVRM?', - 'dict_values': {'off': 0, 'on': 1, '0': 0, '1': 1, 0: 0, 1: 1}, - 'return_type': int}) - - self.add_device_property({ - 'name': 'power_on_status_clear', - 'write_string': '*PSC {}', - 'query_string': '*PSC?', - 'dict_values': {'off': 0, 'on': 1, '0': 0, '1': 1, 0: 0, 1: 1}, + 'name': 'buffer_points', + 'query_string': 'SPTS?', + 'read_only': True, 'return_type': int}) # Display and Output Methods @@ -396,7 +446,10 @@ def get_channel_offset_expand(self, channel): response = self.query('OEXP? {}'.format(channel)).strip('\n') response = response.split(',') - return self._channel1_offset, self._channel1_expand + setattr(self, '_channel{}_offset', response[0]) + setattr(self, '_channel{}_expand', response[1]) + + return response def set_channel_offset_expand(self, channel, offset, expand): ''' @@ -425,20 +478,20 @@ def set_channel_offset_expand(self, channel, offset, expand): self.write('OEXP {}, {}, {}'.format(channel, offset, index)) - # Auxillary Input/Ouptut Methods + # auxiliary Input/Ouptut Methods def read_aux_input(self, index): ''' - Reads the voltage input to an auxillary input channel + Reads the voltage input to an auxiliary input channel Parameters ---------- index: int - Auxillary input channel number 1, 2, 3, or 4 + auxiliary input channel number 1, 2, 3, or 4 ''' indicies = [1, 2, 3, 4] - assert index in indicies, 'Auxillary input index must be 1, 2, 3, or 4' + assert index in indicies, 'auxiliary input index must be 1, 2, 3, or 4' return float(self.query('OAUX ? {}'.format(index))) @@ -584,13 +637,13 @@ def snap(self, *args): sources = ['x', 'y', 'r', 'theta', 'aux1', 'aux2', 'aux3', 'aux4', 'frequency', 'display1', 'display2'] - assert (len(args) >= 2) and (len(args <= 6)), 'Snap accepts 2 to 6 readable sources' + assert (len(args) >= 2) and (len(args) <= 6), 'Snap accepts 2 to 6 readable sources' for source in args: assert source in sources, ( 'Readable sources include x, y, r, theta, aux1, aux2, aux3, aux4, frequency, display1, display2') - indicies = [sources.index(arg) + 1 for arg in args] + indicies = [str(sources.index(arg) + 1) for arg in args] query_string = 'SNAP? ' + ', '.join(indicies) responses = self.query(query_string).strip('\n') @@ -679,273 +732,6 @@ def clear_status_bytes(self): ''' self.write('*CLS') - def get_standard_status_event_enable_register(self, bit=None): - ''' - Gets the standard status event enable register byte by single bit or all bits - - Parameters - --------- - bit: int (None) - Bit to query, if None, returns all bit values - - Returns - ------- - int - ''' - - if bit is None: - return self.query('ESE?') - else: - assert (bit >= 0) and (bit <= 7), 'Bit index must be 0-7' - return self.query('ESE? {}'.format(bit)) - - def set_standard_status_event_enable_register(self, value, bit=None): - ''' - Sets the standard event status enable register by single bit or all bits - - Parameters - ---------- - value: int - If bit==None, value must be between 0 and 255 and - sets all bits - bit: int (None) - Value must be between 0 and 7, and bit must be 0 or 1 - Sets the value of a single bit - ''' - if bit is None: - assert ( - value >= 0) and ( - value <= 255), 'Standard status event byte must be between 0 and 255 when no bit is supplied' - self.write('ESE {}'.format(value)) - else: - assert value in [0, 1], 'Standard status event value must be 0 or 1 when a bit index is supplied' - assert (bit >= 0) and (bit <= 7), 'Standard status event bit must be between 0 and 7' - self.write('ESE {}, {}'.format(bit, value)) - - def get_standard_event_status_byte(self, bit=None): - ''' - Queries the standard event status byte - - Parameters - ---------- - bit: int (None) - If inlcuded, queries only the value at bit - otherwise, queries all values - - Returns - -------- - int - - ''' - if bit is None: - return self.query('ESR?') - else: - assert (bit >= 0) and (bit <= 7), 'Bit index must be 0-7' - return self.query('ESR? {}'.format(bit)) - - def get_serial_poll_enable_register(self, bit=None): - ''' - Queries the standard event status byte - - Parameters - ---------- - bit: int (None) - If inlcuded, queries only the value at bit - otherwise, queries all values - - Returns - -------- - int - - ''' - - if bit is None: - return self.query('SRE?') - else: - assert (bit >= 0) and (bit <= 7), 'Bit index must be 0-7' - return self.query('SRE? {}'.format(bit)) - - def set_serial_poll_enable_register(self, value, bit=None): - ''' - Sets the serial poll enable register by single bit or all bits - - Parameters - ---------- - value: int - If bit==None, value must be between 0 and 255 and - sets all bits - bit: int (None) - Value must be between 0 and 7, and bit must be 0 or 1 - Sets the value of a single bit - ''' - - if bit is None: - assert ( - value >= 0) and ( - value <= 255), 'Serial event poll register value be between 0 and 255 when no bit is supplied' - self.write('SRE {}'.format(value)) - else: - assert value in [0, 1], 'Serial event poll register value must be 0 or 1 when a bit index is supplied' - assert (bit >= 0) and (bit <= 7), 'Serial even poll register bit must be between 0 and 7' - self.write('SRE {}, {}'.format(bit, value)) - - def get_status_poll_byte(self, bit=None): - ''' - Queries the status poll byte - - Parameters - ---------- - bit: int (None) - If inlcuded, queries only the value at bit - otherwise, queries all values - - Returns - -------- - int - - ''' - - if bit is None: - return self.query('STB?') - else: - assert (bit >= 0) and (bit <= 7), 'Bit index must be 0-7' - return self.query('STB? {}'.format(bit)) - - def get_error_status_enable_register(self, bit=None): - ''' - Queries the error status enable register settings - - Parameters - ---------- - bit: int (None) - If inlcuded, queries only the value at bit - otherwise, queries all values - - Returns - -------- - int - - ''' - - if bit is None: - return self.query('ERRE?') - else: - assert (bit >= 0) and (bit <= 7), 'Bit index must be 0-7' - return self.query('ERRE? {}'.format(bit)) - - def set_error_status_enable_register(self, value, bit=None): - ''' - Sets the error status enable register by single bit or all bits - - Parameters - ---------- - value: int - If bit==None, value must be between 0 and 255 and - sets all bits - bit: int (None) - Value must be between 0 and 7, and bit must be 0 or 1 - Sets the value of a single bit - ''' - - if bit is None: - assert ( - value >= 0) and ( - value <= 255), 'Error status enable register must be between 0 and 255 when no bit is supplied' - self.write('ERRE {}'.format(value)) - else: - assert value in [0, 1], 'Error status enable register must be 0 or 1 when a bit index is supplied' - assert (bit >= 0) and (bit <= 7), 'Error status enable register must be between 0 and 7' - self.write('ERRE {}, {}'.format(bit, value)) - - def get_error_status_byte(self, bit=None): - ''' - Queries the error status byte - - Parameters - ---------- - bit: int (None) - If inlcuded, queries only the value at bit - otherwise, queries all values - - Returns - -------- - int - - ''' - if bit is None: - return self.query('ERRS?') - else: - assert (bit >= 0) and (bit <= 7), 'Bit index must be 0-7' - return self.query('ERRS? {}'.format(bit)) - - def get_lia_status_enable_register(self, bit=None): - ''' - Queries the lockin amplifier enable register settings - - Parameters - ---------- - bit: int (None) - If inlcuded, queries only the value at bit - otherwise, queries all values - - Returns - -------- - int - - ''' - - if bit is None: - return self.query('LIAE?') - else: - assert (bit >= 0) and (bit <= 7), 'Bit index must be 0-7' - return self.query('LIAE? {}'.format(bit)) - - def set_lia_status_enable_register(self, value, bit=None): - ''' - Sets the lockin status enable register by single bit or all bits - - Parameters - ---------- - value: int - If bit==None, value must be between 0 and 255 and - sets all bits - bit: int (None) - Value must be between 0 and 7, and bit must be 0 or 1 - Sets the value of a single bit - ''' - - if bit is None: - assert ( - value >= 0) and ( - value <= 255), 'LIA status enable register must be between 0 and 255 when no bit is supplied' - self.write('LIAE {}'.format(value)) - else: - assert value in [0, 1], 'LIA status enable register must be 0 or 1 when a bit index is supplied' - assert (bit >= 0) and (bit <= 7), 'LIA status enable register must be between 0 and 7' - self.write('LIAE {}, {}'.format(bit, value)) - - def get_lia_status_byte(self, bit=None): - ''' - Queries the lockin amplifier byte - - Parameters - ---------- - bit: int (None) - If inlcuded, queries only the value at bit - otherwise, queries all values - - Returns - -------- - int - - ''' - - if bit is None: - return self.query('LIAS?') - else: - assert (bit >= 0) and (bit <= 7), 'Bit index must be 0-7' - return self.query('LIAS? {}'.format(bit)) - # Custom Multi-Settings Methods def set_buffer_mode(self, sample_rate): diff --git a/pyscan/drivers/testing/auto_test_driver.py b/pyscan/drivers/testing/auto_test_driver.py index 103ccb18..dcc75bbe 100644 --- a/pyscan/drivers/testing/auto_test_driver.py +++ b/pyscan/drivers/testing/auto_test_driver.py @@ -7,6 +7,8 @@ from pyscan.general.get_pyscan_version import get_pyscan_version import os from datetime import datetime +import re +import pprint ''' WARNING! @@ -70,7 +72,7 @@ def validate_blacklist(test_instrument): def save_initial_state(device): saved_settings = [] - print(device.__dict__.keys()) + # print(device.__dict__.keys()) for attribute_name in device.__dict__.keys(): if "_settings" in attribute_name: '''try: @@ -95,14 +97,15 @@ def restore_initial_state(device, saved_settings): val = setting[1] if _name in device.black_list_for_testing: - err_msg = ("WARNING! BLACKLISTED PROPERTY WAS SOMEHOW CHANGED. Was {}, now {}\n".format(val, device[setter]) - + "PROCEED WITH CAUTION!") + err_msg = f"WARNING! BLACKLISTED PROPERTY WAS SOMEHOW CHANGED. Was {val}, now {device[setter]}\n" assert val == device[setter], err_msg restored_settings.append((setter, device[setter])) continue - - if 'ranges' in device["_{}_settings".format(setter)].keys(): + elif 'read_only' in device["_{}_settings".format(setter)].keys(): + continue + elif 'ranges' in device["_{}_settings".format(setter)].keys(): val = device["_{}_settings".format(setter)]['return_type'](val) + try: device[setter] = val except Exception: @@ -132,25 +135,28 @@ def reset_device_properties(device): if var_name in device.black_list_for_testing: blacklisted.append((var_name, device[var_name])) continue - if ('values' in keys) and ('indexed_' not in keys) and ('dict_' not in keys): + elif 'read_only' in keys: + continue + elif ('values' in keys) and ('indexed_' not in keys) and ('dict_' not in keys): device[var_name] = device[name]['values'][0] - elif 'range' in device[name].keys(): + elif 'range' in keys: device[var_name] = device[name]['range'][0] - elif 'ranges' in device[name].keys(): + elif 'ranges' in keys: # write to reset val later pass - elif 'indexed_values' in device[name].keys(): + elif 'indexed_values' in keys: device[var_name] = device[name]['indexed_values'][0] - elif 'dict_values' in device[name].keys(): + elif 'dict_values' in keys: for key in device[name]['dict_values'].keys(): device[var_name] = key break else: assert False, "no valid type present in setting: {}. Must be one of {}.".format( - name, ['values', 'range', 'ranges', 'indexed_values', 'dict_values']) + name, ['values', 'range', 'indexed_values', 'dict_values', 'read_only']) if len(blacklisted) > 0: - print("These blacklisted settings and their corresponding values were not reset: ", blacklisted) + print("Blacklisted settings that will not be tested or changed are: ") + pprint.pprint(blacklisted) # check that the initialized state has the expected attributes @@ -169,8 +175,8 @@ def check_attribute_values(device, attributes, ev): # designed for testing read only properties of any type def check_read_only_property(device, key): name = device[key]['name'] - settings = device[name + '_settings'] - return_type = device + settings = device['_' + name + '_settings'] + return_type = device[key]['return_type'] # I'm not sure that this will work. It might be that only the underscore property can be used to access # the property value. @@ -317,7 +323,7 @@ def check_dict_property(device, key): # implements above checks for all attributes by type -def check_properties(test_instrument): +def check_properties(test_instrument, verbose=True): # This is a critical step to ensuring safety when testing drivers with actual instruments validate_blacklist(test_instrument) @@ -326,13 +332,19 @@ def check_properties(test_instrument): instrument_name = test_instrument.__class__.__name__ # values_idx, range_idx, idx_vals_idx, dict_vals_idx = [], [], [], [] saved_settings = save_initial_state(test_instrument) - print("Initial state for the {} was: {}".format(instrument_name, saved_settings)) + if verbose: + print("Initial state for the {} was: ".format(instrument_name)) + pprint.pprint(saved_settings) + print("\n") reset_device_properties(test_instrument) reset_settings = save_initial_state(test_instrument) - print("Reset state for the {} was: {}".format(instrument_name, reset_settings)) + if verbose: + print("Reset state for the {} was: ".format(instrument_name)) + pprint.pprint(reset_settings) + print("\n") - print("Beginning tests for: ", test_instrument.__class__.__name__) + print("\nBeginning tests for: ", test_instrument.__class__.__name__, " version ", test_instrument._version) settings = [] total_settings = 0 @@ -373,13 +385,13 @@ def check_properties(test_instrument): else: assert False, "no valid type present in setting: {}. Must be one of {}.".format( name, ['values', 'range', 'indexed_values', 'dict_values']) - - restored_settings = restore_initial_state(test_instrument, saved_settings) + print(name) + restored_settings = restore_initial_state(test_instrument, saved_settings) diff = set(restored_settings) ^ set(saved_settings) mid_string = 'properties found and tested out of' - print("{} range {} {} total settings found.".format(range_counter, mid_string, total_settings)) + print("\n{} range {} {} total settings found.".format(range_counter, mid_string, total_settings)) print("{} values {} {} total settings found.".format(values_counter, mid_string, total_settings)) print("{} indexed values {} {} total settings found.".format(idx_vals_counter, mid_string, total_settings)) print("{} dict values {} {} total settings found.".format(dict_vals_counter, mid_string, total_settings)) @@ -398,15 +410,20 @@ def check_properties(test_instrument): if isinstance(test_instrument, TestInstrumentDriver): assert values_counter == range_counter == idx_vals_counter == dict_vals_counter == 1 print("Drivers test unit seems to be working as expected.") - print("Settings restored to: {}".format(restored_settings)) + + if verbose: + print("\nSettings restored to: ") + pprint.pprint(restored_settings) + if (len(diff) > 0): - print("Restored settings are different for the following: ", diff) + print("\nRestored settings are different for the following: ", diff) + print("\n") assert hasattr(test_instrument, '_version'), "The instrument had no attribute _version" - print("The previous instrument version was: ", test_instrument._version) + # print("The (previous) instrument version was: ", test_instrument._version) -def write_log(device, exception): +def write_log(device, exception=None, save_multiple_lines=False): try: driver_file_name = str(type(device)).split("'")[1].split(".")[-2] except Exception: @@ -434,12 +451,14 @@ def write_log(device, exception): path = os.path.join(directory, driver_file_name) if driver_file_name in driver_test_logs_file_names: - with open(path, 'r') as test_log: - existing_log = test_log.read() + 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') - test_log.write(existing_log) + if save_multiple_lines: + test_log.write(existing_log) else: print("No test log file detected for this driver. Creating a new one.") @@ -472,7 +491,7 @@ def check_attribute_doc_strings(test_instrument): try: doc_string = test_instrument.get_property_docstring(name) except Exception: - assert False, "Doc string could not be found for {}".format(name) + assert False, "Doc string could not be found or not properly formatted for {}".format(name) splits = doc_string.split('\n') assert name in splits[0], "attribute name not found on first line of doc_string for {}".format(name) @@ -480,6 +499,51 @@ def check_attribute_doc_strings(test_instrument): assert [len(splits[1]) > 3], "doc string's second line is not long enough for {}".format(name) +def extract_attributes_from_docstring(doc_string): + # Assuming attributes are listed with 4 leading spaces and followed by a colon + attributes = [] + in_attributes_section = False + + for line in doc_string.split('\n'): + # Check if we've reached the Methods section + if 'Methods' in line: + break # Stop processing if we've reached Methods + + # Check for the start of Attributes section + if 'Attributes' in line: + in_attributes_section = True + continue # Go to the next line to read attributes + + # If we are in the Attributes section, extract attributes + if in_attributes_section: + if line.strip() == '': + continue # Skip empty lines + # Match lines that start with 4 spaces and contain a word followed by a colon + match = re.match(r'^\s{4}(\w+)\s*:', line) + if match: + attributes.append(match.group(1)) + + return attributes + + +def extract_methods_from_docstring(doc_string): + # Assuming methods are listed under 'Methods' section + methods = [] + in_methods_section = False + + for line in doc_string.split('\n'): + if 'Methods' in line: + in_methods_section = True + elif in_methods_section: + if line.strip() == '': + break + match = re.match(r'\s*(\w+)\s*\(', line) + if match: + methods.append(match.group(1)) + + return methods + + # parses and checks the instruments doc string for proper formatting def check_doc_strings(test_instrument): print("Checking driver doc string.") @@ -501,32 +565,39 @@ def check_doc_strings(test_instrument): assert line.startswith(' ') or line == '', "Improper indentation of line {}".format(repr(line)) check_attribute_doc_strings(test_instrument) + + # Extract attributes and methods from the docstring + attributes = extract_attributes_from_docstring(doc_string) + methods = extract_methods_from_docstring(doc_string) + + # Check that each attribute and method in the docstring exists in the test_instrument + for attribute in attributes: + assert hasattr(test_instrument, attribute), f"Attribute '{attribute}' listed in docstring but not the driver." + + for method in methods: + assert hasattr(test_instrument, method), f"Method '{method}' listed in docstring but not the driver." + # write formatting test cases here. -def test_driver(device=TestInstrumentDriver(), skip_log=False, expected_attributes=None, expected_values=None): +def test_driver(device=TestInstrumentDriver(), skip_log=False, expected_attributes=None, expected_values=None, + verbose=True): if expected_attributes is not None: check_has_attributes(device, expected_attributes) if expected_values is not None: check_attribute_values(device, expected_attributes, expected_values) - try: - check_properties(device) - exception = None - except Exception as e: - exception = str(e) + check_properties(device, verbose) + print(f"\033[92m Property implementation tests passed, instrument: {device.__class__.__name__} looks ready to go. \033[0m") + + check_doc_strings(device) + print("\033[92m Docstring tests passed and looking good. \033[0m") # Note, based on this execution order - # the driver log can pass the driver for functional tests success before ensuring doc string is properly formatted + # the driver log can pass the driver for functional tests success + # before ensuring doc string is properly formatted if skip_log is False: - write_log(device, exception) - - if exception is not None: - assert False, exception - else: - print("\033[1;32mTests passed, instrument {} looks ready to go.\033[0m".format(device.__class__.__name__)) - - check_doc_strings(device) + write_log(device) - print("Tests passed, instrument {} should be ready to go.".format(device.__class__.__name__)) + print(f"\033[1;32m {device.__class__.__name__} test results logged. \033[0m") 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 83777243..39cd1aa2 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.0 tested on pyscan version v0.3.0 at 2024-05-24 13:50:29 \ No newline at end of file +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 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 639425e1..b579f033 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 v0.1.0 tested on pyscan version v0.3.0 at 2024-05-28 12:35:11 \ No newline at end of file +Passed with stanford830 version v1.0.3 tested on pyscan version v0.8.0 at 2024-10-03 08:29:18 diff --git a/test/measurement/test_experiment.py b/test/measurement/test_experiment.py index 8648fd81..3704e6df 100644 --- a/test/measurement/test_experiment.py +++ b/test/measurement/test_experiment.py @@ -8,6 +8,7 @@ import shutil import numpy as np import pytest +# import time import re import os