Claire/Claire.ino

1091 lines
29 KiB
C++

//#define DISABLE_WIFI
#include <WiFiManager.h> // https://github.com/admarschoonen/WiFiManager
#include <dESPatch.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <Adafruit_NeoPixel.h>
#include <math.h>
#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 <Adafruit_BMP280.h> // 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://target.luon.net:2356//forecast?&metrics=PAQI&metrics=AQI&metrics=pollen&metrics=UVI";
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 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;
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;
}
}
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
}