Arduino code to implement a MQTT-enabled air quality sensor using an ESP8266 (NodeMCU) microcontroller, BME280 climate sensor, SGP30 gas sensor, and PMA5003I particulate matter sensor. It is designed for use with Home Assistant and supports auto-discovery of all the measurands.
Acquire hardware in the BOM and connect as per the Hardware Configuration section. Set up the MQTT integration for Home Assistant and enable MQTT Discovery. Update the Software Configuration, compile, and flash the NodeMCU. Optionally monitor the serial port to see the initialization sequence, consisting of checking communication with the sensors, connecting to WiFi and MQTT, and sending the MQTT discovery messages to Home Assistant. Shortly Home Assistant should populate the sensor values from the sensor.
Qty | Part Number | Description | Link |
---|---|---|---|
1 | ESP8266 (NodeMCU v2) | NodeMCU | Amazon |
1 | BME280 | SPI/I2C Temperature/Humidity/Pressure Sensor | Adafruit |
1 | SGP30 | I2C VOC and eCO2 Sensor | Adafruit |
1 | PMAS5003I | I2C Particulate Matter Sensor | Adafruit |
The Fritzing breadboard for the sensor is shown below.
Sensor power comes from the NodeMCU power input (i.e. USB port) and provides +5V to all sensors. Wiring color corresponds to the signal names:
Color | Signal |
---|---|
Red | +5V USB |
Black | Ground |
Blue | SDA |
Yellow | SCL |
Warning The MQTT messages that Home Assistant uses to auto-discover this sensor are bigger than the maximum buffer size defined in the AdaFruit MQTT Library. In order for auto-discovery to work, you must find and change the
MAXBUFFERSIZE
constant in theAdafruit_MQTT.h
file and set it to a larger value like500
. Migrating to the PubSub library may fix this, and is under investigation.
The config.cpp-sample
file should be copied and renamed to config.cpp
, and the following settings filled in (these sensitive values should not be stored in source control):
const char * wifi_ssid = "ssid-iot";
const char * wifi_passwd = "ssid-psk";
Set these to match the SSID and PSK you wish to use to connect this sensor.
const char * mqtt_host = "mqtt.local";
const char * mqtt_user = "sensors";
const char * mqtt_passwd = "sensors";
const char * mqtt_fingerprint = "01 23 45 67 89 AB CD EF 01 23 45 67 89 AB CD EF 01 23 45 67";
const uint16_t mqtt_port = MQTT_DEFAULT_PORT
Set these to match the ACL of your MQTT broker. Anonymous connection to the MQTT broker is not currently supported. TLS connections are supported by setting the mqtt_fingerprint
variable to the SHA1 fingerprint of the X509 certificate served by the MQTT broker. See here for more details. TLS connections can be disabled by commenting out the MQTT_SECURE
definition in config.h
.
Additional settings are in config.h
:
#define READ_BASELINE_INTERVAL 2
#define READ_SENSOR_INTERVAL 1
#define PUBLISH_INTERVAL 30
These define the intervals of the main sensor tasks. READ_BASELINE_INTERVAL
sets the interval in minutes between reading the baseline values of the SGP30 sensor and writing them to EEPROM. The default is to store them every hour as recommended by the datasheet. The baseline values are persisted across resets. READ_SENSOR_INTERVAL
is the polling interval for the sensors themselves, and PUBLISH_INTERVAL
is how often new values are sent to MQTT. These two settings are in seconds and default to polling the sensors once per second and publishing data every 30 seconds.
// #define PUBLISH_ERROR_COUNT
Uncomment this line to send PMS5003I communication error counts to MQTT on every sensor polling interval. By default these are reported along with the baseline settings every hour. Note this is in the code because I did not have a PMS5003I sensor originally and use a second Arduino to perform an I2C to UART bridge to emulate the I2C interface of the PMS5003I. Hopefully the actual I2C board does not have nearly as many communication errors as mine.
#define PMS5003_REPORT_ENV
Comment this line to report the "standard atmosphere" values from the PMS5003 instead of the compensated values.
#define MQTT_TOPIC_BASE "sensor/aq"
This defines the MQTT base topic. MQTT topics have the form ${MQTT_TOPIC_BASE}/${SGP30_SN}/${ENDPOINT}
where SGP30_SN
is the serial number of the SGP30 sensor (used as the module serial number) and ${ENDPOINT}
is the topic endpoint. See the MQTT Endpoints section for more details.
#define MQTT_SECURE
Comment this line to use an insecure connection to the MQTT server.
#define MQTT_PROCESS_MS 100
This sets the timeout for the Arduino_MQTT_Client::processPackets()
call to process topic subscriptions. This gets called in the main loop so should be set relatively short (and definintely shorter than the minimum task interval for sensor polling or reporting).
#define DEBUG
#define DEBUG_BAUD 115200
Comment out the first line to disable debug logging to the serial port, or change DEBUG_BAUD
to the preferred baud rate of your serial terminal (change platformio.ini
as well if you are using the integrated terminal emulator).
The Arduino code depends on several libraries that must be installed first. I had to install BusIO and Unified Sensor manually for PlatformIO to successfully build the code.
Library | Version | Usage |
---|---|---|
Adafruit MQTT Library | ^2.5.0 | MQTT Communication |
Adafruit SGP30 Sensor | ^2.0.0 | SGP30 Driver |
Adafruit BME280 Library | ^2.1.4 | BME280 Driver |
Adafruit PM25 AQI Sensor | ^1.0.6 | PMAS5003I Driver |
Task Scheduler | ^3.3.0 | Arduino Cooperative Multitasking |
EEPROM Rotate | ^0.9.2 | Flash Wear Leveling for NodeMCU |
Adafruit BusIO | ^1.8.2 | Dependency of Adafruit PM25 AQI Sensor Library |
Adafruit Unified Sensor | ^1.1.4 | Dependency of BME280 and SGP30 Sensor Libraries |
My build environment is Visual Studio Code with the PlatformIO plugin. The platformio.ini
file should automatically install the required libraries when building the first time.
There are five MQTT endpoints defined for this sensor:
-
${MQTT_TOPIC_BASE}/${SGP30_SN}/data
: JSON objects containing sensor data are published to this endpoint by the sensor, everyPUBLISH_INTERVAL
seconds. The JSON structure is as follows:{ "t": 29.3, "p": 100794.4, "rh": 37.1, "tvoc": 8, "co2": 400, "pm10": 12, "pm25": 19, "pm100": 22, "particles03": 2163, "particles05": 634, "particles10": 134, "particles25": 11, "particles50": 5, "particles100": 1 }
The reported values include temperature in degrees celsius, pressure in mbar, relative humidity in percent, total VOC concentration in ppb, effective CO2 concentration in ppb, particulate matter concentration (1.0µm, 2.5µm, and 10µm sizes) in µg/m³, and actual particle counts for the 0.3µm, 0.5µm, 1.0µm, 2.5µm, 5.0µm, and 10µm size buckets.
-
${MQTT_TOPIC_BASE}/${SGP30_SN}/status
: JSON objects containing sensor status are published to this endpoint by the sensor, everyREAD_BASELINE_INTERVAL
minutes. The JSON structure is as follows:{ "status": "ONLINE", "sgp30_errors": 0, "pms5003_errors": 69, "bl_tvoc": 37545, "bl_eco2": 37744 }
The
status
field is always set toONLINE
currently. The number of errors reported by the SGP30 and PMS5003 interfaces are reported in their respective fields, and the current SGP30 baseline values are reported as well. -
${MQTT_TOPIC_BASE}/${SGP30_SN}/echo
: This is used as a sensor health check. Data published to this endpoint is sent back on the${MQTT_TOPIC_BASE}/${SGP30_SN}/echo/reply
endpoint. -
${MQTT_TOPIC_BASE}/${SGP30_SN}/echo/reply
: Response endpoint for echo data. -
${MQTT_TOPIC_BASE}/${SGP30_SN}/cmd
: Sensor command interface. Currently only aresetBaselines
command is implemented, which resets the SGP30 baseline values to 0. SendresetBaselines
as a string to this endpoint to reset the baselines.
The sensor provides auto-discovery data for Home Assistant to automatically create entities for the sensor. Not all fields are auto-created, but the following sensors are created:
sensor.aq_{sgp30id}_aqi_errors
: PMS5003 Running Error Count (resets to 0 on sensor reset)sensor.aq_{sgp30id}_baseline_eco2
: Effective CO2 baseline value from SGP30sensor.aq_{sgp30id}_baseline_tvoc
: Total VOC baseline value from SGP30sensor.aq_{sgp30id}_eco2
: Effective CO2 concentration (ppb)sensor.aq_{sgp30id}_humidity
: Relative humidity (%)sensor.aq_{sgp30id}_pm10
: 1.0µm particulate matter concentration (µg/m3)sensor.aq_{sgp30id}_pm25
: 2.5µm particulate matter concentration (µg/m3)sensor.aq_{sgp30id}_pm100
: 10µm particulate matter concentration (µg/m3)sensor.aq_{sgp30id}_pressure
: Atmospheric pressure (mbar)sensor.aq_{sgp30id}_sgp_errors
: SGP30 Running Error Count (resets to 0 on sensor reset)sensor.aq_{sgp30id}_temperature
: Air temperature (°C)sensor.aq_{sgp30id}_tvoc
: Total VOC concentration (ppm)
MIT License
This projects was created in 2021 by Jonathan Krauss (@asymworks).