diff --git a/include/backend_communication.hpp b/include/backend_communication.hpp index 322b73c..8103dbd 100644 --- a/include/backend_communication.hpp +++ b/include/backend_communication.hpp @@ -8,6 +8,10 @@ #include "../include/wifi_handler.hpp" class BackendCommunication { + public: + // Public enums + enum State { ERROR, STILL_VALID, UPDATED }; + public: // Constructor BackendCommunication(WifiHandler& p_wifi_handler); @@ -18,6 +22,8 @@ class BackendCommunication { bool getWeatherData(); + State update(); + private: // Private methods bool parseConfig(String& p_payload); @@ -30,8 +36,14 @@ class BackendCommunication { // Public member objects JsonDocument m_config; bool m_is_config_valid; + String m_config_timestamp; JsonDocument m_weather_data; + int32_t m_next_call; + + private: + // Private static members + constexpr static uint32_t INTERVAL_MS = 60U * 1000U; private: // Private member objects diff --git a/include/display.hpp b/include/display.hpp index 8b2d0f6..afe153c 100644 --- a/include/display.hpp +++ b/include/display.hpp @@ -31,14 +31,15 @@ class Display { std::vector mergeJsonArraysFromTimestampHelper(uint32_t p_time_stamp, ArduinoJson::V710PB22::JsonArray p_array_a, ArduinoJson::V710PB22::JsonArray p_array_b); + float mapPrecipitationToColorIndex(const float p_rain_intensity); std::tuple mapValueToColorRGB(float p_value); std::tuple mapValueToColorRGBW(float p_value); void setPixelToColorMappedValue(uint8_t index, float p_value); private: // Private static members - constexpr static uint16_t m_NUM_LEDS_PROTO_V1 = 29U; - constexpr static int16_t m_DATA_PIN_PROTO_V1 = 14; + constexpr static uint16_t NUM_LEDS_PROTO_V1 = 29U; + constexpr static int16_t DATA_PIN_PROTO_V1 = 14; private: // Private members diff --git a/include/sensor_manager.hpp b/include/sensor_manager.hpp index d1c39ed..bd1fd43 100644 --- a/include/sensor_manager.hpp +++ b/include/sensor_manager.hpp @@ -8,19 +8,19 @@ class SensorManager { // Public types struct SensorData { __typeof__(Bsec::iaq) iaq; - __typeof__(Bsec::iaqAccuracy) iaqAccuracy; - __typeof__(Bsec::staticIaq) staticIaq; - __typeof__(Bsec::co2Equivalent) co2Equivalent; - __typeof__(Bsec::breathVocEquivalent) breathVocEquivalent; - __typeof__(Bsec::rawTemperature) rawTemperature; + __typeof__(Bsec::iaqAccuracy) iaq_accuracy; + __typeof__(Bsec::staticIaq) static_iaq; + __typeof__(Bsec::co2Equivalent) co2_equivalent; + __typeof__(Bsec::breathVocEquivalent) breath_voc_equivalent; + __typeof__(Bsec::rawTemperature) raw_temperature; __typeof__(Bsec::pressure) pressure; - __typeof__(Bsec::rawHumidity) rawHumidity; - __typeof__(Bsec::gasResistance) gasResistance; - __typeof__(Bsec::stabStatus) stabStatus; - __typeof__(Bsec::runInStatus) runInStatus; + __typeof__(Bsec::rawHumidity) raw_humidity; + __typeof__(Bsec::gasResistance) gas_resistance; + __typeof__(Bsec::stabStatus) stab_status; + __typeof__(Bsec::runInStatus) run_in_status; __typeof__(Bsec::temperature) temperature; __typeof__(Bsec::humidity) humidity; - __typeof__(Bsec::gasPercentage) gasPercentage; + __typeof__(Bsec::gasPercentage) gas_percentage; }; public: @@ -40,16 +40,15 @@ class SensorManager { private: // Private static members - constexpr static uint16_t m_NUM_LEDS_PROTO_V1 = 29U; - constexpr static int16_t m_DATA_PIN_PROTO_V1 = 14; - constexpr static uint8_t m_BME_CS = 5U; - constexpr static uint8_t m_BSEC_CONFIG_IAQ[] = { + constexpr static uint8_t BME_CS = 5U; + constexpr static uint8_t BSEC_CONFIG_IAQ[] = { #include "config/generic_33v_3s_4d/bsec_iaq.txt" }; private: // Private members - Bsec m_iaqSensor; + Bsec m_iaq_sensor; + uint32_t m_next_call; }; #endif // __SENSOR_MANAGER_HPP__ diff --git a/src/backend_communication.cpp b/src/backend_communication.cpp index 0fd2061..ac4097e 100644 --- a/src/backend_communication.cpp +++ b/src/backend_communication.cpp @@ -3,25 +3,20 @@ #include "../include/backend_communication.hpp" #include "../include/wifi_handler.hpp" -bool isConfigValid = false; -String configTimestamp = ""; - -static HTTPClient http; - -JsonDocument weatherData; - BackendCommunication::BackendCommunication(WifiHandler& p_wifi_handler) : m_wifi_handler(p_wifi_handler) { return; } bool BackendCommunication::getConfig() { + HTTPClient http; + String mac = m_wifi_handler.getMac(false); String url = "https://target.luon.net/~admar/Claire/" + urlencode(mac) + "/config.json"; String payload = ""; - bool isSuccess = false; + bool is_success = false; http.begin(url, root_ca); - if (isConfigValid) { - http.addHeader("If-Modified-Since", configTimestamp); + if (m_is_config_valid) { + http.addHeader("If-Modified-Since", m_config_timestamp); } const char* headerKeys[] = {"Last-Modified"}; @@ -33,46 +28,69 @@ bool BackendCommunication::getConfig() { if (httpCode == 200) { // Content is in payload payload = http.getString(); - isSuccess = parseConfig(payload); + is_success = parseConfig(payload); - if (isSuccess) { - configTimestamp = http.header("Last-Modified"); + if (is_success) { + m_config_timestamp = http.header("Last-Modified"); } } else if (httpCode == 304) { // Content did not change - isSuccess = true; + is_success = true; } else { Serial.println(String(millis()) + " Got http code " + httpCode); } http.end(); - return isSuccess; + return is_success; } bool BackendCommunication::getWeatherData() { + HTTPClient http; + String url = "https://sinoptik.luon.net/forecast?address=" + urlencode(m_config["address"]) + "&metrics=precipitation&metrics=UVI&metrics=AQI&metrics=pollen"; String payload = ""; - bool isSuccess = false; + bool is_success = false; http.begin(url, root_ca); - int httpCode = http.GET(); + int http_code = http.GET(); - if (httpCode > 0) { + if (http_code > 0) { payload = http.getString(); - isSuccess = parseWeatherData(payload); + is_success = parseWeatherData(payload); } else { - Serial.println(String(millis()) + " Got http code " + httpCode); + Serial.println(String(millis()) + " Got http code " + http_code); } http.end(); - return isSuccess; + return is_success; +} + +BackendCommunication::State BackendCommunication::update() { + int32_t time = static_cast(millis() & 0x7FFFFFFF); + int32_t time_delta = m_next_call - time; + + if (time_delta > 0) { + return STILL_VALID; + } + + m_next_call = (time + INTERVAL_MS) & 0x7FFFFFFF; + + if (not getConfig()) { + return ERROR; + } + + if (not getWeatherData()) { + return ERROR; + } + + return UPDATED; } bool BackendCommunication::parseConfig(String& payload) { - bool isSuccess = false; + bool is_success = false; m_config.clear(); @@ -85,11 +103,11 @@ bool BackendCommunication::parseConfig(String& payload) { Serial.println(String(millis()) + " Unsupported config version"); } else { m_is_config_valid = true; - isSuccess = true; + is_success = true; } } - return isSuccess; + return is_success; } bool BackendCommunication::parseWeatherData(String& payload) { diff --git a/src/display.cpp b/src/display.cpp index f1daa10..b82d483 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -35,12 +35,12 @@ void Display::setPollenPlusAQIForecastLeds(uint32_t p_current_time, ArduinoJson: } void Display::setPrecipitationForecastLeds(uint32_t p_current_time, ArduinoJson::V710PB22::JsonArray p_precipitation) { - float previous_value = 0.0f; + float previous_value = mapPrecipitationToColorIndex(0.0f); uint32_t index = 0U; for (JsonObject elem : p_precipitation) { uint32_t time = elem["time"].as(); - float value = elem["value"].as(); + float color_index = mapPrecipitationToColorIndex(elem["value"].as()); if ((index == 0U) and (time > p_current_time)) { setPixelToColorMappedValue(m_precipitation_forecast_map.at(index), previous_value); @@ -48,11 +48,11 @@ void Display::setPrecipitationForecastLeds(uint32_t p_current_time, ArduinoJson: } if (index > 0U) { - setPixelToColorMappedValue(m_precipitation_forecast_map.at(index), value); + setPixelToColorMappedValue(m_precipitation_forecast_map.at(index), color_index); index += 1U; } - previous_value = value; + previous_value = color_index; if (index >= m_precipitation_forecast_map.size()) { break; @@ -134,13 +134,18 @@ void Display::show() { float Display::findMaxValueInTwelveHourInterval(uint32_t p_time_stamp, ArduinoJson::V710PB22::JsonArray p_array) { float max_value = 0.0f; + float previous_value = 0.0f; for (auto elem : p_array) { uint32_t time = elem["time"].as(); + float value = elem["value"].as(); constexpr uint32_t TWELVE_HOURS_IN_SECONDS = 12U * 60U * 60U; + if ((time >= p_time_stamp) and (time < p_time_stamp + TWELVE_HOURS_IN_SECONDS)) { - max_value = std::max(max_value, elem["value"].as()); + max_value = std::max(max_value, previous_value); } + + previous_value = value; } return max_value; @@ -217,6 +222,38 @@ std::vector Display::mergeJsonArraysFromTimestampHelper(uint32_t p_time_s return merged_vector; } +float Display::mapPrecipitationToColorIndex(const float p_rain_intensity) { + float color_index = 0.0f; + + if (p_rain_intensity < 0.1f) { + color_index = 0.0f; + } else if (p_rain_intensity < 0.22f) { + color_index = 1.0f; + } else if (p_rain_intensity < 0.47f) { + color_index = 2.0f; + } else if (p_rain_intensity < 1.0f) { + color_index = 3.0f; + } else if (p_rain_intensity < 2.2f) { + color_index = 4.0f; + } else if (p_rain_intensity < 4.7f) { + color_index = 5.0f; + } else if (p_rain_intensity < 10.0f) { + color_index = 6.0f; + } else if (p_rain_intensity < 22.0f) { + color_index = 7.0f; + } else if (p_rain_intensity < 47.0f) { + color_index = 8.0f; + } else if (p_rain_intensity < 100.0f) { + color_index = 9.0f; + } else if (p_rain_intensity < 220.0f) { + color_index = 10.0f; + } else { + color_index = 11.0f; + } + + return color_index; +} + std::tuple Display::mapValueToColorRGB(float p_value) { if (p_value < 1.0) { return {0, 0, 0}; // black (good) diff --git a/src/main.cpp b/src/main.cpp index 867ebd7..5e71dd3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,16 +83,13 @@ void setup() { } void loop(void) { - uint32_t time_start = millis(); + sensor_manager.read(); + display.setLocalAirQualityLed(sensor_manager.data.static_iaq); - backend_communication.getConfig(); - - if (not backend_communication.m_is_config_valid) { + if (backend_communication.update() == BackendCommunication::State::ERROR) { display.setErrorLed(true); - Serial.println(String("Uptime: " + String(millis())) + ". Error: invalid config"); + Serial.println("Uptime: " + String(millis()) + ", backend error"); } else { - backend_communication.getWeatherData(); - uint32_t utc_time = backend_communication.m_weather_data["time"].as(); Serial.println("Uptime: " + String(millis()) + ", UTC time: " + String(utc_time)); @@ -104,26 +101,12 @@ void loop(void) { display.setUVIForecastLed(utc_time, backend_communication.m_weather_data["UVI"].as()); - display.setLocalAirQualityLed(sensor_manager.data.staticIaq); - display.setWifiStatusLed(false); } - sensor_manager.read(); - display.show(); + dESPatch.checkForUpdate(true); - uint32_t time_end = millis(); - uint32_t time_delta = time_end - time_start; - - uint32_t delay_time = 0U; - constexpr uint32_t ONE_SECOND_IN_MILLISECONDS = 1000U; - constexpr uint32_t INTERVAL = 60U * ONE_SECOND_IN_MILLISECONDS; - - if (time_delta < INTERVAL) { - delay_time = INTERVAL - time_delta; - } - - delay(delay_time); + delay(1000U); } diff --git a/src/sensor_manager.cpp b/src/sensor_manager.cpp index 3025630..0740c25 100644 --- a/src/sensor_manager.cpp +++ b/src/sensor_manager.cpp @@ -9,18 +9,18 @@ SensorManager::SensorManager() { bool SensorManager::setup() { SPI.begin(); - m_iaqSensor.begin(m_BME_CS, SPI); - String output = "BSEC library version " + String(m_iaqSensor.version.major) + "." + - String(m_iaqSensor.version.minor) + "." + String(m_iaqSensor.version.major_bugfix) + "." + - String(m_iaqSensor.version.minor_bugfix); + m_iaq_sensor.begin(BME_CS, SPI); + String output = "BSEC library version " + String(m_iaq_sensor.version.major) + "." + + String(m_iaq_sensor.version.minor) + "." + String(m_iaq_sensor.version.major_bugfix) + "." + + String(m_iaq_sensor.version.minor_bugfix); Serial.println(output); if (not checkIaqSensorStatus()) { return false; } - m_iaqSensor.setConfig(m_BSEC_CONFIG_IAQ); - m_iaqSensor.setTemperatureOffset(13.5f); + m_iaq_sensor.setConfig(BSEC_CONFIG_IAQ); + m_iaq_sensor.setTemperatureOffset(13.5f); if (not checkIaqSensorStatus()) { return false; @@ -40,7 +40,7 @@ bool SensorManager::setup() { BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, BSEC_OUTPUT_GAS_PERCENTAGE}; - m_iaqSensor.updateSubscription(sensorList, 13, BSEC_SAMPLE_RATE_LP); + m_iaq_sensor.updateSubscription(sensorList, 13, BSEC_SAMPLE_RATE_LP); if (checkIaqSensorStatus()) { return false; @@ -50,47 +50,60 @@ bool SensorManager::setup() { } bool SensorManager::read() { - if (m_iaqSensor.run()) { // If new data is available - data.iaq = m_iaqSensor.iaq; - data.iaqAccuracy = m_iaqSensor.iaqAccuracy; - data.staticIaq = m_iaqSensor.staticIaq; - data.co2Equivalent = m_iaqSensor.co2Equivalent; - data.breathVocEquivalent = m_iaqSensor.breathVocEquivalent; - data.rawTemperature = m_iaqSensor.rawTemperature; - data.pressure = m_iaqSensor.pressure; - data.rawHumidity = m_iaqSensor.rawHumidity; - data.gasResistance = m_iaqSensor.gasResistance; - data.stabStatus = m_iaqSensor.stabStatus; - data.runInStatus = m_iaqSensor.runInStatus; - data.temperature = m_iaqSensor.temperature; - data.humidity = m_iaqSensor.humidity; - data.gasPercentage = m_iaqSensor.gasPercentage; - } else { - Serial.println("sensor status: " + String(checkIaqSensorStatus())); + uint32_t time = millis(); + + if ((m_next_call == 0) or (time < m_next_call)) { + return false; + } + + bool is_run_result_valid = m_iaq_sensor.run(); + bool is_sensor_status_valid = checkIaqSensorStatus(); + + if (is_run_result_valid and is_sensor_status_valid) { // If new data is available + data.iaq = m_iaq_sensor.iaq; + data.iaq_accuracy = m_iaq_sensor.iaqAccuracy; + data.static_iaq = m_iaq_sensor.staticIaq; + data.co2_equivalent = m_iaq_sensor.co2Equivalent; + data.breath_voc_equivalent = m_iaq_sensor.breathVocEquivalent; + data.raw_temperature = m_iaq_sensor.rawTemperature; + data.pressure = m_iaq_sensor.pressure; + data.raw_humidity = m_iaq_sensor.rawHumidity; + data.gas_resistance = m_iaq_sensor.gasResistance; + data.stab_status = m_iaq_sensor.stabStatus; + data.run_in_status = m_iaq_sensor.runInStatus; + data.temperature = m_iaq_sensor.temperature; + data.humidity = m_iaq_sensor.humidity; + data.gas_percentage = m_iaq_sensor.gasPercentage; + + m_next_call = static_cast(m_iaq_sensor.nextCall & 0xFFFFFFFF); + + Serial.println("Next call: " + String(m_next_call)); + return true; + } else { + return false; } - return true; } bool SensorManager::checkIaqSensorStatus() { - if (m_iaqSensor.bsecStatus != BSEC_OK) { - if (m_iaqSensor.bsecStatus < BSEC_OK) { - Serial.println("BSEC error code: " + String(m_iaqSensor.bsecStatus)); + if (m_iaq_sensor.bsecStatus != BSEC_OK) { + if (m_iaq_sensor.bsecStatus < BSEC_OK) { + Serial.println("BSEC error code: " + String(m_iaq_sensor.bsecStatus)); return false; } else { - Serial.println("BSEC warning code: " + String(m_iaqSensor.bsecStatus)); + Serial.println("BSEC warning code: " + String(m_iaq_sensor.bsecStatus)); return false; } } - if (m_iaqSensor.bme68xStatus != BME68X_OK) { - if (m_iaqSensor.bme68xStatus < BME68X_OK) { - Serial.println("BME68X error code: " + String(m_iaqSensor.bme68xStatus)); + if (m_iaq_sensor.bme68xStatus != BME68X_OK) { + if (m_iaq_sensor.bme68xStatus < BME68X_OK) { + Serial.println("BME68X error code: " + String(m_iaq_sensor.bme68xStatus)); return false; } else { - Serial.println("BME68X warning code: " + String(m_iaqSensor.bme68xStatus)); + Serial.println("BME68X warning code: " + String(m_iaq_sensor.bme68xStatus)); return false; }