it was fun
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user