Skip to content

Instantly share code, notes, and snippets.

@jwalanta
Created May 19, 2022 02:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jwalanta/3f62724c687fca5d7d3b9e3206b236dc to your computer and use it in GitHub Desktop.
Save jwalanta/3f62724c687fca5d7d3b9e3206b236dc to your computer and use it in GitHub Desktop.
AirGradient code with Prometheus metrics
/*
Modified version of the AirGradient code at
https://github.com/airgradienthq/arduino/blob/master/examples/C02_PM_SHT_OLED_WIFI/C02_PM_SHT_OLED_WIFI.ino
## Changes from the original code ##
- Instead of sending the metrics to the server, this version starts a web server and exposes Prometheus
compatible metrics at /metrics
- Instead of cycling the AQI and CO2 metrics on the display every minute, show all of them with smaller font
-=-=-=-=-=-=-=
This is the code for the AirGradient DIY Air Quality Sensor with an ESP8266 Microcontroller.
It is a high quality sensor showing PM2.5, CO2, Temperature and Humidity on a small display and can send data over Wifi.
For build instructions please visit https://www.airgradient.com/diy/
Compatible with the following sensors:
Plantower PMS5003 (Fine Particle Sensor)
SenseAir S8 (CO2 Sensor)
SHT30/31 (Temperature/Humidity Sensor)
Please install ESP8266 board manager (tested with version 3.0.0)
The codes needs the following libraries installed:
"WifiManager by tzapu, tablatronix" tested with Version 2.0.3-alpha
"ESP8266 and ESP32 OLED driver for SSD1306 displays by ThingPulse, Fabrice Weinberg" tested with Version 4.1.0
If you have any questions please visit our forum at https://forum.airgradient.com/
Configuration:
Please set in the code below which sensor you are using and if you want to connect it to WiFi.
You can also switch PM2.5 from ug/m3 to US AQI and Celcius to Fahrenheit
If you are a school or university contact us for a free trial on the AirGradient platform.
https://www.airgradient.com/schools/
Kits with all required components are available at https://www.airgradient.com/diyshop/
MIT License
*/
#include <AirGradient.h>
#include <WiFiManager.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Wire.h>
#include "SSD1306Wire.h"
AirGradient ag = AirGradient();
SSD1306Wire display(0x3c, SDA, SCL);
// update frequency
int updateFreq = 30 * 1000; // 30 seconds
int lastUpdate = 0;
// current values
int currentPM, currentCO2;
TMP_RH currentSHT;
// web server
int port = 80;
ESP8266WebServer server(port);
// logging
String serialStr = "";
void setup() {
Serial.begin(9600);
display.init();
display.flipScreenVertically();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_10);
display.drawString(32, 16, "AirGradient");
display.drawString(32, 26, String(ESP.getChipId(), HEX));
String macAddr = WiFi.macAddress().c_str();
macAddr.replace(":","");
display.drawString(32, 36, macAddr);
display.display();
ag.PMS_Init();
ag.CO2_Init();
ag.TMP_RH_Init(0x44);
connectToWifi();
display.drawString(32, 46, "WiFi OK!");
display.display();
delay(2000);
server.on("/", handleRoot);
server.on("/metrics", handleMetrics);
server.onNotFound(handleNotFound);
server.begin();
}
void gatherData() {
currentPM = ag.getPM2_Raw();
currentCO2 = ag.getCO2_Raw();
currentSHT = ag.periodicFetchData();
}
void displayData() {
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
// labels
display.setFont(ArialMT_Plain_10);
display.drawString(32, 36, "AQI");
display.drawString(32, 52, "CO2");
// values
String line = "";
line = String(int(currentSHT.t * 9 / 5) + 32) + "F " + String(currentSHT.rh) + "%";
display.setFont(ArialMT_Plain_16);
display.drawString(32, 16, line);
line = String(PM_TO_AQI_US(currentPM));
display.setFont(ArialMT_Plain_16);
display.drawString(55, 32, line);
line = String(currentCO2);
display.setFont(ArialMT_Plain_16);
display.drawString(55, 48, line);
display.display();
if (currentCO2 > 1000 || PM_TO_AQI_US(currentPM) > 200) {
for (int i = 0; i < 3; i++) {
delay(500);
display.invertDisplay();
delay(500);
display.normalDisplay();
}
}
}
String composeTextMetrics() {
String message = "";
message += "Device ID: " + String(ESP.getChipId(), HEX) + "\n";
message += "Mac Address: " + String(WiFi.macAddress().c_str()) + "\n";
message += "Temperature: " + String((currentSHT.t * 9 / 5) + 32) + "F (" + String(currentSHT.t) + "C)\n";
message += "Humidity: " + String(currentSHT.rh) + "%\n";
message += "PM2.5: " + String(currentPM) + "\n";
message += "AQI: " + String(PM_TO_AQI_US(currentPM)) + "\n";
message += "CO2: " + String(currentCO2) + "\n";
return message;
}
String composePrometheusMetrics() {
String message = "";
String labels = "{id=\"" + String(ESP.getChipId(), HEX) + "\",mac=\"" + WiFi.macAddress().c_str() + "\"}";
// wifi rssi
message += "# HELP wifi WiFi RSSI\n";
message += "# TYPE wifi gauge\n";
message += "wifi";
message += labels;
message += String(WiFi.RSSI());
message += "\n";
// temp celcius
message += "# HELP tempc Temperature in Celcius\n";
message += "# TYPE tempc gauge\n";
message += "tempc";
message += labels;
message += String(currentSHT.t);
message += "\n";
// temp fahrenheit
message += "# HELP tempf Temperature in Fahrenheit\n";
message += "# TYPE tempf gauge\n";
message += "tempf";
message += labels;
message += String((currentSHT.t * 9 / 5) + 32);
message += "\n";
// humidity
message += "# HELP humidity Relative humidity in percentage\n";
message += "# TYPE humidity gauge\n";
message += "humidity";
message += labels;
message += String(currentSHT.rh);
message += "\n";
// pm2
message += "# HELP pm25 Particulate Matter PM2.5 value\n";
message += "# TYPE pm25 gauge\n";
message += "pm25";
message += labels;
message += String(currentPM);
message += "\n";
// aqi
message += "# HELP aqi Air Quality Index (AQI) value\n";
message += "# TYPE aqi gauge\n";
message += "aqi";
message += labels;
message += String(PM_TO_AQI_US(currentPM));
message += "\n";
// co2
message += "# HELP co2 CO2 value in ppm\n";
message += "# TYPE co2 gauge\n";
message += "co2";
message += labels;
message += String(currentCO2);
message += "\n";
return message;
}
void handleNotFound() {
String message = "404 Not Found\n\n";
server.send(404, "text/plain", message);
}
void handleRoot() {
server.send(200, "text/plain", composeTextMetrics());
}
void handleMetrics() {
server.send(200, "text/plain", composePrometheusMetrics());
}
void loop() {
if (lastUpdate == 0 || (millis() - lastUpdate) > updateFreq) {
// gather data every updateFreq and display
gatherData();
// display data on display
displayData();
// display data on serial
serialStr = String(millis()) + " Updating data";
Serial.println(serialStr);
Serial.println(composeTextMetrics());
lastUpdate = millis();
}
server.handleClient();
}
// Wifi Manager
void connectToWifi() {
WiFiManager wifiManager;
//WiFi.disconnect(); //to delete previous saved hotspot
String HOTSPOT = "AIRGRADIENT-" + String(ESP.getChipId(), HEX);
wifiManager.setTimeout(120);
if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) {
Serial.println("failed to connect and hit timeout");
delay(3000);
ESP.restart();
delay(5000);
}
}
// Calculate PM2.5 US AQI
int PM_TO_AQI_US(int pm02) {
if (pm02 <= 12.0) return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
else if (pm02 <= 35.4) return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
else if (pm02 <= 55.4) return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
else if (pm02 <= 150.4) return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
else if (pm02 <= 250.4) return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
else if (pm02 <= 350.4) return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
else if (pm02 <= 500.4) return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
else return 500;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment