Hendra Kusumah
Published © GPL3+

AtmosGuard C5: Dual-Band IAQ & Cloud Logger

A xiao esp32c5 based air quality monitor with the capability to connect 5Ghz wifi band

BeginnerFull instructions provided5 hours44
AtmosGuard C5: Dual-Band IAQ & Cloud Logger

Things used in this project

Hardware components

Seeed Studio xiao esp32c5
×1
Grove - CO2 & Temperature & Humidity Sensor (SCD30)
Seeed Studio Grove - CO2 & Temperature & Humidity Sensor (SCD30)
×1
Seeed Studio Grove - Formaldehyde Sensor (SFA30) - HCHO Sensor
×1
Seeed Studio XIAO Expansion Board
Seeed Studio XIAO Expansion Board
×1

Software apps and online services

Arduino IDE
Arduino IDE
Google Sheets
Google Sheets

Story

Read more

Schematics

block diagram

Code

Full code for the project

Arduino
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_SCD30.h>
#include <SensirionI2cSfa3x.h>
#include <U8g2lib.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiManager.h> 

// --- Configuration ---
const char* googleScriptURL = "https://script.google.com/macros/s/AKfycbzyxuc0YlxeMAwmiETb3lmHG30eD9KtVkH_YVTAO0IiyrJAOiGTJc_4feYuMRkoJ2yg/exec"; 

Adafruit_SCD30 scd30;
SensirionI2cSfa3x sfa3x;
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

float co2 = 0, temp = 0, hum = 0, hcho = 0;
unsigned long lastEntry = 0;
const long interval = 60000; 

void setup() {
    // 1. Core Startup
    Serial.begin(115200);
    delay(2000); // Give the C5 time to stabilize serial
    Serial.println("\n--- Starting Air Monitor (C5) ---");

 
    
    // 3. Display Init
    u8g2.begin();
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_ncenB08_tr);
    u8g2.drawStr(0, 20, "Starting WiFi...");
    u8g2.sendBuffer();
       // 2. I2C Initialization (Critical for C5)
    Wire.begin(); 
    Wire.setClock(100000);
    // 4. WiFiManager with Timeout (Fixes Boot Loops)
    WiFiManager wm;
    wm.setConfigPortalTimeout(120); // Auto-exit after 2 mins if no one connects
    wm.setConnectTimeout(30);       // Wait 30s for router to respond
    
    if (!wm.autoConnect("AirMonitor-AP")) {
        Serial.println("WiFi Timeout - Continuing to local mode...");
        u8g2.clearBuffer();
        u8g2.drawStr(0, 20, "WiFi Failed");
        u8g2.drawStr(0, 40, "Local Mode Only");
        u8g2.sendBuffer();
        delay(3000);
    } else {
        Serial.println("WiFi Connected!");
    }

    // 5. Sensor Init
    if (!scd30.begin()) { 
        Serial.println("SCD30 Fail - Check Wiring"); 
    } else {
        Serial.println("SCD30 Online");
    }
    
    sfa3x.begin(Wire, SFA3X_I2C_ADDR_5D);
    sfa3x.startContinuousMeasurement();
    Serial.println("SFA3x Online");
}

void sendToSheets(String status) {
    if (WiFi.status() != WL_CONNECTED) return;

    HTTPClient http;
    String url = String(googleScriptURL) + 
                 "?co2=" + String(co2) + 
                 "&hcho=" + String(hcho) + 
                 "&temp=" + String(temp) + 
                 "&hum=" + String(hum) + 
                 "&status=" + status;

    Serial.println("Logging to Google Sheets...");
    if (http.begin(url)) {
        http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
        int httpCode = http.GET();
        if (httpCode > 0) Serial.printf("Sheets Success: %d\n", httpCode);
        else Serial.printf("Sheets Error: %s\n", http.errorToString(httpCode).c_str());
        http.end();
    }
}

void loop() {
    // Sensor Reading
    if (scd30.dataReady() && scd30.read()) {
        co2 = scd30.CO2;
        temp = scd30.temperature;
        hum = scd30.relative_humidity;
    }
    float s_hum, s_temp;
    sfa3x.readMeasuredValues(hcho, s_hum, s_temp);

    // Air Quality Logic
    String statusStr = "GOOD";
    if (co2 > 1500 || hcho > 100) statusStr = "POOR";
    else if (co2 > 1000 || hcho > 60) statusStr = "FAIR";

    // OLED Update
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_ncenB10_tr);
    u8g2.drawStr(0, 12, statusStr.c_str());
    u8g2.drawHLine(0, 16, 128);
    u8g2.setFont(u8g2_font_ncenB08_tr);
    u8g2.setCursor(0, 32); u8g2.print("CO2: "); u8g2.print((int)co2);
    u8g2.setCursor(0, 46); u8g2.print("HCHO: "); u8g2.print(hcho, 1);
    
    if (WiFi.status() == WL_CONNECTED) {
        u8g2.setCursor(0, 62); u8g2.print("WiFi: OK");
    } else {
        u8g2.setCursor(0, 62); u8g2.print("WiFi: OFF");
    }
    u8g2.sendBuffer();

    // Sheets Logging
    if (millis() - lastEntry >= interval) {
        sendToSheets(statusStr);
        lastEntry = millis();
    }

    delay(1000);
}

Credits

Hendra Kusumah
51 projects • 165 followers
Love hacking and making new things from IoT to robotics

Comments