From c11a6749360f88a3946328594623f0fb80031ae9 Mon Sep 17 00:00:00 2001 From: admar Date: Fri, 22 Apr 2022 21:20:37 +0200 Subject: [PATCH] Initial commit --- Claire.ino | 1099 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1099 insertions(+) create mode 100644 Claire.ino diff --git a/Claire.ino b/Claire.ino new file mode 100644 index 0000000..d378a9c --- /dev/null +++ b/Claire.ino @@ -0,0 +1,1099 @@ +//#define DISABLE_WIFI + +#include // https://github.com/admarschoonen/WiFiManager +#include + +#include +#include +#include + +#include + + +#include +#include "PMS.h" // PMS Library from Mariusz Kacki https://github.com/fu-hsi/pms + +#include "ClosedCube_HDC1080.h" // ClosedCube_HDC1080 from closedcube https://github.com/closedcube/ClosedCube_HDC1080_Arduino +#include // BMP280 from Adafruit https://github.com/adafruit/Adafruit_BMP280_Library +#include "Adafruit_CCS811.h" // CCS811 from Adafruit https://github.com/adafruit/Adafruit_CCS811 + +#define PMSA003_RESET 18 +#define PMSA003_RX 17 +#define PMSA003_TX 16 +#define PMSA003_SET 19 + +ClosedCube_HDC1080 hdc1080; + +Adafruit_BMP280 bmp; + +Adafruit_CCS811 ccs; + +PMS pms(Serial2); +PMS::DATA PMSA003_data; + +typedef enum { + PMS_state_sleeping, + PMS_state_stabilizing, + PMS_state_ready +} PMS_state_t; + +PMS_state_t PMS_state; + +unsigned long CCS811_new_state_time = 0; + + + +#define NUM_LEDS 93 +//#define NUM_LEDS 10 +#define DATA_PIN 5 + +#define PMSA003_RESET 18 +#define PMSA003_RX 17 +#define PMSA003_TX 16 +#define PMSA003_SET 19 + +#define BUIENRADAR_START_LED 32 +#define BUIENRADAR_SKIP_LED 1 +#define BUIENRADAR_NUM_LEDS 12 + +#define PAQI_START_LED 72 +#define PAQI_SKIP_LED 0 +#define PAQI_NUM_LEDS 12 + +// #define UVI_LED 92 // LED in center +#define UVI_LED 84 // LED above center +#define AQI_LED 86 // LED to the right +#define POLLEN_LED 90 // LED to the left +#define IAQI_LED 88 // LED below center + +#define SHOW_AQI_LED +#define SHOW_POLLEN_LED +#define SHOW_UVI_LED + +#define MIN(x, y) (((x) <= (y)) ? (x) : (y)) + +Adafruit_NeoPixel leds(NUM_LEDS, DATA_PIN, NEO_GRB + NEO_KHZ800); + +static HTTPClient http; +//static const String baseUrlAQI = "http://luon.net:2355/forecast?formula=[\"PAQI\",\"AQI\",\"pollen\",\"UVI\"]"; +static const String baseUrlAQI = "http://target.luon.net:2356//forecast?&metrics=PAQI&metrics=AQI&metrics=pollen&metrics=UVI"; +//static const String baseUrlPrecipitation = "http://luon.net:2355/forecast?formula=[\"precipitation\"]"; +static const String baseUrlPrecipitation = "http://target.luon.net:2356//forecast?metrics=precipitation"; + + +WiFiManager wifiManager; +DESPatch dESPatch; +const char* root_ca = \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\n" \ + "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ + "DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\n" \ + "PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\n" \ + "Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" \ + "AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\n" \ + "rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\n" \ + "OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\n" \ + "xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n" \ + "7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\n" \ + "aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" \ + "HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\n" \ + "SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\n" \ + "ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\n" \ + "AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\n" \ + "R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\n" \ + "JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\n" \ + "Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n" \ + "-----END CERTIFICATE-----\n"; + +const float location[] = {51.445466493287434, 5.515445691496135}; // Telefoonstraat, Eindhoven +const String address = "Telefoonstraat 18, Eindhoven"; +//const float location[] = {51.51831326813842, 4.451744264773111}; // Bandeliersberg, Roosendaal + +//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 RainPickerDataArray12 { + const int len = 12; + long time[12]; + float value[12]; +} RainPickerDataArray12; + +typedef struct RainPickerDataArray5 { + const int len = 5; + long time[5]; + float value[5]; +} RainPickerDataArray5; + + +typedef struct RainpickerData { + float lat; + float lon; + float time; + + RainPickerDataArray24 PAQI; + RainPickerDataArray24 AQI; + RainPickerDataArray24 pollen; + RainPickerDataArray5 UVI; + RainPickerDataArray24 precipitation; +} RainpickerData; + +RainpickerData rainpickerData; + +StaticJsonDocument<6144> doc; + +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; + } +} + +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; + + // sanity check delay - allows reprogramming if accidently blowing power w/leds + delay(2000); + + leds.begin(); // INITIALIZE NeoPixel strip object + + Serial.begin(115200); + Serial.println("buienradarklok starting"); + +#ifndef DISABLE_WIFI + //wifiManager.resetSettings(); + wifiManager.configure("clairvoyance-", true, LED_BUILTIN, true, BUTTON_BUILTIN, 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()); + + //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 + + + hdc1080.begin(0x40); + Serial.print("HDC1080 Manufacturer ID=0x"); + Serial.println(hdc1080.readManufacturerId(), HEX); // 0x5449 ID of Texas Instruments + Serial.print("HDC1080 Device ID=0x"); + Serial.println(hdc1080.readDeviceId(), HEX); // 0x1050 ID of the device + + if (!bmp.begin(BMP280_ADDRESS_ALT, BMP280_CHIPID)) { + Serial.println(F("Could not find a valid BMP280 sensor. Check wiring or try different address.")); + while (1) { + delay(10); + } + } + + /* Default settings from datasheet. */ + bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ + Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */ + Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ + Adafruit_BMP280::FILTER_X16, /* Filtering. */ + Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */ + + if (!ccs.begin()) { + Serial.println("Could not find CSS811 sensor."); + while (1) { + delay(10); + } + + ccs.setDriveMode(CCS811_DRIVE_MODE_IDLE); // wait for at least 10 minutes before entering a new state! + CCS811_new_state_time = millis() + 600000; + + // Wait for the sensor to be ready + while(!ccs.available()); + } + + Serial2.begin(9600, SERIAL_8N1, PMSA003_TX, PMSA003_RX); // arg 3: ESP32 RX pin, arg 4: ESP32 TX pin + pms.passiveMode(); + Serial.println("Waking up and waiting 30 seconds for stable readings..."); + pms.wakeUp(); + PMS_state = PMS_state_stabilizing; + +} + +void interpolateValues(Adafruit_NeoPixel * leds, int numLeds, int startLed, int skipLed, \ + int * colors, int numColors) { + for (int led = 0; led < numLeds; led = led + skipLed + 1) { + int idxColorLow, idxColorHigh; + int idxLedLow, idxLedHigh; + int r, g, b; + + idxColorLow = (numColors * led) / numLeds; + idxColorHigh = idxColorLow + 1; + idxLedLow = (idxColorLow * numLeds) / numColors; + idxLedHigh = (idxColorHigh * numLeds) / numColors; + + if (idxColorHigh >= numColors) { + idxColorHigh = numColors - 1; + } + + if (idxLedHigh >= numLeds) { + idxLedHigh = numLeds - 1; + } + + int rgbLow = colors[idxColorLow]; + int rgbHigh = colors[idxColorHigh]; + + int rLow = (rgbLow & 0xFF0000) >> 16; + int gLow = (rgbLow & 0x00FF00) >> 8; + int bLow = (rgbLow & 0x0000FF); + + int rHigh = (rgbHigh & 0xFF0000) >> 16; + int gHigh = (rgbHigh & 0x00FF00) >> 8; + int bHigh = (rgbHigh & 0x0000FF); + + if (idxLedLow == idxLedHigh) { + r = rLow; + g = gLow; + b = bLow; + } else { + r = ((rHigh - rLow) * (led - idxLedLow) + ((idxLedHigh - idxLedLow) >> 1)) / (idxLedHigh - idxLedLow) + rLow; + g = ((gHigh - gLow) * (led - idxLedLow) + ((idxLedHigh - idxLedLow) >> 1)) / (idxLedHigh - idxLedLow) + gLow; + b = ((bHigh - bLow) * (led - idxLedLow) + ((idxLedHigh - idxLedLow) >> 1)) / (idxLedHigh - idxLedLow) + bLow; + } + leds->setPixelColor(led + startLed, leds->Color(r, g, b)); + } +} + +/* +void getJson(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 = baseUrl + "&lat=" + latS + "&lon=" + lonS; + + Serial.println(url); + + http.begin(url); + httpCode = http.GET(); + + if (httpCode > 0) { + payload = http.getString(); + parseJson(&payload); + } else { + Serial.print(__func__); + Serial.print("(): got http code "); + Serial.println(httpCode); + } + http.end(); +}*/ + +// 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.println(url); + + http.begin(url); + httpCode = http.GET(); + + if (httpCode > 0) { + payload = http.getString(); + parseJson(&payload); + } else { + Serial.print(__func__); + 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.println(url); + + http.begin(url); + httpCode = http.GET(); + + if (httpCode > 0) { + payload = http.getString(); + parseJson(&payload); + } else { + Serial.print(__func__); + 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.println(url); + + http.begin(url); + httpCode = http.GET(); + + if (httpCode > 0) { + payload = http.getString(); + parseJson(&payload); + } else { + Serial.print(__func__); + 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.println(url); + + http.begin(url); + httpCode = http.GET(); + + if (httpCode > 0) { + payload = http.getString(); + parseJson(&payload); + } else { + Serial.print(__func__); + 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 + y = (0 << 16) | (0 << 8) | 160; // 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; +} + +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 int calculateIAQI(void) { + int IAQI[3] = {0}; + int result = 0; + bool values_equal = true; + + uint16_t eCO2 = ccs.geteCO2(); + + if (eCO2 < 400) { + IAQI[0] = 0; + } else if (eCO2 < 600) { + IAQI[0] = 1; + } else if (eCO2 < 800) { + IAQI[0] = 2; + } else if (eCO2 < 1500) { + IAQI[0] = 4; + } else if (eCO2 < 1800) { + IAQI[0] = 7; + } else if (eCO2 < 2500) { + IAQI[0] = 9; + } else { + IAQI[0] = 10; + } + + if (PMSA003_data.PM_AE_UG_2_5 < 10) { + IAQI[1] = 0; + } else if (PMSA003_data.PM_AE_UG_2_5 < 15) { + IAQI[1] = 1; + } else if (PMSA003_data.PM_AE_UG_2_5 < 20) { + IAQI[1] = 2; + } else if (PMSA003_data.PM_AE_UG_2_5 < 30) { + IAQI[1] = 3; + } else if (PMSA003_data.PM_AE_UG_2_5 < 40) { + IAQI[1] = 4; + } else if (PMSA003_data.PM_AE_UG_2_5 < 50) { + IAQI[1] = 5; + } else if (PMSA003_data.PM_AE_UG_2_5 < 70) { + IAQI[1] = 6; + } else if (PMSA003_data.PM_AE_UG_2_5 < 90) { + IAQI[1] = 7; + } else if (PMSA003_data.PM_AE_UG_2_5 < 100) { + IAQI[1] = 8; + } else if (PMSA003_data.PM_AE_UG_2_5 < 140) { + IAQI[1] = 9; + } else { + IAQI[1] = 10; + } + + + if (PMSA003_data.PM_AE_UG_10_0 < 10) { + IAQI[1] = 0; + } else if (PMSA003_data.PM_AE_UG_10_0 < 20) { + IAQI[1] = 1; + } else if (PMSA003_data.PM_AE_UG_10_0 < 30) { + IAQI[1] = 2; + } else if (PMSA003_data.PM_AE_UG_10_0 < 45) { + IAQI[1] = 3; + } else if (PMSA003_data.PM_AE_UG_10_0 < 60) { + IAQI[1] = 4; + } else if (PMSA003_data.PM_AE_UG_10_0 < 75) { + IAQI[1] = 5; + } else if (PMSA003_data.PM_AE_UG_10_0 < 100) { + IAQI[1] = 6; + } else if (PMSA003_data.PM_AE_UG_10_0 < 125) { + IAQI[1] = 7; + } else if (PMSA003_data.PM_AE_UG_10_0 < 150) { + IAQI[1] = 8; + } else if (PMSA003_data.PM_AE_UG_10_0 < 200) { + IAQI[1] = 9; + } else { + IAQI[1] = 10; + } + + result = 0; + for (int n = 0; n < sizeof(IAQI) / sizeof(IAQI[0]); n++) { + if (result < IAQI[n]) { + result = IAQI[n]; + } + } + + for (int n = 1; n < sizeof(IAQI) / sizeof(IAQI[0]); n++) { + if (IAQI[n] != IAQI[0]) { + values_equal = false; + break; + } + } + + if ((values_equal) && (IAQI[0] != 0)) { + result = result + 1; + } + + /* + Serial.print("IAQI data:"); + for (int n = 0; n < sizeof(IAQI) / sizeof(IAQI[0]); n++) { + Serial.print(" "); + Serial.print(IAQI[n]); + } + Serial.println(""); + Serial.print("IAQI score: "); + Serial.println(result);*/ + + return result; +} + +static void updateLedsNoWifi(void) { + uint32_t ledIdx; + uint32_t ledCount; + uint32_t color; + int r, g, b, n; + + // 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); + + leds.setPixelColor(ledIdx, leds.Color(r, g, b)); + ledIdx = ledIdx + PAQI_SKIP_LED + 1; + } +} + +static void updateLeds(void) { + uint32_t ledIdx; + uint32_t ledCount; + uint32_t color; + int r, g, b, 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); + + leds.setPixelColor(ledIdx, leds.Color(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); + + leds.setPixelColor(ledIdx, leds.Color(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); + + leds.setPixelColor(AQI_LED, leds.Color(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); + + leds.setPixelColor(POLLEN_LED, leds.Color(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); + + leds.setPixelColor(UVI_LED, leds.Color(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); + + 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); + /*Serial.print("setting led "); + Serial.print(ledIdx); + Serial.print(" to value 0x"); + Serial.println(color, HEX);*/ + leds.setPixelColor(ledIdx, leds.Color(r, g, b)); + } + + + color = colormap(calculateIAQI()); + + r = (color & 0xFF0000) >> 16; + g = (color & 0x00FF00) >> 8; + b = (color & 0x0000FF); + + /* Serial.print("setting led "); + Serial.print(IAQI_LED); + Serial.print(" to value 0x"); + Serial.println(color, HEX);*/ + leds.setPixelColor(IAQI_LED, leds.Color(r, g, b)); +} + +void readSensors() { + static unsigned long t_start = 0; + static unsigned long bmp_time = 0; + static unsigned long hdc1080_time = 0; + static unsigned long ccs811_time = 0; + unsigned long t_now; + uint32_t pms_counter = 0; + double hdc1080_T_raw = -400; + double hdc1080_RH_raw = -400; + double ccs811_T_raw = -400; + + t_now = millis(); + + switch (PMS_state) { + case PMS_state_sleeping: + if (t_now - t_start >= 300000) { + Serial.println("Waking up and waiting 30 seconds for stable readings..."); + pms.wakeUp(); + PMS_state = PMS_state_stabilizing; + t_start = t_now; + } + break; + case PMS_state_stabilizing: + if (t_now - t_start >= 30000) { + Serial.println("Sensor should be stable now..."); + PMS_state = PMS_state_ready; + } + break; + case PMS_state_ready: + Serial.println("Reading sensor..."); + pms.requestRead(); + do { + if (pms.readUntil(PMSA003_data)) { + Serial.print("PM 1.0 (ug/m3): "); + Serial.println(PMSA003_data.PM_AE_UG_1_0); + + Serial.print("PM 2.5 (ug/m3): "); + Serial.println(PMSA003_data.PM_AE_UG_2_5); + + Serial.print("PM 10.0 (ug/m3): "); + Serial.println(PMSA003_data.PM_AE_UG_10_0); + + Serial.println(); + break; + } else { + Serial.println("No data :("); + pms_counter++; + if (pms_counter > 10) { + break; + } + } + } while(1); + + Serial.println("Sleeping for ~ 270 seconds..."); + pms.sleep(); + PMS_state = PMS_state_sleeping; + break; + default: + break; + } + + if (t_now - bmp_time > 5000) { + double bmp280_T_raw = bmp.readTemperature(); + double bmp280_T_corrected = bmp280_T_raw - (25.5 - 18.4); + + Serial.print(F("BMP280: temperature = ")); + Serial.print(bmp280_T_raw); + Serial.print(", "); + Serial.print(bmp280_T_corrected); + + Serial.println(" *C"); + + Serial.print(F("BMP280: pressure = ")); + Serial.print(bmp.readPressure()); + Serial.println(" Pa"); + + Serial.print(F("BMP280: approx altitude = ")); + Serial.print(bmp.readAltitude(1013.25)); /* Adjusted to local forecast! */ + Serial.println(" m"); + + bmp_time = t_now; + } + + if (t_now - hdc1080_time > 5000) { + hdc1080_T_raw = hdc1080.readTemperature(); + hdc1080_RH_raw = hdc1080.readHumidity(); + + // Both HDC1080 and BMP280 temperature readings are off by ~ 7 degrees + double hdc1080_T_corrected = hdc1080_T_raw - (25.5 - 18.4); + + /* + * Re-calculate humidity based on corrected temperature; formulas from + * https://www.thecalculator.co/others/Relative-Humidity-Calculator-682.html + */ + // double T_dew = (pow(hdc1080_RH_raw/100, 1/8)*(112+0.9*hdc1080_T_raw) )+(0.1*hdc1080_T_raw)-112; + // double hdc1080_RH_corrected = 100*((112-0.1*hdc1080_T_corrected+T_dew)/(112+0.9*hdc1080_T_corrected)); + + // http://hyperphysics.phy-astr.gsu.edu/hbase/Kinetic/relhum.html + double VD_sat_raw = 5.018 + 0.32321 * hdc1080_T_raw + + 0.0081847 * hdc1080_T_raw * hdc1080_T_raw + + 0.00031243 * hdc1080_T_raw * hdc1080_T_raw * hdc1080_T_raw; + + double VD_act = hdc1080_RH_raw * VD_sat_raw / 100; + + double VD_sat_corrected = 5.018 + 0.32321 * hdc1080_T_corrected + + 0.0081847 * hdc1080_T_corrected * hdc1080_T_corrected + + 0.00031243 * hdc1080_T_corrected * hdc1080_T_corrected * hdc1080_T_corrected; + + // double hdc1080_RH_corrected = VD_act / VD_sat_corrected * 100; + double hdc1080_RH_corrected = (hdc1080_RH_raw + VD_act / VD_sat_corrected * 100) / 2; + + + + Serial.print("HDC1080: temperature = "); + Serial.print(hdc1080_T_raw); + Serial.print(", "); + Serial.print(hdc1080_T_corrected); + Serial.println(" *C"); + Serial.print("HDC1080: RH = "); + Serial.print(hdc1080_RH_raw); + Serial.print(", "); + Serial.print(hdc1080_RH_corrected); + Serial.println(" %"); + + //ccs.setEnvironmentalData(hdc1080_RH_corrected, hdc1080_T_corrected); + + hdc1080_time = t_now; + } + + if ((CCS811_new_state_time != 0) && (CCS811_new_state_time < millis())) { + Serial.println(""); + Serial.println("SETTING CCS811 TO DRIVE MODE 10 SEC"); + Serial.println(""); + ccs.setDriveMode(CCS811_DRIVE_MODE_10SEC); // wait for at least 10 minutes before entering a new state! + CCS811_new_state_time = 0; + } + + if ((ccs.available()) && (t_now - ccs811_time > 5000)) { + if (!ccs.readData()) { + Serial.print("CCS811: eCO2 = "); + Serial.print(ccs.geteCO2()); + Serial.println("ppm"); + Serial.print("CCS811: eTVOC = "); + Serial.println(ccs.getTVOC()); + } else { + Serial.println("Error reading CCS811 data"); + } + Serial.println(); + ccs811_time = t_now; + } +} + +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; + + //buienradar((float *) location, 12, rainColor); + //luchtmeetnet((float *) location, 12, AQIColor, gmtOffset_sec, daylightOffset_sec); + +#ifndef DISABLE_WIFI + getPrecipitation(address); + + if (counter == 0) { + getAQI(address); + } + + counter = counter + 1; + + if (counter == 5) { + // AQI should only be retreived every 5 minutes + counter = 0; + } +#endif + + /*Serial.println("precipitation:"); + for (int n = 0; n < rainpickerData.precipitation.len; n++) { + Serial.print(" "); + Serial.print(rainpickerData.precipitation.time[n]); + Serial.print(": "); + Serial.println(rainpickerData.precipitation.value[n]); + }*/ + } + + //readSensors(); + +#ifdef DISABLE_WIFI + updateLedsNoWifi(); +#else + updateLeds(); +#endif + + leds.show(); + + delay(100); +#ifndef DISABLE_WIFI + dESPatch.checkForUpdate(true); +#endif +}