From 05c079f5170a2785296124c11277bb1e70ea3982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=92scar=20Casajuana?= Date: Sat, 8 Jun 2024 19:33:41 +0200 Subject: [PATCH] =?UTF-8?q?Add=20forecast=20data=20=F0=9F=8C=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/weatherxm/__init__.py | 7 +- custom_components/weatherxm/weather.py | 82 ++++++++++++++++++-- custom_components/weatherxm/weatherxm_api.py | 21 +++++ 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/custom_components/weatherxm/__init__.py b/custom_components/weatherxm/__init__.py index 60aa486..161c341 100644 --- a/custom_components/weatherxm/__init__.py +++ b/custom_components/weatherxm/__init__.py @@ -30,7 +30,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_update_data(): """Fetch data from API endpoint.""" try: - return await hass.async_add_executor_job(api.get_devices) + devices = await hass.async_add_executor_job(api.get_devices) + for device in devices: + device['forecast'] = await hass.async_add_executor_job(api.get_forecast_data, device['id']) + return devices except Exception as err: raise UpdateFailed(f"Error communicating with API: {err}") @@ -64,4 +67,4 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def update_listener(hass: HomeAssistant, entry: ConfigEntry): """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) \ No newline at end of file + await hass.config_entries.async_reload(entry.entry_id) diff --git a/custom_components/weatherxm/weather.py b/custom_components/weatherxm/weather.py index 7321d5d..b49707d 100644 --- a/custom_components/weatherxm/weather.py +++ b/custom_components/weatherxm/weather.py @@ -1,10 +1,18 @@ -import logging -from homeassistant.components.weather import WeatherEntity +from homeassistant.components.weather import ( + WeatherEntity, + WeatherEntityFeature, + Forecast, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.entity import generate_entity_id - +from homeassistant.const import ( + UnitOfPrecipitationDepth, + UnitOfPressure, + UnitOfSpeed, + UnitOfTemperature, +) from .const import DOMAIN from .utils import async_setup_entities_list @@ -15,7 +23,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e device_id=device['id'], alias=alias, address=device['address'], - current_weather=device['current_weather'] + current_weather=device['current_weather'], + forecast=device.get('forecast', []) )) async_add_entities(entities, True) @@ -79,7 +88,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e } class WeatherXMWeather(CoordinatorEntity, WeatherEntity): - def __init__(self, coordinator, entity_id, device_id, alias, address, current_weather): + """ WeatherXM Weather Entity """ + + _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS + _attr_native_pressure_unit = UnitOfPressure.HPA + _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR + _attr_supported_features = ( + WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY + ) + _attr_native_temperature_unit = UnitOfTemperature.CELSIUS + + def __init__(self, coordinator, entity_id, device_id, alias, address, current_weather, forecast): """Initialize.""" super().__init__(coordinator) self.entity_id = entity_id @@ -87,6 +106,7 @@ def __init__(self, coordinator, entity_id, device_id, alias, address, current_we self._address = address self._alias = alias self._current_weather = current_weather + self._forecast = forecast self._attr_name = alias self._attr_unique_id = alias @@ -185,3 +205,55 @@ def state_attributes(self): "wind_gust_speed": self.wind_gust_speed, }) return data + + @property + def forecast(self): + return { + "hourly": self.forecast_hourly[:24], # Limit to 24 hourly forecasts + "daily": self.forecast_daily[:7], # Limit to 7 daily forecasts + } + + @property + def forecast_hourly(self): + forecasts = [] + for daily in self._forecast: + for hourly in daily.get("hourly", []): + hourly_forecast = { + "datetime": hourly.get("timestamp"), + "temperature": hourly.get("temperature"), + "precipitation": hourly.get("precipitation"), + "precipitation_probability": hourly.get("precipitation_probability"), + "wind_speed": hourly.get("wind_speed"), + "wind_bearing": hourly.get("wind_direction"), + "condition": ICON_TO_CONDITION_MAP.get(hourly.get("icon"), "unknown"), + } + forecasts.append(hourly_forecast) + return forecasts + + @property + def forecast_daily(self): + forecasts = [] + for daily in self._forecast: + day_data = daily.get("daily", {}) + daily_forecast = { + "datetime": day_data.get("timestamp"), + "temperature": day_data.get("temperature_max"), + "templow": day_data.get("temperature_min"), + "precipitation": day_data.get("precipitation_intensity"), + "precipitation_probability": day_data.get("precipitation_probability"), + "wind_speed": day_data.get("wind_speed"), + "wind_bearing": day_data.get("wind_direction"), + "condition": ICON_TO_CONDITION_MAP.get(day_data.get("icon"), "unknown"), + } + forecasts.append(daily_forecast) + return forecasts + + @property + def state(self): + return self.condition + + async def async_forecast_daily(self): + return self.forecast_daily[:7] + + async def async_forecast_hourly(self): + return self.forecast_hourly[:24] diff --git a/custom_components/weatherxm/weatherxm_api.py b/custom_components/weatherxm/weatherxm_api.py index 9587ed7..396bb9e 100644 --- a/custom_components/weatherxm/weatherxm_api.py +++ b/custom_components/weatherxm/weatherxm_api.py @@ -2,6 +2,8 @@ import json import logging +from datetime import datetime, timedelta + _LOGGER = logging.getLogger(__name__) class WeatherXMAPI: @@ -44,3 +46,22 @@ def get_devices(self): except requests.RequestException as e: _LOGGER.error("Error fetching devices: %s", e) return [] + + def get_forecast_data(self, device_id): + today = datetime.now().strftime('%Y-%m-%d') + future = (datetime.now() + timedelta(days=7)).strftime('%Y-%m-%d') + url = f"{self.host}/api/v1/me/devices/{device_id}/forecast?fromDate={today}&toDate={future}" + headers = { + 'Authorization': f'Bearer {self.auth_token}', + 'accept': 'application/json' + } + try: + response = requests.get(url, headers=headers) + if response.status_code == 200: + return response.json() + else: + _LOGGER.error("Failed to get forecast data: %s", response.text) + return [] + except requests.RequestException as e: + _LOGGER.error("Error fetching forecast data: %s", e) + return []