eCO2 monitoring with SGP30 on a ESP32

OUT OF SCOPE

  • How to install, setup and use Arduino IDE
  • How to install the necessary libraries (#include)
  • MQTT broker and setup

.

BILL OF MATERIAL

  • SGP30

sgp30

  • ESP32 (in my case, I make use of a Wemos ESP-32 module equipped with a 18650 battery holder)
    esp32_18650
  • 4pcs female/female Dupont wires
  • I make use of Home Assistant “Mosquito broker” add-on
  • I make use of Arduino IDE to load sketch below onto ESP32

.

WIRING
SGP-30 <<<—>>> ESP-32
VIN <----------------> VCC
GND <---------------> GND
SCL <----------------> 23
SDA <----------------> 21

.

SKETCH
This is a basic sketch just to show functionality. Read “NOTES” below for accurate readings.

#include <WiFi.h>
#include <Wire.h>
#include <PubSubClient.h>
#include "Adafruit_SGP30.h"

Adafruit_SGP30 sgp;

/* return absolute humidity [mg/m^3] with approximation formula
* @param temperature [°C]
* @param humidity [%RH]
*/
uint32_t getAbsoluteHumidity(float temperature, float humidity) {
    // approximation formula from Sensirion SGP30 Driver Integration chapter 3.15
    const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3]
    const uint32_t absoluteHumidityScaled = static_cast<uint32_t>(1000.0f * absoluteHumidity); // [mg/m^3]
    return absoluteHumidityScaled;
}

// Update these with values suitable for your network.
const char* ssid = “your_ssid_here”;
const char* password = “your_wifi_passphrase_here“;
const char* mqtt_server = “your_mqtt_broker_ip_here“;
#define mqtt_port 1883  // default
#define MQTT_USER “mqtt_username_here”
#define MQTT_PASSWORD “mqtt_password_here”

#define MQTT_SERIAL_PUBLISH_CH "/icircuit/ESP32/serialdata/tx"
#define MQTT_SERIAL_RECEIVER_CH "/icircuit/ESP32/serialdata/rx"

#define tvoc_topic "sensor/tvoc"
#define eco2_topic "sensor/eco2"

WiFiClient wifiClient;
PubSubClient client(wifiClient);

void setup_wifi() {
    delay(10);
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    randomSeed(micros());
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str(),MQTT_USER,MQTT_PASSWORD)) {
      Serial.println("connected");
      //Once connected, publish an announcement...
      client.publish("/icircuit/presence/ESP32/", "hello world");
      // ... and resubscribe
      client.subscribe(MQTT_SERIAL_RECEIVER_CH);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void callback(char* topic, byte *payload, unsigned int length) {
    Serial.println("-------new message from broker-----");
    Serial.print("channel:");
    Serial.println(topic);
    Serial.print("data:"); 
    Serial.write(payload, length);
    Serial.println();
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  if (! sgp.begin()){
    Serial.println("Sensor not found :(");
    while (1);
  }
  Serial.print("Found SGP30 serial #");
  Serial.print(sgp.serialnumber[0], HEX);
  Serial.print(sgp.serialnumber[1], HEX);
  Serial.println(sgp.serialnumber[2], HEX);

  // If you have a baseline measurement from before you can assign it to start, to 'self-calibrate'
  //sgp.setIAQBaseline(0x8E68, 0x8F41);  // Will vary for each sensor!
  
  Serial.setTimeout(500);// Set time out for 
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
  reconnect();
}

int counter = 0;
long lastMsg = 0;
void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  long now = millis();
  if (now - lastMsg > 1000) {
    lastMsg = now;
  
  // put your main code here, to run repeatedly:
  // If you have a temperature / humidity sensor, you can set the absolute humidity to enable the humditiy compensation for the air quality signals
  //float temperature = 22.1; // [°C]
  //float humidity = 45.2; // [%RH]
  //sgp.setHumidity(getAbsoluteHumidity(temperature, humidity));

    if (! sgp.IAQmeasure()) {
      Serial.println("Measurement failed");
      return;
    }
    Serial.print("TVOC "); Serial.print(sgp.TVOC); Serial.print(" ppb\t");
    Serial.print("eCO2 "); Serial.print(sgp.eCO2); Serial.println(" ppm");
    client.publish(tvoc_topic, String(sgp.TVOC).c_str(), true);
    client.publish(eco2_topic, String(sgp.eCO2).c_str(), true);

    if (! sgp.IAQmeasureRaw()) {
      Serial.println("Raw Measurement failed");
      return;
    }
    Serial.print("Raw H2 "); Serial.print(sgp.rawH2); Serial.print(" \t");
    Serial.print("Raw Ethanol "); Serial.print(sgp.rawEthanol); Serial.println("");

    counter++;
    if (counter == 30) {
      counter = 0;

      uint16_t TVOC_base, eCO2_base;
      if (! sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) {
        Serial.println("Failed to get baseline readings");
        return;
      }
      Serial.print("****Baseline values: eCO2: 0x"); Serial.print(eCO2_base, HEX);
      Serial.print(" & TVOC: 0x"); Serial.println(TVOC_base, HEX);
    }
  }
}

.

HOME ASSISTANT: CONFIGURATION.YAML

sensor:
  - platform: mqtt
    name: "TVOC"
    state_topic: "sensor/tvoc"
    qos: 0
    unit_of_measurement: "ppb"
  - platform: mqtt
    name: "eCO2"
    state_topic: "sensor/eco2"
    qos: 0
    unit_of_measurement: "ppm"

After restarting Home Assistant, the two entities (sensor.eco2 and sensor.eco2) should be available under “Developer Tools” --> “States”.

.

NOTES
For precise and stable measurements (according to manufacture):

  1. Sample rate 1Hz that is, 1 sample per second.
  2. Provide humidity and temperature values. Note the absoluteHumidity variable declaration in the sketch above.
  3. Set the baseline values on SGP30. See the remarks on the code. I find stable values after 4.5 hours running.

.

SOME EXAMPLES
eco2 tvoc

3 Likes

This is already available under ESPHome although the calibration code is currently incorrect but has been corrected ready for the next update.

I’ve been running it for a couple of months without a calibration.

1 Like

Hi @Tamadite just a quick question about wiring. SGP30 input is between 1.62v and 1.98v but esp32 vout is 3.3v How have you done?

Not sure about the version you have but mine manages direct supply from the ESP. Not exactly the model I use but here I attach a picture about it.

Nice project, I like your use of MQTT for that.

Sensirion has a number of really good high quality environmental sensors. I thought about building a small air quality station myself when I have some time, using some of their sensors. But I wonder how precise the eCO2 measurements are compared to a real CO2 sensor. Do you have any data on that ? Would be interesting to compare.

Not everybody wants to use ESPHome though, some people prefer having full control over their own firmware and use MQTT instead :slightly_smiling_face:

I’m not sure about its accuracy in comparison to a real/expensive/life-limited CO2 sensor. I wish I could have one. My idea was to use it for reading plant CO2 production (personal interest/hobby). What I can tell is that sometimes it gives high CO2 values without any apparent reason e.g. in the middle of the night or when nobody at home. They are not just random erroneous measurements because you get all consecutive readings forming the peak. The sensor is placed away from the sun in an open area in the living room 1.5 meters aprox. from the floor. I notice a high correlation between sun light and “abnormal” CO2 peaks. My guess is that the sun beams may heat some materials (plastic, fabrics, paints, etc.) that may generate some fumes not detectable (odour) by humans… that’s my wild guess.
FYI, considering air quality I measure PM2.5 and PM10 contributing to https://maps.sensor.community/ with two sensors in two different locations. At the same time, these two sensors are displayed in my HA console.

1 Like

Hi Tamadite,

can you explain how do you intergrate this statistics from https://maps.sensor.community/

Luftdaten in Home Assistant: Luftdaten - Home Assistant