meteocat 2.0.3 → 2.2.2

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.
@@ -5,6 +5,7 @@ import json
5
5
  import aiofiles
6
6
  import logging
7
7
  import asyncio
8
+ import unicodedata
8
9
  from datetime import datetime, timedelta, timezone, time
9
10
  from zoneinfo import ZoneInfo
10
11
  from typing import Dict, Any
@@ -18,6 +19,7 @@ from meteocatpy.data import MeteocatStationData
18
19
  from meteocatpy.uvi import MeteocatUviData
19
20
  from meteocatpy.forecast import MeteocatForecast
20
21
  from meteocatpy.alerts import MeteocatAlerts
22
+ from meteocatpy.quotes import MeteocatQuotes
21
23
 
22
24
  from meteocatpy.exceptions import (
23
25
  BadRequestError,
@@ -35,6 +37,7 @@ from .const import (
35
37
  DEFAULT_VALIDITY_HOURS,
36
38
  DEFAULT_VALIDITY_MINUTES,
37
39
  DEFAULT_ALERT_VALIDITY_TIME,
40
+ DEFAULT_QUOTES_VALIDITY_TIME,
38
41
  ALERT_VALIDITY_MULTIPLIER_100,
39
42
  ALERT_VALIDITY_MULTIPLIER_200,
40
43
  ALERT_VALIDITY_MULTIPLIER_500,
@@ -55,6 +58,8 @@ DEFAULT_CONDITION_SENSOR_UPDATE_INTERVAL = timedelta(minutes=5)
55
58
  DEFAULT_TEMP_FORECAST_UPDATE_INTERVAL = timedelta(minutes=5)
56
59
  DEFAULT_ALERTS_UPDATE_INTERVAL = timedelta(minutes=10)
57
60
  DEFAULT_ALERTS_REGION_UPDATE_INTERVAL = timedelta(minutes=5)
61
+ DEFAULT_QUOTES_UPDATE_INTERVAL = timedelta(minutes=10)
62
+ DEFAULT_QUOTES_FILE_UPDATE_INTERVAL = timedelta(minutes=5)
58
63
 
59
64
  # Definir la zona horaria local
60
65
  TIMEZONE = ZoneInfo("Europe/Madrid")
@@ -92,6 +97,52 @@ async def load_json_from_file(input_file: str) -> dict:
92
97
  _LOGGER.error("Error al decodificar JSON del archivo %s: %s", input_file, err)
93
98
  return {}
94
99
 
100
+ def normalize_name(name):
101
+ """Normaliza el nombre eliminando acentos y convirtiendo a minúsculas."""
102
+ name = unicodedata.normalize("NFKD", name).encode("ASCII", "ignore").decode("utf-8")
103
+ return name.lower()
104
+
105
+ # Definir _quotes_lock para evitar que varios coordinadores inicien una carrera para modificar quotes.json al mismo tiempo
106
+ _quotes_lock = asyncio.Lock()
107
+
108
+ async def _update_quotes(hass: HomeAssistant, plan_name: str) -> None:
109
+ """Actualiza las cuotas en quotes.json después de una consulta."""
110
+ async with _quotes_lock:
111
+ quotes_file = hass.config.path(
112
+ "custom_components", "meteocat", "files", "quotes.json"
113
+ )
114
+ try:
115
+ data = await load_json_from_file(quotes_file)
116
+
117
+ # Validar estructura del archivo
118
+ if not data or not isinstance(data, dict):
119
+ _LOGGER.warning("quotes.json está vacío o tiene un formato inválido: %s", data)
120
+ return
121
+ if "plans" not in data or not isinstance(data["plans"], list):
122
+ _LOGGER.warning("Estructura inesperada en quotes.json: %s", data)
123
+ return
124
+
125
+ # Buscar el plan y actualizar las cuotas
126
+ for plan in data["plans"]:
127
+ if plan.get("nom") == plan_name:
128
+ plan["consultesRealitzades"] += 1
129
+ plan["consultesRestants"] = max(0, plan["consultesRestants"] - 1)
130
+ _LOGGER.debug(
131
+ "Cuota actualizada para el plan %s: Consultas realizadas %s, restantes %s",
132
+ plan_name, plan["consultesRealitzades"], plan["consultesRestants"]
133
+ )
134
+ break # Salimos del bucle al encontrar el plan
135
+
136
+ # Guardar cambios en el archivo
137
+ await save_json_to_file(data, quotes_file)
138
+
139
+ except FileNotFoundError:
140
+ _LOGGER.error("El archivo quotes.json no fue encontrado en la ruta esperada: %s", quotes_file)
141
+ except json.JSONDecodeError:
142
+ _LOGGER.error("Error al decodificar quotes.json, posiblemente el archivo está corrupto.")
143
+ except Exception as e:
144
+ _LOGGER.exception("Error inesperado al actualizar las cuotas en quotes.json: %s", str(e))
145
+
95
146
  class MeteocatSensorCoordinator(DataUpdateCoordinator):
96
147
  """Coordinator para manejar la actualización de datos de los sensores."""
97
148
 
@@ -142,6 +193,9 @@ class MeteocatSensorCoordinator(DataUpdateCoordinator):
142
193
  )
143
194
  _LOGGER.debug("Datos de sensores actualizados exitosamente: %s", data)
144
195
 
196
+ # Actualizar las cuotas usando la función externa
197
+ await _update_quotes(self.hass, "XEMA") # Asegúrate de usar el nombre correcto del plan aquí
198
+
145
199
  # Validar que los datos sean una lista de diccionarios
146
200
  if not isinstance(data, list) or not all(isinstance(item, dict) for item in data):
147
201
  _LOGGER.error(
@@ -196,7 +250,7 @@ class MeteocatSensorCoordinator(DataUpdateCoordinator):
196
250
  err,
197
251
  )
198
252
  # Intentar cargar datos en caché si hay un error
199
- cached_data = load_json_from_file(self.station_file)
253
+ cached_data = await load_json_from_file(self.station_file)
200
254
  if cached_data:
201
255
  _LOGGER.warning("Usando datos en caché para la estación %s.", self.station_id)
202
256
  return cached_data
@@ -340,6 +394,9 @@ class MeteocatUviCoordinator(DataUpdateCoordinator):
340
394
  )
341
395
  _LOGGER.debug("Datos actualizados exitosamente: %s", data)
342
396
 
397
+ # Actualizar las cuotas usando la función externa
398
+ await _update_quotes(self.hass, "Prediccio") # Asegúrate de usar el nombre correcto del plan aquí
399
+
343
400
  # Validar que los datos sean un dict con una clave 'uvi'
344
401
  if not isinstance(data, dict) or 'uvi' not in data:
345
402
  _LOGGER.error("Formato inválido: Se esperaba un dict con la clave 'uvi'. Datos: %s", data)
@@ -380,7 +437,7 @@ class MeteocatUviCoordinator(DataUpdateCoordinator):
380
437
  err,
381
438
  )
382
439
  # Intentar cargar datos en caché si hay un error
383
- cached_data = load_json_from_file(self.uvi_file)
440
+ cached_data = await load_json_from_file(self.uvi_file)
384
441
  if cached_data:
385
442
  _LOGGER.warning("Usando datos en caché para la ciudad %s.", self.town_id)
386
443
  return cached_data.get('uvi', [])
@@ -551,9 +608,27 @@ class MeteocatEntityCoordinator(DataUpdateCoordinator):
551
608
 
552
609
  async def _fetch_and_save_data(self, api_method, file_path: str) -> dict:
553
610
  """Obtiene datos de la API y los guarda en un archivo JSON."""
554
- data = await asyncio.wait_for(api_method(self.town_id), timeout=30)
555
- await save_json_to_file(data, file_path)
556
- return data
611
+ try:
612
+ data = await asyncio.wait_for(api_method(self.town_id), timeout=30)
613
+
614
+ # Procesar los datos antes de guardarlos
615
+ for day in data.get('dies', []):
616
+ for var, details in day.get('variables', {}).items():
617
+ if var == 'precipitacio' and isinstance(details.get('valor'), str) and details['valor'].startswith('-'):
618
+ details['valor'] = '0.0'
619
+
620
+ await save_json_to_file(data, file_path)
621
+
622
+ # Actualizar cuotas dependiendo del tipo de predicción
623
+ if api_method.__name__ == 'get_prediccion_horaria':
624
+ await _update_quotes(self.hass, "Prediccio")
625
+ elif api_method.__name__ == 'get_prediccion_diaria':
626
+ await _update_quotes(self.hass, "Prediccio")
627
+
628
+ return data
629
+ except Exception as err:
630
+ _LOGGER.error(f"Error al obtener datos de la API para {file_path}: {err}")
631
+ raise
557
632
 
558
633
  async def _async_update_data(self) -> dict:
559
634
  """Actualiza los datos de predicción horaria y diaria."""
@@ -602,8 +677,8 @@ class MeteocatEntityCoordinator(DataUpdateCoordinator):
602
677
  _LOGGER.exception("Error inesperado al obtener datos de predicción: %s", err)
603
678
 
604
679
  # Si ocurre un error, intentar cargar datos desde los archivos locales
605
- hourly_cache = load_json_from_file(self.hourly_file) or {}
606
- daily_cache = load_json_from_file(self.daily_file) or {}
680
+ hourly_cache = await load_json_from_file(self.hourly_file) or {}
681
+ daily_cache = await load_json_from_file(self.daily_file) or {}
607
682
 
608
683
  _LOGGER.warning(
609
684
  "Cargando datos desde caché para %s. Datos horarios: %s, Datos diarios: %s",
@@ -1203,6 +1278,9 @@ class MeteocatAlertsCoordinator(DataUpdateCoordinator):
1203
1278
  )
1204
1279
  raise ValueError("Formato de datos inválido")
1205
1280
 
1281
+ # Actualizar cuotas usando la función externa
1282
+ await _update_quotes(self.hass, "Prediccio") # Asegúrate de usar el nombre correcto del plan aquí
1283
+
1206
1284
  # Añadir la clave 'actualitzat' con la fecha y hora actual de la zona horaria local
1207
1285
  current_time = datetime.now(timezone.utc).astimezone(TIMEZONE).isoformat()
1208
1286
  data_with_timestamp = {
@@ -1557,3 +1635,221 @@ class MeteocatAlertsRegionCoordinator(DataUpdateCoordinator):
1557
1635
  _LOGGER.info("Detalles recibidos: %s", alert_data.get("detalles", []))
1558
1636
 
1559
1637
  return alert_data
1638
+
1639
+ class MeteocatQuotesCoordinator(DataUpdateCoordinator):
1640
+ """Coordinator para manejar la actualización de las cuotas de la API de Meteocat."""
1641
+
1642
+ def __init__(
1643
+ self,
1644
+ hass: HomeAssistant,
1645
+ entry_data: dict,
1646
+ ):
1647
+ """
1648
+ Inicializa el coordinador de cuotas de Meteocat.
1649
+
1650
+ Args:
1651
+ hass (HomeAssistant): Instancia de Home Assistant.
1652
+ entry_data (dict): Datos de configuración obtenidos de core.config_entries.
1653
+ """
1654
+ self.api_key = entry_data["api_key"] # Usamos la API key de la configuración
1655
+ self.meteocat_quotes = MeteocatQuotes(self.api_key)
1656
+
1657
+ self.quotes_file = os.path.join(
1658
+ hass.config.path(),
1659
+ "custom_components",
1660
+ "meteocat",
1661
+ "files",
1662
+ "quotes.json"
1663
+ )
1664
+
1665
+ super().__init__(
1666
+ hass,
1667
+ _LOGGER,
1668
+ name=f"{DOMAIN} Quotes Coordinator",
1669
+ update_interval=DEFAULT_QUOTES_UPDATE_INTERVAL,
1670
+ )
1671
+
1672
+ async def _async_update_data(self) -> Dict:
1673
+ """Actualiza los datos de las cuotas desde la API de Meteocat o usa datos en caché según la antigüedad."""
1674
+ existing_data = await load_json_from_file(self.quotes_file) or {}
1675
+
1676
+ # Definir la duración de validez de los datos
1677
+ validity_duration = timedelta(minutes=DEFAULT_QUOTES_VALIDITY_TIME)
1678
+
1679
+ # Si no existe el archivo
1680
+ if not existing_data:
1681
+ return await self._fetch_and_save_new_data()
1682
+ else:
1683
+ # Comprobar la antigüedad de los datos
1684
+ last_update = datetime.fromisoformat(existing_data['actualitzat']['dataUpdate'])
1685
+ now = datetime.now(timezone.utc).astimezone(TIMEZONE)
1686
+
1687
+ # Comparar la antigüedad de los datos
1688
+ if now - last_update >= validity_duration:
1689
+ return await self._fetch_and_save_new_data()
1690
+ else:
1691
+ # Devolver los datos del archivo existente
1692
+ _LOGGER.debug("Usando datos existentes de cuotas: %s", existing_data)
1693
+ return {
1694
+ "actualizado": existing_data['actualitzat']['dataUpdate']
1695
+ }
1696
+
1697
+ async def _fetch_and_save_new_data(self):
1698
+ """Obtiene nuevos datos de la API y los guarda en el archivo JSON."""
1699
+ try:
1700
+ data = await asyncio.wait_for(
1701
+ self.meteocat_quotes.get_quotes(),
1702
+ timeout=30 # Tiempo límite de 30 segundos
1703
+ )
1704
+ _LOGGER.debug("Datos de cuotas actualizados exitosamente: %s", data)
1705
+
1706
+ if not isinstance(data, dict):
1707
+ _LOGGER.error("Formato inválido: Se esperaba un diccionario, pero se obtuvo %s", type(data).__name__)
1708
+ raise ValueError("Formato de datos inválido")
1709
+
1710
+ # Modificar los nombres de los planes con normalización
1711
+ plan_mapping = {
1712
+ "xdde_": "XDDE",
1713
+ "prediccio_": "Prediccio",
1714
+ "referencia basic": "Basic",
1715
+ "xema_": "XEMA",
1716
+ "quota": "Quota"
1717
+ }
1718
+
1719
+ modified_plans = []
1720
+ for plan in data["plans"]:
1721
+ normalized_nom = normalize_name(plan["nom"])
1722
+ new_name = next((v for k, v in plan_mapping.items() if normalized_nom.startswith(k)), plan["nom"])
1723
+
1724
+ modified_plans.append({
1725
+ "nom": new_name,
1726
+ "periode": plan["periode"],
1727
+ "maxConsultes": plan["maxConsultes"],
1728
+ "consultesRestants": plan["consultesRestants"],
1729
+ "consultesRealitzades": plan["consultesRealitzades"]
1730
+ })
1731
+
1732
+ # Añadir la clave 'actualitzat' con la fecha y hora actual de la zona horaria local
1733
+ current_time = datetime.now(timezone.utc).astimezone(TIMEZONE).isoformat()
1734
+ data_with_timestamp = {
1735
+ "actualitzat": {
1736
+ "dataUpdate": current_time
1737
+ },
1738
+ "client": data["client"],
1739
+ "plans": modified_plans
1740
+ }
1741
+
1742
+ # Guardar los datos en un archivo JSON
1743
+ await save_json_to_file(data_with_timestamp, self.quotes_file)
1744
+
1745
+ # Devolver tanto los datos de alertas como la fecha de actualización
1746
+ return {
1747
+ "actualizado": data_with_timestamp['actualitzat']['dataUpdate']
1748
+ }
1749
+
1750
+ except asyncio.TimeoutError as err:
1751
+ _LOGGER.warning("Tiempo de espera agotado al obtener las cuotas de la API de Meteocat.")
1752
+ raise ConfigEntryNotReady from err
1753
+ except ForbiddenError as err:
1754
+ _LOGGER.error("Acceso denegado al obtener cuotas de la API de Meteocat: %s", err)
1755
+ raise ConfigEntryNotReady from err
1756
+ except TooManyRequestsError as err:
1757
+ _LOGGER.warning("Límite de solicitudes alcanzado al obtener cuotas de la API de Meteocat: %s", err)
1758
+ raise ConfigEntryNotReady from err
1759
+ except (BadRequestError, InternalServerError, UnknownAPIError) as err:
1760
+ _LOGGER.error("Error al obtener cuotas de la API de Meteocat: %s", err)
1761
+ raise
1762
+ except Exception as err:
1763
+ _LOGGER.exception("Error inesperado al obtener cuotas de la API de Meteocat: %s", err)
1764
+
1765
+ # Intentar cargar datos en caché si hay un error
1766
+ cached_data = await load_json_from_file(self.quotes_file)
1767
+ if cached_data:
1768
+ _LOGGER.warning("Usando datos en caché para las cuotas de la API de Meteocat.")
1769
+ return cached_data
1770
+
1771
+ _LOGGER.error("No se pudo obtener datos actualizados ni cargar datos en caché.")
1772
+ return None
1773
+
1774
+ class MeteocatQuotesFileCoordinator(DataUpdateCoordinator):
1775
+ """Coordinator para manejar la actualización de las cuotas desde quotes.json."""
1776
+
1777
+ def __init__(
1778
+ self,
1779
+ hass: HomeAssistant,
1780
+ entry_data: dict,
1781
+ ):
1782
+ """
1783
+ Inicializa el coordinador del sensor de cuotas de la API de Meteocat.
1784
+
1785
+ Args:
1786
+ hass (HomeAssistant): Instancia de Home Assistant.
1787
+ entry_data (dict): Datos de configuración obtenidos de core.config_entries.
1788
+ update_interval (timedelta): Intervalo de actualización.
1789
+ """
1790
+ self.town_id = entry_data["town_id"] # Usamos el ID del municipio
1791
+
1792
+ super().__init__(
1793
+ hass,
1794
+ _LOGGER,
1795
+ name="Meteocat Quotes File Coordinator",
1796
+ update_interval=DEFAULT_QUOTES_FILE_UPDATE_INTERVAL,
1797
+ )
1798
+ self.quotes_file = os.path.join(
1799
+ hass.config.path(),
1800
+ "custom_components",
1801
+ "meteocat",
1802
+ "files",
1803
+ "quotes.json"
1804
+ )
1805
+
1806
+ async def _async_update_data(self) -> Dict[str, Any]:
1807
+ """Carga los datos de quotes.json y devuelve el estado de las cuotas."""
1808
+ existing_data = await self._load_json_file()
1809
+
1810
+ if not existing_data:
1811
+ _LOGGER.warning("No se encontraron datos en quotes.json.")
1812
+ return {}
1813
+
1814
+ return {
1815
+ "actualizado": existing_data.get("actualitzat", {}).get("dataUpdate"),
1816
+ "client": existing_data.get("client", {}).get("nom"),
1817
+ "plans": [
1818
+ {
1819
+ "nom": plan.get("nom"),
1820
+ "periode": plan.get("periode"),
1821
+ "maxConsultes": plan.get("maxConsultes"),
1822
+ "consultesRestants": plan.get("consultesRestants"),
1823
+ "consultesRealitzades": plan.get("consultesRealitzades"),
1824
+ }
1825
+ for plan in existing_data.get("plans", [])
1826
+ ]
1827
+ }
1828
+
1829
+ async def _load_json_file(self) -> dict:
1830
+ """Carga el archivo JSON de forma asincrónica."""
1831
+ try:
1832
+ async with aiofiles.open(self.quotes_file, "r", encoding="utf-8") as f:
1833
+ data = await f.read()
1834
+ return json.loads(data)
1835
+ except FileNotFoundError:
1836
+ _LOGGER.warning("El archivo %s no existe.", self.quotes_file)
1837
+ return {}
1838
+ except json.JSONDecodeError as err:
1839
+ _LOGGER.error("Error al decodificar JSON del archivo %s: %s", self.quotes_file, err)
1840
+ return {}
1841
+
1842
+ async def get_plan_info(self, plan_name: str) -> dict:
1843
+ """Obtiene la información de un plan específico."""
1844
+ data = await self._async_update_data()
1845
+ for plan in data.get("plans", []):
1846
+ if plan.get("nom") == plan_name:
1847
+ return {
1848
+ "nom": plan.get("nom"),
1849
+ "periode": plan.get("periode"),
1850
+ "maxConsultes": plan.get("maxConsultes"),
1851
+ "consultesRestants": plan.get("consultesRestants"),
1852
+ "consultesRealitzades": plan.get("consultesRealitzades"),
1853
+ }
1854
+ _LOGGER.warning("Plan %s no encontrado en quotes.json.", plan_name)
1855
+ return {}
@@ -8,6 +8,6 @@
8
8
  "iot_class": "cloud_polling",
9
9
  "issue_tracker": "https://github.com/figorr/meteocat/issues",
10
10
  "loggers": ["meteocatpy"],
11
- "requirements": ["meteocatpy==0.0.20", "packaging>=20.3", "wrapt>=1.14.0"],
12
- "version": "2.0.3"
11
+ "requirements": ["meteocatpy==1.0.0", "packaging>=20.3", "wrapt>=1.14.0"],
12
+ "version": "2.2.2"
13
13
  }
@@ -10,7 +10,10 @@ import voluptuous as vol
10
10
  from .const import (
11
11
  CONF_API_KEY,
12
12
  LIMIT_XEMA,
13
- LIMIT_PREDICCIO
13
+ LIMIT_PREDICCIO,
14
+ LIMIT_XDDE,
15
+ LIMIT_QUOTA,
16
+ LIMIT_BASIC
14
17
  )
15
18
  from meteocatpy.town import MeteocatTown
16
19
  from meteocatpy.exceptions import (
@@ -64,6 +67,9 @@ class MeteocatOptionsFlowHandler(OptionsFlow):
64
67
  self.api_key = user_input.get(CONF_API_KEY)
65
68
  self.limit_xema = user_input.get(LIMIT_XEMA)
66
69
  self.limit_prediccio = user_input.get(LIMIT_PREDICCIO)
70
+ self.limit_xdde = user_input.get(LIMIT_XDDE)
71
+ self.limit_quota = user_input.get(LIMIT_QUOTA)
72
+ self.limit_basic = user_input.get(LIMIT_BASIC)
67
73
 
68
74
  # Validar la nueva API Key utilizando MeteocatTown
69
75
  if self.api_key:
@@ -84,7 +90,8 @@ class MeteocatOptionsFlowHandler(OptionsFlow):
84
90
  errors["base"] = "unknown"
85
91
 
86
92
  # Validar que los límites sean números positivos
87
- if not cv.positive_int(self.limit_xema) or not cv.positive_int(self.limit_prediccio):
93
+ limits_to_validate = [self.limit_xema, self.limit_prediccio, self.limit_xdde, self.limit_quota, self.limit_basic]
94
+ if not all(cv.positive_int(limit) for limit in limits_to_validate if limit is not None):
88
95
  errors["base"] = "invalid_limit"
89
96
 
90
97
  if not errors:
@@ -96,6 +103,12 @@ class MeteocatOptionsFlowHandler(OptionsFlow):
96
103
  data_update[LIMIT_XEMA] = self.limit_xema
97
104
  if self.limit_prediccio:
98
105
  data_update[LIMIT_PREDICCIO] = self.limit_prediccio
106
+ if self.limit_xdde:
107
+ data_update[LIMIT_XDDE] = self.limit_xdde
108
+ if self.limit_quota:
109
+ data_update[LIMIT_QUOTA] = self.limit_quota
110
+ if self.limit_basic:
111
+ data_update[LIMIT_BASIC] = self.limit_basic
99
112
 
100
113
  self.hass.config_entries.async_update_entry(
101
114
  self._config_entry,
@@ -110,6 +123,9 @@ class MeteocatOptionsFlowHandler(OptionsFlow):
110
123
  vol.Required(CONF_API_KEY): str,
111
124
  vol.Required(LIMIT_XEMA, default=self._config_entry.data.get(LIMIT_XEMA)): cv.positive_int,
112
125
  vol.Required(LIMIT_PREDICCIO, default=self._config_entry.data.get(LIMIT_PREDICCIO)): cv.positive_int,
126
+ vol.Required(LIMIT_XDDE, default=self._config_entry.data.get(LIMIT_XDDE)): cv.positive_int,
127
+ vol.Required(LIMIT_QUOTA, default=self._config_entry.data.get(LIMIT_QUOTA)): cv.positive_int,
128
+ vol.Required(LIMIT_BASIC, default=self._config_entry.data.get(LIMIT_BASIC)): cv.positive_int,
113
129
  })
114
130
  return self.async_show_form(
115
131
  step_id="update_api_and_limits", data_schema=schema, errors=errors
@@ -122,9 +138,13 @@ class MeteocatOptionsFlowHandler(OptionsFlow):
122
138
  if user_input is not None:
123
139
  self.limit_xema = user_input.get(LIMIT_XEMA)
124
140
  self.limit_prediccio = user_input.get(LIMIT_PREDICCIO)
141
+ self.limit_xdde = user_input.get(LIMIT_XDDE)
142
+ self.limit_quota = user_input.get(LIMIT_QUOTA)
143
+ self.limit_basic = user_input.get(LIMIT_BASIC)
125
144
 
126
145
  # Validar que los límites sean números positivos
127
- if not cv.positive_int(self.limit_xema) or not cv.positive_int(self.limit_prediccio):
146
+ limits_to_validate = [self.limit_xema, self.limit_prediccio, self.limit_xdde, self.limit_quota, self.limit_basic]
147
+ if not all(cv.positive_int(limit) for limit in limits_to_validate if limit is not None):
128
148
  errors["base"] = "invalid_limit"
129
149
 
130
150
  if not errors:
@@ -133,7 +153,10 @@ class MeteocatOptionsFlowHandler(OptionsFlow):
133
153
  data={
134
154
  **self._config_entry.data,
135
155
  LIMIT_XEMA: self.limit_xema,
136
- LIMIT_PREDICCIO: self.limit_prediccio
156
+ LIMIT_PREDICCIO: self.limit_prediccio,
157
+ LIMIT_XDDE: self.limit_xdde,
158
+ LIMIT_QUOTA: self.limit_quota,
159
+ LIMIT_BASIC: self.limit_basic
137
160
  },
138
161
  )
139
162
  # Recargar la integración para aplicar los cambios dinámicamente
@@ -144,6 +167,9 @@ class MeteocatOptionsFlowHandler(OptionsFlow):
144
167
  schema = vol.Schema({
145
168
  vol.Required(LIMIT_XEMA, default=self._config_entry.data.get(LIMIT_XEMA)): cv.positive_int,
146
169
  vol.Required(LIMIT_PREDICCIO, default=self._config_entry.data.get(LIMIT_PREDICCIO)): cv.positive_int,
170
+ vol.Required(LIMIT_XDDE, default=self._config_entry.data.get(LIMIT_XDDE)): cv.positive_int,
171
+ vol.Required(LIMIT_QUOTA, default=self._config_entry.data.get(LIMIT_QUOTA)): cv.positive_int,
172
+ vol.Required(LIMIT_BASIC, default=self._config_entry.data.get(LIMIT_BASIC)): cv.positive_int,
147
173
  })
148
174
  return self.async_show_form(
149
175
  step_id="update_limits_only", data_schema=schema, errors=errors