Several improvements:

* Fix color mapping of precipitation
* Fix bug in calculation of max value of pollen / AQI
* Added update() method to backend which will only update after interval period has passed
This commit is contained in:
Admar Schoonen 2024-09-07 00:03:53 +02:00
parent 09dc9805bc
commit 7094d20eca
7 changed files with 165 additions and 102 deletions

View file

@ -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

View file

@ -31,14 +31,15 @@ class Display {
std::vector<float> 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<uint8_t, uint8_t, uint8_t> mapValueToColorRGB(float p_value);
std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> 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

View file

@ -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__

View file

@ -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<int32_t>(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) {

View file

@ -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<uint32_t>();
float value = elem["value"].as<float>();
float color_index = mapPrecipitationToColorIndex(elem["value"].as<float>());
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<uint32_t>();
float value = elem["value"].as<float>();
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<float>());
max_value = std::max(max_value, previous_value);
}
previous_value = value;
}
return max_value;
@ -217,6 +222,38 @@ std::vector<float> 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<uint8_t, uint8_t, uint8_t> Display::mapValueToColorRGB(float p_value) {
if (p_value < 1.0) {
return {0, 0, 0}; // black (good)

View file

@ -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<JsonInteger>();
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<JsonArray>());
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);
}

View file

@ -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<uint32_t>(m_iaq_sensor.nextCall & 0xFFFFFFFF);
Serial.println("Next call: " + String(m_next_call));
return true;
} else {
return false;
}
}
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;
}