0df1f47461
+ 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
243 lines
5.6 KiB
Arduino
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
|
|
}
|