Release v0.4.0: complete rewrite #1
7 changed files with 165 additions and 102 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
29
src/main.cpp
29
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<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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue