it was fun

This commit is contained in:
Ano-sys
2026-09-27 00:41:41 +02:00
commit 455aefe8db
2 changed files with 231 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
# Awair Data Grabber
## Disclaimer
This project retrieves sensor data from an Awair Element through its enabled Local API and displays it on an 128x64 OLED. The IdeaSpark ESP8266 board uses different OLED pins than a standard NodeMCU ESP8266.
## Requirements
- ESP8266 Arduino core
- ArduinoJson
- U8g2
## Usage
- enable the Local API for your Awair Element in the Awair Home app
- set `OLED_SCL` and `OLED_SDA` according to your board specification, the current defaults are GPIO 14 and GPIO 12
- set `SSID` and `PASSWD` for a network that can reach the Awair Element
- set the full `SERVER_URL`, for example `http://awair-elem-123456.local/air-data/latest`
- flash the firmware, the sensor data will be displayed after a successful request
+212
View File
@@ -0,0 +1,212 @@
#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 1000
#define SERVER_URL "http://awair-server/air-data/latest"
#define SSID "-"
#define PASSWD "-"
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_failed(void){
panel.clearBuffer();
panel.setFont(u8g2_font_ncenB08_tr);
panel.drawStr(0, 10, "Something failed");
panel.sendBuffer();
}
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();
http.setTimeout(3000);
WiFi.begin(SSID, PASSWD);
while(WiFi.status() != WL_CONNECTED) delay(100);
Serial.println("Connected!");
}
void loop() {
/*
static auto start = millis() - UPDATE_INTERVAL_MS;
auto current = millis();
if(current - start < UPDATE_INTERVAL_MS) return;
start = current;
*/
if(WiFi.status() != WL_CONNECTED){ delay(100); return; }
Serial.println("Requesting data...");
if(request_data(&data) != 0){ panel.show_failed(); }
if(data.has_updated()){
Serial.println("Got new data -> Updating...");
panel.update(data);
data.reset_updated();
}
else Serial.println("Got data but nothing new!");
Serial.println("---------- ---------- ----------\n");
delay(UPDATE_INTERVAL_MS); // simple delay is enough <- no state machine
}