From 0c67b8d2ab94cb6bb860affd5820dbe0bc24eb04 Mon Sep 17 00:00:00 2001 From: SchiZzA Date: Fri, 6 Feb 2026 15:16:05 +0100 Subject: [PATCH] Update Windy integration to use station ID and password authentication - Replace API key with station ID and password for authentication - Change Windy API endpoint to v2 observation update - Adapt data conversion for WSLink to Windy format - Update config flow and translations accordingly --- custom_components/sws12500/__init__.py | 2 +- custom_components/sws12500/config_flow.py | 19 ++++-- custom_components/sws12500/const.py | 5 +- .../sws12500/translations/cs.json | 6 +- .../sws12500/translations/en.json | 6 +- custom_components/sws12500/windy_func.py | 62 ++++++++++++++++--- 6 files changed, 78 insertions(+), 22 deletions(-) diff --git a/custom_components/sws12500/__init__.py b/custom_components/sws12500/__init__.py index 62f14b8..42d119a 100644 --- a/custom_components/sws12500/__init__.py +++ b/custom_components/sws12500/__init__.py @@ -161,7 +161,7 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator): # Optional forwarding to external services. This is kept here (in the webhook handler) # to avoid additional background polling tasks. if self.config.options.get(WINDY_ENABLED, False): - await self.windy.push_data_to_windy(data) + await self.windy.push_data_to_windy(data, _wslink) if self.config.options.get(POCASI_CZ_ENABLED, False): await self.pocasi.push_data_to_server(data, "WSLINK" if _wslink else "WU") diff --git a/custom_components/sws12500/config_flow.py b/custom_components/sws12500/config_flow.py index 8fbaec2..1de781c 100644 --- a/custom_components/sws12500/config_flow.py +++ b/custom_components/sws12500/config_flow.py @@ -31,9 +31,10 @@ from .const import ( POCASI_CZ_SEND_INTERVAL, POCASI_CZ_SEND_MINIMUM, SENSORS_TO_LOAD, - WINDY_API_KEY, WINDY_ENABLED, WINDY_LOGGER_ENABLED, + WINDY_STATION_ID, + WINDY_STATION_PW, WSLINK, ) @@ -94,8 +95,8 @@ class ConfigOptionsFlowHandler(OptionsFlow): } self.windy_data = { - WINDY_API_KEY: self.config_entry.options.get(WINDY_API_KEY), - WINDY_ENABLED: self.config_entry.options.get(WINDY_ENABLED, False), + WINDY_STATION_ID: self.config_entry.options.get(WINDY_STATION_ID, ""), + WINDY_STATION_PW: self.config_entry.options.get(WINDY_STATION_PW, ""), WINDY_LOGGER_ENABLED: self.config_entry.options.get( WINDY_LOGGER_ENABLED, False ), @@ -103,7 +104,11 @@ class ConfigOptionsFlowHandler(OptionsFlow): self.windy_data_schema = { vol.Optional( - WINDY_API_KEY, default=self.windy_data.get(WINDY_API_KEY, "") + WINDY_STATION_ID, default=self.windy_data.get(WINDY_STATION_ID, "") + ): str, + vol.Optional( + WINDY_STATION_PW, + default=self.windy_data.get(WINDY_STATION_PW, ""), ): str, vol.Optional(WINDY_ENABLED, default=self.windy_data[WINDY_ENABLED]): bool or False, @@ -202,8 +207,10 @@ class ConfigOptionsFlowHandler(OptionsFlow): errors=errors, ) - if (user_input[WINDY_ENABLED] is True) and (user_input[WINDY_API_KEY] == ""): - errors[WINDY_API_KEY] = "windy_key_required" + if (user_input[WINDY_ENABLED] is True) and ( + (user_input[WINDY_STATION_ID] == "") or (user_input[WINDY_STATION_PW] == "") + ): + errors[WINDY_STATION_ID] = "windy_key_required" return self.async_show_form( step_id="windy", data_schema=vol.Schema(self.windy_data_schema), diff --git a/custom_components/sws12500/const.py b/custom_components/sws12500/const.py index 7d538d1..e70343b 100644 --- a/custom_components/sws12500/const.py +++ b/custom_components/sws12500/const.py @@ -6,7 +6,7 @@ from typing import Final DOMAIN = "sws12500" DEFAULT_URL = "/weatherstation/updateweatherstation.php" WSLINK_URL = "/data/upload.php" -WINDY_URL = "https://stations.windy.com/pws/update/" +WINDY_URL = "https://stations.windy.com/api/v2/observation/update" DATABASE_PATH = "/config/home-assistant_v2.db" POCASI_CZ_URL: Final = "http://ms.pocasimeteo.cz" @@ -41,7 +41,8 @@ POCASI_CZ_UNEXPECTED: Final = ( "Pocasti Meteo responded unexpectedly 3 times in row. Resendig is now disabled!" ) -WINDY_API_KEY = "WINDY_API_KEY" +WINDY_STATION_ID = "WINDY_STATION_ID" +WINDY_STATION_PW = "WINDY_STATION_PWD" WINDY_ENABLED: Final = "windy_enabled_checkbox" WINDY_LOGGER_ENABLED: Final = "windy_logger_checkbox" WINDY_NOT_INSERTED: Final = "Data was succefuly sent to Windy, but not inserted by Windy API. Does anyone else sent data to Windy?" diff --git a/custom_components/sws12500/translations/cs.json b/custom_components/sws12500/translations/cs.json index 32cf0a8..2eb9b65 100644 --- a/custom_components/sws12500/translations/cs.json +++ b/custom_components/sws12500/translations/cs.json @@ -66,12 +66,14 @@ "description": "Přeposílání dat z metostanice na Windy", "title": "Konfigurace Windy", "data": { - "WINDY_API_KEY": "Klíč API KEY získaný z Windy", + "WINDY_STATION_ID": "ID stanice, získaný z Windy", + "WINDY_STATION_PWD": "Heslo stanice, získané z Windy", "windy_enabled_checkbox": "Povolit přeposílání dat na Windy", "windy_logger_checkbox": "Logovat data a odpovědi z Windy" }, "data_description": { - "WINDY_API_KEY": "Klíč API KEY získaný z https://api.windy.com/keys", + "WINDY_STATION_ID": "ID stanice získaný z https://stations.windy.com/station", + "WINDY_STATION_PWD": "Heslo stanice získané z https://stations.windy.com/station", "windy_logger_checkbox": "Zapnout pouze v případě, že chcete poslat ladící informace vývojáři." } }, diff --git a/custom_components/sws12500/translations/en.json b/custom_components/sws12500/translations/en.json index f7381de..4a3d5d6 100644 --- a/custom_components/sws12500/translations/en.json +++ b/custom_components/sws12500/translations/en.json @@ -60,12 +60,14 @@ "description": "Resend weather data to your Windy stations.", "title": "Configure Windy", "data": { - "WINDY_API_KEY": "API KEY provided by Windy", + "WINDY_STATION_ID": "Station ID obtained form Windy", + "WINDY_STATION_PWD": "Station password obtained from Windy", "windy_enabled_checkbox": "Enable resending data to Windy", "windy_logger_checkbox": "Log Windy data and responses" }, "data_description": { - "WINDY_API_KEY": "Windy API KEY obtained from https://https://api.windy.com/keys", + "WINDY_STATION_ID": "Windy station ID obtained from https://stations.windy.com/stations", + "WINDY_STATION_PWD": "Windy station password obtained from https://stations.windy.com/stations", "windy_logger_checkbox": "Enable only if you want to send debuging data to the developer." } }, diff --git a/custom_components/sws12500/windy_func.py b/custom_components/sws12500/windy_func.py index b839a5a..aed6cb5 100644 --- a/custom_components/sws12500/windy_func.py +++ b/custom_components/sws12500/windy_func.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta import logging from aiohttp.client_exceptions import ClientError -from py_typecheck.core import checked +from py_typecheck import checked from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -12,11 +12,12 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( PURGE_DATA, - WINDY_API_KEY, WINDY_ENABLED, WINDY_INVALID_KEY, WINDY_LOGGER_ENABLED, WINDY_NOT_INSERTED, + WINDY_STATION_ID, + WINDY_STATION_PW, WINDY_SUCCESS, WINDY_UNEXPECTED, WINDY_URL, @@ -84,7 +85,34 @@ class WindyPush: if "Unauthorized" in response: raise WindyApiKeyError - async def push_data_to_windy(self, data: dict[str, str]) -> bool: + def _covert_wslink_to_pws(self, indata: dict[str, str]) -> dict[str, str]: + """Convert WSLink API data to Windy API data protocol.""" + if "t1ws" in indata: + indata["wind"] = indata.pop("t1ws") + if "t1wgust" in indata: + indata["gust"] = indata.pop("t1wgust") + if "t1wdir" in indata: + indata["winddir"] = indata.pop("t1wdir") + if "t1hum" in indata: + indata["humidity"] = indata.pop("t1hum") + if "t1dew" in indata: + indata["dewpoint"] = indata.pop("t1dew") + if "t1tem" in indata: + indata["temp"] = indata.pop("t1tem") + if "rbar" in indata: + indata["mbar"] = indata.pop("rbar") + if "t1rainhr" in indata: + indata["precip"] = indata.pop("t1rainhr") + if "t1uvi" in indata: + indata["uv"] = indata.pop("t1uvi") + if "t1solrad" in indata: + indata["solarradiation"] = indata.pop("t1solrad") + + return indata + + async def push_data_to_windy( + self, data: dict[str, str], wslink: bool = False + ) -> bool: """Pushes weather data do Windy stations. Interval is 5 minutes, otherwise Windy would not accepts data. @@ -109,23 +137,39 @@ class WindyPush: if purge in purged_data: _ = purged_data.pop(purge) - if "dewptf" in purged_data: - dewpoint = round(((float(purged_data.pop("dewptf")) - 32) / 1.8), 1) - purged_data["dewpoint"] = str(dewpoint) + if wslink: + # WSLink -> Windy params + self._covert_wslink_to_pws(purged_data) if ( - windy_api_key := checked(self.config.options.get(WINDY_API_KEY), str) + windy_station_id := checked(self.config.options.get(WINDY_STATION_ID), str) ) is None: _LOGGER.error("Windy API key is not provided! Check your configuration.") return False - request_url = f"{WINDY_URL}{windy_api_key}" + if ( + windy_station_pw := checked(self.config.options.get(WINDY_STATION_PW), str) + ) is None: + _LOGGER.error( + "Windy station password is missing! Check your configuration." + ) + return False + + request_url = f"{WINDY_URL}" + + purged_data["id"] = windy_station_id + + purged_data["time"] = "now" + + headers = {"Authorization": f"Bearer {windy_station_pw}"} if self.log: _LOGGER.info("Dataset for windy: %s", purged_data) session = async_get_clientsession(self.hass, verify_ssl=False) try: - async with session.get(request_url, params=purged_data) as resp: + async with session.get( + request_url, params=purged_data, headers=headers + ) as resp: status = await resp.text() try: self.verify_windy_response(status)