Sensors
For documentation on which sensors are currently supported and how to configure them, see the User Guide sensors page.
See the Sensor Reference for documentation on the sensor-related source code.
Datastructure
Each sensor returns data of the type SensorData, which can be found in the file common/src/common/datastructure.py. This is a dictionary with values of numerical type and keys of type RecordID which is a union of all values of record_id found in the sensor metadata. See Update the Sensor Metadata for more information.
Adding Sensor Support
The following is a list of instructions for adding support for a new sensor. Before starting, first verify the following:
- The existing sensor interface contained withing the Sensor Reference and the capacities for sensor configuration outlined in the Config will be able to support the sensor. If the sensor requires any dynamic runtime configuration that can't happen within a single Python module, it may not be able to work.
- The code for reading from the sensor that you plan to use is compatible with Micropython.
Update the Sensor Metadata
The first step is to add the sensors to the list of recognized sensors. In the common module, the file common/src/common/sensors.py stores this metadata, and it is split into three parts:
- The
SensorCodeEnumis an enumerated string where each supported sensor gets its own code. It is used to identify the type of sensor when data is uploaded to the database. - The
sensor_metadatadictionary maps values ofSensorCodeEnumto a typed dictionary of the typeSensorMetadata. TheSensorMetadatadictionary has the following attributes:name: A human-readable string name for the sensor, used in the display of the sensor type in the interface. Usually, this will be identical to the value ofSensorCodeEnum, but in case the sensor code is not what is desired to have displayed as the name of the sensor type, thenameattribute allows changing that.values: A list ofSensorValueMetadatatyped dictionaries, which specify the structure of numerical data returned by the sensor and have the following attributes:name: A human readable name of the sensor value. Used for display of the sensor data in the interface. For example, "Temperature" or "CO2 Concentration".record_id: The string key under which this sensor value will be stored in theSensorDatadictionary returned by this sensor. For example, if a sensor has twoSensorValueMetadata, one with arecord_idof"temperature"and the other with arecord_idof"humidity"we can expect theSensorDatadictionary returned by the sensor to have the structure{"temperature": 20, "humidity": 80}. It is important thatrecord_ids be unique amongst the otherSensorValueMetadatafor a particular sensor type as a dictionary can only have one value per key.unit: A string representing the unit system that the measured value is in. Used in the interface to display the data with the proper unit.
- The
RecordIDtype is a union of strings which contains all values ofrecord_idfound in thesensor_metadatadictionary. It is used for type hinting that a value is one of the keys expected withinSensorData. Currently, this is defined as a union of literal strings with theLiteraltype, but in the future it would be best if this were derived directly from thesensor_metadatadictionary.
With this in mind, adding a new sensor to this metadata requires the following:
- Add a new, unique code to
SensorCodeEnum. - Add a new entry to the
sensor_metadatadictionary, ensuring that the full structure ofSensorDatathe sensor will return is accounted for with correct values ofSensorValueMetadata. - If any
record_ids were added to the metadata that weren't in there before, add them to theRecordIDtype.
Implement the SensorDriver
The concrete implementations of the AbstractSensorDriver interface are located in the folder collector/sensors/drivers. The interface itself is very simple and only has two functions:
__init__(self, config: SensorConfig)intakes theSensorConfigobject from the SensorManager and takes care of any required initialization of variables or sub-drivers. Note that both thegpioandattributesdictionaries on the config aren't previously validated, so this function may also ensure that all required keys are present.poll() -> SensorDataexecutes the code required to retrieve data from the sensor and returns a dictionary with the keys as configured insensor_metadata. If this function fails, it should raise an exception, with a helpful error message, as the exception string is what is uploaded to the database.
Additionally, the file collector/sensors/sensor_codes.py contains the sensor_drivers dictionary, which maps all possible values of SensorCodeEnum to their associated drivers.
Therefore, to add a new sensor, the following steps are required:
- Create a new
.pyfile in thecollector/sensors/driversdirectory, and create a class called<SensorType>SensorDriverwhich implements theAbstractSensorDriverclass. - Add the class to the
sensor_driversdictionary in thecollector/sensors/sensor_codes.pyfile with the correct value ofSensorCodeEnumas the key.
Update the Documentation.
Finally, the following steps are required to add the supported sensor to the documentation:
- Add the newly added value of
SensorCodeEnumtodocs/user-guide/collector/sensord.mdwith a description of the type of sensor and a link to the sensor page. - Add a new section under the Configurations header for the sensor. The section should have 3 sub-sections:
- The GPIO section documents each key in the
gpioconfiguration object for theSensorConfigfor this sensor. - The Extra Attributes section documents each key in the
attributesconfiguration object for theSensorConfigfor this sensor. - The Output Structure section documents the expected structure of
SensorDatafor this sensor and should match the configuration insensor_metadata.
- The GPIO section documents each key in the
- Add an example configuration for this sensor to the
config.example.jsondocument located incollector/src.