diff --git a/drivers_test_notebooks/OxfordIPS120_test_notebook.ipynb b/drivers_test_notebooks/OxfordIPS120_test_notebook.ipynb new file mode 100644 index 00000000..f04c9ab9 --- /dev/null +++ b/drivers_test_notebooks/OxfordIPS120_test_notebook.ipynb @@ -0,0 +1,1243 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "114e61cb-2ac0-4a53-bf0e-c7888e554e2d", + "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": [ + "import os\n", + "import sys\n", + "import time\n", + "import threading\n", + "from pathlib import Path\n", + "from time import sleep\n", + "import pyscan as ps\n", + "import ipywidgets as widgets\n", + "from IPython.display import display" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3932781-6336-48fe-9166-eb7cbf5006e9", + "metadata": {}, + "outputs": [], + "source": [ + "ips120 = ps.new_instrument(gpib_address=25)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f032e08-49b6-4ad7-8566-e442dd36519e", + "metadata": {}, + "outputs": [], + "source": [ + "ips120.read_termination = \"\\r\"\n", + "ips120.write_termination = \"\\r\"\n", + "print(ips120.read_stb())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86adfcb3-0009-4fb8-b9aa-a2ae19ea8fa5", + "metadata": {}, + "outputs": [], + "source": [ + "ips120.query('X')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d9c822d-3353-4087-a2d6-c840e510ead9", + "metadata": {}, + "outputs": [], + "source": [ + "ips120.read()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8d0c2aef-d8d9-4d6a-aaa4-22aeb849691f", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "instruments = ps.ItemAttribute()\n", + "instruments.ips120 = ps.new_instrument(gpib_address=25)\n", + "devices = ps.ItemAttribute()\n", + "devices.magnet = ps.OxfordIPS120(instruments.ips120,field_limit=8, field_rate_limit=0.2, field_to_current_ratio=0.2227)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a888bba8-a6aa-44cd-938c-28433e4a52a0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heater off\n", + "At rest\n", + "activity: To Zero\n", + "output_field = 0.0\n", + "field_set_point = 5.0\n", + "field_rate = 0.2\n" + ] + } + ], + "source": [ + "print(devices.magnet.print_state())" + ] + }, + { + "cell_type": "markdown", + "id": "d478caef-dd58-4011-98a5-c98e0689658c", + "metadata": {}, + "source": [ + "## Testing magnet driver" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ef5d656f-7e6c-4aec-95cc-8929cf983ac4", + "metadata": {}, + "outputs": [], + "source": [ + "# don't sweep anywhere while testing\n", + "devices.magnet.remote()\n", + "devices.magnet.hold()" + ] + }, + { + "cell_type": "markdown", + "id": "cd14c8ac-9c16-4f1b-8d53-04679a783e76", + "metadata": {}, + "source": [ + "### magnet limits" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "88bc36e2-5c61-47a8-93a8-9a1f151e9fd2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8\n", + "0.2\n", + "0.2227\n" + ] + } + ], + "source": [ + "print(devices.magnet._field_limit)\n", + "print(devices.magnet._field_rate_limit)\n", + "print(devices.magnet._field_to_current_ratio)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5f49bd77-26c9-4605-80a0-8fd1be41dd46", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "35.93\n", + "8.001611\n" + ] + } + ], + "source": [ + "print(devices.magnet.safe_current_limit_positive)\n", + "print(devices.magnet.safe_current_limit_positive*devices.magnet._field_to_current_ratio)" + ] + }, + { + "cell_type": "markdown", + "id": "c03e3fee-fada-4b0d-8918-cc0fc7311eac", + "metadata": {}, + "source": [ + "### properties" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c7a6bf3c-c19c-45e5-8dbd-baf701b954bf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.0\n" + ] + } + ], + "source": [ + "print(devices.magnet.field_set_point)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "dccb8369-e95c-4c9c-8dd7-8bfa5e76b923", + "metadata": {}, + "outputs": [], + "source": [ + "initial_value = devices.magnet.field_set_point\n", + "new_value = 0.111\n", + "devices.magnet.field_set_point = new_value\n", + "result = devices.magnet.field_set_point\n", + "assert result==new_value\n", + "devices.magnet.field_set_point = initial_value\n", + "result = devices.magnet.field_set_point\n", + "assert result==initial_value" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f3940807", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.2\n" + ] + } + ], + "source": [ + "print(devices.magnet.field_rate)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e5596e5f", + "metadata": {}, + "outputs": [], + "source": [ + "initial_value = devices.magnet.field_rate\n", + "new_value = 0.111\n", + "devices.magnet.field_rate = new_value\n", + "result = devices.magnet.field_rate\n", + "assert result==new_value\n", + "devices.magnet.field_rate = initial_value\n", + "result = devices.magnet.field_rate\n", + "assert result==initial_value" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "158473d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "22.456\n" + ] + } + ], + "source": [ + "print(devices.magnet.current_set_point)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "93df54cf", + "metadata": {}, + "outputs": [], + "source": [ + "initial_value = devices.magnet.current_set_point\n", + "new_value = 0.111\n", + "devices.magnet.current_set_point = new_value\n", + "result = devices.magnet.current_set_point\n", + "assert result==new_value\n", + "devices.magnet.current_set_point = initial_value\n", + "result = devices.magnet.current_set_point\n", + "assert result==initial_value" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "622aaf10", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9\n" + ] + } + ], + "source": [ + "print(devices.magnet.current_rate)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "ece5e1d5", + "metadata": {}, + "outputs": [], + "source": [ + "initial_value = devices.magnet.current_rate\n", + "new_value = 0.22 # current can only be set to two digits in non-extended mode\n", + "devices.magnet.current_rate = new_value\n", + "result = devices.magnet.current_rate\n", + "assert result==new_value\n", + "devices.magnet.current_rate = initial_value\n", + "result = devices.magnet.current_rate\n", + "assert result==initial_value" + ] + }, + { + "cell_type": "markdown", + "id": "c45141a5-c080-4df5-83d8-d19d82bbfe29", + "metadata": {}, + "source": [ + "### read-only properties" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "660c4640-ed68-44c1-af4a-5551a347e0e0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0\n" + ] + } + ], + "source": [ + "print(devices.magnet.output_current)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "45844641", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-0.02\n" + ] + } + ], + "source": [ + "print(devices.magnet.voltage)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "7178f259", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.09\n" + ] + } + ], + "source": [ + "print(devices.magnet.measured_current)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "4c4f458d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0\n" + ] + } + ], + "source": [ + "print(devices.magnet.output_field)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0492a897", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.5\n" + ] + } + ], + "source": [ + "print(devices.magnet.software_voltage_limit)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "313cd9bc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0\n" + ] + } + ], + "source": [ + "print(devices.magnet.persistent_current)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "46a98f50", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-0.078\n" + ] + } + ], + "source": [ + "print(devices.magnet.trip_field)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "9cbfcec5-d623-4bc7-ab2e-7e9729bd7eef", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0\n" + ] + } + ], + "source": [ + "print(devices.magnet.persistent_field)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "9e498bd2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-35.93\n" + ] + } + ], + "source": [ + "print(devices.magnet.safe_current_limit_negative)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "9270c945", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "35.93\n" + ] + } + ], + "source": [ + "print(devices.magnet.safe_current_limit_positive)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "67b5a56b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "18.3\n" + ] + } + ], + "source": [ + "print(devices.magnet.lead_resistance)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "6f67ea45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "51.7\n" + ] + } + ], + "source": [ + "print(devices.magnet.magnet_inductance)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "51951497", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "IPS120-10 Version 3.07 (c) OXFORD 1996\n" + ] + } + ], + "source": [ + "print(devices.magnet.firmware_version)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "cffbc249-92f7-4b6e-9b34-a190d058ed53", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "X00A0C3H0M10P03\n" + ] + } + ], + "source": [ + "print(devices.magnet.status_string)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "1d463b2e-5c90-4cac-b8d7-a1b6e2a5374c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "quench_status = False\n" + ] + } + ], + "source": [ + "print(f\"quench_status = {devices.magnet.quench_status}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "37244aed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "heater_status = False\n" + ] + } + ], + "source": [ + "print(f\"heater_status = {devices.magnet.heater_status}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "a267eb62", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sweeping_status = False\n" + ] + } + ], + "source": [ + "print(f\"sweeping_status = {devices.magnet.sweeping_status}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "d0f29612", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "persistent_status = False\n" + ] + } + ], + "source": [ + "print(f\"persistent_status = {devices.magnet.persistent_status}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "c328fa05", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "remote_status = True\n" + ] + } + ], + "source": [ + "print(f\"remote_status = {devices.magnet.remote_status}\")" + ] + }, + { + "cell_type": "markdown", + "id": "ad484b76-01c0-40e3-bace-fee46414dfa3", + "metadata": {}, + "source": [ + "### write-only properties" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "f3ca2872-7e6a-4770-a276-02f74a232ff9", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.remote_control = \"local_locked\"" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "2e522e60", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.remote_control = \"remote_locked\"" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "7720458b", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.remote_control = \"local_unlocked\"" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "fda8a8dc", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.remote_control = \"remote_unlocked\"" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "febaad6e-09a1-4435-9ddb-042d15568960", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.communications_protocol = \"normal\"" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "09698c16-7a8b-44fb-8380-b2a7d414bc03", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.22\n" + ] + } + ], + "source": [ + "devices.magnet.current_rate = 0.222\n", + "print(devices.magnet.current_rate)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "aa75d1b5", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.communications_protocol = \"extended\"" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "2caa82ab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.222\n" + ] + } + ], + "source": [ + "devices.magnet.current_rate = 0.222\n", + "print(devices.magnet.current_rate)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "7235f709-727f-4f25-b089-4396f7c4d85e", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.heater_control = 'off'" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "1312fb4f", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.heater_control = 'on'" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "8b4a0755", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.heater_control = 'force'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb302f8d-f3f9-443c-bba8-0cbd15918f10", + "metadata": {}, + "outputs": [], + "source": [ + "# only run if magnet is at zero\n", + "# devices.magnet.activity_control = \"clamp\"" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "e0aefdce", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.activity_control = \"hold\"" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "17af6062", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.activity_control = \"to_zero\"" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "5f06f175", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.activity_control = \"to_set_point\"" + ] + }, + { + "cell_type": "markdown", + "id": "044bc85a-98a2-4b3d-9677-9d6e100df74f", + "metadata": {}, + "source": [ + "### list all properties" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "aa92cb52-a30a-43cd-ae4a-7868cecc3e91", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "field_set_point: 4.99996\n", + "field_rate: 0.0495\n", + "current_set_point: 22.456\n", + "current_rate: 0.222\n", + "output_current: 0.0\n", + "voltage: -0.02\n", + "measured_current: 0.11\n", + "output_field: 0.0\n", + "software_voltage_limit: 2.5\n", + "persistent_current: 0.0\n", + "trip_field: -0.0776\n", + "persistent_field: 0.0\n", + "switch_heater_current: 20.0\n", + "safe_current_limit_negative: -35.93\n", + "safe_current_limit_positive: 35.93\n", + "lead_resistance: 18.3\n", + "magnet_inductance: 51.7\n", + "firmware_version: IPS120-10 Version 3.07 (c) OXFORD 1996\n", + "status_string: X00A2C3H1M10P03\n" + ] + } + ], + "source": [ + "for prop in devices.magnet.get_pyscan_properties():\n", + " if 'write_only' not in devices.magnet[f'_{prop}_settings']:\n", + " print(f'{prop}: {devices.magnet[prop]}')" + ] + }, + { + "cell_type": "markdown", + "id": "eaebdb30-c0f3-417a-942b-8da07126992e", + "metadata": {}, + "source": [ + "### methods" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "9c303dc9-a9d9-43bf-bbd8-9ec87380c857", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.local() # keyword: locked=False" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "a935a8bb-2f23-45bc-a598-11e61ffca0d2", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.remote() # keyword: locked=False" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "14712428-c581-478c-b572-b610c7bdbbe3", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "devices.magnet.heater('off')" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "372d5931-36ca-4c8a-8a15-c7d0baa01164", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.heater('on')" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "851993a8-6326-479c-b479-c03eef087c7e", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.to_set_point()" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "e3c46264-dcef-44e9-a36f-44e61a9abe8c", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.hold()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "f5d09c36-fbf5-4b5b-a88f-d44c13e27cd3", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.to_zero()" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "8d9547e5-422c-4d3b-bf9c-cb2478305bda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "system status (operation) : Normal (X1=0)\n", + "system status (voltage) : Normal (X2=0)\n", + "Activity ; To Zero (A=2)\n", + "LOC/REM status ; Remote & Unlocked (C=3)\n", + "Heater ; On (H=1)\n", + "Mode (rate) ; Tesla, Immediate, Fast (M1=1)\n", + "Mode (sweep) ; At Rest (M2=0)\n" + ] + } + ], + "source": [ + "status = devices.magnet.status()\n", + "print(status)" + ] + }, + { + "cell_type": "markdown", + "id": "69010774-8d57-47cf-84f1-2fc3ce10bba0", + "metadata": {}, + "source": [ + "### magnet field property - only test with magnet cold" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "3c81d80e-aa14-4b31-89ee-dea52a10b00e", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.hold()\n", + "devices.magnet.remote()\n", + "devices.magnet.heater('on')\n", + "original_rate = devices.magnet.field_rate\n", + "devices.magnet.field_rate = 0.1\n", + "value = devices.magnet.output_field\n", + "db = 0.01\n", + "new_value = value + db\n", + "if new_value > devices.magnet._field_limit:\n", + " new_value = value - db\n", + "devices.magnet.field = new_value\n", + "assert devices.magnet.output_field == new_value" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "f5e5be95-8cca-4161-a084-1a30bebf3e4f", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.field = value\n", + "assert devices.magnet.output_field == value" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "7bb77b13-3f67-4fb1-98db-4c75675dfa80", + "metadata": {}, + "outputs": [], + "source": [ + "devices.field_rate = original_rate" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "b0624387-61c4-455c-b717-6a8c08b6796b", + "metadata": {}, + "outputs": [], + "source": [ + "devices.magnet.heater(\"off\")" + ] + }, + { + "cell_type": "markdown", + "id": "36712e85-c835-4353-80e3-23558d278fa9", + "metadata": {}, + "source": [ + "## help" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "24c3e4a0-405e-4d81-a11f-29774fe1e62a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\u001b[1;31mType:\u001b[0m OxfordIPS120\n", + "\u001b[1;31mString form:\u001b[0m \n", + "\u001b[1;31mFile:\u001b[0m c:\\snl\\projects\\pyscan\\pyscan\\drivers\\oxfordips120.py\n", + "\u001b[1;31mDocstring:\u001b[0m \n", + "Class to control Oxford Instruments Intelligent Power Supply IPS120 Superconducting Magnet Power Supply\n", + "\n", + "Parameters\n", + "----------\n", + "instrument :\n", + " Visa string or an instantiated instrument (return value from\n", + " :func:`.new_instrument`)\n", + "\n", + "Attributes\n", + "----------\n", + "(Properties)\n", + "\n", + "field: float\n", + " Get/set target field sweep (blocking)\n", + "current_rate: float\n", + " Get/set rate for changing magnet current\n", + "current_set_point: float\n", + " Get/set set point for magnet current\n", + "field_set_point: float\n", + " Get/set set point for magnet field; range defined by attribute field_limit: [-8, 8]\n", + "field_rate: float\n", + " Get/set rate for changing magnet field; range defined by attribute field_rate_limit: [0, 0.2]\n", + "\n", + "(Read-only properties)\n", + "\n", + "output_current: read-only\n", + " Get current in magnet power supply\n", + "voltage: read-only\n", + " Get voltage across leads\n", + "measured_current: read-only\n", + " Get measured current in leads\n", + "output_field: read-only\n", + " Get field from current in magnet power supply (not actual field if persistent)\n", + "software_voltage_limit: read-only\n", + " Get max voltage\n", + "persistent_current: read-only\n", + " Get current in magnet where heater was turned off\n", + "trip_field: read-only\n", + " Get field where last magnet quench occurred\n", + "persistent_field: read-only\n", + " Get field in magnet where heater was turned off\n", + "switch_heater_current: read-only\n", + " Get current in switch heater\n", + "safe_current_limit_negative: read-only\n", + " Get max negative current\n", + "safe_current_limit_positive: read-only\n", + " Get max positive current\n", + "lead_resistance: read-only\n", + " Get resistance\n", + "magnet_inductance: read-only\n", + " Get inductance\n", + "firmware_version: read-only\n", + " Get power supply model\n", + "status_string: read-only\n", + "\n", + "(Write-only properties)\n", + "\n", + "remote_control: str\n", + " Set local/remote to \"local_locked\", \"remote_locked\", \"local_unlocked\", \"remote_unlocked\"\n", + "communications_protocol: str\n", + " Set rate and set point precision to \"normal\", \"extended\"\n", + "heater_control: str\n", + " Set heater to \"off\", \"on\", \"force\"\n", + "activity_control: str\n", + " Set sweep activity to \"hold\", \"to_set_point\", \"to_zero\", \"clamp\"\n", + "\n", + "Methods\n", + "-------\n", + "heater()\n", + " turn heater on/off and deal correctly with persistent mode\n", + "print_state()\n", + " summarize state of the magnet\n", + "print_status()\n", + " not implemented yet\n", + "print_properties()\n", + " not_implemented yet\n", + "hold()\n", + " activity_control with checks\n", + "to_zero()\n", + " activity_control with checks\n", + "to_set_point()\n", + " activity_control with checks\n", + "clamp()\n", + " activity_control with checks\n", + "\u001b[1;31mInit docstring:\u001b[0m\n", + "OxfordIPS120 initilization requires keyword arguments:\n", + " field_limit: maximum magnetic field (T)\n", + " field_rate_limit: maximum sweep rate (T/min)\n", + " field_to_current_ratio: constant to switch between field and current (T/A)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "devices.magnet?" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "cb1c7cf8-d943-4e2f-8a3b-6a948b297927", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\u001b[1;31mType:\u001b[0m property\n", + "\u001b[1;31mString form:\u001b[0m \n", + "\u001b[1;31mDocstring:\u001b[0m \n", + "No doc string found for output_field.\n", + "Please update the drivers doc string to include this attribute." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "devices.magnet.output_field?" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyscan/drivers/instrument_driver.py b/pyscan/drivers/instrument_driver.py index 95421f8e..5add29ce 100644 --- a/pyscan/drivers/instrument_driver.py +++ b/pyscan/drivers/instrument_driver.py @@ -141,6 +141,9 @@ def add_device_property(self, settings): set_function = self.set_indexed_values_property elif 'dict_values' in settings: set_function = self.set_dict_values_property + elif 'read_only' in settings: + # read_only does not require a set_function + pass else: assert False, "Key 'values', 'range', indexed_values', 'read_only', or 'dict_values' must be in settings." diff --git a/pyscan/drivers/oxfordips120.py b/pyscan/drivers/oxfordips120.py index 7ee5a46c..82bd3ef9 100644 --- a/pyscan/drivers/oxfordips120.py +++ b/pyscan/drivers/oxfordips120.py @@ -1,191 +1,732 @@ # -*- coding: utf-8 -*- +from datetime import datetime +from time import sleep, time + +import numpy as np + from .instrument_driver import InstrumentDriver class OxfordIPS120(InstrumentDriver): - '''Class to control Oxford Instruments Intelligent Power Supply IPS120 Superconducting Magnet Power Supply + """Class to control Oxford Instruments Intelligent Power Supply IPS120 Superconducting Magnet Power Supply Parameters ---------- instrument : - Visa string or an instantiated instrument (return value - from :func:`~pyscan.drivers.newinstrument.new_instrument`) - - ''' + Visa string or an instantiated instrument (return value from + :func:`.new_instrument`) - def __init__(self, instrument): - - super().__init__(instrument) - - self.instrument.read_termination = '\r' - self.instrument.write_termination = '\r' - self.set_remote_unlocked() + Attributes + ---------- + (Properties) + + field: float + Get/set target field sweep (blocking) + current_rate: float + Get/set rate for changing magnet current + current_set_point: float + Get/set set point for magnet current + field_set_point: float + Get/set set point for magnet field; range defined by attribute field_limit: [-8, 8] + field_rate: float + Get/set rate for changing magnet field; range defined by attribute field_rate_limit: [0, 0.2] + + (Read-only properties) + + output_current: read-only + Get current in magnet power supply + voltage: read-only + Get voltage across leads + measured_current: read-only + Get measured current in leads + output_field: read-only + Get field from current in magnet power supply (not actual field if persistent) + software_voltage_limit: read-only + Get max voltage + persistent_current: read-only + Get current in magnet where heater was turned off + trip_field: read-only + Get field where last magnet quench occurred + persistent_field: read-only + Get field in magnet where heater was turned off + switch_heater_current: read-only + Get current in switch heater + safe_current_limit_negative: read-only + Get max negative current + safe_current_limit_positive: read-only + Get max positive current + lead_resistance: read-only + Get resistance + magnet_inductance: read-only + Get inductance + firmware_version: read-only + Get power supply model + status_string: read-only + + (Write-only properties) + + remote_control: str + Set local/remote to "local_locked", "remote_locked", "local_unlocked", "remote_unlocked" + communications_protocol: str + Set rate and set point precision to "normal", "extended" + heater_control: str + Set heater to "off", "on", "force" + activity_control: str + Set sweep activity to "hold", "to_set_point", "to_zero", "clamp" + + Methods + ------- + heater() + turn heater on/off and deal correctly with persistent mode + print_state() + summarize state of the magnet + print_status() + not implemented yet + print_properties() + not_implemented yet + hold() + activity_control with checks + to_zero() + activity_control with checks + to_set_point() + activity_control with checks + clamp() + activity_control with checks + """ + + def __init__( + self, + instrument, + *, + field_limit, + field_rate_limit, + field_to_current_ratio, + debug=False, + ): + """ + OxfordIPS120 initilization requires keyword arguments: + field_limit: maximum magnetic field (T) + field_rate_limit: maximum sweep rate (T/min) + field_to_current_ratio: constant to switch between field and current (T/A) + """ + super().__init__(instrument, debug) - self.field_limit = 8 - self.field_rate_limit = 0.1 self._version = "0.1.0" - self.debug = False + self.instrument.read_termination = "\r" + self.instrument.write_termination = "\r" + self.remote_control = "remote_unlocked" + + # make sure the buffer is empty: + # read status byte by performing a serial poll + # bit 1: byte available + # bit 4: message available + # bit 6: requesting service (reset after read) + stb = self.instrument.read_stb() + if not stb: + stb = self.instrument.read_stb() + if stb & 16: + message = self.instrument.read() + print(f"Message in buffer: {message}") + + # magnet specific settings + self._field_limit = field_limit + self._field_rate_limit = field_rate_limit + self._field_to_current_ratio = field_to_current_ratio + + self.debug = debug self.initialize_properties() + self.update_properties() + + self.check_field_to_current_ratio() + + def query(self, string, timeout=1): + """ + Overload query for IPS120 + Read until status indicates full message is received. + """ + # message = self.query(string) + self.write(string) + stb = self.instrument.read_stb() + # wait for message + i = 0 + start_time = time() + while (time() - start_time) < timeout: + while not (stb & 16): + if self.debug: + if stb & 2: + print("byte available but not message") + stb = self.instrument.read_stb() + i += 1 + if self.debug: + # typically i=4, time = 0.01 + print(f"needed to wait {i}; {time() - start_time} seconds") + message = self.read() + return message + + raise IPS120Error("IPS120 query Timeout") + + def check_field_to_current_ratio(self): + """ + Compare safe_current_limit_positive to the field_limit + """ + + calculated_current_limit = self._field_limit / self._field_to_current_ratio + # IPS120 has 4 dig accuracy in ratio and 2 dig accuracy in safe_current limits + # we need to check the calculated current to tolerance of +- 0.01 A + tol = 0.01 + safe_current = self.safe_current_limit_positive + if (calculated_current_limit > safe_current - tol) and ( + calculated_current_limit < safe_current + tol + ): + + return + + assert ( + False + ), "field_limit and field_to_current_ratio ({}) not consistent with IPS120 safe_current ({})".format( + calculated_current_limit, safe_current + ) def initialize_properties(self): - self.target_field - self.field_sweep_rate - self.get_current - self.get_target_current - self.get_current_sweep_rate - self.get_field - + """ + The IPS120 does not have traditional get/set parameters. The control + parameters are set using command. Getting values is accomplished with + either "examine status", X or "read parameter" R {}. + """ + + # control commands + + self.add_device_property( + { + "name": "field_set_point", + "write_string": "$J{}", + "query_string": "R8", + "range": [-self._field_limit, self._field_limit], + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "field_rate", + "write_string": "$T{}", + "query_string": "R9", + "range": [-self._field_rate_limit, self._field_rate_limit], + "return_type": ips120_float, + } + ) + + # TODO: fix all rounding issues related to T->A and precision + self.add_device_property( + { + "name": "current_set_point", + "write_string": "$I{}", + "query_string": "R5", + "range": [ + np.round(-self._field_limit / self._field_to_current_ratio, 2), + np.round(self._field_limit / self._field_to_current_ratio, 2), + ], + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "current_rate", + "write_string": "$S{}", + "query_string": "R6", + "range": [ + np.round(-self._field_rate_limit / self._field_to_current_ratio, 2), + np.round(self._field_rate_limit / self._field_to_current_ratio, 2), + ], + "return_type": ips120_float, + } + ) + + # read-only properties + + self.add_device_property( + { + "name": "output_current", + "query_string": "R0", + "read_only": float, + "return_type": ips120_float, + } + ) + + self.add_device_property( + {"name": "voltage", "query_string": "R1", "return_type": ips120_float} + ) + + self.add_device_property( + { + "name": "measured_current", + "query_string": "R2", + "return_type": ips120_float, + } + ) + + self.add_device_property( + {"name": "output_field", "query_string": "R7", "return_type": ips120_float} + ) + + self.add_device_property( + { + "name": "software_voltage_limit", + "query_string": "R15", + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "persistent_current", + "query_string": "R16", + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "trip_field", # mA + "query_string": "R17", + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "persistent_field", + "query_string": "R18", + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "switch_heater_current", + "query_string": "R20", + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "safe_current_limit_negative", + "query_string": "R21", + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "safe_current_limit_positive", + "query_string": "R22", + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "lead_resistance", + "query_string": "R23", + "return_type": ips120_float, + } + ) + + self.add_device_property( + { + "name": "magnet_inductance", + "query_string": "R24", + "return_type": ips120_float, + } + ) + + self.add_device_property( + {"name": "firmware_version", "query_string": "V", "return_type": str} + ) + + self.add_device_property( + {"name": "status_string", "query_string": "X", "return_type": str} + ) + + # write-only properties + + self.add_device_property( + { + "name": "remote_control", + "write_string": "$C{}", + "dict_values": { + "local_locked": 0, + "remote_locked": 1, + "local_unlocked": 2, + "remote_unlocked": 3, + }, + "return_type": int, + } + ) + + self.add_device_property( + { + "name": "communications_protocol", + "write_string": "Q{}", # no echo since this command clears output buffer + "dict_values": { + "normal": 0, + "extended": 4, + }, # do not allow switch to + "return_type": int, + } + ) + + self.add_device_property( + { + "name": "heater_control", + "write_string": "$H{}", + "indexed_values": ["off", "on", "force"], + "return_type": int, + } + ) + + self.add_device_property( + { + "name": "activity_control", + "write_string": "$A{}", + "dict_values": {"hold": 0, "to_set_point": 1, "to_zero": 2, "clamp": 4}, + "return_type": int, + } + ) + + def print_state(self): + """ + Print operating state of the IPS120 power supply + """ + status = self.status() + value = "" + if self.quench_status: + value += "QUENCHED" + if self.heater_status: + value += "Heater on" + else: + value += "Heater off" + value += "\n" + if self.sweeping_status: + value += "Field is changing" + else: + value += "At rest" + if self.persistent_status: + value += "\n" + value += "Magnet is persistent" + value += "\n" + value += f"persistent_field = {self.get_persistent_field}" + value += "\n" + value += f"activity: {status.A_value()}" + value += "\n" + value += f"output_field = {self.output_field}" + value += "\n" + value += f"field_set_point = {self.field_set_point}" + value += "\n" + value += f"field_rate = {self.field_rate}" + + return value + + # TODO: implement status message + def print_status(self): + """ + Print current status of the IPS120 power supply + """ + pass + + # TODO: combine hold(), to_set_point() and to_zero() into activity property def hold(self): - self.write('$A0') + if not self.remote_status: + raise IPS120Error( + "Control commands require the power supply to be in remote mode" + ) + self.activity_control = "hold" def to_set_point(self): - self.write('$A1') + if not self.remote_status: + raise IPS120Error( + "Control commands require the power supply to be in remote mode" + ) + self.activity_control = "to_set_point" def to_zero(self): - self.write('$A2') - - def clamp(self): - self.write('$A4') - - def set_local_locked(self): - self.write('$C0') - - def set_remote_lock(self): - self.write('$C1') - - def set_local_unlocked(self): - self.write('$C2') + if not self.remote_status: + raise IPS120Error( + "Control commands require the power supply to be in remote mode" + ) + self.activity_control = "to_zero" + + # TODO: should be a property + def heater(self, state): + """ + Set the state of the heater. Record the time when the heater was changed + """ + + if not self.remote_status: + raise IPS120Error( + "Control commands require the power supply to be in remote mode" + ) + + # TODO: deal with persistent magnet + if self.persistent_status: + raise IPS120Error( + "take magnet out of persistent mode before changing heater" + ) + + if state == "on": + if not self.heater_status: + self.time_heater_toggle = datetime.now() + self.heater_control = "on" + sleep(30) + elif state == "off": + if self.heater_status: + self.time_heater_toggle = datetime.now() + self.heater_control = "off" + sleep(30) + else: + print("State must be 'on' or 'off'") - def set_remote_unlocked(self): - self.write('$C3') + def remote(self, locked=False): + if locked: + self.remote_control = "remote_locked" + else: + self.remote_control = "remote_unlocked" - def get_current(self): - self._current = float(self.query_until_return('R2').replace('R', '')) - return self._current + def local(self, locked=False): + if locked: + self.remote_control = "local_locked" + else: + self.remote_control = "local_unlocked" - def get_target_current(self): - self._target_current = float(self.query_until_return('R5').replace('R', '')) - return self._target_current + @property + def field(self): + """ + TODO add settings dictionary with range to make this consistent with pyscan property + """ + self._field = self.output_field + return self._field - def get_current_sweep_rate(self): - self._current_sweep_rate = float(self.query_until_return('R6').replace('R', '')) - return self._current_sweep_rate + @field.setter + def field(self, new_value): + self.hold() + self.field_set_point = new_value + self.to_set_point() + # TODO: check if field is sweeping or at set point (if not either, then wait until it is sweeping) + sleep(0.1) + while self.sweeping_status: + sleep(0.1) - def get_field(self): - self._field = float(self.query_until_return('R7').replace('R', '')) - return self._field + def status(self): + status_string = self.query("X") + return Status(status_string) @property - def target_field(self): - self._target_field = float(self.query_until_return('R8').replace('R', '')) - return self._target_field - - @target_field.setter - def target_field(self, new_value): - if (new_value >= -self.field_limit) and (new_value < self.field_limit): - self.write('$J{}'.format(round(new_value, 4))) - self._target_field = round(new_value, 4) + def quench_status(self): + """ + True when the magnet quenched + """ + + status = self.status() + if status.X1 == 1: + self._quench_status = True else: - print('Target field out of range, must be 0 < set point < {}'.format(self.field_limit)) + self._quench_status = False + return self._quench_status @property - def field_sweep_rate(self): - self._field_sweep_rate = float(self.query_until_return('R9').replace('R', '')) - return self._field_sweep_rate - - @field_sweep_rate.setter - def field_sweep_rate(self, new_value): - if (new_value >= 0) and (new_value < self.field_rate_limit): - self.write('$T{}'.format(round(new_value, 3))) - self._field_sweep_rate = round(new_value, 3) + def heater_status(self): + """ + heater True for on and False for off + """ + + status = self.status() + if status.H == 0: + self._heater_status = False + elif status.H == 1: + self._heater_status = True else: - print('Sweep rate out of range, must be 0 < rate < {}'.format(self.field_rate_limit)) - - def set_heat_on(self): - return self.query('&H0') - - def set_heat_off(self): - return self.query('&H1') - - def set_target_field(self, new_value): - self.write('$J{}'.format(round(new_value, 4))) - - def get_status(self): - - status = self.query_until_return('X') + # magnet persistant or heater fault - may need to have a heater_status object in future + self._heater_status = False + return self._heater_status - X1 = {0: 'Normal', - 1: 'Quenched', - 2: 'Over Heated', - 4: 'Warming Up', - 8: 'Fault'} - status1 = X1[int(status[1])] - - # X2 = {0: 'Normal', - # 1: 'On Positive Voltage Limit', - # 2: 'On Negative Voltage Limit', - # 4: 'Outside Negative Current Limit', - # 8: 'Outside Positive Current Limit'} - - status2 = X1[int(status[2])] - - print('Status: {}, {}'.format(status1, status2)) - - A = {0: 'Hold', - 1: 'To Set Point', - 2: 'To Zero', - 4: 'Clamped'} - - activity = A[int(status[4])] - - print('Activity: {}'.format(activity)) - - C = {0: 'Local & Locked', - 1: 'Remote & Locked', - 2: 'Local & Unlocked', - 3: 'Remote & Unlocked', - 4: 'Auto-Run-Down', - 5: 'Auto-Run-Down', - 6: 'Auto-Run-Down', - 7: 'Auto-Run-Down'} - - loc = C[int(status[6])] - - print('Local/Remote Status: {}'.format(loc)) - - H = {0: 'Off, Magnet at Zero', - 1: 'On', - 2: 'Off, Maget at Field', - 5: 'Heater Fault', - 8: 'No Switch Fitted'} - - heater = H[int(status[8])] - - print('Heater: {}'.format(heater)) - - M1 = {0: 'Amps, Fast', - 1: 'Tesla, Fast', - 4: 'Amps, Slow', - 5: 'Tesla, Slow'} - - M2 = {0: 'at rest', - 1: 'Sweeping', - 2: 'Sweep Limit', - 3: 'Sweeping & Sweep Limiting'} - - mode1 = M1[int(status[10])] - mode2 = M2[int(status[11])] - - print('Mode: {}; {}'.format(mode1, mode2)) - - def query_until_return(self, query, n=10): + @property + def sweeping_status(self): + """ + True when the field is changing, False when the field is at rest + """ + + status = self.status() + if status.M2 == 0: + self._sweeping_status = False + else: + self._sweeping_status = True + return self._sweeping_status - message = self.query(query) + @property + def remote_status(self): + """ + True when magnet is in remote mode, False when it is in local mode. + """ + + status = self.status() + if (status.C == 1) or (status.C == 3): + self._remote_status = True + else: + self._remote_status = False - for i in range(n): + return self._remote_status - if len(message) != 0: - return message - else: - message = self.query('&') + @property + def persistent_status(self): + """ + True when the magnet is in persistent mode + """ + + status = self.status() + if status.H == 2: + self._persistent_status = True + else: + self._persistent_status = False + return self._persistent_status + + +class Status: + def __init__(self, status_string="X00A1C3H1M10P03"): + """ + Returns: + Tuple of dictionaries, X1, X2, A, C, H, M1 and M2 + Each dictionary has a + 'description': what status is reported + 'value': integer from status string + 'states': text explanation of the status from the integer value + """ + + # get index values + self.X1_name = "system status (operation)" + self.X1 = int(status_string[1]) + self.X2_name = "system status (voltage)" + self.X2 = int(status_string[2]) + self.A_name = "Activity" + self.A = int(status_string[4]) + self.C_name = "LOC/REM status" + self.C = int(status_string[6]) + self.H_name = "Heater" + self.H = int(status_string[8]) + self.M1_name = "Mode (rate)" + self.M1 = int(status_string[10]) + self.M2_name = "Mode (sweep)" + self.M2 = int(status_string[11]) + + def __repr__(self): + value = "\n" + value += f"{self.X1_name} : {self.X1_value()} (X1={self.X1})" + value += "\n" + value += f"{self.X2_name} : {self.X2_value()} (X2={self.X2})" + value += "\n" + value += f"{self.A_name} ; {self.A_value()} (A={self.A})" + value += "\n" + value += f"{self.C_name} ; {self.C_value()} (C={self.C})" + value += "\n" + value += f"{self.H_name} ; {self.H_value()} (H={self.H})" + value += "\n" + value += f"{self.M1_name} ; {self.M1_value()} (M1={self.M1})" + value += "\n" + value += f"{self.M2_name} ; {self.M2_value()} (M2={self.M2})" + + return value + + def X1_value(self): + indexed_values = { + 0: "Normal", + 1: "Quenched", + 2: "Over Heated", + 4: "Warming Up", + 8: "Fault", + } + return indexed_values[self.X1] + + def X2_value(self): + indexed_values = { + 0: "Normal", + 1: "On Positive Voltage Limit", + 2: "On Negative Voltage Limit", + 4: "Outside Negative Current Limit", + 8: "Outside Positive Current Limit", + } + return indexed_values[self.X2] + + def A_value(self): + indexed_values = {0: "Hold", 1: "To Set Point", 2: "To Zero", 4: "Clamped"} + return indexed_values[self.A] + + def C_value(self): + indexed_values = { + 0: "Local & Locked", + 1: "Remote & Locked", + 2: "Local & Unlocked", + 3: "Remote & Unlocked", + 4: "Auto-Run-Down", + 5: "Auto-Run-Down", + 6: "Auto-Run-Down", + 7: "Auto-Run-Down", + } + return indexed_values[self.C] + + def H_value(self): + indexed_values = { + 0: "Off Magnet at Zero", + 1: "On", + 2: "Off Magnet at Field", + 5: "Heater Fault", + 8: "No Switch Fitted", + } + return indexed_values[self.H] + + def M1_value(self): + indexed_values = { + 0: "Amps, Immediate, Fast", + 1: "Tesla, Immediate, Fast", + 2: "Amps, Sweep, Fast", + 3: "Tesla, Sweep, Fast", + 4: "Amps, Immediate, Slow", + 5: "Tesla, Immediate, Slow", + 6: "Amps, Sweep, Slow", + 7: "Tesla, Sweep, Slow", + } + return indexed_values[self.M1] + + def M2_value(self): + indexed_values = { + 0: "At Rest", + 1: "Sweeping", + 2: "Sweep Limiting", + 3: "Sweeping & Sweep Limiting", + 4: "Polarity Fault", + 5: "Sweeping & Polarity Fault", + 6: "Amps, Sweep, Slow", + 7: "Tesla, Sweep, Slow", + } + return indexed_values[self.M2] + + +class IPS120Error(Exception): + pass + + +def ips120_float(string): + """ + Helper function used with return_type to drop character and convert to a float. + """ + + return float(string[1:]) + + +def ips120_int(string): + """ + Helper function used with return_type to drop character and convert to a float. + """ + + return int(string[1:])