
Air Quality Sensors Library
Generic sensor manager, abstractions and bindings of multiple sensors libraries: Honeywell, Plantower, Panasonic, Sensirion, etc. and CO2 sensors. Also it's handling other environment sensors. This library is for general purpose, but also is the sensors library base of CanAirIO project.
For developers you can also check the complete library documentation here
| Don't forget to star ⭐ this repository |
Supported sensors
PM sensors
| Sensor model | UART | I2C | Detection mode | Status |
| Honeywell HPMA115S0 | Yes | — | Auto | DEPRECATED |
| Panasonic SN-GCJA5L | Yes | Yes | Auto | STABLE |
| Plantower models | Yes | — | Auto | STABLE |
| Nova SDS011 | Yes | — | Auto | STABLE |
| IKEA Vindriktning | Yes | — | Select | STABLE |
| IKEA Vindstyrka | No | Yes | Auto | STABLE |
| Sensirion SPS30 | Yes | Yes | Select / Auto | STABLE |
NOTE:
Panasonic via UART on ESP8266 may need to be selected in detection.
CO2 sensors
| Sensor model | UART | I2C | Detection mode | Status |
| Sensirion SCD30 | — | Yes | Auto | STABLE |
| Sensirion SCD4x | — | Yes | Auto | STABLE |
| Sensirion SEN5x | — | Yes | Auto | STABLE |
| Sensirion SGP41 | — | Yes | Auto | TESTING |
| MHZ19 | Yes | — | Select | STABLE |
| CM1106 | Yes | — | Select | STABLE |
| SenseAir S8 | Yes | — | Select | STABLE |
Commercial Air Quality Stations
| Device model | Sensors Supported | Status |
| Airgradient OpenAir Outdoor | CO2, PM, VOC, NOX, T, H | STABLE |
| M5Stack Air Quality Kit | CO2, PM, VOC, T, H | DEVEL |
Environmental sensors supported
| Sensor model | Protocol | Detection mode | Status |
| AM2320 | i2c | Auto | STABLE |
| SHT31 | i2c | Auto | STABLE |
| AHT10 | i2c | Auto | STABLE |
| BME280 | i2c | Auto | STABLE |
| BMP280 | i2c | Auto | STABLE |
| BME680 | i2c | Auto | STABLE |
| DfRobot SEN0469 NH3 | i2c | Auto | TESTING |
| DFRobot SEN0466 CO | i2c | Auto | TESTING |
| DFRobot SEN0472 O3 | i2c | Auto | TESTING |
| DFRobot SEN0471 NO2 | i2c | Auto | TESTING |
| Geiger CAJOE | GPIO | Select | TESTING |
| NoiseSensor (I2C slave) | I2C | Auto | TESTING |
| DHTxx | TwoWire | Select | DISABLED |
NOTE:
DHT22 is supported but is not recommended. Please see the documentation.
Platforms supported
| Platform | Variants | Notes | Status |
| ESP32 | DevKIt and Similar | (recommended) | STABLE |
| ESP32S2 | LilyGo, Devkit, Lolin | (recommended) | STABLE |
| ESP32S3 | LilyGo, XIAO, Devkit | (recommended) | STABLE |
| ESP32C3 | Devkit, AirGradient, Lolin | (recommended) | STABLE |
| ESP32C6 | XIAO | unstable | DEVEL |
| ESP8266 | 12 | D1MINI tested and similar (old) | STABLE |
| Atmelsam | Seeed Wio Terminal | Only works via i2c on left port | STABLE |
| Arduino | Atmel | Some third party libraries fails | IN PROGRESS |
Features
- Unified variables and getters for all sensors
- Auto UART port selection (Hw, Sw, UART1, UART2, etc)
- Multiple i2c sensors and one UART sensor supported at the same time
- Two I2C channel supported (Wire and Wire1)
- Real time registry of sensor units (see multivariable)
- Get vendor names of all devices detected
- Preselected main stream UART pins from popular boards
- Auto config UART port for Plantower, Honeywell and Panasonic sensors
- Unified calibration trigger for all CO2 sensors
- Unified CO2 Altitude compensation
- Unified temperature offset for CO2 and environment sensors
- Automatic I2C NoiseSensor integration with LAeq, PMI and peak/min tracking on ESP32-C3/S2/S3 (outputs in mV with sensor validation)
- Add support for Kelvin and Fahrenheit on environment and CO2 sensors
- Public access to main objects of each library (full methods access)
- Get unit symbol and name and each sub-sensor
- Get the main group type: NONE, PM, CO2 and ENV.
- Basic debug mode support toggle in execution
Full list of all third party libraries supported here
Installation
- Open platformio.ini, a project configuration file located in the root of PlatformIO project.
- Add the following line to the lib_deps option of [env:] section:
hpsaturn/CanAirIO Air Quality Sensors Library@^0.7.7
- Build a project, PlatformIO will automatically install dependencies.
Or via command line:7
pio pkg install --library "hpsaturn/CanAirIO Air Quality Sensors Library@^0.7.7"
Quick implementation
void setOnDataCallBack(voidCbFn cb)
Get sensor data.
Definition: Sensors.cpp:325
void init(u_int pms_type=0, int pms_rx=PMS_RX, int pms_tx=PMS_TX)
All sensors init.
Definition: Sensors.cpp:155
Full implementation
You can review a full implementation on CanAirIO project firmware, but a little brief is the next:
void onSensorDataOk() {
Serial.print(
"PM2.5: " + String(sensors.
getPM25()));
Serial.print(
" CO2: " + String(sensors.
getCO2()));
Serial.print(
" CO2H: " + String(sensors.
getCO2humi()));
Serial.print(
" CO2T: " + String(sensors.
getCO2temp()));
}
void onSensorDataError(const char * msg){
Serial.println("Sensor read error: "+String(msg));
}
void setup() {
delay(500);
}
void loop() {
}
void setTempOffset(float offset)
Set temperature offset for all temperature sensors.
Definition: Sensors.cpp:461
void loop()
Main sensors loop. All sensors are read here, please call it on main loop.
Definition: Sensors.cpp:72
void setCO2AltitudeOffset(float altitude)
set CO2 altitude offset (m)
Definition: Sensors.cpp:290
float getHumidity() const
get humidity % value of environment sensor
Definition: Sensors.cpp:361
uint16_t getPM25() const
get PM2.5 ug/m3 value
Definition: Sensors.cpp:346
void setSeaLevelPressure(float hpa)
set the sea level pressure (hPa)
Definition: Sensors.cpp:312
uint16_t getCO2() const
get CO2 ppm value
Definition: Sensors.cpp:355
float getTemperature() const
get temperature value from environment sensor
Definition: Sensors.cpp:400
void setOnErrorCallBack(errorCbFn cb)
Optional callback for get the sensors errors.
Definition: Sensors.cpp:331
float getCO2humi() const
get humidity % value of CO2 sensor device
Definition: Sensors.cpp:358
void setSampleTime(int seconds)
set loop time interval for each sensor sample
Definition: Sensors.cpp:237
void setDebugMode(bool enable)
Optional for increase the debug level.
Definition: Sensors.cpp:337
float getCO2temp() const
get temperature value from the CO2 sensor device
Definition: Sensors.cpp:387
void detectI2COnly(bool enable)
Forced to enable I2C sensors only. Recommended to use only if you are using a I2C sensor and improve ...
Definition: Sensors.cpp:597
void setTemperatureUnit(TEMPUNIT tunit)
set the temperature type unit
Definition: Sensors.cpp:367
DFRobot Gravity gas sensors
- Library (manufacturer): DFRobot_MultiGasSensor — the PlatformIO dependency is in
unified-lib-deps.ini pointing to that repository.
- Fixed I²C addresses (group 7):
| Sensor | Address | Reference |
| CO | 0x78 | SEN0466 |
| O₃ | 0x79 | SEN0472 |
| NH₃ | 0x7A | SEN0469 |
| NO₂ | 0x7B | SEN0471 |
- Custom addresses: any address can be overridden via
build_flags, e.g.:
build_flags = -D DFROBOT_CO_I2C_ADDR=0x74
- Preheating: the wiki recommends **>5 min** at power-on (and up to 24 h if the sensor has been idle for a long time).
- Compensation: the library applies its own temperature and pressure compensation using external sensors (BME280, etc.) or the DFRobot's own internal temperature if no external sensor is available.
NoiseSensor auto-detection uses the same I2C bus as the rest of the sensors (Wire) and is available on all boards supported by the sensorlib.
NoiseSensor readings
When the NoiseSensor module is detected via I2C on the main bus (Wire), the library exposes dedicated helpers:
getNoise() – instantaneous reading in mV
getNoiseAverage() / getNoisePeak() / getNoiseMin() – LAeq statistics per cycle in mV
getNoiseLegalAverage() / getNoiseLegalMaximum() – legal averages in mV
getNoiseLd()- Day index in db
getNoiseLe()- Evening index in db
getNoiseLn() - Night index in db
getNoiseLden() - Global day-evening-night index in db
getNoiseL90() - Low noise level in db
Multivariable demo
In this demo on two different devices with multiple sensors, you can choose the possible sub sensors units or variables:

In this demo on a simple sketch you could have a dynamic list of variables of multiple sensor brands:

Multivariable alternative implementation
The last version added new getters to have the current status of each unit of each sensor connected to the device in real time. Also you can retrieve the list of device names and other stuff:
For example:
#include <Arduino.h>
#include <Sensors.hpp>
void printSensorsDetected() {
Serial.println("-->[MAIN] Sensors detected count\t: " + String(sensors_count));
Serial.println("-->[MAIN] Sensors units count \t: " + String(units_count));
Serial.print( "-->[MAIN] Sensors devices names\t: ");
int i = 0;
Serial.print(",");
}
Serial.println();
}
void printSensorsValues() {
Serial.println("\n-->[MAIN] Preview sensor values:");
while(unit != UNIT::NUNIT) {
Serial.print("-->[MAIN] " + uName + ": " + String(uValue) + " " + uSymb);
}
}
void onSensorDataOk() {
Serial.println("======= E X A M P L E T E S T =========");
printSensorsDetected();
printSensorsValues();
Serial.println("=========================================");
}
void setup() {
Serial.begin(115200);
delay(100);
}
void loop() {
}
String getUnitSymbol(UNIT unit) const
get the sensor unit symbol
Definition: Sensors.cpp:691
String getSensorName(SENSORS sensor) const
get the sensor name
Definition: Sensors.cpp:625
String getUnitName(UNIT unit) const
get the sensor unit name
Definition: Sensors.cpp:681
uint8_t getUnitsRegisteredCount() const
get device sensors units detected count
Definition: Sensors.cpp:674
float getUnitValue(UNIT unit)
get the sensor unit value (float)
Definition: Sensors.cpp:750
UNIT getNextUnit()
get the next sensor unit available
Definition: Sensors.cpp:697
uint8_t getSensorsRegisteredCount() const
get device sensors detected count
Definition: Sensors.cpp:606
uint8_t * getSensorsRegistered()
get the sensor registry for retrieve the sensor names
Definition: Sensors.cpp:648
Units are registered automatically so they are available for multivariable dashboards alongside the rest of the sensors.
UART detection demo

CanAirIO sensorlib auto configuration demo on Youtube
Wiring
The current version of library supports 3 kinds of wiring connection, UART, i2c and TwoWire, in the main boards the library uses the default pins of each board, but in some special cases the pins are:
UART
Predefined UART
The library has pre-defined some UART pin configs, these are selected at compile time. Maybe you don't need to change anything with your board, and maybe the following alternatives work for you:
| Board model | TX | RX | Notes |
| ESP32GENERIC | 1 | 3 | ESP32 Pio defaults |
| TTGOT7 / ESP32DEVKIT / D1MINI / NODEFINED | 16 | 17 | CanAirIO devices ** |
| TTGO_TDISPLAY | 12 | 13 | |
| M5COREINK | 14 | 13 | |
| TTGO TQ | 18 | 13 | |
| HELTEC | 18 | 17 | |
| WEMOSOLED | 15 | 13 | |
| ESP32PICOD4 | 3 | 1 | |
** These pins are used when you compile your project without specifying any build variable or when your board isn't in the list.
Custom UART
Also you could define a custom UART pins in the init() method and select specific sensors model, like this:
sensors.
init(SENSORS::SDS011,yourRX,yourTX);
I2C (recommended)
We are using the default pins for each board, sometimes the pins are 21,22, please check your board schematic.
TwoWire (deprecated soon)
For now we are using it only for DHT sensors on PIN 23. For more info please review the following lines here.
Examples
PlatformIO (recommended)
We recommend PlatformIO because it is easier than Arduino IDE. For that, please first install PlatformIO and its command line tools (Windows, macOS and Linux), the pio command, then connect your compatible board to the USB and run the following command:
pio run -e esp32 --target upload
Also you can see some examples that have platformio.ini files for your project.
Arduino IDE
Only import the ino file of the sample and install the libraries listed in library.json and this library. Complete list of libraries used here
Arduino CLI
To run the examples, you first need to install arduino-cli or the Arduino IDE with the libraries referenced in lib_deps in the file platformio.ini, because Arduino doesn't install them automatically like PlatformIO. Then put the CanAirIO sensor library in your library directory, you can download it from the releases section.
Also you need to add the alternative links to support the ESP32 boards:
in the .arduino15/arduino-cli.yaml file add:
board_manager:
additional_urls:
- https://arduino.esp8266.com/stable/package_esp8266com_index.json
- https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
From arduino-cli you can run the basic example on an ESP32 board following these steps:
arduino-cli core update-index
arduino-cli core install esp32:esp32:lolin32
arduino-cli compile --fqbn esp32:esp32:lolin32 basic
arduino-cli upload --fqbn esp32:esp32:lolin32:UploadSpeed=115200 -p /dev/ttyUSB0 basic
where basic is the basic example in the examples directory.
Supporting the project
If you want to contribute to the code or documentation, consider posting a bug report, feature request, or a pull request.
When creating a pull request, we recommend that you do the following:
- Clone the repository
- Create a new branch for your fix or feature. For example, git checkout -b fix/my-fix or git checkout -b feat/my-feature.
- Run any clang formatter if it is code, for example using the
vscode formatter. We are using Google style. More info here
- Documenting the PR description or code will be great
- Target your pull request to be merged with the
devel branch
Also you can make a donation, become a patron, or buy a device:

TODO
- [x] Auto detection for UART sensors (Honeywell, Panasonic and Plantower)
- [x] Added SPS30 library with auto UART detection
- [x] Disable/enable logs (debug mode flag)
- [x] Added bme280, bmp280, aht10, sht31, am2320 i2c sensors
- [x] Exposed public sub-libraries objects, sps30, aht10, etc.
- [x] Added old DHT sensors
- [x] Added CO2 sensors: MHZ19, SCD30, CM1106 via UART
- [x] Added SDS011 particle meter
- [x] BME680 support
- [x] Added Sensirion SPS30 and Panasonic SN-GCJA5 via i2c
- [x] Enable/Disable UART detection for force only i2c
- [x] Temperature and Altitude compensation
- [x] SenseAir S8 via UART support
- [x] Multivariable selection (getNextUnit(), getUnitName(), etc)
- [x] Two I2C channel supported for M5Stack Devices (M5StickC tested)
- [x] Added CO, NO2 and NH3 sensors
- [x] Added Geiger sensor support
- [x] New IKEA VINDSTYRKA device support
- [x] Sea level setting for Pressure sensors and others
- [x] Support for second UART port
Projects using this Library
- CanAirIO Device: ESP32 Air quality device for mobile and fixed stations. (PM2.5 and CO2)
- CO2 Gadget: A high-quality CO2 meter with color display. (CO2)
- M5CoreInk Multi Sensor: Wall CO2, T, H, P, Alt sensor with low consumption (30 days)
- (Please add yours)
Credits
Thanks to all collaborators and the CanAirIO community for testing and reports. Visit us on Telegram