Merge branch 'feat-weather' of https://github.com/jpirnay/crosspoint-reader into mybuild
This commit is contained in:
@@ -366,6 +366,18 @@ STR_WEATHER_DAY_THU: "Thu"
|
||||
STR_WEATHER_DAY_FRI: "Fri"
|
||||
STR_WEATHER_DAY_SAT: "Sat"
|
||||
STR_WEATHER_DAY_SUN: "Sun"
|
||||
STR_WEATHER_MONTH_JAN: "Jan"
|
||||
STR_WEATHER_MONTH_FEB: "Feb"
|
||||
STR_WEATHER_MONTH_MAR: "Mar"
|
||||
STR_WEATHER_MONTH_APR: "Apr"
|
||||
STR_WEATHER_MONTH_MAY: "May"
|
||||
STR_WEATHER_MONTH_JUN: "Jun"
|
||||
STR_WEATHER_MONTH_JUL: "Jul"
|
||||
STR_WEATHER_MONTH_AUG: "Aug"
|
||||
STR_WEATHER_MONTH_SEP: "Sep"
|
||||
STR_WEATHER_MONTH_OCT: "Oct"
|
||||
STR_WEATHER_MONTH_NOV: "Nov"
|
||||
STR_WEATHER_MONTH_DEC: "Dec"
|
||||
STR_WEATHER_DESC_CLEAR_SKY: "Clear sky"
|
||||
STR_WEATHER_DESC_MAINLY_CLEAR: "Mainly clear"
|
||||
STR_WEATHER_DESC_PARTLY_CLOUDY: "Partly cloudy"
|
||||
|
||||
@@ -350,6 +350,18 @@ STR_WEATHER_DAY_THU: "Do"
|
||||
STR_WEATHER_DAY_FRI: "Fr"
|
||||
STR_WEATHER_DAY_SAT: "Sa"
|
||||
STR_WEATHER_DAY_SUN: "So"
|
||||
STR_WEATHER_MONTH_JAN: "Jan"
|
||||
STR_WEATHER_MONTH_FEB: "Feb"
|
||||
STR_WEATHER_MONTH_MAR: "Mär"
|
||||
STR_WEATHER_MONTH_APR: "Apr"
|
||||
STR_WEATHER_MONTH_MAY: "Mai"
|
||||
STR_WEATHER_MONTH_JUN: "Jun"
|
||||
STR_WEATHER_MONTH_JUL: "Jul"
|
||||
STR_WEATHER_MONTH_AUG: "Aug"
|
||||
STR_WEATHER_MONTH_SEP: "Sep"
|
||||
STR_WEATHER_MONTH_OCT: "Okt"
|
||||
STR_WEATHER_MONTH_NOV: "Nov"
|
||||
STR_WEATHER_MONTH_DEC: "Dez"
|
||||
|
||||
STR_WEATHER_DESC_CLEAR_SKY: "Klarer Himmel"
|
||||
STR_WEATHER_DESC_MAINLY_CLEAR: "Überwiegend klar"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#include "../../src/network/HttpDownloader.h"
|
||||
@@ -12,9 +13,7 @@ namespace {
|
||||
constexpr char WEATHER_CACHE_FILE[] = "/.crosspoint/weather_cache.json";
|
||||
|
||||
std::string buildForecastUrl(const WeatherSettingsStore& settings) {
|
||||
// Open-Meteo supports plain HTTP. Using HTTP here avoids TLS handshake
|
||||
// instability observed on some ESP32-C3 builds.
|
||||
std::string url = "http://api.open-meteo.com/v1/forecast?";
|
||||
std::string url = "https://api.open-meteo.com/v1/forecast?";
|
||||
url += "latitude=" + std::to_string(settings.getLatitude());
|
||||
url += "&longitude=" + std::to_string(settings.getLongitude());
|
||||
url +=
|
||||
@@ -33,8 +32,33 @@ std::string buildForecastUrl(const WeatherSettingsStore& settings) {
|
||||
url += "&forecast_hours=48";
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string buildRequestSignature(const WeatherSettingsStore& settings) {
|
||||
char latitude[24];
|
||||
char longitude[24];
|
||||
snprintf(latitude, sizeof(latitude), "%.6f", settings.getLatitude());
|
||||
snprintf(longitude, sizeof(longitude), "%.6f", settings.getLongitude());
|
||||
|
||||
std::string signature = "lat=";
|
||||
signature += latitude;
|
||||
signature += "|lon=";
|
||||
signature += longitude;
|
||||
signature += "|temp=";
|
||||
signature += settings.getTempUnitParam();
|
||||
signature += "|wind=";
|
||||
signature += settings.getWindUnitParam();
|
||||
signature += "|precip=";
|
||||
signature += settings.getPrecipUnitParam();
|
||||
signature += "|days=";
|
||||
signature += std::to_string(settings.getForecastDays());
|
||||
return signature;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string WeatherClient::buildRequestSignature(const WeatherSettingsStore& settings) {
|
||||
return ::buildRequestSignature(settings);
|
||||
}
|
||||
|
||||
WeatherData WeatherClient::getWeather(const WeatherSettingsStore& settings, bool forceRefresh) {
|
||||
if (!settings.hasLocation()) {
|
||||
WeatherData data;
|
||||
@@ -42,19 +66,26 @@ WeatherData WeatherClient::getWeather(const WeatherSettingsStore& settings, bool
|
||||
return data;
|
||||
}
|
||||
|
||||
const std::string requestSignature = buildRequestSignature(settings);
|
||||
|
||||
if (!forceRefresh) {
|
||||
WeatherData cached;
|
||||
if (loadCache(cached) && cached.valid) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (now - cached.fetchedAt < CACHE_TTL_SECONDS) {
|
||||
LOG_DBG("WEA", "Using cached weather data (age: %ld s)", (long)(now - cached.fetchedAt));
|
||||
if (cached.requestSignature != requestSignature) {
|
||||
LOG_DBG("WEA", "Ignoring cache with mismatched request signature");
|
||||
Storage.remove(WEATHER_CACHE_FILE);
|
||||
} else {
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (now - cached.fetchedAt < CACHE_TTL_SECONDS) {
|
||||
LOG_DBG("WEA", "Using cached weather data (age: %ld s)", (long)(now - cached.fetchedAt));
|
||||
return cached;
|
||||
}
|
||||
LOG_DBG("WEA", "Cache expired (age: %ld s)", (long)(now - cached.fetchedAt));
|
||||
// Cache-first behavior: return stale cache and let the caller decide
|
||||
// whether/when to perform a network refresh.
|
||||
return cached;
|
||||
}
|
||||
LOG_DBG("WEA", "Cache expired (age: %ld s)", (long)(now - cached.fetchedAt));
|
||||
// Cache-first behavior: return stale cache and let the caller decide
|
||||
// whether/when to perform a network refresh.
|
||||
return cached;
|
||||
}
|
||||
|
||||
LOG_DBG("WEA", "No cache available; caller should establish network and force refresh");
|
||||
@@ -68,6 +99,7 @@ WeatherData WeatherClient::getWeather(const WeatherSettingsStore& settings, bool
|
||||
|
||||
WeatherData WeatherClient::fetchFromApi(const WeatherSettingsStore& settings) {
|
||||
WeatherData data;
|
||||
data.requestSignature = buildRequestSignature(settings);
|
||||
std::string url = buildForecastUrl(settings);
|
||||
LOG_DBG("WEA", "fetchFromApi[1] start");
|
||||
LOG_DBG("WEA", "fetchFromApi[2] url length=%zu", url.size());
|
||||
@@ -188,6 +220,7 @@ bool WeatherClient::saveCache(const WeatherData& data) {
|
||||
Storage.mkdir("/.crosspoint");
|
||||
|
||||
JsonDocument doc;
|
||||
doc["requestSignature"] = data.requestSignature;
|
||||
doc["fetchedAt"] = data.fetchedAt;
|
||||
doc["timezone"] = data.timezone;
|
||||
doc["utcOffsetSeconds"] = data.utcOffsetSeconds;
|
||||
@@ -253,6 +286,7 @@ bool WeatherClient::loadCache(WeatherData& data) {
|
||||
}
|
||||
|
||||
data.fetchedAt = doc["fetchedAt"] | (time_t)0;
|
||||
data.requestSignature = doc["requestSignature"] | std::string("");
|
||||
data.timezone = doc["timezone"] | std::string("");
|
||||
data.utcOffsetSeconds = doc["utcOffsetSeconds"] | 0;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class WeatherClient {
|
||||
private:
|
||||
static WeatherData fetchFromApi(const WeatherSettingsStore& settings);
|
||||
static bool parseWeatherJson(const std::string& json, WeatherData& data);
|
||||
static std::string buildRequestSignature(const WeatherSettingsStore& settings);
|
||||
static bool saveCache(const WeatherData& data);
|
||||
static bool loadCache(WeatherData& data);
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ struct WeatherData {
|
||||
CurrentWeather current;
|
||||
std::vector<DailyForecast> daily;
|
||||
std::vector<HourlyForecast> hourly;
|
||||
std::string requestSignature;
|
||||
std::string timezone;
|
||||
int utcOffsetSeconds = 0;
|
||||
time_t fetchedAt = 0;
|
||||
|
||||
@@ -4,180 +4,7 @@
|
||||
|
||||
// Weather icon glyph source attribution:
|
||||
// https://github.com/erikflowers/weather-icons
|
||||
// 64x64 monochrome icon bitmaps in this file are adapted from that icon set.
|
||||
|
||||
// ============================================================================
|
||||
// 64x64 Large Weather Icons (1-bit, MSB-first, row-major)
|
||||
// Each row = 64 pixels = 8 bytes. Total = 64 * 8 = 512 bytes per icon.
|
||||
// Pixel ON (1) = white/clear, Pixel OFF (0) = black/drawn.
|
||||
// These are "inverted" bitmaps - 0 bits are drawn as black pixels.
|
||||
// ============================================================================
|
||||
|
||||
// Clear Day - Sun with rays
|
||||
// clang-format off
|
||||
static const uint8_t ICON_CLEAR_DAY_48[] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFE,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,
|
||||
0xFF,0x8F,0xFE,0x7F,0xF1,0xFF, 0xFF,0x1F,0xFE,0x7F,0xF8,0xFF, 0xFF,0x3F,0x80,0x01,0xFC,0xFF,
|
||||
0xFF,0x7F,0x00,0x00,0xFE,0xFF, 0xFF,0xFC,0x00,0x00,0x3F,0xFF, 0xFF,0xF8,0x00,0x00,0x1F,0xFF,
|
||||
0xFF,0xF0,0x00,0x00,0x0F,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF,
|
||||
0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xE0,0xC0,0x00,0x00,0x03,0x07,
|
||||
0xE0,0x80,0x00,0x00,0x01,0x07, 0xF0,0x80,0x00,0x00,0x01,0x0F, 0xF8,0x80,0x00,0x00,0x01,0x1F,
|
||||
0xFC,0x80,0x00,0x00,0x01,0x3F, 0xFC,0x80,0x00,0x00,0x01,0x3F, 0xF8,0x80,0x00,0x00,0x01,0x1F,
|
||||
0xF0,0x80,0x00,0x00,0x01,0x0F, 0xE0,0x80,0x00,0x00,0x01,0x07, 0xE0,0xC0,0x00,0x00,0x03,0x07,
|
||||
0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF,
|
||||
0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xF0,0x00,0x00,0x0F,0xFF, 0xFF,0xF8,0x00,0x00,0x1F,0xFF,
|
||||
0xFF,0xFC,0x00,0x00,0x3F,0xFF, 0xFF,0x7F,0x00,0x00,0xFE,0xFF, 0xFF,0x3F,0x80,0x01,0xFC,0xFF,
|
||||
0xFF,0x1F,0xFE,0x7F,0xF8,0xFF, 0xFF,0x8F,0xFE,0x7F,0xF1,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFE,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFE,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
};
|
||||
|
||||
// Clear Night - Moon crescent
|
||||
static const uint8_t ICON_CLEAR_NIGHT_48[] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFC,0x0F,0xFF,0xFF, 0xFF,0xFF,0xF0,0x03,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE0,0x01,0xFF,0xFF, 0xFF,0xFF,0xC0,0x00,0xFF,0xFF, 0xFF,0xFF,0x80,0x00,0x7F,0xFF,
|
||||
0xFF,0xFF,0x00,0x00,0x3F,0xFF, 0xFF,0xFF,0x00,0x00,0x3F,0xFF, 0xFF,0xFE,0x00,0x00,0x1F,0xFF,
|
||||
0xFF,0xFE,0x00,0x00,0x3F,0xFF, 0xFF,0xFC,0x00,0x00,0x7F,0xFF, 0xFF,0xFC,0x00,0x00,0xFF,0xFF,
|
||||
0xFF,0xFC,0x00,0x01,0xFF,0xFF, 0xFF,0xF8,0x00,0x03,0xFF,0xFF, 0xFF,0xF8,0x00,0x07,0xFF,0xFF,
|
||||
0xFF,0xF8,0x00,0x07,0xFF,0xFF, 0xFF,0xF8,0x00,0x0F,0xFF,0xFF, 0xFF,0xF8,0x00,0x0F,0xFF,0xFF,
|
||||
0xFF,0xF8,0x00,0x0F,0xFF,0xFF, 0xFF,0xF8,0x00,0x0F,0xFF,0xFF, 0xFF,0xF8,0x00,0x0F,0xFF,0xFF,
|
||||
0xFF,0xF8,0x00,0x0F,0xFF,0xFF, 0xFF,0xF8,0x00,0x07,0xFF,0xFF, 0xFF,0xF8,0x00,0x07,0xFF,0xFF,
|
||||
0xFF,0xF8,0x00,0x03,0xFF,0xFF, 0xFF,0xFC,0x00,0x01,0xFF,0xFF, 0xFF,0xFC,0x00,0x00,0xFF,0xFF,
|
||||
0xFF,0xFC,0x00,0x00,0x7F,0xFF, 0xFF,0xFE,0x00,0x00,0x3F,0xFF, 0xFF,0xFE,0x00,0x00,0x1F,0xFF,
|
||||
0xFF,0xFF,0x00,0x00,0x1F,0xFF, 0xFF,0xFF,0x00,0x00,0x3F,0xFF, 0xFF,0xFF,0x80,0x00,0x7F,0xFF,
|
||||
0xFF,0xFF,0xC0,0x00,0xFF,0xFF, 0xFF,0xFF,0xE0,0x01,0xFF,0xFF, 0xFF,0xFF,0xF0,0x03,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFC,0x0F,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
};
|
||||
|
||||
// Partly Cloudy Day - Sun behind cloud
|
||||
static const uint8_t ICON_PARTLY_CLOUDY_DAY_48[] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFE,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7F,0xFF,0xFF, 0xFF,0x9F,0xFE,0x7F,0xF9,0xFF,
|
||||
0xFF,0x3F,0xFE,0x7F,0xFC,0xFF, 0xFF,0x7F,0x80,0x01,0xFE,0xFF, 0xFF,0xFF,0x00,0x00,0xFF,0xFF,
|
||||
0xFF,0xFC,0x00,0x00,0x3F,0xFF, 0xFF,0xF8,0x00,0x00,0x1F,0xFF, 0xFF,0xF0,0x00,0x00,0x0F,0xFF,
|
||||
0xFF,0xF0,0x00,0x00,0x0F,0xFF, 0xE0,0xE0,0x00,0x00,0x07,0x07, 0xF0,0xE0,0x00,0x00,0x07,0x0F,
|
||||
0xF8,0xE0,0x00,0x00,0x07,0x1F, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xF0,0x00,0x00,0x0F,0xFF,
|
||||
0xFF,0xF0,0x00,0x00,0x0F,0xFF, 0xFF,0xF8,0x00,0x00,0x1F,0xFF, 0xFF,0xFC,0x00,0x00,0x3F,0xFF,
|
||||
0xFF,0xFE,0x00,0x00,0x7F,0xFF, 0xFF,0xFC,0x00,0x00,0x3F,0xFF, 0xFF,0xF0,0x00,0x00,0x0F,0xFF,
|
||||
0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0x80,0x00,0x00,0x01,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0x00,0x00,0x00,0x00,0xFF, 0xFE,0x00,0x00,0x00,0x00,0x7F,
|
||||
0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F,
|
||||
0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFF,0x00,0x00,0x00,0x00,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0xF0,0x00,0x00,0x0F,0xFF,
|
||||
0xFF,0xFE,0x00,0x00,0x7F,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
};
|
||||
|
||||
// Partly Cloudy Night - Moon behind cloud (reuse partly cloudy day for simplicity)
|
||||
static const uint8_t* ICON_PARTLY_CLOUDY_NIGHT_48 = ICON_PARTLY_CLOUDY_DAY_48;
|
||||
|
||||
// Overcast - Full cloud
|
||||
static const uint8_t ICON_OVERCAST_48[] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFC,0x3F,0xFF,0xFF,
|
||||
0xFF,0xFF,0xF0,0x0F,0xFF,0xFF, 0xFF,0xFF,0xE0,0x07,0xFF,0xFF, 0xFF,0xFF,0xC0,0x03,0xFF,0xFF,
|
||||
0xFF,0xFF,0x80,0x01,0xFF,0xFF, 0xFF,0xFF,0x00,0x00,0xFF,0xFF, 0xFF,0xFF,0x00,0x00,0xFF,0xFF,
|
||||
0xFF,0xFE,0x00,0x00,0x7F,0xFF, 0xFF,0xFC,0x00,0x00,0x3F,0xFF, 0xFF,0xF8,0x00,0x00,0x1F,0xFF,
|
||||
0xFF,0xF0,0x00,0x00,0x0F,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0x00,0x00,0x00,0x00,0xFF, 0xFE,0x00,0x00,0x00,0x00,0x7F,
|
||||
0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F,
|
||||
0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFF,0x00,0x00,0x00,0x00,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0xF0,0x00,0x00,0x0F,0xFF,
|
||||
0xFF,0xFE,0x00,0x00,0x7F,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
};
|
||||
|
||||
// Rain - Cloud with rain drops
|
||||
static const uint8_t ICON_RAIN_48[] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFC,0x3F,0xFF,0xFF, 0xFF,0xFF,0xF0,0x0F,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE0,0x07,0xFF,0xFF, 0xFF,0xFF,0xC0,0x03,0xFF,0xFF, 0xFF,0xFF,0x80,0x01,0xFF,0xFF,
|
||||
0xFF,0xFF,0x00,0x00,0xFF,0xFF, 0xFF,0xFE,0x00,0x00,0x7F,0xFF, 0xFF,0xF8,0x00,0x00,0x1F,0xFF,
|
||||
0xFF,0xF0,0x00,0x00,0x0F,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0x00,0x00,0x00,0x00,0xFF, 0xFE,0x00,0x00,0x00,0x00,0x7F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFF,0x00,0x00,0x00,0x00,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xFC,0x00,0x00,0x3F,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFB,0xDF,0xBF,0x7F,0xFF,
|
||||
0xFF,0xF3,0xCF,0x9F,0x3F,0xFF, 0xFF,0xF3,0xCF,0x9F,0x3F,0xFF, 0xFF,0xE7,0xE7,0xCF,0x9F,0xFF,
|
||||
0xFF,0xE7,0xE7,0xCF,0x9F,0xFF, 0xFF,0xCF,0xF3,0xE7,0xCF,0xFF, 0xFF,0xCF,0xF3,0xE7,0xCF,0xFF,
|
||||
0xFF,0xCF,0xF3,0xE7,0xCF,0xFF, 0xFF,0xDF,0xFB,0xF7,0xEF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
};
|
||||
|
||||
// Snow - Cloud with snowflakes
|
||||
static const uint8_t ICON_SNOW_48[] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFC,0x3F,0xFF,0xFF, 0xFF,0xFF,0xF0,0x0F,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE0,0x07,0xFF,0xFF, 0xFF,0xFF,0xC0,0x03,0xFF,0xFF, 0xFF,0xFF,0x80,0x01,0xFF,0xFF,
|
||||
0xFF,0xFF,0x00,0x00,0xFF,0xFF, 0xFF,0xFE,0x00,0x00,0x7F,0xFF, 0xFF,0xF8,0x00,0x00,0x1F,0xFF,
|
||||
0xFF,0xF0,0x00,0x00,0x0F,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0x00,0x00,0x00,0x00,0xFF, 0xFE,0x00,0x00,0x00,0x00,0x7F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFF,0x00,0x00,0x00,0x00,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xFC,0x00,0x00,0x3F,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xF9,0xE7,0xCF,0x3F,0xFF,
|
||||
0xFF,0xFC,0xF3,0xE7,0x9F,0xFF, 0xFF,0xFE,0x79,0xF3,0xCF,0xFF, 0xFF,0xFF,0x3C,0xF9,0xE7,0xFF,
|
||||
0xFF,0xFE,0x79,0xF3,0xCF,0xFF, 0xFF,0xFC,0xF3,0xE7,0x9F,0xFF, 0xFF,0xF9,0xE7,0xCF,0x3F,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
};
|
||||
|
||||
// Thunderstorm - Cloud with lightning bolt
|
||||
static const uint8_t ICON_THUNDERSTORM_48[] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFC,0x3F,0xFF,0xFF, 0xFF,0xFF,0xF0,0x0F,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE0,0x07,0xFF,0xFF, 0xFF,0xFF,0xC0,0x03,0xFF,0xFF, 0xFF,0xFF,0x80,0x01,0xFF,0xFF,
|
||||
0xFF,0xFF,0x00,0x00,0xFF,0xFF, 0xFF,0xFE,0x00,0x00,0x7F,0xFF, 0xFF,0xF8,0x00,0x00,0x1F,0xFF,
|
||||
0xFF,0xF0,0x00,0x00,0x0F,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0x00,0x00,0x00,0x00,0xFF, 0xFE,0x00,0x00,0x00,0x00,0x7F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFC,0x00,0x00,0x00,0x00,0x3F,
|
||||
0xFC,0x00,0x00,0x00,0x00,0x3F, 0xFE,0x00,0x00,0x00,0x00,0x7F, 0xFF,0x00,0x00,0x00,0x00,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0xE0,0x00,0x00,0x07,0xFF, 0xFF,0xFC,0x00,0x00,0x3F,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xF8,0xFF,0xFF,0xFF, 0xFF,0xFF,0xF1,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE3,0xFF,0xFF,0xFF, 0xFF,0xFF,0xC7,0xFF,0xFF,0xFF, 0xFF,0xFF,0x80,0x3F,0xFF,0xFF,
|
||||
0xFF,0xFF,0x00,0x7F,0xFF,0xFF, 0xFF,0xFF,0xF0,0xFF,0xFF,0xFF, 0xFF,0xFF,0xE1,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xC3,0xFF,0xFF,0xFF, 0xFF,0xFF,0x87,0xFF,0xFF,0xFF, 0xFF,0xFF,0x0F,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0x1F,0xFF,0xFF,0xFF, 0xFF,0xFF,0x3F,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
};
|
||||
|
||||
// Fog - Horizontal lines
|
||||
static const uint8_t ICON_FOG_48[] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0x00,0x00,0x00,0x00,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x80,0x00,0x00,0x01,0xFF,
|
||||
0xFF,0x00,0x00,0x00,0x00,0xFF, 0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0x80,0x00,0x00,0x01,0xFF,
|
||||
0xFF,0xC0,0x00,0x00,0x03,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0x80,0x00,0x00,0x01,0xFF, 0xFF,0x00,0x00,0x00,0x00,0xFF, 0xFF,0x80,0x00,0x00,0x01,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
// Large icons are provided by generated WI48_* arrays in WeatherIcons48.h.
|
||||
|
||||
// ============================================================================
|
||||
// 24x24 Small Weather Icons (1-bit, MSB-first)
|
||||
@@ -332,69 +159,6 @@ const uint8_t* getWeatherIconSmall(WeatherIconType type) {
|
||||
}
|
||||
#endif // WEATHER_ENABLE_SMALL_ICONS
|
||||
|
||||
const char* getWeatherDescription(int wmoCode) {
|
||||
switch (wmoCode) {
|
||||
case 0:
|
||||
return "Clear sky";
|
||||
case 1:
|
||||
return "Mainly clear";
|
||||
case 2:
|
||||
return "Partly cloudy";
|
||||
case 3:
|
||||
return "Overcast";
|
||||
case 45:
|
||||
return "Fog";
|
||||
case 48:
|
||||
return "Rime fog";
|
||||
case 51:
|
||||
return "Light drizzle";
|
||||
case 53:
|
||||
return "Drizzle";
|
||||
case 55:
|
||||
return "Dense drizzle";
|
||||
case 56:
|
||||
return "Freezing drizzle";
|
||||
case 57:
|
||||
return "Dense freezing drizzle";
|
||||
case 61:
|
||||
return "Slight rain";
|
||||
case 63:
|
||||
return "Moderate rain";
|
||||
case 65:
|
||||
return "Heavy rain";
|
||||
case 66:
|
||||
return "Freezing rain";
|
||||
case 67:
|
||||
return "Heavy freezing rain";
|
||||
case 71:
|
||||
return "Slight snow";
|
||||
case 73:
|
||||
return "Moderate snow";
|
||||
case 75:
|
||||
return "Heavy snow";
|
||||
case 77:
|
||||
return "Snow grains";
|
||||
case 80:
|
||||
return "Slight showers";
|
||||
case 81:
|
||||
return "Moderate showers";
|
||||
case 82:
|
||||
return "Violent showers";
|
||||
case 85:
|
||||
return "Snow showers";
|
||||
case 86:
|
||||
return "Heavy snow showers";
|
||||
case 95:
|
||||
return "Thunderstorm";
|
||||
case 96:
|
||||
return "Thunderstorm, hail";
|
||||
case 99:
|
||||
return "Thunderstorm, heavy hail";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* getWindDirectionText(int degrees) {
|
||||
// Normalize to 0-360
|
||||
degrees = ((degrees % 360) + 360) % 360;
|
||||
|
||||
@@ -60,9 +60,10 @@ inline WeatherIconType getWeatherIconType(int wmoCode, bool isDay) {
|
||||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
return WeatherIconType::SHOWERS;
|
||||
case 85:
|
||||
case 86:
|
||||
return WeatherIconType::SHOWERS;
|
||||
return WeatherIconType::SNOW;
|
||||
case 95:
|
||||
case 96:
|
||||
case 99:
|
||||
@@ -72,9 +73,6 @@ inline WeatherIconType getWeatherIconType(int wmoCode, bool isDay) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get human-readable description for WMO weather code
|
||||
const char* getWeatherDescription(int wmoCode);
|
||||
|
||||
// Get the appropriate large icon bitmap (64x64, 1-bit, MSB first)
|
||||
const uint8_t* getWeatherIconLarge(WeatherIconType type);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ bool WeatherSettingsStore::saveToFile() const {
|
||||
JsonDocument doc;
|
||||
doc["latitude"] = latitude;
|
||||
doc["longitude"] = longitude;
|
||||
doc["locationConfigured"] = locationConfigured;
|
||||
doc["locationName"] = locationName;
|
||||
doc["tempUnit"] = static_cast<uint8_t>(tempUnit);
|
||||
doc["windUnit"] = static_cast<uint8_t>(windUnit);
|
||||
@@ -47,6 +48,7 @@ bool WeatherSettingsStore::loadFromFile() {
|
||||
|
||||
latitude = doc["latitude"] | 0.0f;
|
||||
longitude = doc["longitude"] | 0.0f;
|
||||
locationConfigured = doc["locationConfigured"] | (latitude != 0.0f || longitude != 0.0f);
|
||||
locationName = doc["locationName"] | std::string("");
|
||||
tempUnit = static_cast<WeatherTempUnit>(doc["tempUnit"] | (uint8_t)0);
|
||||
windUnit = static_cast<WeatherWindUnit>(doc["windUnit"] | (uint8_t)0);
|
||||
@@ -63,10 +65,18 @@ bool WeatherSettingsStore::loadFromFile() {
|
||||
void WeatherSettingsStore::setLocation(float lat, float lon, const std::string& name) {
|
||||
latitude = lat;
|
||||
longitude = lon;
|
||||
locationConfigured = true;
|
||||
locationName = name;
|
||||
LOG_DBG("WEA", "Set location: %s (%.4f, %.4f)", name.c_str(), lat, lon);
|
||||
}
|
||||
|
||||
void WeatherSettingsStore::clearLocation() {
|
||||
latitude = 0;
|
||||
longitude = 0;
|
||||
locationConfigured = false;
|
||||
locationName.clear();
|
||||
}
|
||||
|
||||
void WeatherSettingsStore::setForecastDays(uint8_t days) {
|
||||
if (days < 1) days = 1;
|
||||
if (days > 5) days = 5;
|
||||
|
||||
@@ -12,6 +12,7 @@ class WeatherSettingsStore {
|
||||
|
||||
float latitude = 0;
|
||||
float longitude = 0;
|
||||
bool locationConfigured = false;
|
||||
std::string locationName;
|
||||
WeatherTempUnit tempUnit = WeatherTempUnit::CELSIUS;
|
||||
WeatherWindUnit windUnit = WeatherWindUnit::KMH;
|
||||
@@ -31,10 +32,11 @@ class WeatherSettingsStore {
|
||||
|
||||
// Location
|
||||
void setLocation(float lat, float lon, const std::string& name);
|
||||
void clearLocation();
|
||||
float getLatitude() const { return latitude; }
|
||||
float getLongitude() const { return longitude; }
|
||||
const std::string& getLocationName() const { return locationName; }
|
||||
bool hasLocation() const { return latitude != 0 || longitude != 0; }
|
||||
bool hasLocation() const { return locationConfigured; }
|
||||
|
||||
// Units
|
||||
void setTempUnit(WeatherTempUnit unit) { tempUnit = unit; }
|
||||
|
||||
Reference in New Issue
Block a user