Files
Ano-sys 0df1f47461 + show startup and Wi-Fi status on the OLED
+ fetch data immediately after connecting
+ empty display and a shorter retry interval for failed requests
~ poll normally every 10 seconds and use the configured local endpoint
~ keep previous readings visible and mark them as outdated on failure
~ set the font before drawing status text and tidy serial messages
- generic failure screen and placeholder network settings
2026-10-02 17:22:33 +02:00

243 lines
5.6 KiB
Arduino

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#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 <typename T>
void update_var(T* var, T value){
if(*var == value) return;
*var = value;
updated = true;
}
public:
void set_score(int value){ update_var<int>(&score, value); }
void set_temp(float value){ update_var<float>(&temp, value); }
void set_humid(float value){ update_var<float>(&humid, value); }
void set_co2(int value){ update_var<int>(&co2, value); }
void set_voc(int value){ update_var<int>(&voc, value); }
void set_pm25(int value){ update_var<int>(&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
}