I wrote a SEN6x driver for periph (PR is here) and I'd like to contribute it to TinyGo as well. I'm currently working on porting it to TinyGo, including:
- Remove
github.com/google/go-cmp/cmp from tests since it uses unimplemented reflect functions
- Remove periph-specific stuff like the
SenseContinuous method
- Avoid heap allocations
- Implement TinyGo's
Sensor interface
Background
The Sensirion SEN6x family includes 6 sensors (SEN62, SEN63C, SEN65, SEN66, SEN68, SEN69C) that support the following measurements, in different combinations depending on the model:
- Particulate matter (PM1.0, PM2.5, PM4, and PM10, with the addition of PM0.5 from the "Read Number Concentration Values" command)
- Relative humidity
- Temperature
- VOC as a Sensirion VOC index value
- NOx as a Sensirion NOx index value
- Formaldehyde (HCHO)
- CO2
Datasheet: https://sensirion.com/media/documents/FAFC548D/693FBB15/PS_DS_SEN6x.pdf
Sensor interface implementation
(I read the entirety of #345 to understand the design.)
I'm considering the mapping from sensor values to drivers.Measurement:
- Particulate matter:
drivers.Concentration I guess? It's measured in μg/m^3, not ppx, but still a concentration
- Relative humidity:
drivers.Humidity
- Temperature:
drivers.Temperature
- VOC index:
drivers.Concentration is the closest of the existing Measurements but this isn't strictly a concentration value
- NOx index: ditto VOC index
- Formaldehyde (HCHO):
drivers.Concentration
- CO2:
drivers.Concentration
I think I'm fine with the mapping above with the exception of particulate matter, which seems different enough from the gas measurements to merit a different measurement type (and it's different from the intent of drivers.Concentration, whose comment says, "Gas or liquid concentration, usually measured in ppm (parts per million)."). Maybe we could add drivers.ParticulateMatter?
The command "Read Measured Values" returns all of these values at once (there is no way to read an individual measurement) so for this sensor the details are academic I guess... But I imagine that we do want a sensor to accurately represent its capabilities, like for composing sensors, right?
To complicate matters, the set of measurements above is actually not the only one. There is also "Read Measured Raw Values" which returns the uncompensated temperature and humidity values and "Read Number Concentration Values" which returns particulate matter in particles/cm^3.
One could imagine a call to Update performing "Read Measured Values", "Read Measured Raw Values", and "Read Number Concentration Values", thereby updating all measurements, but that's wasteful. I expect that most users of this sensor will only use the main measurements returned by "Read Measured Values". They're all I'm using for my project.
I'd like to allow the user to request the raw values and PM number concentrations only if they desire; tripling the number of I2C transactions per Update seems silly.
I can see several options for handling this:
Update only updates the values returned by "Read Measured Values". The others are for niche use cases that the user can implement manually using Device.ReadMeasuredRawValues and Device.ReadNumberConcentrationValues.
- We add a new
Measurement like drivers.Raw or something that tells Update that we want the measurements from "Read Measured Raw Values" and "Read Number Concentration Values". If drivers.Raw is not given we'll only send the "Read Measured Values" command.
- We add a new method (or my driver just implements)
UpdateRaw that handles the raw measurements.
Config
The driver design guide suggests creating a Config struct to capture the device's config and a Configure method to set config on a device. Seems reasonable. It kinda breaks down for the SEN6x though because there's a ton that can be configured:
- Temperature offset parameters
- Temperature acceleration parameters
- VOC algorithm tuning parameters
- VOC algorithm state
- NOx algorithm tuning parameters
- CO2 forced recalibration, which is a process that includes sending config to the device
- Ambient pressure for the CO2 sensor
- Altitude for the CO2 sensor
My periph driver defines structs for each of these (well, for the collections of params, some like pressure are just scalars). Putting them all together and setting them via one Configure method would result in a huge struct and 8 I2C commands per call.
I'm inclined to leave config separated out so that users can configure only what they need to. Thoughts?
I wrote a SEN6x driver for periph (PR is here) and I'd like to contribute it to TinyGo as well. I'm currently working on porting it to TinyGo, including:
github.com/google/go-cmp/cmpfrom tests since it uses unimplementedreflectfunctionsSenseContinuousmethodSensorinterfaceBackground
The Sensirion SEN6x family includes 6 sensors (SEN62, SEN63C, SEN65, SEN66, SEN68, SEN69C) that support the following measurements, in different combinations depending on the model:
Datasheet: https://sensirion.com/media/documents/FAFC548D/693FBB15/PS_DS_SEN6x.pdf
Sensorinterface implementation(I read the entirety of #345 to understand the design.)
I'm considering the mapping from sensor values to
drivers.Measurement:drivers.ConcentrationI guess? It's measured in μg/m^3, not ppx, but still a concentrationdrivers.Humiditydrivers.Temperaturedrivers.Concentrationis the closest of the existingMeasurements but this isn't strictly a concentration valuedrivers.Concentrationdrivers.ConcentrationI think I'm fine with the mapping above with the exception of particulate matter, which seems different enough from the gas measurements to merit a different measurement type (and it's different from the intent of
drivers.Concentration, whose comment says, "Gas or liquid concentration, usually measured in ppm (parts per million)."). Maybe we could adddrivers.ParticulateMatter?The command "Read Measured Values" returns all of these values at once (there is no way to read an individual measurement) so for this sensor the details are academic I guess... But I imagine that we do want a sensor to accurately represent its capabilities, like for composing sensors, right?
To complicate matters, the set of measurements above is actually not the only one. There is also "Read Measured Raw Values" which returns the uncompensated temperature and humidity values and "Read Number Concentration Values" which returns particulate matter in particles/cm^3.
One could imagine a call to
Updateperforming "Read Measured Values", "Read Measured Raw Values", and "Read Number Concentration Values", thereby updating all measurements, but that's wasteful. I expect that most users of this sensor will only use the main measurements returned by "Read Measured Values". They're all I'm using for my project.I'd like to allow the user to request the raw values and PM number concentrations only if they desire; tripling the number of I2C transactions per
Updateseems silly.I can see several options for handling this:
Updateonly updates the values returned by "Read Measured Values". The others are for niche use cases that the user can implement manually usingDevice.ReadMeasuredRawValuesandDevice.ReadNumberConcentrationValues.Measurementlikedrivers.Rawor something that tellsUpdatethat we want the measurements from "Read Measured Raw Values" and "Read Number Concentration Values". Ifdrivers.Rawis not given we'll only send the "Read Measured Values" command.UpdateRawthat handles the raw measurements.Config
The driver design guide suggests creating a
Configstruct to capture the device's config and aConfiguremethod to set config on a device. Seems reasonable. It kinda breaks down for the SEN6x though because there's a ton that can be configured:My periph driver defines structs for each of these (well, for the collections of params, some like pressure are just scalars). Putting them all together and setting them via one
Configuremethod would result in a huge struct and 8 I2C commands per call.I'm inclined to leave config separated out so that users can configure only what they need to. Thoughts?