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