Here you go: I made a little sensor (temperature, humidity, air quality, estimated CO2, and barometric pressure plus pressure trend) that is based on Arduino Nano ESP32 and connects via Arduino Cloud.
As usual, I documented this thoroughly: that complete documentation can be fund in the PDF attachment. The appendix may be useful to first-time cloud users. It took me a while to figure out, so maybe it can save others that time.
Web connected home env monitor.pdf (3.9 MB)
Here FWIW is the code, which is also in the PDF:
/*---------------------------------------------------------------------------
HOME AIR QUALITY MONITOR, CONNECTED VIA ARDUINO CLOUD
----------------------------------------------------------------------------
Requires:
- WiFi connection
- Arduino nano ESP32
- DHT22 temp/humidity sensor (connected via a digital pin)
- SGP30 air quality sensor (connected via I2C)
- BMP180 barometer/temp sensor (connected via I2C)
- Three LEDs, three 220R resistors
Author: Michael Willems - michael@willems.ca
Date: 27 Oct 2023
Update History:
0.5 - 29 Oct 2023 Added 3hr barometer trends as per UK Met Office
0.4 - 29 Oct 2021 Added ".0" to divisor to make 1/3 hour trend a float
0.3 - 28 Oct 2023 Added BMP180 pressure/temp2
Added sensor calibration offsets
Added QNH (air pressure)
Added array for three hour pressure history
0.2 - 27 Oct 2023 Read TVOC/eCO2 once a second;
Average the last five values for eCO2 and TVOC.
0.1 - 27 Oct 2023 Initial version
//-------------------------------------------------------------------------
*/
/*
Sketch generated by the Arduino IoT Cloud Thing "Untitled"
https://create.arduino.cc/cloud/things/776d5639-30b7-4443-a72f-0e341cd55e2e
Arduino IoT Cloud Variables description
The following variables are automatically generated and updated when changes are made to the Thing
String baroTrendName;
String status;
float humidity;
float pressureTrend1hr;
float pressureTrend3hr;
float temperature;
int eCO2;
int QNH;
int tvoc;
int uptime;
Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
which are called when their values are changed from the Dashboard.
These functions are generated with the Thing and added at the end of this sketch.
*/
//---------------------------------------------------------------------------
// DECLARATIONS, DEFINITIONS, INCLUDES, ETC
//---------------------------------------------------------------------------
#include "thingProperties.h"
// The DHT (temp/humid) sensor library:
#include <DHT.h>
#define DHTPIN 2 // Digital pin connected to the DHT sensor
#define DHTTYPE DHT22 // DHT 22 (AM2302)
DHT dht(DHTPIN, DHTTYPE);
// The SGP30 (air etc) sensor:
#include "Adafruit_SGP30.h"
Adafruit_SGP30 sgp;
uint32_t tvoclevel; // total volatile compounds
uint32_t eCO2level; // estimated CO2 level
// The trend/averaging history memories:
unsigned long tvocmem[] = {0, 1, 2, 3, 4}; // for averaging last 5 tvoc values
unsigned long eCO2mem[] = {0, 1, 2, 3, 4}; // for averaging last 5 eCO2 values
unsigned long pressuremem[36]; // air pressure memory for 1- & 3-hr trend
// The BMP085 air pressure/temp sensor:
#include <Adafruit_BMP085.h>
Adafruit_BMP085 bmp;
float temp2;
float pressure;
// LEDs etc:
int activeled = 13; // on board LED, flashes 1x/sec to indicate functioning
int redled = 4; // error reading a sensor
int yellowled = 5; // starting up
int greenled = 6; // all good
// Counters:
unsigned long oneseccounter;
unsigned long threeseccounter;
unsigned long fiveminutecounter;
//------ Sensor Calibration Offset values: -------
// this is to adjust tghe individual sensor you use
// also to get a QNH barometer value instead of QFE
//------------------------------------------------
#define bmpPressureOffset 8
#define bmpTempOffset -3
#define dhtTempOffset -0.5
#define dhtHumidOffset -6
//------------------------------------------------
//---------------------------------------------------------------------------
// THE SETUP:
//---------------------------------------------------------------------------
void setup() {
pinMode(activeled, OUTPUT);
pinMode(redled, OUTPUT);
pinMode(yellowled, OUTPUT);
pinMode(greenled, OUTPUT);
digitalWrite (yellowled, HIGH); //start with yellow LED = startup phase
digitalWrite (redled, LOW);
digitalWrite (greenled, LOW);
// Initialize serial and wait for port to open:
Serial.begin(9600);
// This delay gives the chance to wait for a Serial Monitor without blocking if none is found
delay(1500);
//turn on I2C:
Wire.begin();
// Defined in thingProperties.h
initProperties();
// Connect to Arduino IoT Cloud
// ArduinoCloud.begin(ArduinoIoTPreferredConnection, false); <-- this caused compile error
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
/*
The following function allows you to obtain more information
related to the state of network and IoT Cloud connection and errors
the higher number the more granular information you’ll get.
This is for output on the serial port.
The default is 0 (only errors).
Maximum is 4
*/
setDebugMessageLevel(2);
ArduinoCloud.printDebugInfo();
//start dht sensor:
dht.begin();
// Turn on SGP air sensor: if not found, turn on red LED and halt.
if (! sgp.begin()) {
Serial.println("Sensor not found :(");
digitalWrite (yellowled, LOW);
digitalWrite (redled, HIGH);
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);
// Turn on BMP180 sensor: if not found, turn on red LED and halt.
if (!bmp.begin()) {
Serial.println("Could not find a BMP AIR PRESSURE sensor, check wiring!");
digitalWrite (yellowled, LOW);
digitalWrite (redled, HIGH);
while (1) {}
}
// set the timers to start:
oneseccounter = millis();
threeseccounter = millis();
fiveminutecounter = millis();
// Clear air pressure 3-hour memory: set all 36 cells to zero
for (int i = 35; i > 0; i--) {
pressuremem[i] = 0;
}
baroTrendName = "undetermined";
status = "Active";
digitalWrite (yellowled, LOW);
digitalWrite (redled, LOW);
digitalWrite (greenled, HIGH);
} // end of setup
//---------------------------------------------------------------------------
// THE LOOP:
//---------------------------------------------------------------------------
void loop() {
ArduinoCloud.update();
// One second count: the stuff to so every second
if ((millis() - oneseccounter) > 1000) {
//Toggle the on-board 'active' LED:
digitalWrite(activeled, !digitalRead(activeled));
// Toggle the flashing status display on the dashboard:
if (status != " ") {
status = " ";
}
else {
if ( (temperature > 20) && (temperature < 26) && (humidity > 29) && (humidity < 51) ) {
status = "nominal";
}
else {
status = "limits";
}
}
// Read TVOC/eCO2 (1x/sec as recommended by manufacturer):
// If it fails, exit this bit but continue (after turning on red LED)
if (! sgp.IAQmeasure()) {
Serial.println("Measurement failed");
digitalWrite (redled, HIGH);
digitalWrite (greenled, LOW);
return;
}
else {
digitalWrite (redled, LOW);
digitalWrite (greenled, HIGH);
}
// -------Average the last five values of tvoc:------
// First shift all the old ones in array down one:
for (int i = 4; i > 0; i--) {
tvocmem[i] = tvocmem[i - 1];
}
// Then add the most recent one at the top:
tvocmem[0] = sgp.TVOC;
//
// Now calculate average of these last 5:
tvoclevel = ( (tvocmem[0] + tvocmem[1] + tvocmem[2] + tvocmem[3] + tvocmem[4] ) / 5 );
tvoc = tvoclevel; //separate step in case we want to later change the web update frequency
// --------------------------------------------------
// -------Average the last five values of eCO2:------
// First shift all the old ones in array down one:
for (int i = 4; i > 0; i--) {
eCO2mem[i] = eCO2mem[i - 1];
}
// Then add the most recent one at the top:
eCO2mem[0] = sgp.eCO2;
//
// Now calculate average of these last 5:
eCO2level = ( (eCO2mem[0] + eCO2mem[1] + eCO2mem[2] + eCO2mem[3] + eCO2mem[4] ) / 5 );
eCO2 = eCO2level; //separate step in case we want to later change the web update frequency
// --------------------------------------------------
// The uptime, in hours (useful to know and display on the dashboard):
uptime = (int(millis() / 3600000));
if (uptime > 999) {
uptime = 999;
}
// Now reset counter so we can start counting another second:
oneseccounter = millis();
} //end of one second section
// Three seconds count: the stuff to do every 3 seconds
if ((millis() - threeseccounter) > 3000) {
// Read Temp/Humid:
temperature = (dht.readTemperature() + dhtTempOffset);
humidity = (dht.readHumidity() + dhtHumidOffset);
// Read pressure:
pressure = ( (bmp.readPressure() ) + (bmpPressureOffset * 100.0) );
temp2 = (bmp.readTemperature() + bmpTempOffset);
Serial.println(pressure);
Serial.println(temp2);
Serial.println("---------");
QNH = (pressure / 100);
pressureTrend1hr = ((pressure - pressuremem[11]) / 100.0);
pressureTrend3hr = ((pressure - pressuremem[35]) / 100.0);
Serial.println(pressureTrend1hr);
/* if ((pressureTrend1hr > 1.3) || (pressureTrend3hr > 4)) {
baroTrendName = "rising";
}
else if ((pressureTrend1hr < -1.3) || (pressureTrend3hr < -4)) {
baroTrendName = "falling";
}
else {
baroTrendName = "steady";
}
*/
if (pressuremem[35] == 0) {
baroTrendName = "not ready";
}
else if (pressureTrend3hr > 6) {
baroTrendName = "rising v.rapidly";
}
else if (pressureTrend3hr > 3.5) {
baroTrendName = "rising quickly";
}
else if (pressureTrend3hr > 1.5) {
baroTrendName = "rising";
}
else if (pressureTrend3hr > 0.1) {
baroTrendName = "rising slowly";
}
else if (pressureTrend3hr < -6) {
baroTrendName = "falling v.rapidly";
}
else if (pressureTrend3hr < -3.5) {
baroTrendName = "falling quickly";
}
else if (pressureTrend3hr < -1.5) {
baroTrendName = "falling";
}
else if (pressureTrend3hr < -0.1) {
baroTrendName = "falling slowly";
}
else baroTrendName = "steady";
// Now reset counter so we can start counting another 3 seconds:
threeseccounter = millis();
} //end of three seconds section
// Five Minute count (for air pressure trend analysis): the stuff to do 5 mins
if ((millis() - fiveminutecounter) > 300000) {
// First shift all the old ones in array down one:
for (int i = 35; i > 0; i--) {
pressuremem[i] = pressuremem[i - 1];
}
// Then add the most recent one at the top:
pressuremem[0] = pressure;
// So now we have updated the last three hours' values, every five minutes, in the array.
// Note: we UPDATE it every five minutes.
// But we check that against the current air pressure, see above, every three seconds.
// We do this (in spite of slight inaccuracy) so that we will catch a very sudden change
// (eg an approaching tornado or derecho).
// Now reset counter so we can start counting another 5 mins:
fiveminutecounter = millis();
} //end of five minute section
} // end of loop