Added Display class; Neopixel is disabled for now

This commit is contained in:
Admar Schoonen 2024-08-30 21:08:34 +02:00
parent 388bd0a84c
commit 3aecd54880
9 changed files with 326 additions and 34 deletions

View file

@ -11,6 +11,7 @@
"/home/admar/Documents/Arduino/buienradarklok/Claire/include", "/home/admar/Documents/Arduino/buienradarklok/Claire/include",
"/home/admar/Documents/Arduino/buienradarklok/Claire/src", "/home/admar/Documents/Arduino/buienradarklok/Claire/src",
"/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", "/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src",
"/home/admar/Documents/Arduino/buienradarklok/Claire/.pio/libdeps/esp32dev/Adafruit NeoPixel",
"/home/admar/Documents/Arduino/buienradarklok/Claire/.pio/libdeps/esp32dev/dESPatch", "/home/admar/Documents/Arduino/buienradarklok/Claire/.pio/libdeps/esp32dev/dESPatch",
"/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", "/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/Update/src",
"/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", "/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src",
@ -250,6 +251,7 @@
"/home/admar/Documents/Arduino/buienradarklok/Claire/include", "/home/admar/Documents/Arduino/buienradarklok/Claire/include",
"/home/admar/Documents/Arduino/buienradarklok/Claire/src", "/home/admar/Documents/Arduino/buienradarklok/Claire/src",
"/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", "/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src",
"/home/admar/Documents/Arduino/buienradarklok/Claire/.pio/libdeps/esp32dev/Adafruit NeoPixel",
"/home/admar/Documents/Arduino/buienradarklok/Claire/.pio/libdeps/esp32dev/dESPatch", "/home/admar/Documents/Arduino/buienradarklok/Claire/.pio/libdeps/esp32dev/dESPatch",
"/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", "/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/Update/src",
"/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", "/home/admar/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src",
@ -505,7 +507,7 @@
"" ""
], ],
"cStandard": "gnu99", "cStandard": "gnu99",
"cppStandard": "gnu++11", "cppStandard": "gnu++20",
"compilerPath": "/home/admar/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc", "compilerPath": "/home/admar/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc",
"compilerArgs": [ "compilerArgs": [
"-mlongcalls", "-mlongcalls",

View file

@ -1,5 +1,5 @@
#ifndef __BACKEND_HPP__ #ifndef __BACKEND_COMMUNICATION_HPP__
#define __BACKEND_HPP__ #define __BACKEND_COMMUNICATION_HPP__
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
@ -14,13 +14,16 @@ class BackendCommunication {
public: public:
// Public methods // Public methods
bool parseJsonConfig(String& payload);
bool getConfig(); bool getConfig();
bool getWeatherData(); bool getWeatherData();
private: private:
// Private methods // Private methods
bool parseConfig(String& p_payload);
bool parseWeatherData(String& p_payload);
String urlencode(String str); String urlencode(String str);
public: public:
@ -35,4 +38,4 @@ class BackendCommunication {
WifiHandler& m_wifi_handler; WifiHandler& m_wifi_handler;
}; };
#endif // __BACKEND_HPP__ #endif // __BACKEND_COMMUNICATION_HPP__

48
include/display.hpp Normal file
View file

@ -0,0 +1,48 @@
#ifndef __DISPLAY_HPP__
#define __DISPLAY_HPP__
#include <Adafruit_NeoPixel.h>
#include <Arduino.h>
#include <ArduinoJson.h>
class Display {
public:
// Constructor
Display();
public:
// Public methods
void setPollenPlusAQIForecastLeds(uint32_t p_current_time, ArduinoJson::V710PB22::JsonArray p_aqi,
ArduinoJson::V710PB22::JsonArray p_pollen);
void setPrecipitationForecastLeds(uint32_t p_current_time, ArduinoJson::V710PB22::JsonArray p_precipitation);
void setUVIForecastLed(uint32_t p_current_time, ArduinoJson::V710PB22::JsonArray p_uvi);
void setLocalAirQualityLed();
void setWifiStatusLed(bool p_enable);
private:
// Private methods
float findMaxValueInTwelveHourInterval(uint32_t p_time_stamp, ArduinoJson::V710PB22::JsonArray p_array);
std::vector<float> mergeJsonArraysFromTimestamp(uint32_t p_time_stamp, ArduinoJson::V710PB22::JsonArray p_array_a,
ArduinoJson::V710PB22::JsonArray p_array_b);
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;
private:
// Private members
Adafruit_NeoPixel m_leds;
std::array<uint8_t, 12U> m_precipitation_forecast_map;
std::array<uint8_t, 12U> m_air_quality_forecast_map;
uint8_t m_max_pollen_map;
uint8_t m_max_air_qualilty_map;
uint8_t m_uvi_map;
uint8_t m_local_air_quality_map;
uint8_t m_wifi_status_map;
};
#endif // __DISPLAY_HPP__

View file

@ -3,10 +3,12 @@
#include <WiFiManager-esp32.h> // https://github.com/admarschoonen/WiFiManager #include <WiFiManager-esp32.h> // https://github.com/admarschoonen/WiFiManager
#include "display.hpp"
class WifiHandler { class WifiHandler {
public: public:
// Constructor // Constructor
WifiHandler(uint8_t p_led_pin); WifiHandler(uint8_t p_led_pin, Display p_display);
public: public:
// Public static methods // Public static methods
@ -28,6 +30,7 @@ class WifiHandler {
private: private:
// Private objects // Private objects
WiFiManager m_wifiManager; WiFiManager m_wifiManager;
Display& m_display;
}; };
#endif // __WIFIHANDLER_HPP__ #endif // __WIFIHANDLER_HPP__

View file

@ -12,7 +12,10 @@
platform = espressif32 platform = espressif32
board = esp32dev board = esp32dev
framework = arduino framework = arduino
build_unflags = -std=gnu++11
build_flags = -std=gnu++2a
lib_deps = lib_deps =
https://github.com/admarschoonen/WIFIMANAGER-ESP32.git#v0.100.0 https://github.com/admarschoonen/WIFIMANAGER-ESP32.git#v0.100.0
https://github.com/admarschoonen/dESPatch.git#v0.9.5 https://github.com/admarschoonen/dESPatch.git#v0.9.5
bblanchon/ArduinoJson @ ^7.1.0 bblanchon/ArduinoJson@^7.1.0
adafruit/Adafruit NeoPixel@^1.12.3

View file

@ -12,27 +12,6 @@ JsonDocument weatherData;
BackendCommunication::BackendCommunication(WifiHandler& p_wifi_handler) : m_wifi_handler(p_wifi_handler) { return; } BackendCommunication::BackendCommunication(WifiHandler& p_wifi_handler) : m_wifi_handler(p_wifi_handler) { return; }
bool BackendCommunication::parseJsonConfig(String& payload) {
bool isSuccess = false;
m_config.clear();
DeserializationError error = deserializeJson(m_config, payload);
if (error) {
Serial.print(String(millis()) + " DeserializeJson() failed: " + String(error.f_str()));
} else {
if (m_config["config version"] != "0.0.1") {
Serial.println(String(millis()) + " Unsupported config version");
} else {
m_is_config_valid = true;
isSuccess = true;
}
}
return isSuccess;
}
bool BackendCommunication::getConfig() { bool BackendCommunication::getConfig() {
String mac = m_wifi_handler.getMac(false); String mac = m_wifi_handler.getMac(false);
String url = "https://target.luon.net/~admar/Claire/" + urlencode(mac) + "/config.json"; String url = "https://target.luon.net/~admar/Claire/" + urlencode(mac) + "/config.json";
@ -54,11 +33,10 @@ bool BackendCommunication::getConfig() {
if (httpCode == 200) { if (httpCode == 200) {
// Content is in payload // Content is in payload
payload = http.getString(); payload = http.getString();
isSuccess = parseJsonConfig(payload); isSuccess = parseConfig(payload);
if (isSuccess) { if (isSuccess) {
configTimestamp = http.header("Last-Modified"); configTimestamp = http.header("Last-Modified");
Serial.println(String(millis()) + " ConfigTimestamp: " + configTimestamp);
} }
} else if (httpCode == 304) { } else if (httpCode == 304) {
// Content did not change // Content did not change
@ -84,7 +62,7 @@ bool BackendCommunication::getWeatherData() {
if (httpCode > 0) { if (httpCode > 0) {
payload = http.getString(); payload = http.getString();
// isSuccess = parseJsonConfig(payload); isSuccess = parseWeatherData(payload);
} else { } else {
Serial.println(String(millis()) + " Got http code " + httpCode); Serial.println(String(millis()) + " Got http code " + httpCode);
} }
@ -93,6 +71,44 @@ bool BackendCommunication::getWeatherData() {
return isSuccess; return isSuccess;
} }
bool BackendCommunication::parseConfig(String& payload) {
bool isSuccess = false;
m_config.clear();
DeserializationError error = deserializeJson(m_config, payload);
if (error) {
Serial.print(String(millis()) + " DeserializeJson() failed: " + String(error.f_str()));
} else {
if (m_config["config version"] != "0.0.1") {
Serial.println(String(millis()) + " Unsupported config version");
} else {
m_is_config_valid = true;
isSuccess = true;
}
}
return isSuccess;
}
bool BackendCommunication::parseWeatherData(String& payload) {
bool isSuccess = false;
m_weather_data.clear();
DeserializationError error = deserializeJson(m_weather_data, payload);
if (error) {
Serial.print(String(millis()) + " DeserializeJson() failed: " + String(error.f_str()));
} else {
m_is_config_valid = true;
isSuccess = true;
}
return isSuccess;
}
String BackendCommunication::urlencode(String str) { String BackendCommunication::urlencode(String str) {
// From https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/ // From https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/
String encodedString = ""; String encodedString = "";

185
src/display.cpp Normal file
View file

@ -0,0 +1,185 @@
#include "../include/display.hpp"
Display::Display() {
// m_leds = Adafruit_NeoPixel(m_NUM_LEDS_PROTO_V1, m_DATA_PIN_PROTO_V1, NEO_RGBW + NEO_KHZ800);
m_precipitation_forecast_map = {11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
m_air_quality_forecast_map = {23, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22};
m_max_pollen_map = 26;
m_max_air_qualilty_map = 24;
m_uvi_map = 27;
m_local_air_quality_map = 25;
m_wifi_status_map = 28;
return;
}
void Display::setPollenPlusAQIForecastLeds(uint32_t p_current_time, ArduinoJson::V710PB22::JsonArray p_aqi,
ArduinoJson::V710PB22::JsonArray p_pollen) {
uint32_t first_aqi_time = p_pollen[0]["aqi"].as<uint32_t>();
uint32_t first_pollen_time = p_pollen[0]["time"].as<uint32_t>();
std::vector<float> merged_array = {};
if (first_aqi_time <= first_pollen_time) {
merged_array = mergeJsonArraysFromTimestamp(p_current_time, p_aqi, p_pollen);
} else {
merged_array = mergeJsonArraysFromTimestamp(p_current_time, p_pollen, p_aqi);
}
uint32_t index = 0;
for (float value : merged_array) {
setPixelToColorMappedValue(m_air_quality_forecast_map.at(index), value);
index++;
}
float max_aqi = findMaxValueInTwelveHourInterval(p_current_time, p_aqi);
setPixelToColorMappedValue(m_max_air_qualilty_map, max_aqi);
float max_pollen = findMaxValueInTwelveHourInterval(p_current_time, p_pollen);
setPixelToColorMappedValue(m_max_pollen_map, max_pollen);
}
void Display::setPrecipitationForecastLeds(uint32_t p_current_time, ArduinoJson::V710PB22::JsonArray p_precipitation) {
float previous_value = 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>();
if ((index == 0U) and (time > p_current_time)) {
setPixelToColorMappedValue(m_precipitation_forecast_map.at(index), previous_value);
index = 1U;
}
if (index > 0U) {
setPixelToColorMappedValue(m_precipitation_forecast_map.at(index), value);
index += 1U;
}
previous_value = value;
}
}
void Display::setUVIForecastLed(uint32_t p_current_time, ArduinoJson::V710PB22::JsonArray p_uvi) {
for (JsonObject elem : p_uvi) {
uint32_t time = elem["time"].as<uint32_t>();
float value = elem["value"].as<float>();
if (time > p_current_time) {
setPixelToColorMappedValue(m_uvi_map, value);
break;
}
}
}
void Display::setLocalAirQualityLed() {}
void Display::setWifiStatusLed(bool p_enable) {}
float Display::findMaxValueInTwelveHourInterval(uint32_t p_time_stamp, ArduinoJson::V710PB22::JsonArray p_array) {
float max_value = 0.0f;
for (auto elem : p_array) {
uint32_t time = elem["time"].as<uint32_t>();
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>());
}
}
return max_value;
}
std::vector<float> Display::mergeJsonArraysFromTimestamp(uint32_t p_time_stamp,
ArduinoJson::V710PB22::JsonArray p_array_a,
ArduinoJson::V710PB22::JsonArray p_array_b) {
// Assumption: timestamp in p_array_a <= p_array_b
std::vector<float> merged_vector = {};
size_t start_point_b = 0;
for (auto elem_a : p_array_a) {
uint32_t time_a = elem_a["time"].as<uint32_t>();
if (time_a >= p_time_stamp) {
float merged_value = elem_a["value"].as<float>();
for (size_t index_b = start_point_b; index_b < p_array_b.size(); index_b++) {
uint32_t time_b = p_array_b[index_b]["time"].as<uint32_t>();
if (time_b >= time_a) {
float value_b = p_array_b[index_b]["value"].as<float>();
merged_value = std::max(merged_value, value_b);
merged_vector.push_back(merged_value);
start_point_b = index_b + 1U;
break;
}
}
}
}
for (size_t index_b = start_point_b; index_b < p_array_b.size(); index_b++) {
float value_b = p_array_b[index_b]["value"].as<float>();
merged_vector.push_back(value_b);
}
return merged_vector;
}
std::tuple<uint8_t, uint8_t, uint8_t> Display::mapValueToColorRGB(float p_value) {
if (p_value < 1.0) {
return {0, 0, 0}; // black (good)
} else if (p_value < 2.0) {
return {0, 0, 160}; // blue (good)
} else if (p_value < 3.0) {
return {0, 255, 255}; // cyan (good)
} else if (p_value < 4.0) {
return {255, 255, 255}; // white (mediocre)
} else if (p_value < 5.0) {
return {200, 200, 32}; // light yellow (mediocre)
} else if (p_value < 6.0) {
return {120, 120, 0}; // yellow (mediocre)
} else if (p_value < 7.0) {
return {200, 80, 0}; // orange (inadequate)
} else if (p_value < 8.0) {
return {255, 50, 0}; // red-orange (inadequate)
} else if (p_value < 9.0) {
return {180, 0, 0}; // red (bad)
} else if (p_value < 10.0) {
return {200, 0, 160}; // magenta (bad)
} else {
return {60, 0, 210}; // purple (terrible)
}
}
std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> Display::mapValueToColorRGBW(float p_value) {
if (p_value < 1.0) {
return {0, 0, 0, 0}; // black (good)
} else if (p_value < 2.0) {
return {0, 0, 255, 0}; // blue (good)
} else if (p_value < 3.0) {
return {0, 255, 255, 0}; // cyan (good)
} else if (p_value < 4.0) {
return {255, 255, 255, 0}; // white (mediocre)
} else if (p_value < 5.0) {
return {200, 200, 32, 0}; // light yellow (mediocre)
} else if (p_value < 6.0) {
return {120, 120, 0, 0}; // yellow (mediocre)
} else if (p_value < 7.0) {
return {200, 80, 0, 0}; // orange (inadequate)
} else if (p_value < 8.0) {
return {255, 50, 0, 0}; // red-orange (inadequate)
} else if (p_value < 9.0) {
return {180, 0, 0, 0}; // red (bad)
} else if (p_value < 10.0) {
return {200, 0, 160, 0}; // magenta (bad)
} else {
return {60, 0, 210, 0}; // purple (terrible)
}
}
void Display::setPixelToColorMappedValue(uint8_t p_index, float p_value) {
auto [red, green, blue, white] = mapValueToColorRGBW(p_value);
m_leds.setPixelColor(p_index, m_leds.Color(red, green, blue, white));
}

View file

@ -1,6 +1,7 @@
#include <dESPatch.h> #include <dESPatch.h>
#include "../include/backend_communication.hpp" #include "../include/backend_communication.hpp"
#include "../include/display.hpp"
#include "../include/wifi_handler.hpp" #include "../include/wifi_handler.hpp"
#ifndef LED_BUILTIN #ifndef LED_BUILTIN
@ -14,7 +15,8 @@
#define CONNECT_BUTTON BUTTON_BUILTIN #define CONNECT_BUTTON BUTTON_BUILTIN
DESPatch dESPatch; DESPatch dESPatch;
WifiHandler wifi_handler(LED_BUILTIN); Display display;
WifiHandler wifi_handler(LED_BUILTIN, display);
BackendCommunication backend_communication(wifi_handler); BackendCommunication backend_communication(wifi_handler);
@ -56,7 +58,37 @@ void loop(void) {
} else { } else {
Serial.println(String(millis()) + Serial.println(String(millis()) +
" Address: " + String(static_cast<const char*>(backend_communication.m_config["address"]))); " Address: " + String(static_cast<const char*>(backend_communication.m_config["address"])));
// getWeatherData();
backend_communication.getWeatherData();
String text = String(millis()) + " Precipitation:";
for (JsonObject elem : backend_communication.m_weather_data["precipitation"].as<JsonArray>()) {
text += " " + String(elem["value"].as<float>());
}
Serial.println(text);
text = String(millis()) + " AQI:";
for (JsonObject elem : backend_communication.m_weather_data["AQI"].as<JsonArray>()) {
text += " " + String(elem["value"].as<float>());
}
Serial.println(text);
text = String(millis()) + " Pollen:";
for (JsonObject elem : backend_communication.m_weather_data["pollen"].as<JsonArray>()) {
text += " " + String(elem["value"].as<float>());
}
Serial.println(text);
text = String(millis()) + " UVI:";
for (JsonObject elem : backend_communication.m_weather_data["UVI"].as<JsonArray>()) {
text += " " + String(elem["value"].as<float>());
}
Serial.println(text);
uint32_t time = backend_communication.m_weather_data["time"].as<JsonInteger>();
text = String(millis()) + " time: " + std::to_string(time).c_str();
Serial.println(text);
// display.setUVIForecastLed(time, backend_communication.m_weather_data["UVI"].as<JsonArray>());
} }
dESPatch.checkForUpdate(true); dESPatch.checkForUpdate(true);
delay(2000); delay(2000);

View file

@ -5,7 +5,7 @@
static uint8_t g_led_pin; static uint8_t g_led_pin;
static Ticker g_ticker; static Ticker g_ticker;
WifiHandler::WifiHandler(uint8_t p_led_pin) { WifiHandler::WifiHandler(uint8_t p_led_pin, Display p_display) : m_display(p_display) {
g_led_pin = p_led_pin; g_led_pin = p_led_pin;
return; return;
} }