meteocat 1.1.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +16 -2
- package/custom_components/meteocat/__init__.py +23 -1
- package/custom_components/meteocat/config_flow.py +56 -35
- package/custom_components/meteocat/const.py +21 -2
- package/custom_components/meteocat/coordinator.py +455 -0
- package/custom_components/meteocat/manifest.json +1 -1
- package/custom_components/meteocat/options_flow.py +104 -25
- package/custom_components/meteocat/sensor.py +340 -3
- package/custom_components/meteocat/strings.json +447 -2
- package/custom_components/meteocat/translations/ca.json +448 -3
- package/custom_components/meteocat/translations/en.json +447 -3
- package/custom_components/meteocat/translations/es.json +447 -2
- package/custom_components/meteocat/version.py +1 -1
- package/filetree.txt +1 -0
- package/images/api_limits.png +0 -0
- package/images/dynamic_sensors.png +0 -0
- package/images/pick_station.png +0 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## [2.0.1](https://github.com/figorr/meteocat/compare/v2.0.0...v2.0.1) (2025-01-26)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* fix translations method ([fbcbfd6](https://github.com/figorr/meteocat/commit/fbcbfd6feeff41f616a1eefdbbc1b2cb177349f1))
|
|
7
|
+
* include mapping for translation key ([c28a18e](https://github.com/figorr/meteocat/commit/c28a18ec25933d9f720bc9da6c789f50736e7a7d))
|
|
8
|
+
|
|
9
|
+
## [2.0.0](https://github.com/figorr/meteocat/compare/v1.1.1...v2.0.0) (2025-01-26)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* 2.0.0 ([8f9ad16](https://github.com/figorr/meteocat/commit/8f9ad169b92da1093c3cdb2ff64ad5ffae9c9b24))
|
|
15
|
+
* update installation methods at README ([e588ffa](https://github.com/figorr/meteocat/commit/e588ffa17c10652992a4c26d19d26b7757b051b5))
|
|
16
|
+
* new alert sensors feature
|
|
17
|
+
|
|
1
18
|
## [1.1.1](https://github.com/figorr/meteocat/compare/v1.1.0...v1.1.1) (2025-01-08)
|
|
2
19
|
|
|
3
20
|
|
package/README.md
CHANGED
|
@@ -22,7 +22,17 @@ Authors:
|
|
|
22
22
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
25
|
-
#### HACS
|
|
25
|
+
#### HACS - Install using the custom repository method.
|
|
26
|
+
|
|
27
|
+
1. First of all you need to add a custom repository like [this](https://hacs.xyz/docs/faq/custom_repositories/).
|
|
28
|
+
1. Then download the integration from HACS.
|
|
29
|
+
1. Restart Home Assistant.
|
|
30
|
+
1. Go to `Settings > Devices & Services`
|
|
31
|
+
1. Click `+ Add Integration`
|
|
32
|
+
1. Search for `Meteocat` and follow the configuration instructions
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
#### HACS - Install from the store
|
|
26
36
|
1. Go to `HACS`
|
|
27
37
|
1. Search for `Meteocat` and add it to HACS
|
|
28
38
|
1. Restart Home Assistant
|
|
@@ -44,7 +54,11 @@ After submitting your API Key you will either be prompted to pick a town from th
|
|
|
44
54
|
|
|
45
55
|
Once you pick the town you will be prompted to pick a station from the list. These are the nearest stations to the picked town.
|
|
46
56
|
|
|
47
|
-

|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
Then you will be asked to set the XEMA and PREDICTIONS limits from the API.
|
|
60
|
+
|
|
61
|
+

|
|
48
62
|
|
|
49
63
|
Then you will be asked to pick an area for the device.
|
|
50
64
|
|
|
@@ -20,6 +20,8 @@ from .coordinator import (
|
|
|
20
20
|
DailyForecastCoordinator,
|
|
21
21
|
MeteocatConditionCoordinator,
|
|
22
22
|
MeteocatTempForecastCoordinator,
|
|
23
|
+
MeteocatAlertsCoordinator,
|
|
24
|
+
MeteocatAlertsRegionCoordinator,
|
|
23
25
|
)
|
|
24
26
|
|
|
25
27
|
from .const import DOMAIN, PLATFORMS
|
|
@@ -27,7 +29,7 @@ from .const import DOMAIN, PLATFORMS
|
|
|
27
29
|
_LOGGER = logging.getLogger(__name__)
|
|
28
30
|
|
|
29
31
|
# Versión
|
|
30
|
-
__version__ = "
|
|
32
|
+
__version__ = "2.0.0"
|
|
31
33
|
|
|
32
34
|
# Definir el esquema de configuración CONFIG_SCHEMA
|
|
33
35
|
CONFIG_SCHEMA = vol.Schema(
|
|
@@ -121,6 +123,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
121
123
|
temp_forecast_coordinator = MeteocatTempForecastCoordinator(hass=hass, entry_data=entry_data)
|
|
122
124
|
await temp_forecast_coordinator.async_config_entry_first_refresh()
|
|
123
125
|
|
|
126
|
+
alerts_coordinator = MeteocatAlertsCoordinator(hass=hass, entry_data=entry_data)
|
|
127
|
+
await alerts_coordinator.async_config_entry_first_refresh()
|
|
128
|
+
|
|
129
|
+
alerts_region_coordinator = MeteocatAlertsRegionCoordinator(hass=hass, entry_data=entry_data)
|
|
130
|
+
await alerts_region_coordinator.async_config_entry_first_refresh()
|
|
131
|
+
|
|
124
132
|
except Exception as err: # Capturar todos los errores
|
|
125
133
|
_LOGGER.exception(f"Error al inicializar los coordinadores: {err}")
|
|
126
134
|
return False
|
|
@@ -137,6 +145,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
137
145
|
"daily_forecast_coordinator": daily_forecast_coordinator,
|
|
138
146
|
"condition_coordinator": condition_coordinator,
|
|
139
147
|
"temp_forecast_coordinator": temp_forecast_coordinator,
|
|
148
|
+
"alerts_coordinator": alerts_coordinator,
|
|
149
|
+
"alerts_region_coordinator": alerts_region_coordinator,
|
|
140
150
|
**entry_data,
|
|
141
151
|
}
|
|
142
152
|
|
|
@@ -193,6 +203,16 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
|
193
203
|
forecast_hourly_data_file = files_folder / f"forecast_{town_id.lower()}_hourly_data.json"
|
|
194
204
|
forecast_daily_data_file = files_folder / f"forecast_{town_id.lower()}_daily_data.json"
|
|
195
205
|
|
|
206
|
+
# Obtener el `region_id` para identificar el archivo a eliminar
|
|
207
|
+
region_id = entry.data.get("region_id")
|
|
208
|
+
if not region_id:
|
|
209
|
+
_LOGGER.warning("No se encontró 'region_id' en la configuración. No se puede eliminar el archivo de alertas de la comarca.")
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
# Archivos JSON de alertas
|
|
213
|
+
alerts_file = files_folder / "alerts.json"
|
|
214
|
+
alerts_region_file = files_folder / f"alerts_{region_id}.json"
|
|
215
|
+
|
|
196
216
|
# Validar la ruta base
|
|
197
217
|
if not custom_components_path.exists():
|
|
198
218
|
_LOGGER.warning(f"La ruta {custom_components_path} no existe. No se realizará la limpieza.")
|
|
@@ -205,5 +225,7 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
|
205
225
|
safe_remove(town_data_file)
|
|
206
226
|
safe_remove(forecast_hourly_data_file)
|
|
207
227
|
safe_remove(forecast_daily_data_file)
|
|
228
|
+
safe_remove(alerts_file)
|
|
229
|
+
safe_remove(alerts_region_file)
|
|
208
230
|
safe_remove(assets_folder, is_folder=True)
|
|
209
231
|
safe_remove(files_folder, is_folder=True)
|
|
@@ -32,7 +32,9 @@ from .const import (
|
|
|
32
32
|
REGION_NAME,
|
|
33
33
|
PROVINCE_ID,
|
|
34
34
|
PROVINCE_NAME,
|
|
35
|
-
STATION_STATUS
|
|
35
|
+
STATION_STATUS,
|
|
36
|
+
LIMIT_XEMA,
|
|
37
|
+
LIMIT_PREDICCIO
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
from .options_flow import MeteocatOptionsFlowHandler
|
|
@@ -193,43 +195,20 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
|
193
195
|
infostation_client = MeteocatInfoStation(self.api_key)
|
|
194
196
|
try:
|
|
195
197
|
station_metadata = await infostation_client.get_infostation(self.station_id)
|
|
198
|
+
# Extraer los valores necesarios de los metadatos
|
|
199
|
+
self.station_type = station_metadata.get("tipus", "")
|
|
200
|
+
self.latitude = station_metadata.get("coordenades", {}).get("latitud", 0.0)
|
|
201
|
+
self.longitude = station_metadata.get("coordenades", {}).get("longitud", 0.0)
|
|
202
|
+
self.altitude = station_metadata.get("altitud", 0)
|
|
203
|
+
self.region_id = station_metadata.get("comarca", {}).get("codi", "")
|
|
204
|
+
self.region_name = station_metadata.get("comarca", {}).get("nom", "")
|
|
205
|
+
self.province_id = station_metadata.get("provincia", {}).get("codi", "")
|
|
206
|
+
self.province_name = station_metadata.get("provincia", {}).get("nom", "")
|
|
207
|
+
self.station_status = station_metadata.get("estats", [{}])[0].get("codi", "")
|
|
208
|
+
return await self.async_step_set_api_limits()
|
|
196
209
|
except Exception as ex:
|
|
197
210
|
_LOGGER.error("Error al obtener los metadatos de la estación: %s", ex)
|
|
198
211
|
errors["base"] = "metadata_fetch_failed"
|
|
199
|
-
station_metadata = {}
|
|
200
|
-
|
|
201
|
-
# Extraer los valores necesarios de los metadatos
|
|
202
|
-
self.station_type = station_metadata.get("tipus", "")
|
|
203
|
-
self.latitude = station_metadata.get("coordenades", {}).get("latitud", 0.0)
|
|
204
|
-
self.longitude = station_metadata.get("coordenades", {}).get("longitud", 0.0)
|
|
205
|
-
self.altitude = station_metadata.get("altitud", 0)
|
|
206
|
-
self.region_id = station_metadata.get("comarca", {}).get("codi", "")
|
|
207
|
-
self.region_name = station_metadata.get("comarca", {}).get("nom", "")
|
|
208
|
-
self.province_id = station_metadata.get("provincia", {}).get("codi", "")
|
|
209
|
-
self.province_name = station_metadata.get("provincia", {}).get("nom", "")
|
|
210
|
-
self.station_status = station_metadata.get("estats", [{}])[0].get("codi", "")
|
|
211
|
-
|
|
212
|
-
return self.async_create_entry(
|
|
213
|
-
title=self.selected_municipi["nom"],
|
|
214
|
-
data={
|
|
215
|
-
CONF_API_KEY: self.api_key,
|
|
216
|
-
TOWN_NAME: self.selected_municipi["nom"],
|
|
217
|
-
TOWN_ID: self.selected_municipi["codi"],
|
|
218
|
-
VARIABLE_NAME: "Temperatura",
|
|
219
|
-
VARIABLE_ID: str(self.variable_id),
|
|
220
|
-
STATION_NAME: self.station_name,
|
|
221
|
-
STATION_ID: self.station_id,
|
|
222
|
-
STATION_TYPE: self.station_type,
|
|
223
|
-
LATITUDE: self.latitude,
|
|
224
|
-
LONGITUDE: self.longitude,
|
|
225
|
-
ALTITUDE: self.altitude,
|
|
226
|
-
REGION_ID: str(self.region_id),
|
|
227
|
-
REGION_NAME: self.region_name,
|
|
228
|
-
PROVINCE_ID: str(self.province_id),
|
|
229
|
-
PROVINCE_NAME: self.province_name,
|
|
230
|
-
STATION_STATUS: str(self.station_status),
|
|
231
|
-
},
|
|
232
|
-
)
|
|
233
212
|
else:
|
|
234
213
|
errors["base"] = "station_not_found"
|
|
235
214
|
|
|
@@ -244,6 +223,48 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
|
244
223
|
return self.async_show_form(
|
|
245
224
|
step_id="select_station", data_schema=schema, errors=errors
|
|
246
225
|
)
|
|
226
|
+
|
|
227
|
+
async def async_step_set_api_limits(self, user_input=None):
|
|
228
|
+
"""Cuarto paso: Introducir los límites de XEMA y PREDICCIO del plan de la API."""
|
|
229
|
+
errors = {}
|
|
230
|
+
|
|
231
|
+
if user_input is not None:
|
|
232
|
+
self.limit_xema = user_input.get(LIMIT_XEMA, 750)
|
|
233
|
+
self.limit_prediccio = user_input.get(LIMIT_PREDICCIO, 100)
|
|
234
|
+
return self.async_create_entry(
|
|
235
|
+
title=self.selected_municipi["nom"],
|
|
236
|
+
data={
|
|
237
|
+
CONF_API_KEY: self.api_key,
|
|
238
|
+
TOWN_NAME: self.selected_municipi["nom"],
|
|
239
|
+
TOWN_ID: self.selected_municipi["codi"],
|
|
240
|
+
VARIABLE_NAME: "Temperatura",
|
|
241
|
+
VARIABLE_ID: str(self.variable_id),
|
|
242
|
+
STATION_NAME: self.station_name,
|
|
243
|
+
STATION_ID: self.station_id,
|
|
244
|
+
STATION_TYPE: self.station_type,
|
|
245
|
+
LATITUDE: self.latitude,
|
|
246
|
+
LONGITUDE: self.longitude,
|
|
247
|
+
ALTITUDE: self.altitude,
|
|
248
|
+
REGION_ID: str(self.region_id),
|
|
249
|
+
REGION_NAME: self.region_name,
|
|
250
|
+
PROVINCE_ID: str(self.province_id),
|
|
251
|
+
PROVINCE_NAME: self.province_name,
|
|
252
|
+
STATION_STATUS: str(self.station_status),
|
|
253
|
+
LIMIT_XEMA: self.limit_xema,
|
|
254
|
+
LIMIT_PREDICCIO: self.limit_prediccio,
|
|
255
|
+
},
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
schema = vol.Schema(
|
|
259
|
+
{
|
|
260
|
+
vol.Required(LIMIT_XEMA, default=750): cv.positive_int,
|
|
261
|
+
vol.Required(LIMIT_PREDICCIO, default=100): cv.positive_int,
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return self.async_show_form(
|
|
266
|
+
step_id="set_api_limits", data_schema=schema, errors=errors
|
|
267
|
+
)
|
|
247
268
|
|
|
248
269
|
@staticmethod
|
|
249
270
|
@callback
|
|
@@ -16,10 +16,22 @@ REGION_ID = "region_id"
|
|
|
16
16
|
REGION_NAME = "region_name"
|
|
17
17
|
PROVINCE_ID = "province_id"
|
|
18
18
|
PROVINCE_NAME = "province_name"
|
|
19
|
+
LIMIT_XEMA = "limit_xema"
|
|
20
|
+
LIMIT_PREDICCIO = "limit_prediccio"
|
|
19
21
|
STATION_STATUS = "station_status"
|
|
20
22
|
HOURLY_FORECAST_FILE_STATUS = "hourly_forecast_file_status"
|
|
21
23
|
DAILY_FORECAST_FILE_STATUS = "daily_forecast_file_status"
|
|
22
24
|
UVI_FILE_STATUS = "uvi_file_status"
|
|
25
|
+
ALERTS = "alerts"
|
|
26
|
+
ALERT_FILE_STATUS = "alert_file_status"
|
|
27
|
+
ALERT_WIND = "alert_wind"
|
|
28
|
+
ALERT_RAIN_INTENSITY = "alert_rain_intensity"
|
|
29
|
+
ALERT_RAIN = "alert_rain"
|
|
30
|
+
ALERT_SEA = "alert_sea"
|
|
31
|
+
ALERT_COLD = "alert_cold"
|
|
32
|
+
ALERT_WARM = "alert_warm"
|
|
33
|
+
ALERT_WARM_NIGHT = "alert_warm_night"
|
|
34
|
+
ALERT_SNOW = "alert_snow"
|
|
23
35
|
|
|
24
36
|
from homeassistant.const import Platform
|
|
25
37
|
|
|
@@ -29,8 +41,15 @@ DEFAULT_NAME = "METEOCAT"
|
|
|
29
41
|
|
|
30
42
|
# Tiempos para validación de API
|
|
31
43
|
DEFAULT_VALIDITY_DAYS = 1 # Número de días a partir de los cuales se considera que el archivo de información está obsoleto
|
|
32
|
-
DEFAULT_VALIDITY_HOURS = 5 # Hora a partir de la
|
|
33
|
-
DEFAULT_VALIDITY_MINUTES = 0
|
|
44
|
+
DEFAULT_VALIDITY_HOURS = 5 # Hora a partir de la cual la API tiene la información actualizada de predicciones disponible para descarga
|
|
45
|
+
DEFAULT_VALIDITY_MINUTES = 0 # Minutos a partir de los cuales la API tiene la información actualizada de predicciones disponible para descarga
|
|
46
|
+
DEFAULT_ALERT_VALIDITY_TIME = 120 # Minutos a partir de los cuales las alertas están obsoletas y se se debe proceder a una nueva llamada a la API
|
|
47
|
+
|
|
48
|
+
# Multiplicadores para la duración de validez basada en limit_prediccio
|
|
49
|
+
ALERT_VALIDITY_MULTIPLIER_100 = 12 # para limit_prediccio <= 100
|
|
50
|
+
ALERT_VALIDITY_MULTIPLIER_200 = 6 # para 100 < limit_prediccio <= 200
|
|
51
|
+
ALERT_VALIDITY_MULTIPLIER_500 = 3 # para 200 < limit_prediccio <= 500
|
|
52
|
+
ALERT_VALIDITY_MULTIPLIER_DEFAULT = 1 # para limit_prediccio > 500
|
|
34
53
|
|
|
35
54
|
# Códigos de sensores de la API
|
|
36
55
|
WIND_SPEED = "wind_speed" # Velocidad del viento
|