ESP8266 with new co2 sensor
05 Jul 2022Finally I have an update on my ESP8266 based Co2 sensor. In my first build I used a CCS811 sensor board, but it did not work properly in my opinion. So now I have removed the CCS811 and replaced it with the SenseAir S8 co2 sensor and SHT31 temp sensor.
Hardware
- Wemos D1 mini
- 0.96" Oled with 128x64 resolution
- SenseAir S8 co2 sensor (I’m using the 004-0-0053 version)
- SHT31 i2c temp and humidity sensor, AFAIK the SHT30, SHT31 or SHT3x is pretty much the same
Wiring
Preparing the Arduino IDE
I’m still using Arduino IDE, it’s easy to use but we need to install some libraries to use our hardware.
Installing ESP8266 support
Go to preferences (file -> preferences)
Add this URL
https://arduino.esp8266.com/stable/package_esp8266com_index.json
to the Additional Boards manager URLs:
and click OK
Go to Tools -> Board -> Boards manager...
Search for ESP8266 and press install button for the “ESP8266 by ESP8266 Community“
When the installation is complete you will be able to find the ESP8266 boards under Tools -> Board -> ESP8266
There is a Blink test sketch under File -> Examples if you want to test your board. There are lots of other examples there as well.
Installing SenseAir S8 libraries
The SenseAir S8 libraries I’m using comes from jcomas/S8_UART with good install instructions already.
Installing libraries
To install the libraries go to Tools -> Manage libraries...
Libraries needed
- Adafruit GFX Library, Graphics stuff for the display
- Adafruit SSD1306, For the display
- Adafruit SHT31, Temp and humidity sensor
- PubSubClient, Mqtt client
The Code
You can find the complete code at the bottom, I will only go through the changes I have made to my code. Most of the Display and MQTT code is pretty much the same
Functions for old sensors removed
because I have removed the crappy eCO2 sensor I was using, I have to remove all the code related to that sensor.
But because I had split my code into sub-routines for reading my sensor and others for updating display or sending to my MQTT server etc. I quickly just removed those two functions
void read_ccs811(); // Read CCS811 sensor data
void read_hdc1080(); // Read HDC1080 sensor data
Just remember to remove the actual function as well, not just the definition at the top.
Removed code from setup()
Some lines of code to initialize the old sensors was removed from the setup() function
// hdc1080 info
hdc1080.begin(0x40);
delay(20);
Serial.println("------------------------");
Serial.print("Manufacturer ID=0x");
Serial.println(hdc1080.readManufacturerId(), HEX); // 0x5449 ID of Texas Instruments
delay(20);
Serial.print("Device ID=0x");
Serial.println(hdc1080.readDeviceId(), HEX); // 0x1050 ID of the device
delay(20);
Serial.println("------------------------");
Serial.print("setup: ccs811 lib version: "); Serial.println(CCS811_HW_VERSION);
Serial.println("------------------------");
Wire.begin();
// Enable CCS811
if ( !ccs811.begin() ) {
Serial.println("setup: CCS811 sensor start error.");
}
New and updated functions for new sensors
New code added for the new sensors
Sensor initialization in setup()
First we need to initialize the sensors in the setup() function
// initialize the SenseAir S8 co2 sensor
S8_Serial.begin(S8_BAUDRATE);
uartS8 = new S8_UART(S8_Serial);
// Try to get the firmware version as a way to verify that we have a sensor attached
uartS8->get_firmware_version(sensorS8.firm_version);
int len = strlen(sensorS8.firm_version);
if(len==0) {
Serial.println("no SenseAir S8");
}
// just print some info to serial for debugging purpose
sensorS8.sensor_type_id = uartS8->get_sensor_type_ID();
sensorS8.abc_period = uartS8->get_ABC_period(); // ABC period is the self calibration delay
Serial.print("SenseAir S8 ID: 0x"); printIntToHex(sensorS8.sensor_type_id, 3); Serial.println("");
Serial.print("SenseAir S8 ABC Period: "); Serial.print(sensorS8.abc_period); Serial.println("");
// Initialize the SHT31 sensor
if( ! sht31.begin(0x44)) {
Serial.println("no SHT3x sensor");
}
New sensor read function
void read_sensorS8() {
sensorS8.co2 = uartS8->get_co2();
// keep old value if the value from the sensor is 0 or lower
if (sensorS8.co2 > 0 ) {
data.CO2 = sensorS8.co2;
}
}
void read_SHT31() {
float t = sht31.readTemperature();
float h = sht31.readHumidity();
data.temp = t;
data.hum = h;
}
Changed code segments
Because the new sensors is using other types my AQS_SensorData has to change
/*
* Sensor data
*/
struct AQS_SensorData {
int16_t CO2;
float temp;
float hum;
AQS_SensorData() : CO2(0), temp(0), hum(0) {}
};
Because the AQS_SensorData was changed the mqtt_publish(), dumpToSerial() and updateDisplay() was updated to reflect those changes
The new updated loop() function
void loop() {
read_sensorS8();
read_SHT31();
mqtt_publish();
dumpToSerial();
updateDisplay();
delay(5000);
}
Complete code
/*
* Air Quality Sensor (AQS)
*
* Written for D1 Mini (ESP8266), Adafruit_SSD1306 compatible display, Senseair S8 C02 sensor.
*
* */
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_SHT31.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "AQS_Config.h"
#include <SoftwareSerial.h>
#include "s8_uart.h" // https://github.com/jcomas/S8_UART
// SenseAir S8
#define RX_PIN D5
#define TX_PIN D6
SoftwareSerial S8_Serial(RX_PIN, TX_PIN);
S8_UART *uartS8;
S8_sensor sensorS8;
// SHT3x i2c temp sensor
Adafruit_SHT31 sht31 = Adafruit_SHT31();
/*
* Sensor data
*/
struct AQS_SensorData {
int16_t CO2;
float temp;
float hum;
AQS_SensorData() : CO2(0), temp(0), hum(0) {}
};
/*
* Global variables for the sensor
*/
AQS_Config cfg; // AirQuality Sensor settings, wifi, name etc.
AQS_SensorData data; // AirQuality Sensor global data storage for sensor input
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET 0 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
IPAddress ip;
// Initializes the espClient. You should change the espClient name if you have multiple ESPs running in your home automation system
WiFiClient espClient;
PubSubClient mqtt(espClient);
void mqtt_connect(); // Connect to MQTT server
void mqtt_callback(char* topic, byte* payload, unsigned int length); // receive messages
void mqtt_publish(); // Publish sensor data to MQTT
void updateDisplay(); // Update display with sensor data
void dumpToSerial(); // Print sensor data to serial port
// Somewhere to store a MQTT message
char mqtt_msg[64];
char new_mqtt_msg = false;
void setup() {
// Local settings for this sensor
cfg.set_name("AQS");
cfg.set_id(3);
cfg.set_ssid("BadassWIFI");
cfg.set_wifi_password("CrazyPassword");
cfg.set_mqtt_server("192.168.0.41");
cfg.set_mqtt_delay(2);
// Pin state
pinMode(D7, OUTPUT);
digitalWrite(D7, HIGH);
Serial.begin(115200);
delay(500); Serial.println();
Serial.print("Hostname: "); Serial.println(cfg.get_hostname());
Serial.print("Mqtt str: "); Serial.println(cfg.get_mqtt_str());
Serial.print("WIFI: "); Serial.println(cfg.get_ssid());
Serial.print("Password: "); Serial.println(cfg.get_wifi_password());
Serial.print("MQTT srv: "); Serial.println(cfg.get_mqtt_server());
Serial.print("MQTT port: "); Serial.println(cfg.get_mqtt_port());
Serial.print("MQTT Delay: "); Serial.println(cfg.get_mqtt_delay());
// by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 128x32)
Wire.begin(); // WHY?
// initialize the SenseAir S8 co2 sensor
S8_Serial.begin(S8_BAUDRATE);
uartS8 = new S8_UART(S8_Serial);
uartS8->get_firmware_version(sensorS8.firm_version);
int len = strlen(sensorS8.firm_version);
if(len==0) {
Serial.println("no SenseAir S8");
}
sensorS8.sensor_type_id = uartS8->get_sensor_type_ID();
sensorS8.abc_period = uartS8->get_ABC_period();
Serial.print("SenseAir S8 ID: 0x"); printIntToHex(sensorS8.sensor_type_id, 3); Serial.println("");
Serial.print("SenseAir S8 ABC Period: "); Serial.print(sensorS8.abc_period); Serial.println("");
if( ! sht31.begin(0x44)) {
Serial.println("no SHT3x sensor");
}
// Show image buffer on the display hardware.
// Since the buffer is intialized with an Adafruit splashscreen
// internally, this will display the splashscreen.
display.display();
delay(500);
// Clear the buffer.
display.clearDisplay();
display.display();
// text display tests
display.setTextSize(1);
display.setTextColor(WHITE);
display.print("Connecting to ");
display.print(cfg.get_ssid());
display.display();
// Connect to WIFI
WiFi.hostname(cfg.get_hostname());
WiFi.begin(cfg.get_ssid(), cfg.get_wifi_password());
while( WiFi.status() != WL_CONNECTED) {
delay(500);
display.print(".");
display.display();
}
ip = WiFi.localIP();
// init MQTT
mqtt.setServer(cfg.get_mqtt_server(), cfg.get_mqtt_port());
mqtt.setCallback(mqtt_callback);
}
// Manual calibrate Sensair S8 sensor, only do this when Co2 is known to be approx 400ppm (outside)
void calibrate_sensorS8() {
Serial.println("Manual calibration triggered");
display.setCursor(0,0);
display.clearDisplay();
display.println("Calibration in progress");
display.display();
// Now trigger manual calibration
digitalWrite(D7, LOW);
// delay for 5 seconds in a loop to update the display while waiting
for( int i=0; i<10; i++ ) {
delay(500);
display.print(".");
display.display();
}
// End manual calibration
digitalWrite(D7, HIGH);
}
void read_sensorS8() {
sensorS8.co2 = uartS8->get_co2();
// keep old value if the value from the sensor is 0 or lower
if (sensorS8.co2 > 0 ) {
data.CO2 = sensorS8.co2;
}
}
void read_SHT31() {
float t = sht31.readTemperature();
float h = sht31.readHumidity();
data.temp = t;
data.hum = h;
}
uint32_t mqtt_counter = 0;
void loop() {
if( new_mqtt_msg ) {
Serial.print("MQTT msg: ");
Serial.println(mqtt_msg);
if ( strcmp(mqtt_msg, "calibrate") == 0 ) {
calibrate_sensorS8();
}
new_mqtt_msg = false;
}
read_sensorS8();
read_SHT31();
mqtt_publish();
dumpToSerial();
updateDisplay();
delay(5000);
}
void dumpToSerial() {
Serial.print("CO2: ");
Serial.print(data.CO2);
Serial.print("ppm, ");
Serial.print("Temp:");
Serial.print(data.temp);
Serial.print("C, ");
Serial.print("Humidity:");
Serial.print(data.hum);
Serial.print("%");
Serial.println("");
}
void updateDisplay() {
display.setCursor(0,0);
display.clearDisplay();
display.print("Air Quality Sensor #");
display.print(cfg.get_id());
display.println("\n");
display.print("CO2: ");
display.print(data.CO2);
display.print(" ppm\n");
display.print("Temp: ");
display.print(data.temp);
display.print("C\n");
display.print("Humidity: ");
display.print(data.hum);
display.print("%\n");
display.print("\n\nIP: ");
display.println(ip);
display.display();
}
void mqtt_publish() {
if (!mqtt.connected()) {
mqtt_connect();
} else {
if(!mqtt.loop()) {
Serial.println("mqtt.loop failed");
}
}
if ( mqtt.connected() && mqtt_counter <= 1 ) {
Serial.println("Publish MQTT message");
char buffer[16];
char msg[128];
dtostrf(data.CO2, 1, 4, buffer);
sprintf(msg, "%s/CO2", cfg.get_mqtt_str());
mqtt.publish(msg, buffer);
dtostrf(data.temp, 1, 4, buffer);
sprintf(msg, "%s/Temperature", cfg.get_mqtt_str());
mqtt.publish(msg, buffer);
dtostrf(data.hum, 1, 4, buffer);
sprintf(msg, "%s/Humidity", cfg.get_mqtt_str());
mqtt.publish(msg, buffer);
// The loop() has a 5 sec delay, so it will run approx 60/5 times per minute
mqtt_counter = (60 / 5) * cfg.get_mqtt_delay();
} else {
mqtt_counter--;
}
}
void mqtt_connect() {
// Loop until we're reconnected
while (!mqtt.connected()) {
Serial.print("Attempting MQTT connection... ");
if ( mqtt.connect(cfg.get_hostname()) ) {
Serial.println("MQTT connected");
mqtt.publish(cfg.get_mqtt_str(), "online");
char msg[50];
sprintf(msg, "%s/cfg/#", cfg.get_mqtt_str());
mqtt.subscribe(msg);
} else {
Serial.print("failed, rc=");
Serial.print(mqtt.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
//Serial.print("Message arrived in topic: ");
//Serial.println(topic);
//Serial.print("Message:");
int i = 0;
for (i = 0; i < length; i++) {
mqtt_msg[i] = payload[i];
//Serial.print((char)payload[i]);
}
mqtt_msg[i] = '\0'; // terminate mqtt message string
new_mqtt_msg = true;
//Serial.println();
//Serial.println("-----------------------");
}