From c58d5031576bdf892f8af7a9a23106d1afbbdd9c Mon Sep 17 00:00:00 2001 From: Rengyr Date: Sun, 29 Jun 2025 12:01:39 +0200 Subject: [PATCH] Weather: Add support for only extended weather data without forecast Android only support for now. With extended data we can parse feels like temperature with GB same as on iOS. --- apps/weather/README.md | 8 +++++--- apps/weather/lib.js | 37 +++++++++++++++++++++++++++---------- apps/weather/settings.js | 15 ++++++++++----- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/apps/weather/README.md b/apps/weather/README.md index 9c692c011..66402b4bd 100644 --- a/apps/weather/README.md +++ b/apps/weather/README.md @@ -77,7 +77,7 @@ Adds: * Expiration time span can be set after which the local weather data is considered as invalid * Automatic weather data request interval can be set (weather data are pushed to Bangle automatically, but this can help in cases when it fails) (requires Gadgetbridge v0.86+) -* Forecast weather data can be enabled (this requires other App to use the data, Weather App itself doesn't show the forecast data) (requires Gadgetbridge v0.86+) +* Extended or forecast weather data can be enabled (this requires other App to use this data, Weather App itself doesn't show the forecast data) (requires Gadgetbridge v0.86+) * Widget can be hidden * To change the units for wind speed, you can install the [`Languages app`](https://banglejs.com/apps/?id=locale) which allows you to choose the units used for speed/distance/temperature and so on. @@ -89,15 +89,17 @@ allows you to choose the units used for speed/distance/temperature and so on. ## Weather App API +Note: except `getWeather()` and `get()` it is android only for now + Weather App can provide weather and forecast data to other Apps. -* Get weather data without forecast: +* Get weather data without forecast/extended: ```javascript const weather = require("weather"); weatherData = weather.getWeather(false); // or weather.get() ``` -* Get weather data with forecast (needs forecast enabled in settings): +* Get weather data with forecast/extended (needs forecast/extended enabled in settings): ```javascript const weather = require("weather"); weatherData = weather.getWeather(true); diff --git a/apps/weather/lib.js b/apps/weather/lib.js index 604e76fca..1f854fe0c 100644 --- a/apps/weather/lib.js +++ b/apps/weather/lib.js @@ -61,13 +61,13 @@ function update(weatherEvent) { storage.write("weather.json", json); exports.emit("update", weather); - // Request forecast if supported by GadgetBridge and set in settings - if (weatherEvent.v != null && (weatherSetting.forecast ?? false)) { + // Request extended/forecast if supported by Weather Provider and set in settings + if (weatherEvent.v != null && (weatherSetting.dataType ?? "basic") !== "basic") { updateWeather(true); } - // Either GadgetBridge doesn't support v2 and/or we don't need forecast, so we use this event for refresh scheduling - if (weatherEvent.v == null || (weatherSetting.forecast ?? false) === false) { + // Either Weather Provider doesn't support v2 and/or we don't need extended/forecast, so we use this event for refresh scheduling + if (weatherEvent.v == null || (weatherSetting.dataType ?? "basic") === "basic") { weatherSetting.time = Date.now(); storage.write("weatherSetting.json", weatherSetting); @@ -93,6 +93,12 @@ function update(weatherEvent) { // Store simpler weather for apps that doesn't need forecast or backward compatibility const weather = downgradeWeatherV2(weather2); storage.write("weather.json", weather); + exports.emit("update", weather); + } else if (weather1.weather != null && weather1.weather.feels === undefined) { + // Grab feels like temperature as we have it in v2 + weather1.weather.feels = decodeWeatherV2FeelsLike(weatherEvent); + storage.write("weather.json", weather); + exports.emit("update", weather); } cloned = undefined; // Clear memory @@ -111,15 +117,15 @@ function updateWeather(force) { // More than 5 minutes if (force || Date.now() - lastFetch >= 5 * 60 * 1000) { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({ t: "weather", v: 2, f: settings.forecast ?? false })); + Bluetooth.println(""); // This empty line is important for correct communication with Weather Provider + Bluetooth.println(JSON.stringify({ t: "weather", v: 2, f: settings.dataType === "forecast" })); } } -function getWeather(forecast) { +function getWeather(extended) { const weatherSetting = storage.readJSON("weatherSetting.json") || {}; - if (forecast === false || !(weatherSetting.forecast ?? false)) { + if (extended === false || (weatherSetting.dataType ?? "basic") === "basic") { // biome-ignore lint/complexity/useOptionalChain: not supported by Espruino return (storage.readJSON("weather.json") ?? {}).weather; } else { @@ -169,7 +175,7 @@ function decodeWeatherV2(jsonData, canDestroyArgument, parseForecast) { return { t: "weather2", v: 2, time: time }; } - // This needs to be kept in sync with GadgetBridge + // This needs to be kept in sync with Weather Provider const weatherCodes = [ [200, 201, 202, 210, 211, 212, 221, 230, 231, 232], [300, 301, 302, 310, 311, 312, 313, 314, 321], @@ -214,7 +220,7 @@ function decodeWeatherV2(jsonData, canDestroyArgument, parseForecast) { moonrise: dataView.getUint32(27, true), moonset: dataView.getUint32(31, true), moonphase: dataView.getUint16(35, true), - feel: dataView.getInt8(37, true), + feels: dataView.getInt8(37, true), }; weather.wrose = windDirection(weather.wdir); @@ -256,6 +262,16 @@ function decodeWeatherV2(jsonData, canDestroyArgument, parseForecast) { return weather; } +function decodeWeatherV2FeelsLike(jsonData) { + if (jsonData == null || jsonData.d == null) { + return undefined; + } + + const buffer = E.toArrayBuffer(atob(jsonData.d)); + + return new DataView(buffer).getInt8(37, true); +} + function downgradeWeatherV2(weather2) { const json = { t: "weather" }; @@ -274,6 +290,7 @@ function downgradeWeatherV2(weather2) { wdir: weather2.wdir, wrose: weather2.wrose, loc: weather2.loc, + feels: weather2.feels, }; return json; diff --git a/apps/weather/settings.js b/apps/weather/settings.js index f0b43b678..443ef898f 100644 --- a/apps/weather/settings.js +++ b/apps/weather/settings.js @@ -19,6 +19,8 @@ storage.write("weatherSetting.json", settings); } + const DATA_TYPE = ["basic", "extended", "forecast"]; + E.showMenu({ "": { "title": "Weather" }, "Expiry": { @@ -45,11 +47,14 @@ }, onchange: (x) => save("refresh", x), }, - Forecast: { - value: "forecast" in settings ? settings.forecast : false, - onchange: () => { - settings.forecast = !settings.forecast; - save("forecast", settings.forecast); + "Data type": { + value: DATA_TYPE.indexOf(settings.dataType ?? "basic"), + format: (v) => DATA_TYPE[v], + min: 0, + max: DATA_TYPE.length - 1, + onchange: (v) => { + settings.dataType = DATA_TYPE[v]; + save("dataType", settings.dataType); }, }, "Hide Widget": {