.. _instrumentation:

Instrumentation
===============

The ``Instrument`` API provide a consistent way of collecting measurements from
a target. Measurements are collected via an instance of a class derived from
:class:`~devlib.instrument.Instrument`. An ``Instrument`` allows collection of
measurement from one or more channels. An ``Instrument`` may support
``INSTANTANEOUS`` or ``CONTINUOUS`` collection, or both.

Example
-------

The following example shows how to use an instrument to read temperature from an
Android target.

.. code-block:: python

    # import and instantiate the Target and the instrument
    # (note: this assumes exactly one android target connected
    #  to the host machine).
    In [1]: from devlib import AndroidTarget, HwmonInstrument

    In [2]: t = AndroidTarget()

    In [3]: i = HwmonInstrument(t)

    # Set up the instrument on the Target. In case of HWMON, this is
    # a no-op, but is included here for completeness.
    In [4]: i.setup()

    # Find out what the instrument is capable collecting from the
    # target.
    In [5]: i.list_channels()
    Out[5]:
    [CHAN(battery/temp1, battery_temperature),
     CHAN(exynos-therm/temp1, exynos-therm_temperature)]

    # Set up a new measurement session, and specify what is to be
    # collected.
    In [6]: i.reset(sites=['exynos-therm'])

    # HWMON instrument supports INSTANTANEOUS collection, so invoking
    # take_measurement() will return a list of measurements take from
    # each of the channels configured during reset()
    In [7]: i.take_measurement()
    Out[7]: [exynos-therm_temperature: 36.0 degrees]

API
---

.. module:: devlib.instrument

Instrument
~~~~~~~~~~

.. class:: Instrument(target, \*\*kwargs)

   An ``Instrument`` allows collection of measurement from one or more
   channels. An ``Instrument`` may support ``INSTANTANEOUS`` or ``CONTINUOUS``
   collection, or both.

.. attribute:: Instrument.mode

   A bit mask that indicates collection modes that are supported by this
   instrument. Possible values are:

   :INSTANTANEOUS: The instrument supports taking a single sample via
                   ``take_measurement()``.
   :CONTINUOUS: The instrument supports collecting measurements over a
                period of time via ``start()``, ``stop()``, ``get_data()``,
		and (optionally) ``get_raw`` methods.

   .. note:: It's possible for one instrument to support more than a single
             mode.

.. attribute:: Instrument.active_channels

   Channels that have been activated via ``reset()``. Measurements will only be
   collected for these channels.

.. method:: Instrument.list_channels()

   Returns a list of :class:`InstrumentChannel` instances that describe what
   this instrument can measure on the current target. A channel is a combination
   of a ``kind`` of measurement (power, temperature, etc) and a ``site`` that
   indicates where on the target the measurement will be collected from.

.. method:: Instrument.get_channels(measure)

   Returns channels for a particular ``measure`` type. A ``measure`` can be
   either a string (e.g. ``"power"``) or a :class:`MeasurmentType` instance.

.. method::  Instrument.setup(\*args, \*\*kwargs)

   This will set up the instrument on the target. Parameters this method takes
   are particular to subclasses (see documentation for specific instruments
   below).  What actions are performed by this method are also
   instrument-specific.  Usually these will be things like  installing
   executables, starting services, deploying assets, etc. Typically, this method
   needs to be invoked at most once per reboot of the target (unless
   ``teardown()`` has been called), but see documentation for the instrument
   you're interested in.

.. method:: Instrument.reset(sites=None, kinds=None, channels=None)

   This is used to configure an instrument for collection. This must be invoked
   before ``start()`` is called to begin collection. This methods sets the
   ``active_channels`` attribute of the ``Instrument``.

   If ``channels`` is provided, it is a list of names of channels to enable and
   ``sites`` and ``kinds`` must both be ``None``.

   Otherwise, if one of ``sites`` or ``kinds`` is provided, all channels
   matching the given sites or kinds are enabled. If both are provided then all
   channels of the given kinds at the given sites are enabled.

   If none of ``sites``, ``kinds`` or ``channels`` are provided then all
   available channels are enabled.

.. method:: Instrument.take_measurement()

   Take a single measurement from ``active_channels``. Returns a list of
   :class:`Measurement` objects (one for each active channel).

   .. note:: This method is only implemented by
             :class:`~devlib.instrument.Instrument`\ s that
             support ``INSTANTANEOUS`` measurement.

.. method:: Instrument.start()

   Starts collecting measurements from ``active_channels``.

   .. note:: This method is only implemented by
             :class:`~devlib.instrument.Instrument`\ s that
             support ``CONTINUOUS`` measurement.

.. method:: Instrument.stop()

   Stops collecting measurements from ``active_channels``. Must be called after
   :func:`start()`.

   .. note:: This method is only implemented by
             :class:`~devlib.instrument.Instrument`\ s that
             support ``CONTINUOUS`` measurement.

.. method:: Instrument.get_data(outfile)

   Write collected data into ``outfile``. Must be called after :func:`stop()`.
   Data will be written in CSV format with a column for each channel and a row
   for each sample. Column heading will be channel, labels in the form
   ``<site>_<kind>`` (see :class:`InstrumentChannel`). The order of the columns
   will be the same as the order of channels in ``Instrument.active_channels``.

   If reporting timestamps, one channel must have a ``site`` named
   ``"timestamp"`` and a ``kind`` of a :class:`MeasurmentType` of an appropriate
   time unit which will be used, if appropriate, during any post processing.

   .. note:: Currently supported time units are seconds, milliseconds and
             microseconds, other units can also be used if an appropriate
             conversion is provided.

   This returns a :class:`MeasurementCsv` instance associated with the outfile
   that can be used to stream :class:`Measurement`\ s lists (similar to what is
   returned by ``take_measurement()``.

   .. note:: This method is only implemented by
             :class:`~devlib.instrument.Instrument`\ s that
             support ``CONTINUOUS`` measurement.

.. method:: Instrument.get_raw()

   Returns a list of paths to files containing raw output from the underlying
   source(s) that is used to produce the data CSV. If no raw output is
   generated or saved, an empty list will be returned. The format of the
   contents of the raw files is entirely source-dependent.

  .. note:: This method is not guaranteed to return valid filepaths after the
            :meth:`teardown` method has been invoked as the raw files may have
            been deleted. Please ensure that copies are created manually
            prior to calling :meth:`teardown` if the files are to be retained.

.. method:: Instrument.teardown()

   Performs any required clean up of the instrument. This usually includes
   removing temporary and raw files (if ``keep_raw`` is set to ``False`` on relevant
   instruments), stopping services etc.

.. attribute:: Instrument.sample_rate_hz

   Sample rate of the instrument in Hz. Assumed to be the same for all channels.

   .. note:: This attribute is only provided by
             :class:`~devlib.instrument.Instrument`\ s that
             support ``CONTINUOUS`` measurement.

Instrument Channel
~~~~~~~~~~~~~~~~~~

.. class:: InstrumentChannel(name, site, measurement_type, \*\*attrs)

   An :class:`InstrumentChannel` describes a single type of measurement that may
   be collected by an :class:`~devlib.instrument.Instrument`. A channel is
   primarily defined by a ``site`` and a ``measurement_type``.

   A ``site`` indicates where  on the target a measurement is collected from
   (e.g. a voltage rail or location of a sensor).

   A ``measurement_type`` is an instance of :class:`MeasurmentType` that
   describes what sort of measurement this is (power, temperature, etc). Each
   measurement type has a standard unit it is reported in, regardless of an
   instrument used to collect it.

   A channel (i.e. site/measurement_type combination) is unique per instrument,
   however there may be more than one channel associated with one site (e.g. for
   both voltage and power).

   It should not be assumed that any site/measurement_type combination is valid.
   The list of available channels can queried with
   :func:`Instrument.list_channels()`.

.. attribute:: InstrumentChannel.site

   The name of the "site" from which the measurements are collected (e.g. voltage
   rail, sensor, etc).

.. attribute:: InstrumentChannel.kind

   A string indicating the type of measurement that will be collected. This is
   the ``name`` of the :class:`MeasurmentType` associated with this channel.

.. attribute:: InstrumentChannel.units

   Units in which measurement will be reported. this is determined by the
   underlying :class:`MeasurmentType`.

.. attribute:: InstrumentChannel.label

   A label that can be attached to measurements associated with with channel.
   This is constructed with ::

       '{}_{}'.format(self.site, self.kind)


Measurement Types
~~~~~~~~~~~~~~~~~

In order to make instruments easer to use, and to make it easier to swap them
out when necessary (e.g. change method of collecting power), a number of
standard measurement types are defined. This way, for example, power will
always be reported as "power" in Watts, and never as "pwr" in milliWatts.
Currently defined measurement types are


+-------------+-------------+---------------+
| name        | units       | category      |
+=============+=============+===============+
| count       | count       |               |
+-------------+-------------+---------------+
| percent     | percent     |               |
+-------------+-------------+---------------+
| time_us     | microseconds|  time         |
+-------------+-------------+---------------+
| time_ms     | milliseconds|  time         |
+-------------+-------------+---------------+
| temperature | degrees     |  thermal      |
+-------------+-------------+---------------+
| power       | watts       | power/energy  |
+-------------+-------------+---------------+
| voltage     | volts       | power/energy  |
+-------------+-------------+---------------+
| current     | amps        | power/energy  |
+-------------+-------------+---------------+
| energy      | joules      | power/energy  |
+-------------+-------------+---------------+
| tx          | bytes       | data transfer |
+-------------+-------------+---------------+
| rx          | bytes       | data transfer |
+-------------+-------------+---------------+
| tx/rx       | bytes       | data transfer |
+-------------+-------------+---------------+


.. instruments:

Available Instruments
---------------------

This section lists instruments that are currently part of devlib.

.. todo:: Add other instruments


Baylibre ACME BeagleBone Black Cape
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. _official project page:            http://baylibre.com/acme/
.. _image built for using the ACME:   https://gitlab.com/baylibre-acme/ACME-Software-Release/blob/master/README.md
.. _libiio (the Linux IIO interface): https://github.com/analogdevicesinc/libiio
.. _Linux Industrial I/O Subsystem:   https://wiki.analog.com/software/linux/docs/iio/iio
.. _Texas Instruments INA226:         http://www.ti.com/lit/ds/symlink/ina226.pdf

From the `official project page`_:

  [The Baylibre Another Cute Measurement Equipment (ACME)] is an extension for
  the BeagleBone Black (the ACME Cape), designed to provide multi-channel power
  and temperature measurements capabilities to the BeagleBone Black (BBB). It
  comes with power and temperature probes integrating a power switch (the ACME
  Probes), turning it into an advanced all-in-one power/temperature measurement
  solution.

  The ACME initiative is completely open source, from HW to SW drivers and
  applications.


The Infrastructure
^^^^^^^^^^^^^^^^^^

Retrieving measurement from the ACME through devlib requires:

  - a BBB running the `image built for using the ACME`_ (micro SD card required);

  - an ACME cape on top of the BBB;

  - at least one ACME probe [#acme_probe_variants]_ connected to the ACME cape;

  - a BBB-host interface (typically USB or Ethernet) [#acme_name_conflicts]_;

  - a host (the one running devlib) with `libiio (the Linux IIO interface)`_
    installed, and a Python environment able to find the libiio Python wrapper
    *i.e.* able to ``import iio`` as communications between the BBB and the
    host rely on the `Linux Industrial I/O Subsystem`_ (IIO).

The ACME probes are built on top of the `Texas Instruments INA226`_ and the
data acquisition chain is as follows:

.. graphviz::

    digraph target {
        rankdir = LR
        bgcolor = transparent

        subgraph cluster_target {

            subgraph cluster_BBB {
                node [style = filled, color = white];
                style = filled;
                color = lightgrey;
                label = "BeagleBone Black";

                drivers -> "IIO Daemon" [dir = both]
            }

            subgraph cluster_INA226 {
                node [style = filled, color = white];
                style = filled;
                color = lightgrey;
                label = INA226;

                ADC -> Processing
                Processing -> Registers
            }

            subgraph cluster_inputs {
                node [style = filled, color = white];
                style = filled;
                color = lightgrey;
                label = Inputs;

                "Bus Voltage" -> ADC;
                "Shunt Voltage" -> ADC;
            }

            Registers -> drivers [dir = both, label = I2C];
        }

        subgraph cluster_IIO {
            style = none
            "IIO Daemon" -> "IIO Interface" [dir = both, label = "Eth./USB"]
        }
    }

For reference, the software stack on the host is roughly given by:

.. graphviz::

    digraph host {
        rankdir = LR
        bgcolor = transparent

        subgraph cluster_host {

            subgraph cluster_backend {
                node [style = filled, color = white];
                style = filled;
                color = lightgrey;
                label = Backend;

                "IIO Daemon" -> "C API" [dir = both]
            }

            subgraph cluster_Python {
                node [style = filled, color = white];
                style = filled;
                color = lightgrey;
                label = Python;

                "C API" -> "iio Wrapper" [dir = both]
                "iio Wrapper" -> devlib [dir = both]
                devlib -> "User" [dir = both]
            }
        }

        subgraph cluster_IIO {
            style = none
            "IIO Interface" -> "IIO Daemon" [dir = both, label = "Eth./USB"]
        }
    }

Ethernet was the only IIO Interface used and tested during the development of
this instrument. However,
`USB seems to be supported <https://gitlab.com/baylibre-acme/ACME/issues/2>`_.
The IIO library also provides "Local" and "XML" connections but these are to be
used when the IIO devices are directly connected to the host *i.e.* in our
case, if we were to run Python and devlib on the BBB. These are also untested.

Measuring Power
^^^^^^^^^^^^^^^

In IIO terminology, the ACME cape is an *IIO context* and ACME probes are *IIO
devices* with *IIO channels*. An input *IIO channel* (the ACME has no *output
IIO channel*) is a stream of samples and an ACME cape can be connected to up to
8 probes *i.e.* have 8 *IIO devices*. The probes are discovered at startup by
the IIO drivers on the BBB and are indexed according to the order in which they
are connected to the ACME cape (with respect to the "Probe *X*" connectors on
the cape).


.. figure:: images/instrumentation/baylibre_acme/cape.png
   :width: 50%
   :alt: ACME Cape
   :align: center

   ACME Cape on top of a BBB: Notice the numbered probe connectors (
   `source <https://baylibre.com/wp-content/uploads/2015/11/20150916_BayLibre_ACME_RevB-010-1030x599.png>`_)


Please note that the numbers on the PCB do not represent the index of a probe
in IIO; on top of being 1-based (as opposed to IIO device indexing being
0-based), skipped connectors do not result in skipped indices *e.g.* if three
probes are connected to the cape at ``Probe 1``, ``Probe 3`` and ``Probe 7``,
IIO (and therefore the entire software stack, including devlib) will still
refer to them as devices ``0``, ``1`` and ``2``, respectively. Furthermore,
probe "hot swapping" does not seem to be supported.

INA226: The probing spearhead
"""""""""""""""""""""""""""""

An ACME probe has 5 *IIO channels*, 4 of which being "IIO wrappers" around what
the INA226 outputs (through its I2C registers): the bus voltage, the shunt
voltage, the shunt current and the load power. The last channel gives the
timestamps and is probably added further down the pipeline. A typical circuit
configuration for the INA226 (useful when shunt-based ACME probes are used as
their PCB does not contain the full circuit unlike the USB and jack variants)
is given by its datasheet:

.. figure:: images/instrumentation/baylibre_acme/ina226_circuit.png
   :width: 90%
   :alt: Typical circuit configuration, INA226
   :align: center

   Typical Circuit Configuration (source: `Texas Instruments INA226`_)


The analog-to-digital converter (ADC)
'''''''''''''''''''''''''''''''''''''

The digital time-discrete sampled signal of the analog time-continuous input
voltage signal is obtained through an analog-to-digital converter (ADC). To
measure the "instantaneous input voltage", the ADC "charges up or down" a
capacitor before measuring its charge.

The *integration time* is the time spend by the ADC acquiring the input signal
in its capacitor. The longer this time is, the more resilient the sampling
process is to unwanted noise. The drawback is that, if the integration time is
increased then the sampling rate decreases. This effect can be somewhat
compared to a *low-pass filter*.

As the INA226 alternatively connects its ADC to the bus voltage and shunt
voltage (see previous figure), samples are retrieved at a frequency of

.. math::
    \frac{1}{T_{bus} + T_{shunt}}

where :math:`T_X` is the integration time for the :math:`X` voltage.

As described below (:meth:`BaylibreAcmeInstrument.reset
<devlib.instrument.baylibre_acme.BaylibreAcmeInstrument.reset>`), the
integration times for the bus and shunt voltage can be set separately which
allows a tradeoff of accuracy between signals. This is particularly useful as
the shunt voltage returned by the INA226 has a higher resolution than the bus
voltage (2.5 μV and 1.25 mV LSB, respectively) and therefore would benefit more
from a longer integration time.

As an illustration, consider the following sampled sine wave and notice how
increasing the integration time (of the bus voltage in this case) "smoothes"
out the signal:

.. figure:: images/instrumentation/baylibre_acme/int_time.png
   :alt: Illustration of the impact of the integration time
   :align: center

   Increasing the integration time increases the resilience to noise


Internal signal processing
''''''''''''''''''''''''''

The INA226 is able to accumulate samples acquired by its ADC and output to the
ACME board (technically, to its I2C registers) the average value of :math:`N`
samples. This is called *oversampling*. While the integration time somewhat
behaves as an analog low-pass filter, the oversampling feature is a digital
low-pass filter by definition. The former should be set to reduce sampling
noise (*i.e.* noise on a single sample coming from the sampling process) while
the latter should be used to filter out high-frequency noise present in the
input signal and control the sampling frequency.

Therefore, samples are available at the output of the INA226 at a frequency

.. math::
    \frac{1}{N(T_{bus} + T_{shunt})}

and oversampling ratio provides a way to control the output sampling frequency
(*i.e.* to limit the required output bandwidth) while making sure the signal
fidelity is as desired.


The 4 IIO channels coming from the INA226 can be grouped according to their
respective origins: the bus and shunt voltages are measured (and, potentially
filtered) while the shunt current and load power are computed. Indeed, the
INA226 contains on-board fixed-point arithmetic units to compute the trivial
expressions:

.. math::

   I_{shunt} = \frac{V_{shunt}}{R_{shunt}}
   ,\ \
   P_{load} = V_{load}\ I_{load}
      \approx V_{bus} \ I_{shunt}

A functional block diagram of this is also given by the datasheet:

.. figure:: images/instrumentation/baylibre_acme/ina226_functional.png
   :width: 60%
   :alt: Functional block diagram, INA226
   :align: center

   Acquisition and Processing: Functional Block Diagram
   (source: `Texas Instruments INA226`_)

In the end, there are therefore 3 channels (bus voltage, shunt voltage and
timestamps) that are necessary to figure out the load power consumption, while
the others are being provided for convenience *e.g.* in case the rest of the
hardware does not have the computing power to make the computation.


Sampling Frequency Issues
"""""""""""""""""""""""""

It looks like the INA226-ACME-BBB setup has a bottleneck preventing the
sampling frequency to go higher than ~1.4 kHz (the maximal theoretical sampling
frequency is ~3.6 kHz). We know that this issue is not internal to the ADC
itself (inside of the INA226) because modifying the integration time affects
the output signal even when the sampling frequency is capped (as shown above)
but it may come from anywhere after that.

Because of this, there is no point in using a (theoretical) sampling frequency
that is larger than 1.4 kHz. But it is important to note that the ACME will
still report the theoretical sampling rate (probably computed with the formula
given above) through :attr:`BaylibreAcmeInstrument.sample_rate_hz` and
:attr:`IIOINA226Instrument.sample_rate_hz` even if it differs from the actual
sampling rate.

Note that, even though this is obvious for the theoretical sampling rate, the
specific values of the bus and shunt integration times do not seem to have an
influence on the measured sampling rate; only their sum matters. This further
points toward a data-processing bottleneck rather than a hardware bug in the
acquisition device.

The following chart compares the evolution of the measured sampling rate with
the expected one as we modify it through :math:`T_{shunt}`, :math:`T_{bus}` and
:math:`N`:

.. figure:: images/instrumentation/baylibre_acme/bottleneck.png
   :alt: Sampling frequency does not go higher than 1.4 kHz
   :align: center

   Theoretical vs measured sampling rates


Furthermore, because the transactions are done through a buffer (see next
section), if the sampling frequency is too low, the connection may time-out
before the buffer is full and ready to be sent. This may be fixed in an
upcoming release.

Buffer-based transactions
"""""""""""""""""""""""""

Samples made available by the INA226 are retrieved by the BBB and stored in a
buffer which is sent back to the host once it is full (see
``buffer_samples_count`` in :meth:`BaylibreAcmeInstrument.setup
<devlib.instrument.baylibre_acme.BaylibreAcmeInstrument.setup>` for setting its
size). Therefore, the larger the buffer is, the longer it takes to be
transmitted back but the less often it has to be transmitted. To illustrate
this, consider the following graphs showing the time difference between
successive samples in a retrieved signal when the size of the buffer changes:

.. figure:: images/instrumentation/baylibre_acme/buffer.png
   :alt: Buffer size impact on the sampled signal
   :align: center

   Impact of the buffer size on the sampling regularity

devlib API
^^^^^^^^^^

ACME Cape + BBB (IIO Context)
"""""""""""""""""""""""""""""

devlib provides wrapper classes for all the IIO connections to an IIO context
given by `libiio (the Linux IIO interface)`_ however only the network-based one
has been tested. For the other classes, please refer to the official IIO
documentation for the meaning of their constructor parameters.

.. module:: devlib.instrument.baylibre_acme

.. class:: BaylibreAcmeInstrument(target=None, iio_context=None, use_base_iio_context=False, probe_names=None)

   Base class wrapper for the ACME instrument which itself is a wrapper for the
   IIO context base class. This class wraps around the passed ``iio_context``;
   if ``use_base_iio_context`` is ``True``, ``iio_context`` is first passed to
   the :class:`iio.Context` base class (see its documentation for how this
   parameter is then used), else ``iio_context`` is expected to be a valid
   instance of :class:`iio.Context`.

   ``probe_names`` is expected to be a string or list of strings; if passed,
   the probes in the instance are named according to it in the order in which
   they are discovered (see previous comment about probe discovery and
   :attr:`BaylibreAcmeInstrument.probes`). There should be as many
   ``probe_names`` as there are probes connected to the ACME. By default, the
   probes keep their IIO names.

   To ensure that the setup is reliable, ``devlib`` requires minimal versions
   for ``iio``, the IIO drivers and the ACME BBB SD image.

.. class:: BaylibreAcmeNetworkInstrument(target=None, hostname=None, probe_names=None)

   Child class of :class:`BaylibreAcmeInstrument` for Ethernet-based IIO
   communication. The ``hostname`` should be the IP address or network name of
   the BBB. If it is ``None``, the ``IIOD_REMOTE`` environment variable will be
   used as the hostname. If that environment variable is empty, the server will
   be discovered using ZeroConf. If that environment variable is not set, a
   local context is created.

.. class:: BaylibreAcmeXMLInstrument(target=None, xmlfile=None, probe_names=None)

   Child class of :class:`BaylibreAcmeInstrument` using the XML backend of the
   IIO library and building an IIO context from the provided ``xmlfile`` (a
   string giving the path to the file is expected).

.. class:: BaylibreAcmeLocalInstrument(target=None, probe_names=None)

   Child class of :class:`BaylibreAcmeInstrument` using the Local IIO backend.

.. attribute:: BaylibreAcmeInstrument.mode

   The collection mode for the ACME is ``CONTINUOUS``.

.. method:: BaylibreAcmeInstrument.setup(shunt_resistor, integration_time_bus, integration_time_shunt, oversampling_ratio, buffer_samples_count=None, buffer_is_circular=False, absolute_timestamps=False, high_resolution=True)

   The ``shunt_resistor`` (:math:`R_{shunt}` [:math:`\mu\Omega`]),
   ``integration_time_bus`` (:math:`T_{bus}` [s]), ``integration_time_shunt``
   (:math:`T_{shunt}` [s]) and ``oversampling_ratio`` (:math:`N`) are copied
   into on-board registers inside of the INA226 to be used as described above.
   Please note that there exists a limited set of accepted values for these
   parameters; for the integration times, refer to
   ``IIOINA226Instrument.INTEGRATION_TIMES_AVAILABLE`` and for the
   ``oversampling_ratio``, refer to
   ``IIOINA226Instrument.OVERSAMPLING_RATIOS_AVAILABLE``. If all probes share
   the same value for these attributes, this class provides
   :attr:`BaylibreAcmeInstrument.OVERSAMPLING_RATIOS_AVAILABLE` and
   :attr:`BaylibreAcmeInstrument.INTEGRATION_TIMES_AVAILABLE`.

   The ``buffer_samples_count`` is the size of the IIO buffer expressed **in
   samples**; this is independent of the number of active channels! By default,
   if ``buffer_samples_count`` is not passed, the IIO buffer of size
   :attr:`IIOINA226Instrument.sample_rate_hz` is created meaning that a buffer
   transfer happens roughly every second.

   If ``absolute_timestamps`` is ``False``, the first sample from the
   ``timestamps`` channel is substracted from all the following samples of this
   channel, effectively making its signal start at 0.

   ``high_resolution`` is used to enable a mode where power and current are
   computed offline on the host machine running ``devlib``: even if the user
   asks for power or current channels, they are not enabled in hardware
   (INA226) and instead the necessary voltage signal(s) are enabled to allow
   the computation of the desired signals using the FPU of the host (which is
   very likely to be much more accurate than the fixed-point 16-bit unit of the
   INA226).

   A circular buffer can be used by setting ``buffer_is_circular`` to ``True``
   (directly passed to :class:`iio.Buffer`).

   Each one of the arguments of this method can either be a single value which
   will be used for all probes or a list of values giving the corresponding
   setting for each probe (in the order of ``probe_names`` passed to the
   constructor) with the exception of ``absolute_timestamps`` (as all signals
   are resampled onto a common time signal) which, if passed as an array, will
   be ``True`` only if all of its elements are ``True``.

.. method:: BaylibreAcmeInstrument.reset(sites=None, kinds=None, channels=None)

   :meth:`BaylibreAcmeInstrument.setup` should **always** be called before
   calling this method so that the hardware is correctly configured. Once this
   method has been called, :meth:`BaylibreAcmeInstrument.setup` can only be
   called again once :meth:`BaylibreAcmeInstrument.teardown` has been called.

   This method inherits from :meth:`Instrument.reset`; call
   :meth:`list_channels` for a list of available channels from a given
   instance.

   Please note that the size of the transaction buffer is proportional to the
   number of active channels (for a fixed ``buffer_samples_count``). Therefore,
   limiting the number of active channels allows to limit the required
   bandwidth. ``high_resolution`` in :meth:`BaylibreAcmeInstrument.setup`
   limits the number of active channels to the minimum required.

.. method:: BaylibreAcmeInstrument.start()

   :meth:`BaylibreAcmeInstrument.reset` should **always** be called before
   calling this method so that the right channels are active,
   :meth:`BaylibreAcmeInstrument.stop` should **always** be called after
   calling this method and no other method of the object should be called
   in-between.

   This method starts the sampling process of the active channels. The samples
   are stored but are not available until :meth:`BaylibreAcmeInstrument.stop`
   has been called.

.. method:: BaylibreAcmeInstrument.stop()

   :meth:`BaylibreAcmeInstrument.start` should **always** be called before
   calling this method so that samples are being captured.

   This method stops the sampling process of the active channels and retrieves
   and pre-processes the samples. Once this function has been called, the
   samples are made available through :meth:`BaylibreAcmeInstrument.get_data`.
   Note that it is safe to call :meth:`BaylibreAcmeInstrument.start` after this
   method returns but this will discard the data previously acquired.

   When this method returns, It is guaranteed that the content of at least one
   IIO buffer will have been captured.

   If different sampling frequencies were used for the different probes, the
   signals are resampled to share the time signal with the highest sampling
   frequency.

.. method:: BaylibreAcmeInstrument.teardown()

   This method can be called at any point (unless otherwise specified *e.g.*
   :meth:`BaylibreAcmeInstrument.start`) to deactive any active probe once
   :meth:`BaylibreAcmeInstrument.reset` has been called. This method does not
   affect already captured samples.

The following graph gives a summary of the allowed calling sequence(s) where
each edge means "can be called directly after":

.. graphviz::

   digraph acme_calls {
       rankdir = LR
       bgcolor = transparent

       __init__ -> setup -> reset -> start -> stop -> teardown

       teardown:sw -> setup [style=dashed]
       teardown -> reset [style=dashed]

       stop -> reset [style=dashed]
       stop:nw -> start [style=dashed]

       reset -> teardown [style=dashed]
   }

.. method:: BaylibreAcmeInstrument.get_data(outfile=None)

   Inherited from :meth:`Instrument.get_data`. If ``outfile`` is ``None``
   (default), the samples are returned as a `pandas.DataFrame` with the
   channels as columns. Else, it behaves like the parent class, returning a
   ``MeasurementCsv``.

.. method:: BaylibreAcmeInstrument.add_channel()

   Should not be used as new channels are discovered through the IIO context.

.. method:: BaylibreAcmeInstrument.list_channels()

    Inherited from :meth:`Instrument.list_channels`.

.. attribute:: BaylibreAcmeInstrument.sample_rate_hz
.. attribute:: BaylibreAcmeInstrument.OVERSAMPLING_RATIOS_AVAILABLE
.. attribute:: BaylibreAcmeInstrument.INTEGRATION_TIMES_AVAILABLE

   These attributes return the corresponding attributes of the probes if they
   all share the same value (and are therefore provided to avoid reading from a
   single probe and expecting the others to share this value). They should be
   used whenever the assumption that all probes share the same value for the
   accessed attribute is made. For this reason, an exception is raised if it is
   not the case.

   If probes are active (*i.e.* :meth:`BaylibreAcmeInstrument.reset` has been
   called), only these are read for the value of the attribute (as others have
   been tagged to be ignored). If not, all probes are used.

.. attribute:: BaylibreAcmeInstrument.probes

   Dictionary of :class:`IIOINA226Instrument` instances representing the probes
   connected to the ACME. If provided to the constructor, the keys are the
   ``probe_names`` that were passed.

ACME Probes (IIO Devices)
"""""""""""""""""""""""""

The following class is not supposed to be instantiated by the user code: the
API is provided as the ACME probes can be accessed through the
:attr:`BaylibreAcmeInstrument.probes` attribute.

.. class:: IIOINA226Instrument(iio_device)

   This class is a wrapper for the :class:`iio.Device` class and takes a valid
   instance as ``iio_device``. It is not supposed to be instantiated by the
   user and its partial documentation is provided for read-access only.

.. attribute:: IIOINA226Instrument.shunt_resistor
.. attribute:: IIOINA226Instrument.sample_rate_hz
.. attribute:: IIOINA226Instrument.oversampling_ratio
.. attribute:: IIOINA226Instrument.integration_time_shunt
.. attribute:: IIOINA226Instrument.integration_time_bus
.. attribute:: IIOINA226Instrument.OVERSAMPLING_RATIOS_AVAILABLE
.. attribute:: IIOINA226Instrument.INTEGRATION_TIMES_AVAILABLE

   These attributes are provided *for reference* and should not be assigned to
   but can be used to make the user code more readable, if needed. Please note
   that, as reading these attributes reads the underlying value from the
   hardware, they should not be read when the ACME is active *i.e* when
   :meth:`BaylibreAcmeInstrument.setup` has been called without calling
   :meth:`BaylibreAcmeInstrument.teardown`.


Examples
""""""""

The following example shows a basic use of an ACME at IP address
``ACME_IP_ADDR`` with 2 probes connected, capturing all the channels during
(roughly) 10 seconds at a sampling rate of 613 Hz and outputing the
measurements to the CSV file ``acme.csv``:

.. code-block:: python

   import time
   import devlib

   acme = devlib.BaylibreAcmeNetworkInstrument(hostname=ACME_IP_ADDR,
                                               probe_names=['battery', 'usb'])

   int_times = acme.INTEGRATION_TIMES_AVAILABLE
   ratios    = acme.OVERSAMPLING_RATIOS_AVAILABLE

   acme.setup(shunt_resistor=20000,
              integration_time_bus=int_times[1],
              integration_time_shunt=int_times[1],
              oversampling_ratio=ratios[1])

   acme.reset()
   acme.start()
   time.sleep(10)
   acme.stop()
   acme.get_data('acme.csv')
   acme.teardown()

It is common to have different resistances for different probe shunt resistors.
Furthermore, we may want to have different sampling frequencies for different
probes (*e.g.* if it is known that the USB voltage changes rather slowly).
Finally, it is possible to set the integration times for the bus and shunt
voltages of a same probe to different values. The following call to
:meth:`BaylibreAcmeInstrument.setup` illustrates these:

.. code-block:: python

   acme.setup(shunt_resistor=[20000, 10000],
              integration_time_bus=[int_times[2], int_times[3]],
              integration_time_shunt=[int_times[3], int_times[4]],
              oversampling_ratio=[ratios[0], ratios[1]])

   for n, p in acme.probes.iteritems():
      print('{}:'.format(n))
      print('    T_bus = {} s'.format(p.integration_time_bus))
      print('    T_shn = {} s'.format(p.integration_time_shunt))
      print('    N     = {}'.format(p.oversampling_ratio))
      print('    freq  = {} Hz'.format(p.sample_rate_hz))

   # Output:
   #
   #   battery:
   #       T_bus = 0.000332 s
   #       T_shn = 0.000588 s
   #       N     = 1
   #       freq  = 1087 Hz
   #   usb:
   #       T_bus = 0.000588 s
   #       T_shn = 0.0011 s
   #       N     = 4
   #       freq  = 148 Hz

Please keep in mind that calling ``acme.get_data('acme.csv')`` after capturing
samples with this setup will output signals with the same sampling frequency
(the highest one among the sampling frequencies) as the signals are resampled
to output a single time signal.

.. rubric:: Footnotes

.. [#acme_probe_variants] There exist different variants of the ACME probe (USB, Jack, shunt resistor) but they all use the same probing hardware (the TI INA226) and don't differ from the point of view of the software stack (at any level, including devlib, the highest one)

.. [#acme_name_conflicts] Be careful that in cases where multiple ACME boards are being used, it may be required to manually handle name conflicts
