blob: 64d61827e2979e7becfaf5a59f45c53aa28137db [file] [log] [blame]
.. _seed-0119:
=============
0119: Sensors
=============
.. seed::
:number: 0119
:name: Sensors
:status: Accepted
:proposal_date: 2023-10-18
:cl: 175479
:authors: Yuval Peress
:facilitator: Taylor Cramer
-------
Summary
-------
This SEED proposes that Pigweed provide a full end-to-end sensor solution,
including a shared set of abstractions and communication primitives. This
will allow Pigweed users to build reusable drivers and portable applications.
----------
Motivation
----------
Sensors are part of nearly every embedded device. However, sensor drivers and
application logic are often bespoke. Current clients of Pigweed must write
sensor drivers using various bus APIs. This results in drivers and applications
that are deeply integrated and not reusable. When drivers are custom-built for
particular applications, they frequently omit significant functionality needed
by other potential users.
This solution does not scale.
Introducing a standard sensor driver interface will allow Pigweed users to
build drivers that can be reused across applications, and applications that
are compatible with multiple different drivers.
A second layer of a sensor framework will then be introduced. This secondary
layer will provide functionality required for more advanced applications and
will include support for virtual sensors and multi-client connections per
sensor. It will also be compatible with Android's `CHRE`_.
---------
Prior Art
---------
Sensor APIs are available today on many platforms:
- `Android`_
- `Apple iOS and macOS`_
- `Linux`_
- `Windows`_
- `Zephyr`_
Where possible, we strive to make it possible for Pigweed's Sensor API to
delegate to these platform-level APIs without significant loss of
functionality.
However, where performance, code size, API comprehensibility, or reusability
are of particular concern, priority is given to ensuring that Pigweed-native
sensor drivers and APIs meet the needs of Pigweed applications.
-------------
System Design
-------------
At the high level, the ``pw_sensor`` module will introduce the following
concepts:
- ``Sensor``: a class which will enable a single user to **configure**,
**read**, or **stream** data. The implementation can vary. Some
implementations may call down to an already implemented sensor driver of the
underlying RTOS, others may be software sensors which themselves will open
``Connection``\s to other sensors, or in other cases, the ``Sensor``
implementation will actually perform remote calls to a sensor on another SoC
or board.
- ``Connection``: a class which will enable us to abstract away the single
client concept of the ``Sensor``. A ``Connection`` is owned by some logic
and uses the same configuration as the ``Sensor``. The only difference is
that the Pigweed provided sensor framework will then mux all the
configurations targeting the same sensor into a single configuration. This
single configuration will be used for the actual ``Sensor`` object (see
`pw sensor configuration mux`_ as an example). When data becomes
available, the sensor framework will demux the data and provide it to the
various clients based on the live ``Connection``\s.
.. image:: 0119-pw-sensor/high-level-view.svg
Actually reading the data is a 2 step process. The first is getting the data
from the ``Sensor`` object. The second step is decoding it into something
that's usable. Data from the first step is generally still in register format
and includes some headers from the driver which will allow the ``Decoder`` to
convert the values to the appropriate representation (most likely ``float``).
.. image:: 0119-pw-sensor/data-pipeline.svg
-----------------
The Sensor Driver
-----------------
The sensor driver provides the 3 functionalities listed above (configuring,
reading, and streaming).
Each sensor may assume it has exactly 1 caller. It is up to the application to
leverage the right locking and arbitration mechanisms if a sensor is to be
shared between parts of the code. Doing this keeps the driver implementor's job
much simpler and allows them to focus on performance, testing, and simplicity.
In order to provide consumers of sensor data with more advanced features, a
sensor framework will also be provided and discussed in the following section
:ref:`the sensor framework`.
Asynchronous APIs
-----------------
Bus transactions are asynchronous by nature and often can be set up to use an
interrupt to signal completion. By making the assumption that all reading,
writing, and configuring of the sensor are asynchronous, it's possible to
provide an API which is callable even from an interrupt context. The final
result of any operation of the API will use the ``pw_async`` logic. Doing so
will allow the clients to avoid concerns about the context in which callbacks
are called and having to schedule work on the right thread. Instead, they're
able to rely on the async dispatcher.
Configuring
-----------
Sensors generally provide some variable configurations. In some cases, these
configurations are global (i.e. they apply to the device). An example of such
global configuration might be a FIFO watermark (via a batching duration). In
other cases, the configuration might apply to specific sub-sensors /
measurements. An example of a specific configuration attribute can be the sample
rate which on an Inertial Measurement Unit (IMU) might be an acceleration and
rotational velocity. We can describe each configuration with the following:
- Measurement type: such as acceleration or rotational velocity
- Measurement index: the index of the measurement. This is almost always 0, but
some sensors do provide multiple samples of the same measurement type (range
finders). In which case it's possible that we would need to configure
separate instances of the sensor.
- Attribute: such as the sample rate, scale, offset, or batch duration
- Value: the value associated with the configuration (might be a ``bool``,
``float``, ``uint64_t``, or something else entirely).
Here's an example:
+---------------+----------------+--------+
| Measurement | Attribute | Value |
+-------+-------+----------------+--------+
| Type | Index | | |
+=======+=======+================+========+
| Accel | 0 | Sample Rate | 1000Hz |
+-------+-------+----------------+--------+
| All | 0 | Batch duration | 200ms |
+-------+-------+----------------+--------+
Reading
-------
Reading a sensor involves initiating some I/O which will fetch an unknown amount
of data. As such, the operation will require some ``Allocator`` to be used along
with a possible *Measurement Type* filter to limit the amount of data being
retrieved and stored. When complete, the result will be provided in a
``pw::ConstByteSpan`` which was allocated from the ``Allocator``. This byte span
can be cached or possibly sent over a wire for decoding.
Streaming
---------
Streaming data from a sensor is effectively the same as reading the sensor with
minor considerations. Instead of filtering "what" data we want, we're able to
specify "when" we want the data. This happens in the form of one or more
interrupts. There will be some additional control over the data returned from
the stream; it will come in the form of an operation. 3 operations will be
supported for streams:
- ``Include``: which tells the driver to include any/all associated data with
the trigger. As an example, a batching trigger will include all the data from
the FIFO so it can be decoded later.
- ``Drop``: which tells the driver to get rid of the associated data and just
report that the event happened. This might be done on a FIFO full event to
reset the state and start over.
- ``Noop``: which tells the driver to just report the event and do nothing with
the associated data (maybe the developer wants to read it separately).
.. note::
We do not allow specifying a measurement filter like we do in the reading API
because it would drastically increase the cost of the driver developer.
Imagine a trigger for the stream on an IMU using the batch duration where we
want to only get the acceleration values from the FIFO. This scenario doesn't
make much sense to support since the caller should simply turn off the
gyroscope in the FIFO via the configuration. Having the gyroscope
measurements in the FIFO usually means they will simply be discarded when
read. This puts a very heavy burden on the driver author to place a filter in
the reader logic as well as in the decoder.
Decoder
-------
The decoder provides functionality to peek into the raw data returned from the
``Sensor``. It should implement functionality such as:
- Checking if a measurement type is present in the buffer. If so, how many
:ref:`pw sensor define frame` and indices?
- Checking how much memory will be required to decode the frame header (which
includes information like the base timestamp, frame count, etc) and each frame
of data.
- Decoding frames of data. There will be a hard mapping of a measurement type to
data representation. Example: a measurement type of *Acceleration* will always
decode to a ``struct acceleration_data``.
.. _the sensor framework:
--------------------
The Sensor Framework
--------------------
The sensor framework is an abstraction above the ``Sensor`` class which provides
a superset of features but on a ``Connection`` object. The framework will be a
singleton object and will provide consumers the following:
- List all sensors represented as read-only ``SensorInfo`` objects.
- Ability to open/close connections. When a connection is open, a ``Connection``
object is returned. The connection can be closed by either calling
``Connection::Close()`` or simply calling the ``Connection``\s deconstructor.
Once the sensor framework is linked into the application, ``Sensor`` objects
should not be manipulated directly. Instead, the only direct client of the
``Sensor``\s is the framework. Users can request a list of all the sensors
(``SensorInfo`` objects). Once the client finds the sensor they want to listen
to, they can request a ``Connection`` to be opened to that sensor. A
``Connection`` provides very similar functionality to that of the ``Sensor`` but
is owned by the framework. As an example, a configuration change made on the
``Connection`` will trigger the framework to mux together all the configurations
of all the connections that point to the same ``Sensor``. Once complete, a
single configuration will be selected and set on the ``Sensor``. Similarly, when
the ``Sensor`` produces data, the data will be demuxed and sent to all the open
``Connection``\s.
Virtual Sensors
---------------
This framework provides an interesting way to build portable virtual (soft)
sensors. If the library containing the virtual sensors depends on the framework,
it's possible for the virtual sensors to own connections, configure the sources,
and perform all the necessary signal processing without compromising other
unknown clients of the same sensor (since the framework handles all the
configuration arbitration).
As an example, a hinge angle sensor could accept 2 ``Connection`` objects to
accelerometers in its constructor. When the hinge angle sensor is configured
(such as sample rate) it would pass the configuration down to the connections
and request the same sample rate from the 2 accelerometers.
--------
Glossary
--------
.. _pw sensor define frame:
Frame
A single time slice. Usually this is used to reference a single sample of
multiple sensor measurement types such as an IMU measuring both acceleration
and rotational velocity at the same time.
--------
Examples
--------
.. _pw sensor configuration mux:
Pigweed will provide some default mechanism for muxing together
``Configuration`` objects. Like many other modules and backends in Pigweed, this
mechanism will be overridable by the application. Below is an example of what it
might look like:
- Assume a client requests samples at 1kHz
- Assume a second client requests samples at 1.1kHz
- The resulting sample rate is 1.1kHz, but it's much more likely that the sensor
doesn't support 1.1kHz and will instead be giving both clients 2kHz of
samples. It will then be up to both clients to decimate the data correctly.
.. note::
Decimating 2kHz down to 1.1kHz isn't as simple as just throwing away 9
samples for every 20. What the client is likely to do is use a weighted
average in order to simulate the 1.1kHz. It's likely that Pigweed should at
some point provide a decimation library with a few common strategies which
would help developers with the task.
.. _`Android`: https://developer.android.com/develop/sensors-and-location/sensors/sensors_overview
.. _`Apple iOS and macOS`: https://developer.apple.com/documentation/sensorkit
.. _CHRE: https://source.android.com/docs/core/interaction/contexthub
.. _Linux: https://www.kernel.org/doc/html/v4.14/driver-api/iio/intro.html
.. _Windows: https://learn.microsoft.com/en-us/windows/win32/sensorsapi/the-sensor-object
.. _Zephyr: https://docs.zephyrproject.org/apidoc/latest/group__sensor__interface.html