#include #include #include #include #ifdef U8X8_HAVE_HW_SPI #include #endif #ifdef U8X8_HAVE_HW_I2C #include #endif #define OLED_SCL 14 #define OLED_SDA 12 #define UPDATE_INTERVAL_MS 10000 #define SHORT_UPDATE_INTERVAL_MS (UPDATE_INTERVAL_MS / 10) #define SERVER_URL "http://[IP_ADDRESS]/air-data/latest" #define SSID "[SSID]" #define PASSWD "[PASS]" WiFiClient client; HTTPClient http; struct AwairData{ private: int score = 0; float temp = .0f; float humid = .0f; int co2 = 0; int voc = 0; int pm25 = 0; bool updated = false; template void update_var(T* var, T value){ if(*var == value) return; *var = value; updated = true; } public: void set_score(int value){ update_var(&score, value); } void set_temp(float value){ update_var(&temp, value); } void set_humid(float value){ update_var(&humid, value); } void set_co2(int value){ update_var(&co2, value); } void set_voc(int value){ update_var(&voc, value); } void set_pm25(int value){ update_var(&pm25, value); } int get_score(void) const { return score; } float get_temp(void) const { return temp; } float get_humid(void) const { return humid; } int get_co2(void) const { return co2; } int get_voc(void) const { return voc; } int get_pm25(void) const { return pm25; } void update(int score, float temp, float humid, int co2, int voc, int pm25){ set_score(score); set_temp(temp); set_humid(humid); set_co2(co2); set_voc(voc); set_pm25(pm25); } bool has_updated(void) const { return updated; } void reset_updated(void){ updated = false; } }; class Panel{ private: U8G2_SSD1306_128X64_NONAME_F_SW_I2C panel; public: Panel() : panel(U8G2_R0, OLED_SCL, OLED_SDA, U8X8_PIN_NONE){} void begin(void){ panel.begin(); } void show_outdated(void){ panel.setFont(u8g2_font_ncenB08_tr); panel.drawStr(71, 9, "(outdated)"); panel.sendBuffer(); } void print_header(const char* msg){ panel.clearBuffer(); panel.setFont(u8g2_font_ncenB08_tr); panel.drawStr(0, 9, msg); panel.sendBuffer(); } void print_empty_data(){ update({}); } void update(const AwairData& data){ char buffer[16]; panel.clearBuffer(); panel.setFont(u8g2_font_ncenB08_tr); panel.drawStr(0, 9, "Score:"); snprintf(buffer, sizeof(buffer), "%d", data.get_score()); panel.drawStr(42, 9, buffer); panel.drawHLine(0, 12, 128); panel.setFont(u8g2_font_6x10_tr); panel.drawStr(0, 24, "Temp"); snprintf(buffer, sizeof(buffer), "%.1f C", data.get_temp()); panel.drawStr(0, 35, buffer); panel.drawStr(68, 24, "Humid"); snprintf(buffer, sizeof(buffer), "%.1f %%", data.get_humid()); panel.drawStr(68, 35, buffer); panel.drawStr(0, 47, "CO2"); panel.drawStr(47, 47, "VOC"); panel.drawStr(87, 47, "PM2.5"); snprintf(buffer, sizeof(buffer), "%d", data.get_co2()); panel.drawStr(0, 59, buffer); snprintf(buffer, sizeof(buffer), "%d", data.get_voc()); panel.drawStr(47, 59, buffer); snprintf(buffer, sizeof(buffer), "%d", data.get_pm25()); panel.drawStr(87, 59, buffer); panel.sendBuffer(); } }; bool parseAwairJson(const String& payload, AwairData& data) { JsonDocument doc; auto error = deserializeJson(doc, payload); if(error){ Serial.print("JSON error: "); Serial.println(error.c_str()); return false; } int score = doc["score"] | 0; float temp = doc["temp"] | .0f; float humid = doc["humid"] | .0f; int co2 = doc["co2"] | 0; int voc = doc["voc"] | 0; int pm25 = doc["pm25"] | 0; data.update(score, temp, humid, co2, voc, pm25); return true; } int request_data(AwairData* data){ if(!http.begin(client, SERVER_URL)){ Serial.println("Failed to request URL!"); return 1; } int http_code = http.GET(); int result = 1; if(http_code == HTTP_CODE_OK){ if(parseAwairJson(http.getString(), *data)) result = 0; } else{ Serial.print("Failed GET request: "); Serial.println(http_code, 10); } http.end(); return result; } Panel panel; AwairData data; void setup() { Serial.begin(115200); panel.begin(); panel.print_header("Starting..."); http.setTimeout(3000); Serial.println("\nConnecting to WiFi..."); panel.print_header("Connecting to WiFi..."); WiFi.begin(SSID, PASSWD); while(WiFi.status() != WL_CONNECTED){ delay(100); Serial.print("."); } Serial.println("\nConnected!"); panel.print_header("Connected to WiFi!"); if(request_data(&data) != 0){ panel.print_empty_data(); } else{ Serial.println("Got new data -> Updating..."); panel.update(data); data.reset_updated(); } delay(SHORT_UPDATE_INTERVAL_MS); } void loop() { /* static auto start = millis() - UPDATE_INTERVAL_MS; auto current = millis(); if(current - start < UPDATE_INTERVAL_MS) return; start = current; */ size_t update_interval = UPDATE_INTERVAL_MS; if(WiFi.status() != WL_CONNECTED){ delay(100); return; } Serial.println("Requesting data..."); if(request_data(&data) != 0){ panel.show_outdated(); update_interval = SHORT_UPDATE_INTERVAL_MS; } else if(data.has_updated()){ Serial.println("Got new data -> Updating..."); panel.update(data); data.reset_updated(); } else Serial.println("No new data!"); Serial.println("\n---------- ---------- ----------\n"); delay(update_interval); // simple delay is enough <- no state machine }