Skip to content

Commit

Permalink
Simplify install and dev docs (#35)
Browse files Browse the repository at this point in the history
* Reworked Getting Started into Quick Start with Anaconda
* Added significant docs for quick start and dev
  • Loading branch information
cjermain committed Jul 29, 2016
1 parent 09851a6 commit f384205
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 104 deletions.
1 change: 1 addition & 0 deletions docs/api/instruments/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This section contains documentation on the instrument classes.
:maxdepth: 2

instruments
validators
comedi

Instruments by manufacturer:
Expand Down
10 changes: 10 additions & 0 deletions docs/api/instruments/validators.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. module:: pymeasure.instruments.validators

###################
Validator functions
###################

Validators are used in conjunction with the :func:`Instrument.control <pymeasure.instruments.Instrument.control>` function to allow properties with complex restrictions for valid values. They are described in more detail in the :ref:`Advanced properties <advanced-properties>` section.

.. automodule:: pymeasure.instruments.validators
:members:
273 changes: 272 additions & 1 deletion docs/dev/adding_instruments.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,273 @@
Adding Instruments
##################
Adding instruments
##################

You can make a significant contribution to PyMeasure by adding a new instrument to the :code:`pymeasure.instruments` package. Even adding an instrument with a few features can help get the ball rolling, since its likely that others are interested in the same instrument.

Before getting started, become familiar with the :doc:`contributing work-flow <contribute>` for PyMeasure, which steps through the process of adding a new feature (like an instrument) to the development version of the source code. This section will describe how to lay out your instrument code.

File structure
==============

Your new instrument should be placed in the directory corresponding to the manufacturer of the instrument. For example, if you are going to add an "Extreme 5000" instrument you should add the following files assuming "Extreme" is the manufacturer. Use lowercase for all filenames to distinguish packages from CamelCase Python classes.

.. code-block:: none
pymeasure/pymeasure/instruments/extreme/
|--> __init__.py
|--> extreme5000.py
Updating the init file
**********************

The :code:`__init__.py` file in the manufacturer directory should import all of the instruments that correspond to the manufacturer, to allow the files to be easily imported. For a new manufacturer, the manufacturer should also be added to :code:`pymeasure/pymeasure/instruments/__init__.py`.

Adding documentation
********************

Documentation for each instrument is required, and helps others understand the features you have implemented. Add a new reStructuredText file to the documentation.

.. code-block:: none
pymeasure/docs/api/instruments/extreme/
|--> index.rst
|--> extreme5000.rst
Copy an existing instrument documentation file, which will automatically generate the documentation for the instrument. The :code:`index.rst` file should link to the :code:`extreme5000` file. For a new manufacturer, the manufacturer should be also linked in :code:`pymeasure/docs/api/instruments/index.rst`.

Instrument file
===============

All standard instruments should be child class of :class:`Instrument <pymeasure.instruments.Instrument>`. This provides the basic functionality for working with :class:`Adapters <pymeasure.adapters.Adapter>`, which perform the actual communication.

The most basic instrument, for our "Extreme 5000" example looks as follows.

.. code-block:: python
#
# This file is part of the PyMeasure package.
#
# Copyright (c) 2013-2016 PyMeasure Developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
from pymeasure.instruments import Instrument
class Extreme5000(Instrument):
""" Represents the Extreme 5000 instrument
"""
def __init__(self, resourceName, **kwargs):
super(Extreme5000, self).__init__(
resourceName,
"Extreme 5000",
**kwargs
)
Make sure to include the PyMeasure license to each file, and add yourself as an author to the :code:`AUTHORS.txt` file.

In principle you are free to write any functions that are necessary for interacting with the instrument. When doing so, make sure to use the :code:`self.ask(command)`, :code:`self.write(command)`, and :code:`self.read()` methods to issue command instead of calling the adapter directly.

In practice, we have developed a number of convenience functions for making instruments easy to write and maintain. The following sections detail these conveniences and are highly encouraged.

Writing properties
==================

In PyMeasure, `Python properties`_ are the preferred method for dealing with variables that are read or set. PyMeasure comes with two convenience functions for making properties for classes. The :func:`Instrument.measurement <pymeasure.instruments.Instrument.measurement>` function returns a property that issues a GPIB/SCPI requests when the value is used. For example, if our "Extreme 5000" has the :code:`*IDN?` command we can write the following property to be added above the :code:`def __init__` line in our above example class.

.. _Python properties: https://docs.python.org/3/howto/descriptor.html#properties

.. code-block:: python
id = Instrument.measurement(
"*IDN?", """ Reads the instrument identification """
)
You will notice that a documentation string is required, and should be descriptive and specific.

When we use this property we will get the identification information.

.. code-block:: python
>> extreme = Extreme5000("GPIB::1")
>> extreme.id # Reads "*IDN?"
'Extreme 5000 identification from instrument'
The :func:`Instrument.control <pymeasure.instruments.Instrument.control>` function extends this behavior by creating a property that you can read and set. For example, if our "Extreme 5000" has the :code:`:VOLT?` and :code:`:VOLT <float>` commands that are in Volts, we can write the following property.

.. code-block:: python
voltage = Instrument.control(
":VOLT?", ":VOLT %g",
""" A floating point property that controls the voltage
in Volts. This property can be set.
"""
)
You will notice that we use the `Python string format`_ :code:`%g` to pass through the floating point.

.. _Python string format: https://docs.python.org/3/library/string.html#format-specification-mini-language

We can use this property to set the voltage to 100 mV, which will execute the command and then request the current voltage.

.. code-block:: python
>> extreme = Extreme5000("GPIB::1")
>> extreme.voltage = 0.1 # Executes ":VOLT 0.1"
>> extreme.voltage # Reads ":VOLT?"
0.1
Using both of these functions, you can create a number of properties for basic measurements and controls. The next section details additional features of :func:`Instrument.control <pymeasure.instruments.Instrument.control>` that allow you to write properties that cover specific ranges, or have to map between a real value to one used in the command.

.. _advanced-properties:

Advanced properties
===================

Many GPIB/SCIP commands are more restrictive than our basic examples above. The :func:`Instrument.control <pymeasure.instruments.Instrument.control>` function has the ability to encode these restrictions using :mod:`validators <pymeasure.instruments.validators>`. A validator is a function that takes a value and a set of values, and returns a valid value or raises an exception. There are a number of pre-defined validators in :mod:`pymeasure.instruments.validators` that should cover most situations. We will cover the four basic types here.

In the examples below we assume you have imported the validators.

In a restricted range
*********************

If you have a property with a restricted range, you can use the :func:`strict_range <pymeasure.instruments.validators.strict_range>` and :func:`truncated_range <pymeasure.instruments.validators.strict_range>` functions.

For example, if our "Extreme 5000" can only support voltages from -1 V to 1 V, we can modify our previous example to use a strict validator over this range.

.. code-block:: python
voltage = Instrument.control(
":VOLT?", ":VOLT %g",
""" A floating point property that controls the voltage
in Volts, from -1 to 1 V. This property can be set. """,
validator=strict_range,
values=[-1, 1]
)
Now our voltage will raise a ValueError if the value is out of the range.

.. code-block:: python
>> extreme = Extreme5000("GPIB::1")
>> extreme.voltage = 100
Exception: ValueError("Value of 100 is not in range [-1, 1]")
This is useful if you want to alert the programmer that they are using an invalid value. However, sometimes it can be nicer to truncate the value to be within the range.

.. code-block:: python
voltage = Instrument.control(
":VOLT?", ":VOLT %g",
""" A floating point property that controls the voltage
in Volts, from -1 to 1 V. Invalid voltages are truncated.
This property can be set. """,
validator=truncated_range,
values=[-1, 1]
)
Now our voltage will not raise an error, and will truncate the value to the range bounds.

.. code-block:: python
>> extreme = Extreme5000("GPIB::1")
>> extreme.voltage = 100 # Executes ":VOLT 1"
>> extreme.voltage
1
In a discrete set
*****************

Often a control property should only take a few discrete values. You can use the :func:`strict_discrete_set <pymeasure.instruments.validators.strict_discrete_set>` and :func:`truncated_discrete_set <pymeasure.instruments.validators.truncated_discrete_set>` functions to handle these situations. The strict version raises an error if the value is not in the set, as in the range examples above.

For example, if our "Extreme 5000" has a :code:`:RANG <float>` command that sets the voltage range that can take values of 10 mV, 100 mV, and 1 V in Volts, then we can write a control as follows.

.. code-block:: python
voltage = Instrument.control(
":RANG?", ":RANG %g",
""" A floating point property that controls the voltage
range in Volts. This property can be set.
""",
validator=truncated_discrete_set,
values=[10e-3, 100e-3, 1]
)
Now we can set the voltage range, which will automatically truncate to an appropriate value.

.. code-block:: python
>> extreme = Extreme5000("GPIB::1")
>> extreme.voltage_range = 0.08
>> extreme.voltage_range
0.1
Using maps
**********

Now that you are familiar with the validators, you can additionally use maps to satisfy instruments which require non-physical values. The :code:`map_values` argument of :func:`Instrument.control <pymeasure.instruments.Instrument.control>` enables this feature.

If your set of values is a list, then the command will use the index of the list. For example, if our "Extreme 5000" instead has a :code:`:RANG <integer>`, where 0, 1, and 2 correspond to 10 mV, 100 mV, and 1 V, then we can use the following control.

.. code-block:: python
voltage = Instrument.control(
":RANG?", ":RANG %d",
""" A floating point property that controls the voltage
range in Volts, which takes values of 10 mV, 100 mV and 1 V.
This property can be set. """,
validator=truncated_discrete_set,
values=[10e-3, 100e-3, 1],
map_values=True
)
Now the actual GPIB/SCIP command is ":RANG 1" for a value of 100 mV, since the index of 100 mV in the values list is 1.

Dictionaries provide a more flexible method for mapping between real-values and those required by the instrument. If instead the :code:`:RANG <integer>` took 1, 2, and 3 to correspond to 10 mV, 100 mV, and 1 V, then we can replace our previous control with the following.

.. code-block:: python
voltage = Instrument.control(
":RANG?", ":RANG %d",
""" A floating point property that controls the voltage
range in Volts, which takes values of 10 mV, 100 mV and 1 V.
This property can be set. """,
validator=truncated_discrete_set,
values={10e-3:1, 100e-3:2, 1:3},
map_values=True
)
The dictionary now maps the keys to specific values. The values and keys can be any type, so this can support properties that use strings.

.. code-block:: python
channel = Instrument.control(
":CHAN?", ":CHAN %d",
""" A string property that controls the measurement channel,
which can take the values X, Y, or Z.
""",
validator=strict_discrete_set,
values={'X':1, 'Y':2, 'Z':3},
map_values=True
)
As you have seen, the :func:`Instrument.control <pymeasure.instruments.Instrument.control>` function can be significantly extended by using validators and maps.
34 changes: 15 additions & 19 deletions docs/dev/coding_standards.rst
Original file line number Diff line number Diff line change
@@ -1,35 +1,31 @@
################
Coding Standards
================
################

In order to maintain consistency across the different instruments in the PyMeasure repository, we enforce the following standards.

Python style guides
###################
===================

Python 3 is used in PyMeasure. The `PEP8 style guide`_ and `PEP257 docstring conventions`_ should be followed.
Only Python 3 is used in PyMeasure. This prevents the maintaininace overhead of supporting Python 2.7, which will loose official support in the future.

The `PEP8 style guide`_ and `PEP257 docstring conventions`_ should be followed.

.. _PEP8 style guide: https://www.python.org/dev/peps/pep-0008/
.. _PEP257 docstring conventions: https://www.python.org/dev/peps/pep-0257/

Function and variable names should be lower case with underscores as needed to seperate words. Camel case should not be used, unless working with Qt, where it is common.

Standard naming
###############
Function and variable names should be lower case with underscores as needed to seperate words. CamelCase should only be used for class names, unless working with Qt, where its use is common.

Since many instruments have similar functions, a few naming conventions have been adopted to make the interface more consistent.
Documentation
=============

.. function names should be all lowercase with underbars if needed.
PyMeasure documents code using reStructuredText and the `Sphinx documentation generator`_. All functions, classes, and methods should be documented in the code using a `docstring`_.

Usage of getter and setter
##########################
Many settings (such as range, enabled status, etc) are provided by the instrument with a pair of actions: one is to read the current setting value, the other is to assign a value to the setting. One can write two methods, get_setting() and set_setting() for instance, to handle these two actions; or altenatively use getter and setter decorators. In most cases, the two ways are equivalent. In order to incorporate different programming styles, and for the convenience of users, our convention is as follow:
- Write two functions get_setting() and set_setting(). The latter one should have only one non-keyword argument (but can have many keyword arguments).
- Define a property setting = property(get_setting, set_setting).
.. _Sphinx documentation generator: http://www.sphinx-doc.org/en/stable/
.. _docstring: http://www.sphinx-doc.org/en/stable/ext/example_numpy.html?highlight=docstring

Using a buffer

.. code-block:: python
Usage of getter and setter functions
====================================

set_buffer
wait_for_buffer
get_buffer
Getter and setter functions are discouraged, since properties provide a more fluid experience. Given the extensive tools avalible for defining properties, detailed in the :ref:`Advanced properties <advanced-properties>` section, these types of properties are prefered.

0 comments on commit f384205

Please sign in to comment.