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