Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
fc50fd0
style: PEP8 and type hints
WilliamHPNielsen Nov 6, 2017
0e46f8a
[WIP] develop some mocking system
WilliamHPNielsen Nov 7, 2017
afd8641
Make all validators expose valid values
WilliamHPNielsen Nov 7, 2017
27d9ccb
add tests for XX.valid_values
WilliamHPNielsen Nov 7, 2017
73bc419
fix two mypy errors
WilliamHPNielsen Nov 7, 2017
344b1c3
fix a bug discovered by codacy
WilliamHPNielsen Nov 7, 2017
db98f05
Merge branch 'validators/expose_valid_vals' into driver/MercuryiPS
WilliamHPNielsen Nov 7, 2017
4197bea
[WIP] Make new mocking system for SCPI instruments
WilliamHPNielsen Nov 8, 2017
831f05d
make VISAInstrument take visalib as argument
WilliamHPNielsen Nov 8, 2017
c7397a6
Introduce PyVISA-sim as a means to mock + clean up
WilliamHPNielsen Nov 9, 2017
17e0f18
remove mocking.py from this branch
WilliamHPNielsen Nov 9, 2017
97fce59
remove base.py from mockers in this branch
WilliamHPNielsen Nov 9, 2017
8a4afc8
Apply PEP8 to test_ami430.py
WilliamHPNielsen Nov 9, 2017
9519ad6
make folder for driver tests, move the one we have
WilliamHPNielsen Nov 10, 2017
a2a4354
Make tests pass, deprecate old non-standard backend selection
WilliamHPNielsen Nov 10, 2017
81e4910
rename a folder
WilliamHPNielsen Nov 10, 2017
84d5af7
add logging to visa.py (ours, in instruments)
WilliamHPNielsen Nov 10, 2017
92a464b
add module level logger and make pyvisa-sim not sleep
WilliamHPNielsen Nov 10, 2017
a720b9b
add more properties to simulated instrument
WilliamHPNielsen Nov 10, 2017
ff79933
PEP8
WilliamHPNielsen Nov 10, 2017
b629b08
make our visa log the instrument name
WilliamHPNielsen Nov 13, 2017
fdfa0c7
expose three simulated AMI430's (one per axis)
WilliamHPNielsen Nov 13, 2017
65153b1
add test for AMI430 using PyVISA-sim backend
WilliamHPNielsen Nov 13, 2017
0e74dd1
[MILESTONE] Finish PyVISA-sim test of AMI430
WilliamHPNielsen Nov 13, 2017
ecc1812
add __init__.py to sims folder
WilliamHPNielsen Nov 14, 2017
97644b7
remove the old AMI430 test
WilliamHPNielsen Nov 14, 2017
54f57b8
add pyvisa-sim to test_requirements.txt
WilliamHPNielsen Nov 14, 2017
fe88e82
make pyvisa-sim instruments respond to writes and remove a setter
WilliamHPNielsen Nov 14, 2017
ec64668
only set write.termination for simulated instruments
WilliamHPNielsen Nov 14, 2017
407107d
remove debug line from driver
WilliamHPNielsen Nov 14, 2017
1f7eef3
remove all traces of old mocking system
WilliamHPNielsen Nov 14, 2017
3b9fabb
rename the test for ami430
WilliamHPNielsen Nov 14, 2017
ac97517
remove last remaining traces of testing
WilliamHPNielsen Nov 14, 2017
0552837
refactor: most elegantly refactor the IPToVisa __init__
WilliamHPNielsen Nov 14, 2017
c51999b
change a multi-line string to a comment
WilliamHPNielsen Nov 14, 2017
c1b1baa
add a test for the visa backend warning
WilliamHPNielsen Nov 14, 2017
b5660b6
Merge branch 'master' into feature/simulated_instruments
jenshnielsen Nov 30, 2017
1372748
use module level logging
WilliamHPNielsen Nov 30, 2017
25f2b1f
remove print call
WilliamHPNielsen Nov 30, 2017
e7addd9
update docstring for valid_values
WilliamHPNielsen Nov 30, 2017
37914d8
Merge branch 'feature/simulated_instruments' of github.com:WilliamHPN…
WilliamHPNielsen Nov 30, 2017
f86b556
update driver tutorial notebook
WilliamHPNielsen Nov 30, 2017
5780753
add an example notebook on simulated instruments
WilliamHPNielsen Dec 1, 2017
42d82a7
make device_clear a NOOP for simulated instruments
WilliamHPNielsen Dec 5, 2017
1f1c7a4
Merge branch 'master' into feature/simulated_instruments
WilliamHPNielsen Dec 5, 2017
2267bb7
Merge branch 'master' into feature/simulated_instruments
WilliamHPNielsen Dec 5, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 160 additions & 90 deletions docs/examples/Creating Instrument Drivers.ipynb

Large diffs are not rendered by default.

394 changes: 394 additions & 0 deletions docs/examples/Creating Simulated PyVISA Instruments.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,394 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Creating Simulated PyVISA Instruments\n",
"\n",
"When developing stuff in a large codebase like QCoDeS, it is often uncanningly easy to submit a change that breaks stuff. Therefore, _continuous integration_ is performed in the form of automated tests that run before new code is allowed into the codebase. The many tests of QCoDeS can be found in `qcodes.tests`. \n",
"\n",
"But how about drivers? They constitute the majority of the codebase, but how can we test them? Wouldn't that require a physical copy each instrument to be present on the California server where we run our tests? It used to be so, but not anymore! For drivers utilising PyVISA (i.e. `VisaInstrument` drivers), we may create simulated instruments to which the drivers may connect.\n",
"\n",
"## What?\n",
"\n",
"This way, we may instantiate drivers and run simple tests on them. Tests like:\n",
"\n",
" * Can the driver even instantiate? This is very relevant when underlying APIs change.\n",
" * Is the drivers (e.g.) \"voltage-to-bytecode\" converter working properly?\n",
"\n",
"## Not!\n",
"\n",
"It is not feasible to simulate any but the most trivial features of the instrument. Simulated instruments can not and should not perform tests like:\n",
"\n",
" * Do we wait sufficiently long for this oscilloscope's trace to be acquired?\n",
" * Does our driver handle overlapping commands of this AWG correctly?\n",
" \n",
"## How?\n",
"\n",
"The basic scheme goes as follows:\n",
"\n",
" * Write a `.yaml` file for the simulated instrument. The instructions for that may be found here: https://pyvisa-sim.readthedocs.io/en/latest/ and specifically here: https://pyvisa-sim.readthedocs.io/en/latest/definitions.html#definitions\n",
" * Then write a test for your instrument and put it in `qcodes/tests/drivers`. The file should have the name `test_<nameofyourdriver>.py`. \n",
" * Check that all is well by running `$ pytest test_<nameofyourdriver>.py`.\n",
" \n",
"Below is an example.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example: Weinschel_8320\n",
"\n",
"The Weinschel 8320 is a very simple driver."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from qcodes.instrument.visa import VisaInstrument\n",
"import qcodes.utils.validators as vals\n",
"import numpy as np\n",
"\n",
"\n",
"class Weinschel_8320(VisaInstrument):\n",
" \"\"\"\n",
" QCoDeS driver for the stepped attenuator\n",
" Weinschel is formerly known as Aeroflex/Weinschel\n",
" \"\"\"\n",
"\n",
" def __init__(self, name, address, **kwargs):\n",
" super().__init__(name, address, terminator='\\r', **kwargs)\n",
"\n",
" self.add_parameter('attenuation', unit='dB',\n",
" set_cmd='ATTN ALL {:02.0f}',\n",
" get_cmd='ATTN? 1',\n",
" vals=vals.Enum(*np.arange(0, 60.1, 2).tolist()),\n",
" get_parser=float)\n",
"\n",
" self.connect_message()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The `.yaml` file"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The simplest `.yaml` file that is still useful, reads, in all its glory:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```\n",
"spec: \"1.0\"\n",
"devices:\n",
" device 1:\n",
" eom:\n",
" GPIB INSTR:\n",
" q: \"\\r\" # MAKE SURE! that this matches the terminator of the driver!\n",
" r: \"\\r\"\n",
" error: ERROR\n",
" dialogues:\n",
" - q: \"*IDN?\"\n",
" r: \"QCoDeS, Weinschel 8320 (Simulated), 1337, 0.0.01\"\n",
" \n",
"\n",
"resources: \n",
" GPIB::1::INSTR:\n",
" device: device 1\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that since no physical connection is made, it doesn't matter what interface we pretend to use (GPIB, USB, ethernet, serial, ...). As a convention, we always write GPIB in the `.yaml` files.\n",
"\n",
"We save the above file as `qcodes/instrument/sims/Weinschel_8320.yaml`. This simulates an instrument with no settable parameter; only an `*IDN?` response. This is enough to instantiate the instrument.\n",
"\n",
"Then we may connect to the simulated instrument."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false,
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Connected to: QCoDeS Weinschel 8320 (Simulation) (serial:1337, firmware:0.0.01) in 0.07s\n"
]
}
],
"source": [
"import qcodes.instrument.sims as sims\n",
"# path to the .yaml file containing the simulated instrument\n",
"visalib = sims.__file__.replace('__init__.py', 'Weinschel_8320.yaml@sim')\n",
"\n",
"wein_sim = Weinschel_8320('wein_sim',\n",
" address='GPIB::1::65535::INSTR', # This matches the address in the .yaml file\n",
" visalib=visalib\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The test"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can write a useful test!"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import pytest\n",
"from qcodes.instrument_drivers.weinschel.Weinschel_8320 import Weinschel_8320\n",
"import qcodes.instrument.sims as sims\n",
"visalib = sims.__file__.replace('__init__.py', 'Weinschel_8320.yaml@sim')\n",
"\n",
"\n",
"# The following decorator makes the driver\n",
"# available to all the functions in this module\n",
"@pytest.fixture(scope='function')\n",
"def driver():\n",
" wein_sim = Weinschel_8320('wein_sim',\n",
" address='GPIB::1::65535::INSTR', \n",
" visalib=visalib\n",
" )\n",
" yield wein_sim\n",
" \n",
" wein_sim.close()\n",
" \n",
" \n",
"def test_init(driver):\n",
" \"\"\"\n",
" Test that simple initialisation works\n",
" \"\"\"\n",
" \n",
" # There is not that much to do, really.\n",
" # We can check that the IDN string reads back correctly\n",
" \n",
" idn_dict = driver.IDN()\n",
" \n",
" assert idn_dict['vendor'] == 'QCoDeS'\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Save the test as `qcodes/tests/drivers/test_weinschel_8320.py`. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Open a command line/console/terminal, navigate to the `qcodes/tests/drivers/` folder and run\n",
"```\n",
">> pytest test_weinschel_8320.py\n",
"```\n",
"\n",
"This should give you an output similar to\n",
"```\n",
"========================================= 1 passed in 0.73 seconds ==========================================\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Congratulations! That was it."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bonus example: including parameters in the simulated instrument"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It is also possible to add queriable parameters to the `.yaml` file, but testing that you can read those back is of limited value. You should only add them if your driver needs them to instantiate, e.g. if it checks that some range or impedance is configured correctly on startup, or - more generally - if a part of your driver code that you'd like to test needs it to run.\n",
"\n",
"For the sake of this example, let us add a test that the driver's parameter's validator will reject an attenuation of less than 0 dBm. Note that this concrete test is redundant, since we have separate tests for validators. It is, however, an excellent example to learn from.\n",
"\n",
"First we update the `.yaml` file to contain a property matching the parameter."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```\n",
"spec: \"1.0\"\n",
"devices:\n",
" device 1:\n",
" eom:\n",
" GPIB INSTR:\n",
" q: \"\\r\" # MAKE SURE! that this matches the terminator of the driver!\n",
" r: \"\\r\"\n",
" error: ERROR\n",
" dialogues:\n",
" - q: \"*IDN?\"\n",
" r: \"QCoDeS, Weinschel 8320 (Simulated), 1337, 0.0.01\"\n",
"\n",
" properties:\n",
"\n",
" attenuation:\n",
" default: 0\n",
" getter:\n",
" q: \"ATTN? 1\" # the set/get commands have to simply be copied over from the driver\n",
" r: \"{:02.0f}\"\n",
" setter:\n",
" q: \"ATTN ALL {:02.0f}\"\n",
" r: OK \n",
"\n",
"resources: \n",
" GPIB::1::INSTR:\n",
" device: device 1\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we update the test script."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import pytest\n",
"from qcodes.instrument_drivers.weinschel.Weinschel_8320 import Weinschel_8320\n",
"import qcodes.instrument.sims as sims\n",
"visalib = sims.__file__.replace('__init__.py', 'Weinschel_8320.yaml@sim')\n",
"\n",
"\n",
"# The following decorator makes the driver\n",
"# available to all the functions in this module\n",
"@pytest.fixture(scope='function')\n",
"def driver():\n",
" wein_sim = Weinschel_8320('wein_sim',\n",
" address='GPIB::1::65535::INSTR', \n",
" visalib=visalib\n",
" )\n",
" yield wein_sim\n",
" \n",
" wein_sim.close()\n",
" \n",
" \n",
"def test_init(driver):\n",
" \"\"\"\n",
" Test that simple initialisation works\n",
" \"\"\"\n",
" \n",
" # There is not that much to do, really.\n",
" # We can check that the IDN string reads back correctly\n",
" \n",
" idn_dict = driver.IDN()\n",
" \n",
" assert idn_dict['vendor'] == 'QCoDeS'\n",
" \n",
" \n",
"def test_attenuation_validation(driver):\n",
" \"\"\"\n",
" Test that incorrect values are rejected\n",
" \"\"\"\n",
" \n",
" bad_values = [-1, 1, 1.5]\n",
" \n",
" for bv in bad_values:\n",
" with pytest.raises(ValueError):\n",
" driver.attenuation(bv)\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Open a command line/console/terminal, navigate to the `qcodes/tests/drivers/` folder and run\n",
"```\n",
">> pytest test_weinschel_8320.py\n",
"```\n",
"\n",
"This should give you an output similar to\n",
"```\n",
"========================================= 2 passed in 0.73 seconds ==========================================\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## That's it!"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Loading