Last active
August 1, 2023 19:16
-
-
Save dfsnow/54067b37341f4f4589e53d15c8a37d17 to your computer and use it in GitHub Desktop.
Arduino sketch to export Prometheus metrics from the AG DIY Pro V3.7, based on Jeff Geerling's code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <AirGradient.h> | |
#include <WiFiManager.h> | |
#include <ESP8266WiFi.h> | |
#include <ESP8266WebServer.h> | |
#include <WiFiClient.h> | |
#include <EEPROM.h> | |
#include <SensirionI2CSgp41.h> | |
#include <NOxGasIndexAlgorithm.h> | |
#include <VOCGasIndexAlgorithm.h> | |
#include <U8g2lib.h> | |
AirGradient ag = AirGradient(); | |
SensirionI2CSgp41 sgp41; | |
VOCGasIndexAlgorithm voc_algorithm; | |
NOxGasIndexAlgorithm nox_algorithm; | |
// time in seconds needed for NOx conditioning | |
uint16_t conditioning_s = 10; | |
// Display bottom right | |
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); | |
// CONFIGURATION START | |
// set the device name when printing metrics | |
const char* deviceId = "CHANGE ME"; | |
// hardware options for AirGradient DIY sensor | |
const bool hasTVOC = true; | |
const bool hasPM = true; | |
const bool hasCO2 = true; | |
const bool hasSHT = true; | |
// set to true to switch from Celcius to Fahrenheit | |
const bool inF = true; | |
// PM2.5 in US AQI (default ug/m3) | |
const bool inUSAQI = true; | |
// Display Position | |
const bool displayTop = false; | |
// connection settings | |
const char* ssid = "CHANGE ME"; | |
const char* password = "CHANGE ME"; | |
const int port = 9926; | |
// update frequency of endpoint | |
const int updateFrequency = 5000; | |
// CONFIGURATION END | |
unsigned long currentMillis = 0; | |
const int oledInterval = 5000; | |
unsigned long previousOled = 0; | |
const int sendToServerInterval = 5000; | |
unsigned long previoussendToServer = 0; | |
const int tvocInterval = 1000; | |
unsigned long previousTVOC = 0; | |
int TVOC = 0; | |
int NOX = 0; | |
const int co2Interval = 5000; | |
unsigned long previousCo2 = 0; | |
int Co2 = 0; | |
const int pm25Interval = 5000; | |
unsigned long previousPm25 = 0; | |
int pm25 = 0; | |
const int tempHumInterval = 2500; | |
unsigned long previousTempHum = 0; | |
float temp = 0; | |
int hum = 0; | |
ESP8266WebServer server(port); | |
void setup() { | |
Serial.begin(115200); | |
Serial.println("Hello"); | |
u8g2.setBusClock(100000); | |
u8g2.begin(); | |
//u8g2.setDisplayRotation(U8G2_R0); | |
EEPROM.begin(512); | |
delay(500); | |
updateOLED2("Warming up the", "sensors...", ""); | |
if (hasTVOC) sgp41.begin(Wire); | |
if (hasPM) ag.PMS_Init(); | |
if (hasCO2) ag.CO2_Init(); | |
if (hasSHT) ag.TMP_RH_Init(0x44); | |
// set WiFi mode to client (without this it may try to act as an AP) | |
WiFi.mode(WIFI_STA); | |
// configure hostname | |
if ((deviceId != NULL) && (deviceId[0] == '\0')) { | |
Serial.printf("No Device ID is Defined, Defaulting to board defaults"); | |
} | |
else { | |
wifi_station_set_hostname(deviceId); | |
WiFi.setHostname(deviceId); | |
} | |
// setup and wait for WiFi | |
WiFi.begin(ssid, password); | |
Serial.println(""); | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(500); | |
updateOLED2("Trying to", "connect...", ""); | |
Serial.print("."); | |
} | |
Serial.println(""); | |
Serial.print("Connected to "); | |
Serial.println(ssid); | |
Serial.print("IP address: "); | |
Serial.println(WiFi.localIP()); | |
Serial.print("MAC address: "); | |
Serial.println(WiFi.macAddress()); | |
Serial.print("Hostname: "); | |
Serial.println(WiFi.hostname()); | |
server.on("/", HandleRoot); | |
server.on("/metrics", HandleRoot); | |
server.onNotFound(HandleNotFound); | |
server.begin(); | |
Serial.println("HTTP server started at ip " + WiFi.localIP().toString() + ":" + String(port)); | |
} | |
void loop() { | |
currentMillis = millis(); | |
updateTVOC(); | |
updateOLED(); | |
updateCo2(); | |
updatePm25(); | |
updateTempHum(); | |
server.handleClient(); | |
} | |
void updateTVOC() | |
{ | |
uint16_t error; | |
char errorMessage[256]; | |
uint16_t defaultRh = 0x8000; | |
uint16_t defaultT = 0x6666; | |
uint16_t srawVoc = 0; | |
uint16_t srawNox = 0; | |
uint16_t defaultCompenstaionRh = 0x8000; // in ticks as defined by SGP41 | |
uint16_t defaultCompenstaionT = 0x6666; // in ticks as defined by SGP41 | |
uint16_t compensationRh = 0; // in ticks as defined by SGP41 | |
uint16_t compensationT = 0; // in ticks as defined by SGP41 | |
delay(1000); | |
compensationT = static_cast<uint16_t>((temp + 45) * 65535 / 175); | |
compensationRh = static_cast<uint16_t>(hum * 65535 / 100); | |
if (conditioning_s > 0) { | |
error = sgp41.executeConditioning(compensationRh, compensationT, srawVoc); | |
conditioning_s--; | |
} else { | |
error = sgp41.measureRawSignals(compensationRh, compensationT, srawVoc, | |
srawNox); | |
} | |
if (currentMillis - previousTVOC >= tvocInterval) { | |
previousTVOC += tvocInterval; | |
TVOC = voc_algorithm.process(srawVoc); | |
NOX = nox_algorithm.process(srawNox); | |
Serial.println(String(TVOC)); | |
} | |
} | |
void updateCo2() | |
{ | |
if (currentMillis - previousCo2 >= co2Interval) { | |
previousCo2 += co2Interval; | |
Co2 = ag.getCO2_Raw(); | |
Serial.println(String(Co2)); | |
} | |
} | |
void updatePm25() | |
{ | |
if (currentMillis - previousPm25 >= pm25Interval) { | |
previousPm25 += pm25Interval; | |
pm25 = ag.getPM2_Raw(); | |
Serial.println(String(pm25)); | |
} | |
} | |
void updateTempHum() | |
{ | |
if (currentMillis - previousTempHum >= tempHumInterval) { | |
previousTempHum += tempHumInterval; | |
TMP_RH result = ag.periodicFetchData(); | |
temp = result.t; | |
hum = result.rh; | |
Serial.println(String(temp)); | |
} | |
} | |
void updateOLED() { | |
if (currentMillis - previousOled >= oledInterval) { | |
previousOled += oledInterval; | |
String ln3; | |
String ln1; | |
if (inUSAQI) { | |
ln1 = "AQI:" + String(PM_TO_AQI_US(pm25)) + " CO2:" + String(Co2); | |
} else { | |
ln1 = "PM:" + String(pm25) + " CO2:" + String(Co2); | |
} | |
String ln2 = "TVOC:" + String(TVOC) + " NOX:" + String(NOX); | |
if (inF) { | |
ln3 = "F:" + String((temp* 9 / 5) + 32) + " H:" + String(hum)+"%"; | |
} else { | |
ln3 = "C:" + String(temp) + " H:" + String(hum)+"%"; | |
} | |
updateOLED2(ln1, ln2, ln3); | |
} | |
} | |
void updateOLED2(String ln1, String ln2, String ln3) { | |
char buf[9]; | |
u8g2.firstPage(); | |
u8g2.firstPage(); | |
do { | |
u8g2.setFont(u8g2_font_t0_16_tf); | |
u8g2.drawStr(1, 10, String(ln1).c_str()); | |
u8g2.drawStr(1, 30, String(ln2).c_str()); | |
u8g2.drawStr(1, 50, String(ln3).c_str()); | |
} while ( u8g2.nextPage() ); | |
} | |
String fetchMetrics() { | |
String message = ""; | |
String idString = "{id=\"" + String(deviceId) + "\",mac=\"" + WiFi.macAddress().c_str() + "\"}"; | |
if (hasPM) { | |
message += "# HELP pm02 Particulate Matter PM2.5 value\n"; | |
message += "# TYPE pm02 gauge\n"; | |
message += "pm02"; | |
message += idString; | |
if (inUSAQI) { | |
message += String(PM_TO_AQI_US(pm25)); | |
} else { | |
message += String(pm25); | |
} | |
message += "\n"; | |
} | |
if (hasTVOC) { | |
message += "# HELP tvoc TVOC value, index\n"; | |
message += "# TYPE tvoc gauge\n"; | |
message += "tvoc"; | |
message += idString; | |
message += String(TVOC); | |
message += "\n"; | |
message += "# HELP nox NOX value, index\n"; | |
message += "# TYPE nox gauge\n"; | |
message += "nox"; | |
message += idString; | |
message += String(NOX); | |
message += "\n"; | |
} | |
if (hasCO2) { | |
message += "# HELP rco2 CO2 value, in ppm\n"; | |
message += "# TYPE rco2 gauge\n"; | |
message += "rco2"; | |
message += idString; | |
message += String(Co2); | |
message += "\n"; | |
} | |
if (hasSHT) { | |
message += "# HELP atmp Temperature, in degrees\n"; | |
message += "# TYPE atmp gauge\n"; | |
message += "atmp"; | |
message += idString; | |
if (inF) { | |
message += String((temp* 9 / 5) + 32); | |
} else { | |
message += String(temp); | |
} | |
message += "\n"; | |
message += "# HELP rhum Relative humidity, in percent\n"; | |
message += "# TYPE rhum gauge\n"; | |
message += "rhum"; | |
message += idString; | |
message += String(hum); | |
message += "\n"; | |
} | |
return message; | |
} | |
void HandleRoot() { | |
if (currentMillis - previoussendToServer >= sendToServerInterval) { | |
previoussendToServer += sendToServerInterval; | |
server.send(200, "text/plain", fetchMetrics() ); | |
} | |
} | |
void HandleNotFound() { | |
String message = "File Not Found\n\n"; | |
message += "URI: "; | |
message += server.uri(); | |
message += "\nMethod: "; | |
message += (server.method() == HTTP_GET) ? "GET" : "POST"; | |
message += "\nArguments: "; | |
message += server.args(); | |
message += "\n"; | |
for (uint i = 0; i < server.args(); i++) { | |
message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; | |
} | |
server.send(404, "text/html", message); | |
} | |
// 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