Skip to content

Add driver for Sensirion SEN6x environmental sensors #877

Description

@mtraver

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions