//#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 // https://github.com/admarschoonen/WiFiManager #include #include #include #include #include #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()) { 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()) { 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()) { 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()) { 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()) { 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 }