103 lines
3.2 KiB
C++
103 lines
3.2 KiB
C++
#include "weather.h"
|
|
#include "config.h"
|
|
#include "location_manager.h"
|
|
#include <WiFi.h>
|
|
#include <WiFiClientSecure.h>
|
|
#include <HTTPClient.h>
|
|
#include <ArduinoJson.h>
|
|
#include <math.h>
|
|
|
|
namespace Weather {
|
|
|
|
namespace {
|
|
WiFiClientSecure client;
|
|
bool clientConfigured = false;
|
|
|
|
Condition currentCondition = Condition::Unknown;
|
|
uint32_t lastFetchMs = 0;
|
|
double lastLat = 0;
|
|
double lastLon = 0;
|
|
bool hasLastLocation = false;
|
|
|
|
// Maps the WMO weather code from Open-Meteo (field "weathercode", see
|
|
// https://open-meteo.com/en/docs) to one of the few icon categories
|
|
// that main.cpp can draw.
|
|
Condition conditionFromWmoCode(int code) {
|
|
if (code == 0) return Condition::Clear;
|
|
if (code == 1 || code == 2) return Condition::PartlyCloudy;
|
|
if (code == 3 || code == 45 || code == 48) return Condition::Cloudy;
|
|
if ((code >= 51 && code <= 67) || (code >= 80 && code <= 82)) return Condition::Rain;
|
|
if (code >= 71 && code <= 77) return Condition::Snow;
|
|
if (code >= 95) return Condition::Thunderstorm;
|
|
return Condition::Unknown;
|
|
}
|
|
|
|
void fetchNow(double lat, double lon) {
|
|
if (WiFi.status() != WL_CONNECTED) return;
|
|
|
|
if (!clientConfigured) {
|
|
client.setInsecure();
|
|
client.setTimeout(Config::HTTP_TIMEOUT_MS);
|
|
clientConfigured = true;
|
|
}
|
|
|
|
HTTPClient http;
|
|
char url[160];
|
|
snprintf(url, sizeof(url),
|
|
"https://api.open-meteo.com/v1/forecast?latitude=%.4f&longitude=%.4f¤t_weather=true",
|
|
lat, lon);
|
|
|
|
http.setTimeout(Config::HTTP_TIMEOUT_MS);
|
|
if (!http.begin(client, url)) return;
|
|
|
|
int code = http.GET();
|
|
if (code != HTTP_CODE_OK) {
|
|
http.end();
|
|
return;
|
|
}
|
|
|
|
// Collect body as a complete string first (getString() reliably handles
|
|
// chunked transfer encoding) instead of parsing directly from
|
|
// http.getStream() - the latter reliably failed with Open-Meteo
|
|
// with an ArduinoJson "InvalidInput" error.
|
|
String body = http.getString();
|
|
http.end();
|
|
|
|
JsonDocument doc;
|
|
DeserializationError err = deserializeJson(doc, body);
|
|
if (err) return;
|
|
|
|
int wmoCode = doc["current_weather"]["weathercode"] | -1;
|
|
if (wmoCode < 0) return;
|
|
|
|
currentCondition = conditionFromWmoCode(wmoCode);
|
|
}
|
|
}
|
|
|
|
void update() {
|
|
double lat = 0, lon = 0;
|
|
LocationManager::getHomeLocation(lat, lon);
|
|
if (lat == 0 && lon == 0) return;
|
|
|
|
// Significant location change (e.g., another location preset activated)
|
|
// - query immediately instead of waiting for the next regular interval,
|
|
// so the icon does not show the old location's weather for minutes.
|
|
bool locationChanged = !hasLastLocation ||
|
|
fabs(lat - lastLat) > 0.01 || fabs(lon - lastLon) > 0.01;
|
|
|
|
uint32_t now = millis();
|
|
if (!locationChanged && (now - lastFetchMs < Config::WEATHER_FETCH_INTERVAL_MS)) {
|
|
return;
|
|
}
|
|
|
|
lastFetchMs = now;
|
|
lastLat = lat;
|
|
lastLon = lon;
|
|
hasLastLocation = true;
|
|
fetchNow(lat, lon);
|
|
}
|
|
|
|
Condition current() { return currentCondition; }
|
|
|
|
}
|