1062 lines
28 KiB
C++
1062 lines
28 KiB
C++
//#define DISABLE_WIFI
|
|
#define HW_PROTO_PAPER 1
|
|
#define HW_PROTO_V1 2
|
|
|
|
#ifndef LED_BUILTIN
|
|
#define LED_BUILTIN 2
|
|
#endif
|
|
|
|
#ifndef BUTTON_BUILTIN
|
|
#define BUTTON_BUILTIN 0
|
|
#endif
|
|
|
|
#include <WiFiManager-esp32.h> // https://github.com/admarschoonen/WiFiManager
|
|
#include <dESPatch.h>
|
|
|
|
#include <WiFi.h>
|
|
#include <WiFiMulti.h>
|
|
#include <HTTPClient.h>
|
|
|
|
#include <Adafruit_NeoPixel.h>
|
|
#include "bsec.h"
|
|
|
|
#include "soc/soc.h"
|
|
#include "soc/rtc_cntl_reg.h"
|
|
|
|
#ifndef LED_BUILTIN
|
|
#define LED_BUILTIN 2
|
|
#endif
|
|
|
|
#define NUM_LEDS_PROTO_PAPER 93
|
|
#define DATA_PIN_PROTO_PAPER 5
|
|
#define CONNECT_SW_PIN_PROTO_PAPER BUTTON_BUILTIN
|
|
#define NUM_LEDS_PROTO_V1 29
|
|
#define DATA_PIN_PROTO_V1 14
|
|
#define CONNECT_SW_PIN_PROTO_V1 21
|
|
|
|
static int BUIENRADAR_START_LED = 0;
|
|
static int BUIENRADAR_SKIP_LED = 1;
|
|
static int BUIENRADAR_NUM_LEDS = 12;
|
|
static int PAQI_START_LED = 0;
|
|
static int PAQI_SKIP_LED = 0;
|
|
static int PAQI_NUM_LEDS = 0;
|
|
static int UVI_LED = 0;
|
|
static int AQI_LED = 0;
|
|
static int POLLEN_LED = 0;
|
|
static int IAQI_LED = 0;
|
|
static int CONNECT_SW_PIN = 0;
|
|
static int LIGHT_PIN = 0;
|
|
|
|
#define SHOW_AQI_LED
|
|
#define SHOW_POLLEN_LED
|
|
#define SHOW_UVI_LED
|
|
|
|
#define MIN(x, y) (((x) <= (y)) ? (x) : (y))
|
|
|
|
Adafruit_NeoPixel leds_rgb_proto_paper(NUM_LEDS_PROTO_PAPER, DATA_PIN_PROTO_PAPER, NEO_GRB + NEO_KHZ800);
|
|
Adafruit_NeoPixel leds_rgbw_proto_v1(NUM_LEDS_PROTO_V1, DATA_PIN_PROTO_V1, NEO_RGBW + NEO_KHZ800);
|
|
|
|
const uint8_t bsec_config_iaq[] = {
|
|
#include "config/generic_33v_3s_4d/bsec_iaq.txt"
|
|
};
|
|
|
|
static int ledMapPrecipitation[12] = {0};
|
|
static int ledMapPAQI[12] = {0};
|
|
static int ledMapUVI = 0;
|
|
static int ledMapPollen = 0;
|
|
static int ledMapIAQI = 0;
|
|
static int ledMapAQI = 0;
|
|
|
|
#define SEALEVELPRESSURE_HPA (1013.25)
|
|
#define BME_CS 5
|
|
String output;
|
|
Bsec iaqSensor;
|
|
|
|
static HTTPClient http;
|
|
static const String baseUrlAQI = "https://sinoptik.luon.net/forecast?metrics=PAQI&metrics=AQI&metrics=pollen&metrics=UVI";
|
|
static const String baseUrlPrecipitation = "https://sinoptik.luon.net/forecast?metrics=precipitation";
|
|
|
|
static int hw_variant = 0;
|
|
|
|
WiFiManager wifiManager;
|
|
DESPatch dESPatch;
|
|
const char* root_ca = \
|
|
"-----BEGIN CERTIFICATE-----\n" \
|
|
"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\n" \
|
|
"-----END CERTIFICATE-----\n";
|
|
|
|
|
|
|
|
const float location[] = {51.445466493287434, 5.515445691496135}; // Telefoonstraat, Eindhoven
|
|
const String address = "Telefoonstraat 18, Eindhoven";
|
|
|
|
//const float location[] = {51.44083, 5.47778}; // Eindhoven
|
|
//const float location[] = {52.09083, 5.12222}; // Utrecht
|
|
//const float location[] = {51.8425, 5.85278}; // Nijmegen
|
|
//const float location[] = {52.37403, 4.88969}; // Amsterdam
|
|
//const float location[] = {52.15833, 4.49306}; // Leiden
|
|
|
|
typedef struct RainPickerDataArray24 {
|
|
const int len = 24;
|
|
long time[24];
|
|
float value[24];
|
|
} RainPickerDataArray24;
|
|
|
|
typedef struct RainPickerDataArray5 {
|
|
const int len = 5;
|
|
long time[5];
|
|
float value[5];
|
|
} RainPickerDataArray5;
|
|
|
|
typedef struct RainpickerData {
|
|
float lat;
|
|
float lon;
|
|
long time;
|
|
|
|
RainPickerDataArray24 PAQI;
|
|
RainPickerDataArray24 AQI;
|
|
RainPickerDataArray24 pollen;
|
|
RainPickerDataArray5 UVI;
|
|
RainPickerDataArray24 precipitation;
|
|
} RainpickerData;
|
|
|
|
RainpickerData rainpickerData;
|
|
|
|
StaticJsonDocument<6144> doc;
|
|
|
|
static bool initDone = false;
|
|
|
|
void parseJson(String * payload)
|
|
{
|
|
doc.clear();
|
|
|
|
DeserializationError error = deserializeJson(doc, * payload);
|
|
|
|
if (error) {
|
|
Serial.print(F("deserializeJson() failed: "));
|
|
Serial.println(error.f_str());
|
|
return;
|
|
}
|
|
|
|
rainpickerData.lat = doc["lat"];
|
|
rainpickerData.lon = doc["lon"];
|
|
rainpickerData.time = doc["time"];
|
|
|
|
int n = 0;
|
|
for (JsonObject elem : doc["PAQI"].as<JsonArray>()) {
|
|
if (n >= rainpickerData.PAQI.len) {
|
|
break;
|
|
}
|
|
rainpickerData.PAQI.time[n] = elem["time"];
|
|
rainpickerData.PAQI.value[n] = elem["value"];
|
|
n = n + 1;
|
|
}
|
|
|
|
n = 0;
|
|
for (JsonObject elem : doc["AQI"].as<JsonArray>()) {
|
|
if (n >= rainpickerData.AQI.len) {
|
|
break;
|
|
}
|
|
rainpickerData.AQI.time[n] = elem["time"];
|
|
rainpickerData.AQI.value[n] = elem["value"];
|
|
n = n + 1;
|
|
}
|
|
|
|
n = 0;
|
|
for (JsonObject elem : doc["pollen"].as<JsonArray>()) {
|
|
if (n >= rainpickerData.pollen.len) {
|
|
break;
|
|
}
|
|
rainpickerData.pollen.time[n] = elem["time"];
|
|
rainpickerData.pollen.value[n] = elem["value"];
|
|
n = n + 1;
|
|
}
|
|
|
|
n = 0;
|
|
for (JsonObject elem : doc["UVI"].as<JsonArray>()) {
|
|
if (n >= rainpickerData.UVI.len) {
|
|
break;
|
|
}
|
|
rainpickerData.UVI.time[n] = elem["time"];
|
|
rainpickerData.UVI.value[n] = elem["value"];
|
|
n = n + 1;
|
|
}
|
|
|
|
n = 0;
|
|
for (JsonObject elem : doc["precipitation"].as<JsonArray>()) {
|
|
if (n >= rainpickerData.precipitation.len) {
|
|
break;
|
|
}
|
|
rainpickerData.precipitation.time[n] = elem["time"];
|
|
rainpickerData.precipitation.value[n] = elem["value"];
|
|
n = n + 1;
|
|
}
|
|
}
|
|
|
|
static void setup_pins_proto_paper(void)
|
|
{
|
|
int n = 0;
|
|
|
|
BUIENRADAR_START_LED = 32;
|
|
BUIENRADAR_SKIP_LED = 1;
|
|
BUIENRADAR_NUM_LEDS = 12;
|
|
|
|
PAQI_START_LED = 72;
|
|
PAQI_SKIP_LED = 0;
|
|
PAQI_NUM_LEDS = 12;
|
|
|
|
// #define UVI_LED 92 // LED in center
|
|
UVI_LED = 84; // LED above center
|
|
AQI_LED = 86; // LED to the right
|
|
POLLEN_LED = 90; // LED to the left
|
|
IAQI_LED = 88; // LED below center
|
|
|
|
CONNECT_SW_PIN = CONNECT_SW_PIN_PROTO_PAPER;
|
|
|
|
n = 0;
|
|
while (n < BUIENRADAR_NUM_LEDS) {
|
|
ledMapPrecipitation[n] = BUIENRADAR_START_LED + n * (BUIENRADAR_SKIP_LED + 1);
|
|
n = n + 1;
|
|
}
|
|
|
|
n = 0;
|
|
while (n < PAQI_NUM_LEDS) {
|
|
ledMapPAQI[n] = PAQI_START_LED + n * (PAQI_SKIP_LED + 1);
|
|
n = n + 1;
|
|
}
|
|
|
|
ledMapAQI = AQI_LED;
|
|
ledMapUVI = UVI_LED;
|
|
ledMapPollen = POLLEN_LED;
|
|
ledMapIAQI = IAQI_LED;
|
|
}
|
|
|
|
static void setup_pins_proto_v1(void)
|
|
{
|
|
int n = 0;
|
|
|
|
BUIENRADAR_START_LED = 0;
|
|
BUIENRADAR_SKIP_LED = 0;
|
|
BUIENRADAR_NUM_LEDS = 12;
|
|
|
|
PAQI_START_LED = 12;
|
|
PAQI_SKIP_LED = 0;
|
|
PAQI_NUM_LEDS = 12;
|
|
|
|
UVI_LED = 27; // LED above center
|
|
AQI_LED = 24; // LED to the right
|
|
POLLEN_LED = 26; // LED to the left
|
|
IAQI_LED = 25; // LED below center
|
|
|
|
CONNECT_SW_PIN = CONNECT_SW_PIN_PROTO_V1;
|
|
|
|
ledMapPrecipitation[0] = 11;
|
|
n = 1;
|
|
while (n < BUIENRADAR_NUM_LEDS) {
|
|
ledMapPrecipitation[n] = BUIENRADAR_START_LED + (n - 1) * (BUIENRADAR_SKIP_LED + 1);
|
|
n = n + 1;
|
|
}
|
|
|
|
ledMapPAQI[0] = 23;
|
|
n = 1;
|
|
while (n < PAQI_NUM_LEDS) {
|
|
ledMapPAQI[n] = PAQI_START_LED + (n - 1) * (PAQI_SKIP_LED + 1);
|
|
n = n + 1;
|
|
}
|
|
|
|
ledMapAQI = AQI_LED;
|
|
ledMapUVI = UVI_LED;
|
|
ledMapPollen = POLLEN_LED;
|
|
ledMapIAQI = IAQI_LED;
|
|
|
|
LIGHT_PIN = 34;
|
|
analogSetPinAttenuation(LIGHT_PIN, ADC_0db);
|
|
analogReadResolution(12);
|
|
}
|
|
|
|
void leds_clear(void) {
|
|
if (hw_variant == HW_PROTO_PAPER) {
|
|
leds_rgb_proto_paper.clear();
|
|
leds_rgb_proto_paper.show();
|
|
} else if (hw_variant == HW_PROTO_V1) {
|
|
leds_rgbw_proto_v1.clear();
|
|
leds_rgbw_proto_v1.show();
|
|
}
|
|
}
|
|
|
|
int checkIaqSensorStatus(void)
|
|
{
|
|
if (iaqSensor.bsecStatus != BSEC_OK) {
|
|
if (iaqSensor.bsecStatus < BSEC_OK) {
|
|
output = "BSEC error code : " + String(iaqSensor.bsecStatus);
|
|
Serial.println(output);
|
|
//for (;;);
|
|
} else {
|
|
output = "BSEC warning code : " + String(iaqSensor.bsecStatus);
|
|
Serial.println(output);
|
|
}
|
|
}
|
|
|
|
if (iaqSensor.bme68xStatus != BME68X_OK) {
|
|
if (iaqSensor.bme68xStatus < BME68X_OK) {
|
|
output = "BME680 error code : " + String(iaqSensor.bme68xStatus);
|
|
Serial.println(output);
|
|
//for (;;);
|
|
} else {
|
|
output = "BME680 warning code : " + String(iaqSensor.bme68xStatus);
|
|
Serial.println(output);
|
|
}
|
|
}
|
|
|
|
return iaqSensor.bme68xStatus;
|
|
}
|
|
|
|
void readSensors(void) {
|
|
static bool readingInProgress = false;
|
|
static unsigned long endTime = 0;
|
|
|
|
if (hw_variant == HW_PROTO_PAPER) {
|
|
return;
|
|
}
|
|
|
|
unsigned long time_trigger = millis();
|
|
if (iaqSensor.run()) { // If new data is available
|
|
output = String(time_trigger);
|
|
output += " Sensor data: Traw: " + String(iaqSensor.rawTemperature);
|
|
output += " *C , P: " + String(iaqSensor.pressure);
|
|
output += " hPa, RHraw: " + String(iaqSensor.rawHumidity);
|
|
output += " %, Rgas: " + String(iaqSensor.gasResistance);
|
|
output += " Ohm, IAQ: " + String(iaqSensor.iaq);
|
|
output += ", accuracy: " + String(iaqSensor.iaqAccuracy);
|
|
output += ", Tcorr: " + String(iaqSensor.temperature);
|
|
output += " *C, RHcorr: " + String(iaqSensor.humidity);
|
|
output += " %, IAQstatic: " + String(iaqSensor.staticIaq);
|
|
output += ", eCO2: " + String(iaqSensor.co2Equivalent);
|
|
output += " ppm, eVOC: " + String(iaqSensor.breathVocEquivalent);
|
|
output += " ppm";
|
|
|
|
int light = 0;
|
|
for (int n = 0; n < 256; n++) {
|
|
light += analogRead(LIGHT_PIN);
|
|
}
|
|
light = (light + 128) >> 8;
|
|
output += ", light: " + String(light);
|
|
Serial.println(output);
|
|
} else {
|
|
checkIaqSensorStatus();
|
|
}
|
|
}
|
|
|
|
void taskBME680( void * parameter )
|
|
{
|
|
while (initDone == false) {
|
|
delay(100);
|
|
}
|
|
|
|
while (true) {
|
|
readSensors();
|
|
delay(100);
|
|
}
|
|
|
|
//Serial.println("Ending task 1");
|
|
//vTaskDelete( NULL );
|
|
}
|
|
|
|
void setup() {
|
|
const char url[] = "https://apikey:cqprlgiafadnidsgeqozcpldkaeqimqw@despatch.luon.net/files/4/despatch.json";
|
|
unsigned long interval = 60; // By default check for updates every 60 seconds
|
|
int x;
|
|
|
|
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // Disable brownout; needed for some dev kits
|
|
|
|
// sanity check delay - allows reprogramming if accidently blowing power w/leds
|
|
delay(2000);
|
|
|
|
Serial.begin(115200);
|
|
Serial.println("buienradarklok starting");
|
|
|
|
http.setReuse(false);
|
|
|
|
SPI.begin();
|
|
iaqSensor.begin(BME_CS, SPI);
|
|
String output = "\nBSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix);
|
|
Serial.println(output);
|
|
x = checkIaqSensorStatus();
|
|
|
|
if (x != BME68X_OK) {
|
|
Serial.println(F("Could not find a valid BME680 sensor; assuming hw variant \"paper prototype\"!"));
|
|
hw_variant = HW_PROTO_PAPER;
|
|
setup_pins_proto_paper();
|
|
leds_rgb_proto_paper.begin(); // INITIALIZE NeoPixel strip object
|
|
} else {
|
|
Serial.println(F("BME680 found; assuming hw variant \"proto v1\""));
|
|
|
|
iaqSensor.setConfig(bsec_config_iaq);
|
|
iaqSensor.setTemperatureOffset(13.5f);
|
|
checkIaqSensorStatus();
|
|
|
|
hw_variant = HW_PROTO_V1;
|
|
setup_pins_proto_v1();
|
|
leds_rgbw_proto_v1.begin(); // INITIALIZE NeoPixel strip object
|
|
}
|
|
|
|
bsec_virtual_sensor_t sensorList[10] = {
|
|
BSEC_OUTPUT_RAW_TEMPERATURE,
|
|
BSEC_OUTPUT_RAW_PRESSURE,
|
|
BSEC_OUTPUT_RAW_HUMIDITY,
|
|
BSEC_OUTPUT_RAW_GAS,
|
|
BSEC_OUTPUT_IAQ,
|
|
BSEC_OUTPUT_STATIC_IAQ,
|
|
BSEC_OUTPUT_CO2_EQUIVALENT,
|
|
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
|
|
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
|
|
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
|
|
};
|
|
|
|
iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP);
|
|
checkIaqSensorStatus();
|
|
|
|
// Print the header
|
|
output = "Timestamp [ms], raw temperature [°C], pressure [hPa], raw relative humidity [%], gas [Ohm], IAQ, IAQ accuracy, temperature [°C], relative humidity [%], Static IAQ, CO2 equivalent, breath VOC equivalent";
|
|
Serial.println(output);
|
|
|
|
leds_clear();
|
|
|
|
#ifndef DISABLE_WIFI
|
|
//wifiManager.resetSettings();
|
|
wifiManager.configure("Claire-", true, LED_BUILTIN, true, CONNECT_SW_PIN, false);
|
|
|
|
//fetches ssid and pass and tries to connect
|
|
//if it does not connect it starts an access point
|
|
//and goes into a blocking loop awaiting configuration
|
|
if (!wifiManager.autoConnect()) {
|
|
Serial.println("failed to connect and hit timeout");
|
|
//reset and try again, or maybe put it to deep sleep
|
|
ESP.restart();
|
|
delay(1000);
|
|
}
|
|
|
|
//if you get here you have connected to the WiFi
|
|
Serial.print("connected with address: ");
|
|
Serial.println(WiFi.localIP());
|
|
|
|
if (hw_variant == HW_PROTO_PAPER) {
|
|
//keep LED on
|
|
digitalWrite(LED_BUILTIN, LED_ON_VALUE_DEFAULT);
|
|
}
|
|
|
|
x = dESPatch.configure(url, true, false, interval, false, root_ca);
|
|
Serial.print("dESPatch.configure() returned with code ");
|
|
Serial.println(x);
|
|
#endif
|
|
initDone = true;
|
|
|
|
xTaskCreate(taskBME680, "taskBME680", 10000, NULL, 1, NULL);
|
|
}
|
|
|
|
// From https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/
|
|
String urlencode(String str)
|
|
{
|
|
String encodedString="";
|
|
char c;
|
|
char code0;
|
|
char code1;
|
|
char code2;
|
|
for (int i =0; i < str.length(); i++){
|
|
c=str.charAt(i);
|
|
if (c == ' '){
|
|
encodedString+= '+';
|
|
} else if (isalnum(c)){
|
|
encodedString+=c;
|
|
} else{
|
|
code1=(c & 0xf)+'0';
|
|
if ((c & 0xf) >9){
|
|
code1=(c & 0xf) - 10 + 'A';
|
|
}
|
|
c=(c>>4)&0xf;
|
|
code0=c+'0';
|
|
if (c > 9){
|
|
code0=c - 10 + 'A';
|
|
}
|
|
code2='\0';
|
|
encodedString+='%';
|
|
encodedString+=code0;
|
|
encodedString+=code1;
|
|
//encodedString+=code2;
|
|
}
|
|
yield();
|
|
}
|
|
|
|
return encodedString;
|
|
}
|
|
|
|
void getPrecipitation(const float * location) {
|
|
int httpCode;
|
|
//String url = baseUrl + "&lat=" + String(location[0]) + "&lon=" + String(location[1]);
|
|
String url, latS, lonS;
|
|
int tmp1, tmp2;
|
|
String payload = "";
|
|
|
|
tmp1 = int(location[0]);
|
|
tmp2 = int((location[0] - tmp1) * 1000000);
|
|
latS = String(tmp1) + "." + String(tmp2);
|
|
tmp1 = int(location[1]);
|
|
tmp2 = int((location[1] - tmp1) * 1000000);
|
|
lonS = String(tmp1) + "." + String(tmp2);
|
|
|
|
url = baseUrlPrecipitation + "&lat=" + latS + "&lon=" + lonS;
|
|
|
|
Serial.print(millis());
|
|
Serial.print(" Retrieving precipitation from ");
|
|
Serial.println(url);
|
|
|
|
http.begin(url, root_ca);
|
|
httpCode = http.GET();
|
|
|
|
if (httpCode > 0) {
|
|
payload = http.getString();
|
|
parseJson(&payload);
|
|
} else {
|
|
Serial.print(millis());
|
|
Serial.print(" Got http code ");
|
|
Serial.println(httpCode);
|
|
}
|
|
http.end();
|
|
}
|
|
|
|
void getPrecipitation(const String address) {
|
|
int httpCode;
|
|
//String url = baseUrl + "&lat=" + String(location[0]) + "&lon=" + String(location[1]);
|
|
String url, a;
|
|
String payload = "";
|
|
|
|
a = urlencode(address);
|
|
|
|
url = baseUrlPrecipitation + "&address=" + a;
|
|
|
|
Serial.print(millis());
|
|
Serial.print(" Retrieving precipitation from ");
|
|
Serial.println(url);
|
|
|
|
http.begin(url, root_ca);
|
|
httpCode = http.GET();
|
|
|
|
if (httpCode > 0) {
|
|
payload = http.getString();
|
|
parseJson(&payload);
|
|
} else {
|
|
Serial.print(millis());
|
|
Serial.print(" Got http code ");
|
|
Serial.println(httpCode);
|
|
}
|
|
http.end();
|
|
}
|
|
|
|
void getAQI(const float * location) {
|
|
int httpCode;
|
|
//String url = baseUrl + "&lat=" + String(location[0]) + "&lon=" + String(location[1]);
|
|
String url, latS, lonS;
|
|
int tmp1, tmp2;
|
|
String payload = "";
|
|
|
|
tmp1 = int(location[0]);
|
|
tmp2 = int((location[0] - tmp1) * 1000000);
|
|
latS = String(tmp1) + "." + String(tmp2);
|
|
tmp1 = int(location[1]);
|
|
tmp2 = int((location[1] - tmp1) * 1000000);
|
|
lonS = String(tmp1) + "." + String(tmp2);
|
|
|
|
url = baseUrlAQI + "&lat=" + latS + "&lon=" + lonS;
|
|
|
|
Serial.print(millis());
|
|
Serial.print(" Retrieving AQI from ");
|
|
Serial.println(url);
|
|
|
|
http.begin(url, root_ca);
|
|
httpCode = http.GET();
|
|
|
|
if (httpCode > 0) {
|
|
payload = http.getString();
|
|
parseJson(&payload);
|
|
} else {
|
|
Serial.print(millis());
|
|
Serial.print(" Got http code ");
|
|
Serial.println(httpCode);
|
|
}
|
|
http.end();
|
|
}
|
|
|
|
void getAQI(const String address) {
|
|
int httpCode;
|
|
//String url = baseUrl + "&lat=" + String(location[0]) + "&lon=" + String(location[1]);
|
|
String url, a;
|
|
String payload = "";
|
|
|
|
a = urlencode(address);
|
|
|
|
url = baseUrlAQI + "&address=" + a;
|
|
|
|
Serial.print(millis());
|
|
Serial.print(" Retrieving AQI from ");
|
|
Serial.println(url);
|
|
|
|
http.begin(url, root_ca);
|
|
httpCode = http.GET();
|
|
|
|
if (httpCode > 0) {
|
|
payload = http.getString();
|
|
parseJson(&payload);
|
|
} else {
|
|
Serial.print(millis());
|
|
Serial.print(" Got http code ");
|
|
Serial.println(httpCode);
|
|
}
|
|
http.end();
|
|
}
|
|
|
|
static int colormap(float x) {
|
|
// Input: intensity
|
|
// Output: RGB encoded pixel color similar to luchtmeetnet legend
|
|
int y = 0;
|
|
|
|
if (x < 1.0) {
|
|
y = (0 << 16) | (0 << 8) | 0; // black (good)
|
|
} else if (x < 2.0) {
|
|
// 1.0 <= x < 2.0
|
|
if (hw_variant == HW_PROTO_PAPER) {
|
|
y = (0 << 16) | (0 << 8) | 160; // blue (good)
|
|
} else if (hw_variant == HW_PROTO_V1) {
|
|
y = (0 << 16) | (0 << 8) | 255; // blue (good)
|
|
}
|
|
} else if (x < 3.0) {
|
|
// 2.0 <= x < 3.0
|
|
y = (0 << 16) | (255 << 8) | 255; // cyan (good)
|
|
} else if (x < 4.0) {
|
|
// 3.0 <= x < 4.0
|
|
y = (255 << 16) | (255 << 8) | 255; // white (mediocre)
|
|
} else if (x < 5.0) {
|
|
// 4.0 <= x < 5.0
|
|
y = (200 << 16) | (200 << 8) | 32; // light yellow (mediocre)
|
|
} else if (x < 6.0) {
|
|
// 5.0 <= x < 6.0
|
|
y = (120 << 16) | (120 << 8) | 0; // yellow (mediocre)
|
|
} else if (x < 7.0) {
|
|
// 6.0 <= x < 7.0
|
|
y = (200 << 16) | (80 << 8) | 0; // orange (inadequate)
|
|
} else if (x < 8.0) {
|
|
// 7.0 <= x < 8.0
|
|
y = (255 << 16) | (50 << 8) | 0; // red orange (inadequate)
|
|
} else if (x < 9.0) {
|
|
// 8.0 <= x < 9.0
|
|
y = (180 << 16) | (0 << 8) | 0; // red (bad)
|
|
} else if (x < 10.0) {
|
|
// 9.0 <= x < 10.0
|
|
y = (200 << 16) | (0 << 8) | 160; // magenta (bad)
|
|
} else {
|
|
// 10.0 <= x
|
|
y = (60 << 16) | (0 << 8) | 210; // purple (terrible)
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
float colormapIaqi(void)
|
|
{
|
|
float x = 0.0f;
|
|
float iaqi = iaqSensor.staticIaq;
|
|
|
|
if (iaqi <= 50.0f) {
|
|
x = 1.0f; // Excellent
|
|
} else if (iaqi <= 100.0f) {
|
|
x = 2.0f; // Good
|
|
} else if (iaqi <= 125.0f) {
|
|
x = 4.0f;
|
|
} else if (iaqi <= 150.0f) {
|
|
x = 4.0f; // Lightly polluted
|
|
} else if (iaqi <= 175.0f) {
|
|
x = 5.0f;
|
|
} else if (iaqi <= 200.0f) {
|
|
x = 6.0f; // Moderately polluted
|
|
} else if (iaqi <= 225.0f) {
|
|
x = 7.0f;
|
|
} else if (iaqi <= 250.0f) {
|
|
x = 8.0f; // Heavily polluted
|
|
} else if (iaqi <= 300.0f) {
|
|
x = 9.0f;
|
|
} else if (iaqi <= 350.0f) {
|
|
x = 10.0f; // Severely polluted
|
|
} else {
|
|
x = 11.0f; // Extremely polluted
|
|
}
|
|
|
|
return colormap(x);
|
|
}
|
|
|
|
static float buienradarMap(float x) {
|
|
// Input: rain intensity (mm/h)
|
|
// Output: floating point number which can be mapped on color scale
|
|
float y = 0;
|
|
|
|
// Use E3 series of preferred numbers to map intensity to color
|
|
if (x < 0.1) {
|
|
y = 0.5;
|
|
} else if (x < 0.22) {
|
|
y = 1.5;
|
|
} else if (x < 0.47) {
|
|
y = 2.5;
|
|
} else if (x < 1.0) {
|
|
y = 3.5;
|
|
} else if (x < 2.2) {
|
|
y = 4.5;
|
|
} else if (x < 4.7) {
|
|
y = 5.5;
|
|
} else if (x < 10) {
|
|
y = 6.5;
|
|
} else if (x < 22) {
|
|
y = 7.5;
|
|
} else if (x < 47) {
|
|
y = 8.5;
|
|
} else if (x < 100) {
|
|
y = 9.5;
|
|
} else {
|
|
y = 10.5;
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
static void ledsSetPixelColor(uint32_t ledIdx, int r, int g, int b, int w) {
|
|
if (hw_variant == HW_PROTO_PAPER) {
|
|
leds_rgb_proto_paper.setPixelColor(ledIdx, leds_rgb_proto_paper.Color(r, g, b));
|
|
} else if (hw_variant == HW_PROTO_V1) {
|
|
leds_rgbw_proto_v1.setPixelColor(ledIdx, leds_rgbw_proto_v1.Color(g, r, b, w));
|
|
}
|
|
}
|
|
|
|
static void ledsSetPixelColor(uint32_t ledIdx, int r, int g, int b) {
|
|
ledsSetPixelColor(ledIdx, r, g, b, 0);
|
|
}
|
|
|
|
static void updateLedsNoWifi(void) {
|
|
uint32_t ledIdx;
|
|
uint32_t ledCount;
|
|
uint32_t color;
|
|
int r, g, b, w, n;
|
|
|
|
if (hw_variant == HW_PROTO_PAPER) {
|
|
// Start by setting all leds to value of the first datapoint
|
|
ledIdx = 11;
|
|
ledCount = 0;
|
|
for (ledCount = 0; ledCount < 11; ledCount++) {
|
|
color = colormap(ledCount);
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
|
|
ledsSetPixelColor(ledIdx, r, g, b);
|
|
ledIdx = ledIdx + PAQI_SKIP_LED + 1;
|
|
}
|
|
} else if (hw_variant == HW_PROTO_V1) {
|
|
// Start by setting all leds to value of the first datapoint
|
|
ledIdx = 0;
|
|
ledCount = 0;
|
|
for (ledCount = 0; ledCount < 11; ledCount++) {
|
|
color = colormap(ledCount);
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
w = (color & 0xFF000000) >> 24;
|
|
|
|
ledsSetPixelColor(ledIdx, r, g, b, w);
|
|
ledIdx = ledIdx + PAQI_SKIP_LED + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void updateLeds(void) {
|
|
uint32_t ledIdx;
|
|
uint32_t ledCount;
|
|
uint32_t color;
|
|
int r, g, b, w, n;
|
|
|
|
// Start by setting all leds to value of the first datapoint
|
|
ledIdx = PAQI_START_LED;
|
|
ledCount = 0;
|
|
for (ledCount = 0; ledCount < PAQI_NUM_LEDS; ledCount++) {
|
|
color = colormap(rainpickerData.PAQI.value[0]);
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
w = (color & 0xFF000000) >> 24;
|
|
|
|
ledsSetPixelColor(ledMapPAQI[ledCount], r, g, b);
|
|
ledIdx = ledIdx + PAQI_SKIP_LED + 1;
|
|
}
|
|
|
|
ledIdx = PAQI_START_LED;
|
|
ledCount = 0;
|
|
for (n = 0; n < rainpickerData.PAQI.len; n++) {
|
|
if (rainpickerData.time < rainpickerData.PAQI.time[n]) {
|
|
ledIdx = ledIdx + PAQI_SKIP_LED + 1;
|
|
ledCount = ledCount + 1;
|
|
}
|
|
if (ledCount >= PAQI_NUM_LEDS) {
|
|
break;
|
|
}
|
|
|
|
color = colormap(rainpickerData.PAQI.value[n]);
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
w = (color & 0xFF000000) >> 24;
|
|
|
|
ledsSetPixelColor(ledMapPAQI[ledCount], r, g, b);
|
|
}
|
|
|
|
#ifdef SHOW_AQI_LED
|
|
// calculate AQI max value for next 12 hours
|
|
uint32_t AQI_max_value = 0;
|
|
//Serial.println("AQI:");
|
|
for (n = 0; n < rainpickerData.AQI.len; n++) {
|
|
/*Serial.print(" ");
|
|
Serial.print(rainpickerData.AQI.time[n]);
|
|
Serial.print(": ");
|
|
Serial.println(rainpickerData.AQI.value[n]);*/
|
|
if (rainpickerData.time - rainpickerData.AQI.time[n] >= 60 * 60) {
|
|
continue;
|
|
}
|
|
if (rainpickerData.AQI.time[n] >= rainpickerData.time + PAQI_NUM_LEDS * 60 * 60) {
|
|
break;
|
|
}
|
|
if (rainpickerData.AQI.value[n] > AQI_max_value) {
|
|
AQI_max_value = rainpickerData.AQI.value[n];
|
|
}
|
|
}
|
|
//Serial.print("AQI max value: ");
|
|
//Serial.println(AQI_max_value);
|
|
color = colormap(AQI_max_value);
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
|
|
ledsSetPixelColor(AQI_LED, r, g, b);
|
|
#endif
|
|
|
|
#ifdef SHOW_POLLEN_LED
|
|
// calculate pollen max value for next 12 hours
|
|
uint32_t pollen_max_value = rainpickerData.pollen.value[0];
|
|
for (n = 0; n < rainpickerData.pollen.len; n++) {
|
|
if (rainpickerData.time - rainpickerData.pollen.time[n] >= 60 * 60) {
|
|
continue;
|
|
}
|
|
if (rainpickerData.pollen.time[n] >= rainpickerData.time + PAQI_NUM_LEDS * 60 * 60) {
|
|
break;
|
|
}
|
|
if (rainpickerData.pollen.value[n] > pollen_max_value) {
|
|
pollen_max_value = rainpickerData.pollen.value[n];
|
|
}
|
|
}
|
|
|
|
color = colormap(pollen_max_value);
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
|
|
ledsSetPixelColor(POLLEN_LED, r, g, b);
|
|
#endif
|
|
|
|
#ifdef SHOW_UVI_LED
|
|
// calculate UVI led value
|
|
// UVI data is daily based --> search for first timestamp in the future and select value of the timestamp just before that
|
|
uint32_t UVI_value = rainpickerData.UVI.value[0];
|
|
|
|
for (n = 0; n < rainpickerData.UVI.len; n++) {
|
|
if (rainpickerData.UVI.time[n] >= rainpickerData.time) {
|
|
break;
|
|
}
|
|
UVI_value = rainpickerData.UVI.value[n];
|
|
}
|
|
color = colormap(UVI_value);
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
|
|
ledsSetPixelColor(UVI_LED, r, g, b);
|
|
#endif
|
|
|
|
// Start by setting all leds to value of the first datapoint
|
|
ledIdx = BUIENRADAR_START_LED;
|
|
for (ledCount = 0; ledCount < BUIENRADAR_NUM_LEDS; ledCount++) {
|
|
color = colormap(rainpickerData.precipitation.value[0]);
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
|
|
ledsSetPixelColor(ledMapPrecipitation[ledCount], r, g, b);
|
|
ledIdx = ledIdx + BUIENRADAR_SKIP_LED + 1;
|
|
}
|
|
|
|
ledIdx = BUIENRADAR_START_LED;
|
|
ledCount = 0;
|
|
for (n = 0; n < rainpickerData.precipitation.len; n++) {
|
|
if (rainpickerData.time < rainpickerData.precipitation.time[n]) {
|
|
ledIdx = ledIdx + BUIENRADAR_SKIP_LED + 1;
|
|
ledCount = ledCount + 1;
|
|
}
|
|
if (ledCount >= BUIENRADAR_NUM_LEDS) {
|
|
break;
|
|
}
|
|
|
|
color = colormap(buienradarMap(rainpickerData.precipitation.value[n]));
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
ledsSetPixelColor(ledMapPrecipitation[ledCount], r, g, b);
|
|
}
|
|
|
|
ledsSetPixelColor(IAQI_LED, r, g, b);
|
|
|
|
if (hw_variant == HW_PROTO_V1) {
|
|
color = colormapIaqi();
|
|
|
|
r = (color & 0xFF0000) >> 16;
|
|
g = (color & 0x00FF00) >> 8;
|
|
b = (color & 0x0000FF);
|
|
|
|
ledsSetPixelColor(IAQI_LED, r, g, b);
|
|
}
|
|
}
|
|
|
|
void readConnectButton(void) {
|
|
static int reset_wifi_timer = millis();
|
|
static bool reset_blocked = true;
|
|
|
|
if (digitalRead(CONNECT_SW_PIN) == HIGH) {
|
|
// button released
|
|
reset_wifi_timer = millis();
|
|
reset_blocked = false;
|
|
digitalWrite(LED_BUILTIN, LOW);
|
|
} else {
|
|
// button pressed
|
|
if (reset_blocked == false) {
|
|
int delta = millis() - reset_wifi_timer;
|
|
if (delta < 10000) {
|
|
if (delta % 1000 < 500) {
|
|
digitalWrite(LED_BUILTIN, HIGH);
|
|
} else {
|
|
digitalWrite(LED_BUILTIN, LOW);
|
|
}
|
|
} else {
|
|
leds_clear();
|
|
|
|
wifiManager.resetSettings();
|
|
|
|
// block reset so user must release button before we can reset again
|
|
reset_blocked = true;
|
|
digitalWrite(LED_BUILTIN, LOW);
|
|
ESP.restart();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void loop(void) {
|
|
static unsigned long t_prev = 0;
|
|
static bool ledOn = false;
|
|
unsigned long t_now = millis();
|
|
const int buttonPin = 0;
|
|
static int rainColor[12] = {0};
|
|
static int AQIColor[12] = {0};
|
|
static bool firstTime = true;
|
|
static int counter = 0;
|
|
|
|
if (firstTime || (t_now - t_prev >= 60000)) {
|
|
t_prev = t_now;
|
|
firstTime = false;
|
|
|
|
#ifndef DISABLE_WIFI
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
Serial.print(millis());
|
|
Serial.print(" wifi status error: ");
|
|
Serial.print(WiFi.status());
|
|
Serial.print("; expected: ");
|
|
Serial.println(WL_CONNECTED);
|
|
bool result = WiFi.reconnect();
|
|
Serial.print(millis());
|
|
Serial.print(" reconnecting result: ");
|
|
Serial.print(result);
|
|
Serial.print("; expected: ");
|
|
Serial.println(ESP_OK);
|
|
}
|
|
getPrecipitation(address);
|
|
|
|
if (counter == 0) {
|
|
getAQI(address);
|
|
}
|
|
|
|
counter = counter + 1;
|
|
|
|
if (counter == 5) {
|
|
// AQI should only be retreived every 5 minutes
|
|
counter = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef DISABLE_WIFI
|
|
updateLedsNoWifi();
|
|
#else
|
|
updateLeds();
|
|
#endif
|
|
|
|
if (hw_variant == HW_PROTO_PAPER) {
|
|
leds_rgb_proto_paper.show();
|
|
} else if (hw_variant == HW_PROTO_V1) {
|
|
leds_rgbw_proto_v1.show();
|
|
}
|
|
|
|
readConnectButton();
|
|
|
|
delay(100);
|
|
#ifndef DISABLE_WIFI
|
|
dESPatch.checkForUpdate(true);
|
|
#endif
|
|
}
|