// Works with AirGradient library version 2.4.15 #include #include #include #include #include #include #include //#include "SGP30.h" #include #include #include #include AirGradient ag = AirGradient(); SensirionI2CSgp41 sgp41; VOCGasIndexAlgorithm voc_algorithm; NOxGasIndexAlgorithm nox_algorithm; // time in seconds needed for NOx conditioning uint16_t conditioning_s = 10; // for peristent saving and loading int addr = 4; byte value; // Display bottom right U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // Replace above if you have display on top left //U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE); // CONFIGURATION START //set to the endpoint you would like to use String APIROOT = "http://hw.airgradient.com/"; // set to true to switch from Celcius to Fahrenheit boolean inF = false; // PM2.5 in US AQI (default ug/m3) boolean inUSAQI = false; // Display Position boolean displayTop = true; // set to true if you want to connect to wifi. You have 60 seconds to connect. Then it will go into an offline mode. boolean connectWIFI=true; // CONFIGURATION END unsigned long currentMillis = 0; const int oledInterval = 5000; unsigned long previousOled = 0; const int sendToServerInterval = 10000; 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; int buttonConfig=4; int lastState = LOW; int currentState; unsigned long pressedTime = 0; unsigned long releasedTime = 0; String payload = ""; //Custom stuff int port = 9091; ESP8266WebServer server(port); void setup() { Serial.begin(115200); Serial.println("Hello"); u8g2.begin(); //u8g2.setDisplayRotation(U8G2_R0); EEPROM.begin(512); delay(500); buttonConfig = String(EEPROM.read(addr)).toInt(); setConfig(); updateOLED2("Press Button", "Now for", "Config Menu"); delay(2000); currentState = digitalRead(D7); if (currentState == HIGH) { updateOLED2("Entering", "Config Menu", ""); delay(3000); lastState = LOW; inConf(); } if (connectWIFI) { connectToWifi(); } updateOLED2("Warming Up", "Serial Number:", String(ESP.getChipId(), HEX)); sgp41.begin(Wire); ag.CO2_Init(); ag.PMS_Init(); ag.TMP_RH_Init(0x44); //Custom 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(); sendToServer(); //Custom server.handleClient(); } //Custom void HandleRoot() { server.send(200, "text/plain", GenerateMetrics() ); } 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); } String GenerateMetrics() { String message = ""; String idString = "{id=\"" + String(ESP.getChipId(), HEX) + "\",mac=\"" + WiFi.macAddress().c_str() + "\"}"; //PM message += "# HELP pm02 Particulate Matter PM2.5 value, in micrograms per cubic meter\n"; message += "# TYPE pm02 gauge\n"; message += "pm02"; message += idString; message += String(pm25); message += "\n"; //co2 message += "# HELP rco2 CO2 value, in ppm\n"; message += "# TYPE rco2 gauge\n"; message += "rco2"; message += idString; message += String(Co2); message += "\n"; //Temp message += "# HELP atmp Temperature, in degrees Celsius\n"; message += "# TYPE atmp gauge\n"; message += "atmp"; message += idString; message += String(temp); message += "\n"; //Humidity message += "# HELP rhum Relative humidity, in percent\n"; message += "# TYPE rhum gauge\n"; message += "rhum"; message += idString; message += String(hum); message += "\n"; //NOX message += "# HELP nox Exhaust gases, in ppmv\n"; message += "# TYPE nox gauge\n"; message += "nox"; message += idString; message += String(NOX); message += "\n"; //TVOC message += "# HELP tvoc Total Volatile Organic Compounds, based on relative index 30\n"; message += "# TYPE tvoc gauge\n"; message += "tvoc"; message += idString; message += String(TVOC); message += "\n"; //DONE NOX TVOC pm25 Co2 temp hum return message; } void inConf(){ setConfig(); currentState = digitalRead(D7); if(lastState == LOW && currentState == HIGH) { pressedTime = millis(); } else if(lastState == HIGH && currentState == LOW) { releasedTime = millis(); long pressDuration = releasedTime - pressedTime; if( pressDuration < 1000 ) { buttonConfig=buttonConfig+1; if (buttonConfig>7) buttonConfig=0; } } if (lastState == HIGH && currentState == HIGH){ long passedDuration = millis() - pressedTime; if( passedDuration > 4000 ) { // to do // if (buttonConfig==4) { // updateOLED2("Saved", "Release", "Button Now"); // delay(1000); // updateOLED2("Starting", "CO2", "Calibration"); // delay(1000); // Co2Calibration(); // } else { updateOLED2("Saved", "Release", "Button Now"); delay(1000); updateOLED2("Rebooting", "in", "5 seconds"); delay(5000); EEPROM.write(addr, char(buttonConfig)); EEPROM.commit(); delay(1000); ESP.restart(); // } } } lastState = currentState; delay(100); inConf(); } void setConfig() { if (buttonConfig == 0) { updateOLED2("Temp. in C", "PM in ug/m3", "Display Top"); u8g2.setDisplayRotation(U8G2_R2); inF = false; inUSAQI = false; } if (buttonConfig == 1) { updateOLED2("Temp. in C", "PM in US AQI", "Display Top"); u8g2.setDisplayRotation(U8G2_R2); inF = false; inUSAQI = true; } if (buttonConfig == 2) { updateOLED2("Temp. in F", "PM in ug/m3", "Display Top"); u8g2.setDisplayRotation(U8G2_R2); inF = true; inUSAQI = false; } if (buttonConfig == 3) { updateOLED2("Temp. in F", "PM in US AQI", "Display Top"); u8g2.setDisplayRotation(U8G2_R2); inF = true; inUSAQI = true; } if (buttonConfig == 4) { updateOLED2("Temp. in C", "PM in ug/m3", "Display Bottom"); u8g2.setDisplayRotation(U8G2_R0); inF = false; inUSAQI = false; } if (buttonConfig == 5) { updateOLED2("Temp. in C", "PM in US AQI", "Display Bottom"); u8g2.setDisplayRotation(U8G2_R0); inF = false; inUSAQI = true; } if (buttonConfig == 6) { updateOLED2("Temp. in F", "PM in ug/m3", "Display Bottom"); u8g2.setDisplayRotation(U8G2_R0); inF = true; inUSAQI = false; } if (buttonConfig == 7) { updateOLED2("Temp. in F", "PM in US AQI", "Display Bottom"); u8g2.setDisplayRotation(U8G2_R0); inF = true; inUSAQI = true; } // to do // if (buttonConfig == 8) { // updateOLED2("CO2", "Manual", "Calibration"); // } } 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((temp + 45) * 65535 / 175); compensationRh = static_cast(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() ); } void sendToServer() { if (currentMillis - previoussendToServer >= sendToServerInterval) { previoussendToServer += sendToServerInterval; payload = "{\"wifi\":" + String(WiFi.RSSI()) + (Co2 < 0 ? "" : ", \"rco2\":" + String(Co2)) + (pm25 < 0 ? "" : ", \"pm02\":" + String(pm25)) + (TVOC < 0 ? "" : ", \"tvoc_index\":" + String(TVOC)) + (NOX < 0 ? "" : ", \"nox_index\":" + String(NOX)) + ", \"atmp\":" + String(temp) + (hum < 0 ? "" : ", \"rhum\":" + String(hum)) + "}"; if(WiFi.status()== WL_CONNECTED){ Serial.println(payload); String POSTURL = APIROOT + "sensors/airgradient:" + String(ESP.getChipId(), HEX) + "/measures"; Serial.println(POSTURL); WiFiClient client; HTTPClient http; http.begin(client, POSTURL); http.addHeader("content-type", "application/json"); int httpCode = http.POST(payload); String response = http.getString(); Serial.println(httpCode); Serial.println(response); http.end(); } else { Serial.println("WiFi Disconnected"); } } } // Wifi Manager void connectToWifi() { WiFiManager wifiManager; //WiFi.disconnect(); //to delete previous saved hotspot String HOTSPOT = "AG-" + String(ESP.getChipId(), HEX); updateOLED2("90s to connect", "to Wifi Hotspot", HOTSPOT); wifiManager.setTimeout(90); if (!wifiManager.autoConnect((const char * ) HOTSPOT.c_str())) { updateOLED2("booting into", "offline mode", ""); Serial.println("failed to connect and hit timeout"); delay(6000); } } // 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; };