• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_sensor-py:
2
3------------------------
4pw_sensor Python package
5------------------------
6The ``pw_sensor`` Python package provides utilities for generating data and code
7for Pigweed sensor drivers.
8
9.. warning::
10   This package is under development and the APIs are *VERY* likely to change.
11
12Using the package
13-----------------
14Typical users of ``pw_sensor`` begin by writing a YAML description of their
15sensor using the `metadata_schema.json`_ format, e.g.:
16
17.. code-block:: yaml
18
19   deps:
20      - "pw_sensor/channels.yaml"
21      - "pw_sensor/attributes.yaml"
22   compatible:
23      org: "Bosch"
24      part: "BMA4xx"
25   channels:
26      acceleration: {}
27      die_temperature: {}
28
29
30``pw_sensor`` provides a validator which will resolve any 'inherited' properties
31and make the final YAML easier for code generators to consume. The returned
32dictionary uses the `resolved_schema.json`_ format.
33
34Every platform/language may implement their own generator.
35Generators consume the validated (schema-compliant) YAML and may produce
36many types of outputs, such as a PDF datasheet, a C++ abstract class definition,
37or a Zephyr header of definitions describing the sensor.
38
39Describing a sensor
40-------------------
41When describing a sensor from the user's perspective, there are 3 primary points
42of interaction:
43
44#. compatible descriptor
45#. channels
46#. attributes
47#. triggers
48
49.. note::
50   Compatible string in Linux's devicetree are used to detail what a hardware
51   device is. They include a manufacturer and a model name in the format:
52   ``<manufacturer>,<model>``. In order to make this a bit more generic and
53   still functional with devicetree, Pigweed's compatible node consists of 2
54   separate properties instead of a single string: ``org`` and ``part``. This
55   abstracts away the devicetree model such that generators may produce other
56   targeted code. To read more about the compatible property, see
57   `Understanding the compatible Property`_
58
59Both *channels* and *attributes* covered in :ref:`seed-0120`, while the
60*compatible* descriptor allows us to have a unique identifier for each sensor.
61Next, we need a way to describe a sensor in a platform and language agnostic
62way.
63
64What are channels?
65==================
66A channel is something that we can measure from a sensor. It's reasonable to ask
67"why not call it a measurement"? The answer is that a measurement isn't specific
68enough. A single illuminance sensor might provide a lux reading for:
69- Total lux (amount of light per square meter)
70- Red lux (amount of red light per square meter)
71- Green lux (amount of green light per square meter)
72- Blue lux (amount of blue light per square meter)
73- UV lux (amount of UV light per square meter)
74- IR lux (amount of infra-red light per square meter)
75
76All these are a "measurement" of light intensity, but they're different
77channels. When defining a channel we need to provide units. In the example
78above, the units are lux. Represented by the symbol "lx". It's likely that when
79verbose logging is needed or when generating documentation we might want to also
80associate a name and a longer description for the channel. This leaves us with
81the following structure for a channel:
82
83.. code-block:: yaml
84
85   <channel_id>:
86      "name": "string"
87      "description": "string"
88      "units":
89         "name": "string"
90         "symbol": "string"
91
92The current design allows us to define red, green, blue, UV, and IR as
93"sub-channels". While we could define them on their own, having a sub-channel
94allows us to make the units immutable. This means that ``illuminance`` will
95always have the same units as ``illuminance_red``, ``illuminance_green``,
96``illuminance_blue``, etc. These are described with a ``sub-channels`` key that
97allows only ``name`` and ``description`` overrides:
98
99.. code-block:: yaml
100
101   <channel_id>:
102      ...
103      subchannels:
104         red:
105            name: "custom name"
106            description: "custom description"
107
108When we construct the final sensor metadata, we can list the channels supported
109by that sensor. In some cases, the same channel may be available more than once.
110This happens at times with temperature sensors. In these cases, we can use the
111``indicies`` key in the channel specifier of the metadata file. Generally, if
112the ``indicies`` is ommitted, it will be assumed that there's 1 instance of the
113channel. Otherwise, we might have something like:
114
115.. code-block:: yaml
116
117   channels:
118      ambient_temperature:
119         indicies:
120            - name: "-X"
121              description: "temperature measured in the -X direction"
122            - name: "X"
123               description: "temperature measured in the +X direction"
124
125What are attributes?
126====================
127Attributes are used to change the behavior of a sensor. They're defined using
128the ``attributes`` key and are structured similarly to ``channels`` since they
129can usually be measured in some way. Here's an example:
130
131.. code-block:: yaml
132
133   attributes:
134      sample_rate:
135         name: "sample rate"
136         description: "frequency at which samples are collected"
137         units:
138            name: "frequency"
139            symbol: "Hz"
140
141When associated with a ``sensor``, ``attributes`` again behave like ``channels``
142but without the ``indicies``:
143
144.. code-block:: yaml
145
146   compatible: ...
147   channels: ...
148   attributes:
149      sample_rate: {}
150
151What are triggers?
152==================
153Triggers are events that have an interrupt associated with them. We can define
154common triggers which sensors can individually subscribe to. The definition
155looks like:
156
157.. code-block:: yaml
158
159   triggers:
160      fifo_watermark:
161         name: "FIFO watermark"
162         description: "Interrupt when the FIFO watermark has been reached (set as an attribute)"
163
164When associated with a ``sensor``, we simply need to match the right key:
165
166.. code-block:: yaml
167
168   compatible: ...
169   channels: ...
170   attributes: ...
171   triggers:
172      fifo_watermark: {}
173
174The ``Validator`` class
175-----------------------
176The ``Validator`` class is used to take a sensor spec YAML file and expand it
177while verifying that all the information is available. It consists of 2 layers:
1781. Declarations
1792. Definitions
180
181The declaration YAML
182====================
183The declaration YAML files allow projects to define new sensor channels and
184attributes for their drivers. This allows proprietary functionality of sensors
185which cannot be made public. Pigweed will provide some baseline set of channels
186and attributes.
187
188The following YAML file is used to create a sensor which counts cakes. The
189sensor provides the ability to get the total cake count or a separate
190large/small cake count (for a total of 3 channels):
191
192.. code-block:: yaml
193
194   # File: my/org/sensors/channels.yaml
195   channels:
196     cakes:
197       description: "The number of cakes seen by the sensor"
198       units:
199         symbol: "cake"
200       sub-channels:
201         small:
202            description: "The number of cakes measuring 6 inches or less"
203         large:
204            description: "The number of cakes measuring more than 6 inches"
205
206The above YAML file will enable a 3 new channels: ``cakes``, ``cakes_small``,
207and ``cakes_large``. All 3 channels will use a unit ``cake``. A sensor
208implementing this channel would provide a definition file:
209
210.. code-block:: yaml
211
212   # File: my/org/sensors/cake/sensor.yaml
213   deps:
214      - "my/org/sensors/channels.yaml"
215   compatible:
216      org: "myorg"
217      part: "cakevision"
218   channels:
219      cakes: {}
220      cakes_small: {}
221      cakes_large: {}
222
223When validated, the above YAML will be converted to fill in the defined values.
224This means that ``channels/cakes`` will be automatically filled with:
225
226- ``name: "cakes"``: automatically derived from the name sinde the definition
227  did not provide a name.
228- ``description: "The number of cakes seen by the sensor"``: attained from the
229  definition file.
230- ``units``
231   - ``name: "cake"``: derived from the definition's ``symbol`` since ``name``
232     is not explicitly specified
233   - ``symbol: "cake"``: attained from definition file
234
235Output
236======
237The resulting output is verbose and is intended to allow callers of the
238validation function to avoid having to cross reference values. Currently, there
239will be 4 keys in the returned dictionary: ``sensors``, ``channels``,
240``attributes``, and ``triggers``.
241
242The ``sensors`` key is a dictionary mapping unique identifiers generated from
243the sensor's compatible string to the resolved values. There will always be
244exactly 1 of these since each sensor spec is required to only describe a single
245sensor (we'll see an example soon for how these are merged to create a project
246level sensor description). Each ``sensor`` will contain: ``name`` string,
247``description`` description struct, ``compatible`` struct, ``channels``
248dictionary, ``attributes`` dictionary, and ``triggers`` dictionary.
249
250The difference between the ``/sensors/channels`` and ``/channels`` dictionaries
251is the inclusion of ``indicies`` in the former. The ``indicies`` can be thought
252of as instantiations of the ``/channels``. All other channel properties will be
253exactly the same. ``/attributes`` and ``/triggers`` are the same as in
254``/sensors/*``.
255
256Sensor descriptor script
257------------------------
258A descriptor script is added to Pigweed via the ``pw sensor-desc`` subcommand.
259This command allows validating multiple sensor descriptors and passing the
260unified descriptor to a generator.
261
262.. list-table:: CLI Flags
263   :header-rows: 1
264
265   * - Flag(s)
266     - Description
267   * - ``--include-path``, ``-I``
268     - Directories in which to search for dependency files.
269   * - ``--verbose``, ``-v``
270     - Increase the verbosity level (can be used multiple times). Default
271       verbosity is WARNING, so additional flags increase it to INFO then DEBUG.
272   * - ``--generator``, ``-g``
273     - Generator ommand to run along with any flags. Data will be passed into
274       the generator as YAML through stdin.
275   * - ``-o``
276     - Write output to file instead of stdout.
277
278What are the include paths used for?
279====================================
280The sensor descriptor includes a ``deps`` list with file names which define
281various attributes used by the sensor. We wouldn't want to check in absolute
282paths in these lists, so instead, it's possible to list a relative path to the
283root of the project, then add include paths to the tool which will help resolve
284the dependencies. This should look familiar to header file resolution in C/C++.
285
286What is a generator?
287====================
288The sensor descriptor script validates each sensor descriptor file then creates
289a superset of all sensors and channels (making sure there aren't conflicts).
290Once complete, it will call the generator (if available) and pass the string
291YAML representation of the superset into the generator via stdin. Some ideas for
292generators:
293
294- Create a header with a list of all channels, assigning each channel a unique
295  ID.
296- Generate RST file with documentation on each supported sensor.
297- Generate stub driver implementation by knowing which channels and attributes
298  are supported.
299
300Example run (prints to stdout):
301
302.. code-block:: bash
303
304   $ pw --no-banner sensor-desc -I pw_sensor/ \
305     -g "python3 pw_sensor/py/pw_sensor/constants_generator.py --package pw.sensor" \
306     pw_sensor/sensor.yaml
307
308.. _Understanding the compatible Property: https://elinux.org/Device_Tree_Usage#Understanding_the_compatible_Property
309.. _metadata_schema.json: https://cs.opensource.google/pigweed/pigweed/+/main:pw_sensor/py/pw_sensor/metadata_schema.json
310.. _resolved_schema.json: https://cs.opensource.google/pigweed/pigweed/+/main:pw_sensor/py/pw_sensor/resolved_schema.json
311