From d50844bc316a84c1f22e3cf96c25217f468845e5 Mon Sep 17 00:00:00 2001 From: Ed Beroset Date: Mon, 18 Nov 2024 09:46:56 -0500 Subject: [PATCH] Use open-meteo instead of openweathermap Originally, the openweathermap application was used, but version 2.5 of that API will no longer work after June 2024, so now we use the open-meteo API instead. This parallels a change that was made in the asteroid-weatherfetch source code and is largely a cut-and-paste copy with a few small changes to use the existing interfaces. Note that eventually, this code could go away, and the watch would parse the JSON data itself, but that would require a change to the bluetooth implementation on the watch. Signed-off-by: Ed Beroset --- asteroidsyncserviced/dbusinterface.cpp | 99 ++++++++++++++++++++------ 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/asteroidsyncserviced/dbusinterface.cpp b/asteroidsyncserviced/dbusinterface.cpp index 077438d..c5a4070 100644 --- a/asteroidsyncserviced/dbusinterface.cpp +++ b/asteroidsyncserviced/dbusinterface.cpp @@ -34,17 +34,66 @@ #include /*! - * \brief Convert JSON weather string to QList + * look up weather icon code, given WMO weather code * - * \param weatherJson String containing weather JSON. An example of the - * minimum acceptable string: - * '{"daily":[ - * {"temp":{"min":289.19,"max":298.9}},{"weather":[{"id":800}]}, - * {"temp":{"min":290.25,"max":300.2}},{"weather":[{"id":800}]} - * ]}' - * see https://openweathermap.org/api/one-call-api for full spec + * sources: + * WMO weather codes: https://www.nodc.noaa.gov/archive/arc0021/0002199/1.1/data/0-data/HTML/WMO-CODE/WMO4677.HTM + * weather icon codes: https://openweathermap.org/weather-conditions */ -static QList parseWeatherJson(const QString &weatherJson) +[[nodiscard]] static int iconlookup(int wxcode) { + int iconcode{800}; // every day is sunny! + switch(wxcode) { + case 0: iconcode = 800; break; // sunny + case 1: iconcode = 801; break; // mainly sunny + case 2: iconcode = 802; break; // partly cloudy + case 3: iconcode = 803; break; // mostly cloudy + case 45: iconcode = 741; break; // foggy + case 48: iconcode = 741; break; // rime fog + case 51: iconcode = 300; break; // light drizzle + case 53: iconcode = 301; break; // drizzle + case 55: iconcode = 302; break; // heavy drizzle + case 56: iconcode = 612; break; // light freezing drizzle + case 57: iconcode = 613; break; // freezing drizzle + case 61: iconcode = 500; break; // light rain + case 63: iconcode = 501; break; // rain + case 65: iconcode = 502; break; // heavy rain + case 66: iconcode = 511; break; // light freezing rain + case 67: iconcode = 511; break; // freezing rain + case 71: iconcode = 600; break; // light snow + case 73: iconcode = 601; break; // snow + case 75: iconcode = 602; break; // heavy snow + case 77: iconcode = 601; break; // snow grains + case 80: iconcode = 500; break; // light showers + case 81: iconcode = 501; break; // showers + case 82: iconcode = 502; break; // heavy showers + case 85: iconcode = 600; break; // light snow showers + case 86: iconcode = 601; break; // snow showers + case 95: iconcode = 211; break; // thunderstorm + case 96: iconcode = 200; break; // light thunderstorms with hail (no hail designation in codes, so just use t'storm) + case 99: iconcode = 211; break; // thunderstorm with hail (no hail designation in codes, so just use t'storm) + + default: { + iconcode = 800; + qDebug() << "unknown weather code passed to iconlookup:" << wxcode; + break; + } + } + return iconcode; +} +/*! + * \brief Convert JSON weather string to settings for asteroid-weather + * + * \param weatherJson String containing weather JSON. As and example, + * if we request the weather data for Cape Town, South Africa, the request + * URL would be + * "https://api.open-meteo.com/v1/forecast?latitude=35.858&longitude=-79.1032&timezone=auto&daily=weather_code,temperature_2m_max,temperature_2m_min" + * and the response might be: + * "latitude":35.850216,"longitude":-79.097015,"generationtime_ms":0.102996826171875,"utc_offset_seconds":-14400,"timezone":"America/New_York","timezone_abbreviation":"EDT","elevation":188.0,"daily_units":{"time":"iso8601","weather_code":"wmo code","temperature_2m_max":"°C","temperature_2m_min":"°C"},"daily":{"time":["2024-04-25","2024-04-26","2024-04-27","2024-04-28","2024-04-29","2024-04-30","2024-05-01"],"weather_code":[3,3,3,3,2,51,2],"temperature_2m_max":[23.2,21.8,20.2,25.9,27.7,28.8,29.6],"temperature_2m_min":[8.8,8.6,11.5,11.2,13.3,14.9,14.2]}} + * + * Note that by default, the temperatures are in degrees C which we must convert to Kelvin for the weather app + * see https://open-meteo.com/ + */ +[[nodiscard]] static QList weatherJsonToVector(const QString &weatherJson) { /* This looks complex, but it's really just a way to compensate for the fact * that with Qt5, size() returned an int, and with Qt6, it returns a qsizetype. @@ -52,22 +101,30 @@ static QList parseWeatherJson(const QString &weatherJson) * below does not trigger a compiler warning. */ static constexpr decltype(std::declval().size()) maxWeatherDays{5}; - QList weatherDays; + QList days; + constexpr double CtoKwithRounding{272.15 + 0.5}; QJsonParseError parseError; + qDebug() << "weather string: " << weatherJson.toUtf8(); auto json = QJsonDocument::fromJson(weatherJson.toUtf8(), &parseError); if (json.isNull()) { qWarning() << "JSON parse error: " << parseError.errorString(); + return days; } - auto daily = json["daily"].toArray(); - int count = std::min(maxWeatherDays, daily.count()); - for (int i = 0; i < count; ++i) { - auto day = daily.at(i).toObject(); - short low = day["temp"].toObject()["min"].toDouble(); - short high = day["temp"].toObject()["max"].toDouble(); - short icon = day["weather"].toArray()[0].toObject()["id"].toInt(); - weatherDays.push_back({icon, low, high}); + auto min = json["daily"]["temperature_2m_min"].toArray(); + auto max = json["daily"]["temperature_2m_max"].toArray(); + auto icon = json["daily"]["weather_code"].toArray(); + int count = std::min(maxWeatherDays, min.count()); + for (int i{0}; i < count; ++i) { + days.push_back({ static_cast(iconlookup(icon[i].toInt())), + static_cast(min[i].toDouble() + CtoKwithRounding), + static_cast(max[i].toDouble() + CtoKwithRounding) + }); + qDebug() << "Day " << i << " of " << count << " = [ " << days[i].m_wxIcon + << ", " << days[i].m_loTemp + << ", " << days[i].m_hiTemp + << " ]"; } - return weatherDays; + return days; } /* Watch Interface */ @@ -138,9 +195,7 @@ void DBusWatch::WeatherSetCityName(QString cityName) void DBusWatch::WeatherSetWeather(QString weatherJson) { - QList weatherDays = parseWeatherJson(weatherJson); - auto wds = QVariant::fromValue>(weatherDays); - + QList weatherDays = weatherJsonToVector(weatherJson); m_weatherService->setWeatherDays(weatherDays); }