meteocat 2.2.5 → 2.2.6

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.
@@ -0,0 +1,51 @@
1
+ name: Mark stale issues and pull requests
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "30 1 * * *" # Ejecutar diariamente a la 01:30 UTC
6
+ workflow_dispatch: # Permite ejecución manual desde la interfaz
7
+
8
+ jobs:
9
+ stale:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ issues: write
13
+ pull-requests: write
14
+
15
+ steps:
16
+ # 1. Issues/PRs generales sin actividad (excepto etiquetados)
17
+ - uses: actions/stale@v8
18
+ with:
19
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
20
+ days-before-stale: 60
21
+ days-before-close: 7
22
+ exempt-issue-labels: "help wanted,bug,feature"
23
+ exempt-pr-labels: "help wanted"
24
+ stale-issue-message: "This issue has had no activity for 60 days and will be closed in a week if there is no further activity."
25
+ stale-pr-message: "This pull request has had no activity for 60 days and will be closed in a week if there is no further activity."
26
+ stale-issue-label: "stale"
27
+ stale-pr-label: "stale"
28
+
29
+ # 2. Issues tipo "feature" sin actividad (solo etiquetar)
30
+ - uses: actions/stale@v8
31
+ with:
32
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
33
+ only-labels: "feature"
34
+ days-before-stale: 21
35
+ days-before-close: -1
36
+ stale-issue-message: >-
37
+ This feature request has had no activity for 3 weeks.
38
+ It has been marked as *help wanted* and will remain open.
39
+ stale-issue-label: "help wanted"
40
+
41
+ # 3. Issues tipo "bug" sin actividad (solo etiquetar)
42
+ - uses: actions/stale@v8
43
+ with:
44
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
45
+ only-labels: "bug"
46
+ days-before-stale: 21
47
+ days-before-close: -1
48
+ stale-issue-message: >-
49
+ This bug report has had no activity for 3 weeks.
50
+ It has been marked as *help wanted* and will remain open.
51
+ stale-issue-label: "help wanted"
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [2.2.6](https://github.com/figorr/meteocat/compare/v2.2.5...v2.2.6) (2025-08-27)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * 2.2.6 ([bb92000](https://github.com/figorr/meteocat/commit/bb9200099ed951168bc891c870c6fd61b758c73d))
7
+ * Fix alerts region data update at first setup of the next entrances ([40018dc](https://github.com/figorr/meteocat/commit/40018dc0bb703a8dc606aaedcfa27b5c3e6bbf7a))
8
+ * Fix delete an entry but keeping common files for the rest of entries ([211e545](https://github.com/figorr/meteocat/commit/211e54510c458378ca46c465303995d1a9df0dbb))
9
+ * Fix region alerts json file for multiple entrances ([2b7072c](https://github.com/figorr/meteocat/commit/2b7072cb6fd7eafa1ab95dcedb2c2dea8f35005d))
10
+
1
11
  ## [2.2.5](https://github.com/figorr/meteocat/compare/v2.2.4...v2.2.5) (2025-02-16)
2
12
 
3
13
 
@@ -33,7 +33,7 @@ from .const import DOMAIN, PLATFORMS
33
33
  _LOGGER = logging.getLogger(__name__)
34
34
 
35
35
  # Versión
36
- __version__ = "2.2.5"
36
+ __version__ = "2.2.6"
37
37
 
38
38
  # Definir el esquema de configuración CONFIG_SCHEMA
39
39
  CONFIG_SCHEMA = vol.Schema(
@@ -192,68 +192,48 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
192
192
  """Limpia cualquier dato adicional al desinstalar la integración."""
193
193
  _LOGGER.info(f"Eliminando datos residuales de la integración: {entry.entry_id}")
194
194
 
195
- # Definir las rutas base a eliminar
195
+ # Definir las rutas base
196
196
  custom_components_path = Path(hass.config.path("custom_components")) / DOMAIN
197
197
  assets_folder = custom_components_path / "assets"
198
198
  files_folder = custom_components_path / "files"
199
199
 
200
- # Definir archivos relacionados a eliminar
200
+ # Archivos comunes
201
201
  symbols_file = assets_folder / "symbols.json"
202
202
  variables_file = assets_folder / "variables.json"
203
+ alerts_file = files_folder / "alerts.json"
204
+ quotes_file = files_folder / "quotes.json"
203
205
 
204
- # Obtener el `station_id` para identificar el archivo a eliminar
206
+ # Archivos específicos de cada entry
205
207
  station_id = entry.data.get("station_id")
206
- if not station_id:
207
- _LOGGER.warning("No se encontró 'station_id' en la configuración. No se puede eliminar el archivo de datos de la estación.")
208
- return
209
-
210
- # Archivo JSON de la estación
211
- station_data_file = files_folder / f"station_{station_id.lower()}_data.json"
212
-
213
- # Obtener el `town_id` para identificar el archivo a eliminar
214
208
  town_id = entry.data.get("town_id")
215
- if not town_id:
216
- _LOGGER.warning("No se encontró 'town_id' en la configuración. No se puede eliminar el archivo de datos de la estación.")
217
- return
218
-
219
- # Archivo JSON UVI del municipio
220
- town_data_file = files_folder / f"uvi_{town_id.lower()}_data.json"
221
-
222
- # Arhivos JSON de las predicciones del municipio a eliminar
223
- forecast_hourly_data_file = files_folder / f"forecast_{town_id.lower()}_hourly_data.json"
224
- forecast_daily_data_file = files_folder / f"forecast_{town_id.lower()}_daily_data.json"
225
-
226
- # Obtener el `region_id` para identificar el archivo a eliminar
227
209
  region_id = entry.data.get("region_id")
228
- if not region_id:
229
- _LOGGER.warning("No se encontró 'region_id' en la configuración. No se puede eliminar el archivo de alertas de la comarca.")
230
- return
231
-
232
- # Archivos JSON de alertas
233
- alerts_file = files_folder / "alerts.json"
234
- alerts_region_file = files_folder / f"alerts_{region_id}.json"
235
-
236
- # Archivo JSON de cuotas
237
- quotes_file = files_folder / f"quotes.json"
238
210
 
239
- # Archivo JSON de rayos
240
- lightning_file = files_folder / f"lightning_{region_id}.json"
241
-
242
- # Validar la ruta base
243
211
  if not custom_components_path.exists():
244
212
  _LOGGER.warning(f"La ruta {custom_components_path} no existe. No se realizará la limpieza.")
245
213
  return
246
214
 
247
- # Eliminar archivos y carpetas
215
+ # Eliminar archivos específicos de la entrada
216
+ if station_id:
217
+ safe_remove(files_folder / f"station_{station_id.lower()}_data.json")
218
+ if town_id:
219
+ safe_remove(files_folder / f"uvi_{town_id.lower()}_data.json")
220
+ safe_remove(files_folder / f"forecast_{town_id.lower()}_hourly_data.json")
221
+ safe_remove(files_folder / f"forecast_{town_id.lower()}_daily_data.json")
222
+ if region_id:
223
+ safe_remove(files_folder / f"alerts_{region_id}.json")
224
+ safe_remove(files_folder / f"lightning_{region_id}.json")
225
+
226
+ # Siempre eliminables
248
227
  safe_remove(symbols_file)
249
228
  safe_remove(variables_file)
250
- safe_remove(station_data_file)
251
- safe_remove(town_data_file)
252
- safe_remove(forecast_hourly_data_file)
253
- safe_remove(forecast_daily_data_file)
254
- safe_remove(alerts_file)
255
- safe_remove(quotes_file)
256
- safe_remove(alerts_region_file)
257
- safe_remove(lightning_file)
258
- safe_remove(assets_folder, is_folder=True)
259
- safe_remove(files_folder, is_folder=True)
229
+
230
+ # 🔑 Solo eliminar los archivos comunes si ya no quedan otras entradas
231
+ remaining_entries = [
232
+ e for e in hass.config_entries.async_entries(DOMAIN)
233
+ if e.entry_id != entry.entry_id
234
+ ]
235
+ if not remaining_entries: # significa que estamos borrando la última
236
+ safe_remove(alerts_file)
237
+ safe_remove(quotes_file)
238
+ safe_remove(assets_folder, is_folder=True)
239
+ safe_remove(files_folder, is_folder=True)
@@ -10,14 +10,13 @@ from datetime import datetime, timezone
10
10
  from zoneinfo import ZoneInfo
11
11
 
12
12
  import voluptuous as vol
13
- from aiohttp import ClientError
14
13
  import aiofiles
15
14
  import unicodedata
16
15
 
17
16
  from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
18
17
  from homeassistant.core import callback
19
18
  from homeassistant.exceptions import HomeAssistantError
20
- from homeassistant.helpers import aiohttp_client, config_validation as cv
19
+ from homeassistant.helpers import config_validation as cv
21
20
 
22
21
  from .const import (
23
22
  DOMAIN,
@@ -38,12 +37,12 @@ from .const import (
38
37
  PROVINCE_NAME,
39
38
  STATION_STATUS,
40
39
  LIMIT_XEMA,
41
- LIMIT_PREDICCIO,
40
+ LIMIT_PREDICCIO,
42
41
  LIMIT_XDDE,
43
42
  LIMIT_BASIC,
44
43
  LIMIT_QUOTA
45
44
  )
46
-
45
+
47
46
  from .options_flow import MeteocatOptionsFlowHandler
48
47
  from meteocatpy.town import MeteocatTown
49
48
  from meteocatpy.symbols import MeteocatSymbols
@@ -51,18 +50,22 @@ from meteocatpy.variables import MeteocatVariables
51
50
  from meteocatpy.townstations import MeteocatTownStations
52
51
  from meteocatpy.infostation import MeteocatInfoStation
53
52
  from meteocatpy.quotes import MeteocatQuotes
54
-
55
53
  from meteocatpy.exceptions import BadRequestError, ForbiddenError, TooManyRequestsError, InternalServerError, UnknownAPIError
56
54
 
57
55
  _LOGGER = logging.getLogger(__name__)
58
-
59
56
  TIMEZONE = ZoneInfo("Europe/Madrid")
60
57
 
61
- def normalize_name(name):
58
+ INITIAL_TEMPLATE = {
59
+ "actualitzat": {"dataUpdate": "1970-01-01T00:00:00+00:00"},
60
+ "dades": []
61
+ }
62
+
63
+ def normalize_name(name: str) -> str:
62
64
  """Normaliza el nombre eliminando acentos y convirtiendo a minúsculas."""
63
65
  name = unicodedata.normalize("NFKD", name).encode("ASCII", "ignore").decode("utf-8")
64
66
  return name.lower()
65
67
 
68
+
66
69
  class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
67
70
  """Flujo de configuración para Meteocat."""
68
71
 
@@ -76,27 +79,25 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
76
79
  self.station_id: str | None = None
77
80
  self.station_name: str | None = None
78
81
  self.region_id: str | None = None
79
- self._cache = {}
80
-
81
- async def fetch_and_save_quotes(self, api_key):
82
+ self.region_name: str | None = None
83
+ self.province_id: str | None = None
84
+ self.province_name: str | None = None
85
+ self.station_type: str | None = None
86
+ self.latitude: float | None = None
87
+ self.longitude: float | None = None
88
+ self.altitude: float | None = None
89
+ self.station_status: str | None = None
90
+
91
+ async def fetch_and_save_quotes(self, api_key: str):
82
92
  """Obtiene las cuotas de la API de Meteocat y las guarda en quotes.json."""
83
93
  meteocat_quotes = MeteocatQuotes(api_key)
84
- quotes_dir = os.path.join(
85
- self.hass.config.path(),
86
- "custom_components",
87
- "meteocat",
88
- "files"
89
- )
94
+ quotes_dir = os.path.join(self.hass.config.path(), "custom_components", "meteocat", "files")
90
95
  os.makedirs(quotes_dir, exist_ok=True)
91
96
  quotes_file = os.path.join(quotes_dir, "quotes.json")
92
97
 
93
98
  try:
94
- data = await asyncio.wait_for(
95
- meteocat_quotes.get_quotes(),
96
- timeout=30
97
- )
98
-
99
- # Modificar los nombres de los planes con normalización
99
+ data = await asyncio.wait_for(meteocat_quotes.get_quotes(), timeout=30)
100
+
100
101
  plan_mapping = {
101
102
  "xdde_": "XDDE",
102
103
  "prediccio_": "Prediccio",
@@ -109,7 +110,6 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
109
110
  for plan in data["plans"]:
110
111
  normalized_nom = normalize_name(plan["nom"])
111
112
  new_name = next((v for k, v in plan_mapping.items() if normalized_nom.startswith(k)), plan["nom"])
112
-
113
113
  modified_plans.append({
114
114
  "nom": new_name,
115
115
  "periode": plan["periode"],
@@ -118,28 +118,23 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
118
118
  "consultesRealitzades": plan["consultesRealitzades"]
119
119
  })
120
120
 
121
- # Añadir la clave 'actualitzat' con la fecha y hora actual de la zona horaria local
122
121
  current_time = datetime.now(timezone.utc).astimezone(TIMEZONE).isoformat()
123
122
  data_with_timestamp = {
124
- "actualitzat": {
125
- "dataUpdate": current_time
126
- },
123
+ "actualitzat": {"dataUpdate": current_time},
127
124
  "client": data["client"],
128
125
  "plans": modified_plans
129
126
  }
130
127
 
131
- # Guardar los datos en el archivo JSON
132
128
  async with aiofiles.open(quotes_file, "w", encoding="utf-8") as file:
133
129
  await file.write(json.dumps(data_with_timestamp, ensure_ascii=False, indent=4))
134
-
135
130
  _LOGGER.info("Cuotas guardadas exitosamente en %s", quotes_file)
136
131
 
137
132
  except Exception as ex:
138
133
  _LOGGER.error("Error al obtener o guardar las cuotas: %s", ex)
139
134
  raise HomeAssistantError("No se pudieron obtener las cuotas de la API")
140
-
135
+
141
136
  async def create_alerts_file(self):
142
- """Crea el archivo alerts.json si no existe."""
137
+ """Crea los archivos de alertas global y regional si no existen."""
143
138
  alerts_dir = os.path.join(
144
139
  self.hass.config.path(),
145
140
  "custom_components",
@@ -147,67 +142,44 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
147
142
  "files"
148
143
  )
149
144
  os.makedirs(alerts_dir, exist_ok=True)
150
- alerts_file = os.path.join(alerts_dir, "alerts.json")
151
145
 
146
+ # Archivo global de alertas
147
+ alerts_file = os.path.join(alerts_dir, "alerts.json")
152
148
  if not os.path.exists(alerts_file):
153
- initial_data = {
154
- "actualitzat": {
155
- "dataUpdate": "1970-01-01T00:00:00+00:00"
156
- },
157
- "dades": []
158
- }
159
149
  async with aiofiles.open(alerts_file, "w", encoding="utf-8") as file:
160
- await file.write(json.dumps(initial_data, ensure_ascii=False, indent=4))
161
-
162
- _LOGGER.info("Archivo alerts.json creado en %s", alerts_file)
163
-
164
- async def create_lightning_file(self):
165
- """Crea el archivo lightning_{self.region_id}.json si no existe."""
166
- lightning_dir = os.path.join(
167
- self.hass.config.path(),
168
- "custom_components",
169
- "meteocat",
170
- "files"
171
- )
172
- os.makedirs(lightning_dir, exist_ok=True)
173
- lightning_file = os.path.join(lightning_dir, f"lightning_{self.region_id}.json")
174
-
175
- if not os.path.exists(lightning_file):
176
- initial_data = {
177
- "actualitzat": {
178
- "dataUpdate": "1970-01-01T00:00:00+00:00"
179
- },
180
- "dades": []
181
- }
182
- async with aiofiles.open(lightning_file, "w", encoding="utf-8") as file:
183
- await file.write(json.dumps(initial_data, ensure_ascii=False, indent=4))
184
-
185
- _LOGGER.info("Archivo %s creado", lightning_file)
186
-
187
- async def async_step_user(
188
- self, user_input: dict[str, Any] | None = None
189
- ) -> ConfigFlowResult:
190
- """Primer paso: Solicitar la API Key."""
150
+ await file.write(json.dumps(INITIAL_TEMPLATE, ensure_ascii=False, indent=4))
151
+ _LOGGER.info("Archivo global %s creado con plantilla inicial", alerts_file)
152
+
153
+ # Solo si existe region_id
154
+ if self.region_id:
155
+ # Archivo regional de alertas
156
+ alerts_region_file = os.path.join(alerts_dir, f"alerts_{self.region_id}.json")
157
+ if not os.path.exists(alerts_region_file):
158
+ async with aiofiles.open(alerts_region_file, "w", encoding="utf-8") as file:
159
+ await file.write(json.dumps(INITIAL_TEMPLATE, ensure_ascii=False, indent=4))
160
+ _LOGGER.info("Archivo regional %s creado con plantilla inicial", alerts_region_file)
161
+
162
+ # Archivo lightning regional
163
+ lightning_file = os.path.join(alerts_dir, f"lightning_{self.region_id}.json")
164
+ if not os.path.exists(lightning_file):
165
+ async with aiofiles.open(lightning_file, "w", encoding="utf-8") as file:
166
+ await file.write(json.dumps(INITIAL_TEMPLATE, ensure_ascii=False, indent=4))
167
+ _LOGGER.info("Archivo lightning %s creado con plantilla inicial", lightning_file)
168
+
169
+ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
170
+ """Primer paso: solicitar API Key."""
191
171
  errors = {}
192
-
193
172
  if user_input is not None:
194
173
  self.api_key = user_input[CONF_API_KEY]
195
-
196
174
  town_client = MeteocatTown(self.api_key)
197
-
198
175
  try:
199
176
  self.municipis = await town_client.get_municipis()
200
- # Aquí obtenemos y guardamos las cuotas
201
177
  await self.fetch_and_save_quotes(self.api_key)
202
- # Aquí creamos el archivo alerts.json si no existe
178
+ # Crea solo el archivo global de alertas (regional se hará después)
203
179
  await self.create_alerts_file()
204
- except (BadRequestError, ForbiddenError, TooManyRequestsError, InternalServerError, UnknownAPIError) as ex:
180
+ except Exception as ex:
205
181
  _LOGGER.error("Error al conectar con la API de Meteocat: %s", ex)
206
182
  errors["base"] = "cannot_connect"
207
- except Exception as ex:
208
- _LOGGER.error("Error inesperado al validar la API Key: %s", ex)
209
- errors["base"] = "unknown"
210
-
211
183
  if not errors:
212
184
  return await self.async_step_select_municipi()
213
185
 
@@ -215,92 +187,44 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
215
187
  return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
216
188
 
217
189
  async def async_step_select_municipi(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
218
- """Segundo paso: Seleccionar el municipio."""
190
+ """Segundo paso: seleccionar el municipio."""
219
191
  errors = {}
220
-
221
192
  if user_input is not None:
222
193
  selected_codi = user_input["municipi"]
223
- self.selected_municipi = next(
224
- (m for m in self.municipis if m["codi"] == selected_codi), None
225
- )
226
-
194
+ self.selected_municipi = next((m for m in self.municipis if m["codi"] == selected_codi), None)
227
195
  if self.selected_municipi:
228
196
  await self.fetch_symbols_and_variables()
229
197
 
230
- if not errors and self.selected_municipi:
198
+ if self.selected_municipi:
231
199
  return await self.async_step_select_station()
232
200
 
233
- schema = vol.Schema(
234
- {vol.Required("municipi"): vol.In({m["codi"]: m["nom"] for m in self.municipis})}
235
- )
236
-
201
+ schema = vol.Schema({vol.Required("municipi"): vol.In({m["codi"]: m["nom"] for m in self.municipis})})
237
202
  return self.async_show_form(step_id="select_municipi", data_schema=schema, errors=errors)
238
203
 
239
204
  async def fetch_symbols_and_variables(self):
240
205
  """Descarga y guarda los símbolos y variables después de seleccionar el municipio."""
241
-
242
- errors = {}
243
-
244
- # Crear directorio de activos (assets) si no existe
245
- assets_dir = os.path.join(
246
- self.hass.config.path(),
247
- "custom_components",
248
- "meteocat",
249
- "assets"
250
- )
206
+ assets_dir = os.path.join(self.hass.config.path(), "custom_components", "meteocat", "assets")
251
207
  os.makedirs(assets_dir, exist_ok=True)
252
-
253
- # Rutas para los archivos de símbolos y variables
254
208
  symbols_file = os.path.join(assets_dir, "symbols.json")
255
209
  variables_file = os.path.join(assets_dir, "variables.json")
256
-
257
210
  try:
258
- # Descargar y guardar los símbolos
259
- symbols_client = MeteocatSymbols(self.api_key)
260
- symbols_data = await symbols_client.fetch_symbols()
261
-
211
+ symbols_data = await MeteocatSymbols(self.api_key).fetch_symbols()
262
212
  async with aiofiles.open(symbols_file, "w", encoding="utf-8") as file:
263
213
  await file.write(json.dumps({"symbols": symbols_data}, ensure_ascii=False, indent=4))
264
-
265
- _LOGGER.info(f"Símbolos guardados en {symbols_file}")
266
-
267
- # Descargar y guardar las variables
268
- variables_client = MeteocatVariables(self.api_key)
269
- variables_data = await variables_client.get_variables()
270
-
214
+ variables_data = await MeteocatVariables(self.api_key).get_variables()
271
215
  async with aiofiles.open(variables_file, "w", encoding="utf-8") as file:
272
216
  await file.write(json.dumps({"variables": variables_data}, ensure_ascii=False, indent=4))
273
-
274
- _LOGGER.info(f"Variables guardadas en {variables_file}")
275
-
276
- # Buscar la variable de temperatura
277
- self.variable_id = next(
278
- (v["codi"] for v in variables_data if v["nom"].lower() == "temperatura"), None
279
- )
280
- if not self.variable_id:
281
- _LOGGER.error("No se encontró la variable 'Temperatura'")
282
- errors["base"] = "variable_not_found"
283
- except (BadRequestError, ForbiddenError, TooManyRequestsError, InternalServerError, UnknownAPIError) as ex:
284
- _LOGGER.error("Error al conectar con la API de Meteocat: %s", ex)
285
- errors["base"] = "cannot_connect"
217
+ self.variable_id = next((v["codi"] for v in variables_data if v["nom"].lower() == "temperatura"), None)
286
218
  except Exception as ex:
287
- _LOGGER.error("Error inesperado al descargar los datos: %s", ex)
288
- errors["base"] = "unknown"
219
+ _LOGGER.error("Error al descargar símbolos o variables: %s", ex)
220
+ raise HomeAssistantError("No se pudieron obtener símbolos o variables")
289
221
 
290
- if errors:
291
- raise HomeAssistantError(errors)
292
-
293
- async def async_step_select_station(
294
- self, user_input: dict[str, Any] | None = None
295
- ) -> ConfigFlowResult:
296
- """Tercer paso: Seleccionar la estación para la variable seleccionada."""
222
+ async def async_step_select_station(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
223
+ """Tercer paso: seleccionar estación."""
297
224
  errors = {}
298
-
299
225
  townstations_client = MeteocatTownStations(self.api_key)
300
226
  try:
301
- stations_data = await townstations_client.get_town_stations(
302
- self.selected_municipi["codi"], self.variable_id
303
- )
227
+ stations_data = await townstations_client.get_town_stations(self.selected_municipi["codi"], self.variable_id)
304
228
  except Exception as ex:
305
229
  _LOGGER.error("Error al obtener las estaciones: %s", ex)
306
230
  errors["base"] = "stations_fetch_failed"
@@ -309,19 +233,15 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
309
233
  if user_input is not None:
310
234
  selected_station_codi = user_input["station"]
311
235
  selected_station = next(
312
- (station for station in stations_data[0]["variables"][0]["estacions"] if station["codi"] == selected_station_codi),
313
- None
236
+ (station for station in stations_data[0]["variables"][0]["estacions"] if station["codi"] == selected_station_codi), None
314
237
  )
315
-
316
238
  if selected_station:
317
239
  self.station_id = selected_station["codi"]
318
240
  self.station_name = selected_station["nom"]
319
241
 
320
242
  # Obtener metadatos de la estación
321
- infostation_client = MeteocatInfoStation(self.api_key)
322
243
  try:
323
- station_metadata = await infostation_client.get_infostation(self.station_id)
324
- # Extraer los valores necesarios de los metadatos
244
+ station_metadata = await MeteocatInfoStation(self.api_key).get_infostation(self.station_id)
325
245
  self.station_type = station_metadata.get("tipus", "")
326
246
  self.latitude = station_metadata.get("coordenades", {}).get("latitud", 0.0)
327
247
  self.longitude = station_metadata.get("coordenades", {}).get("longitud", 0.0)
@@ -332,9 +252,9 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
332
252
  self.province_name = station_metadata.get("provincia", {}).get("nom", "")
333
253
  self.station_status = station_metadata.get("estats", [{}])[0].get("codi", "")
334
254
 
335
- # Crear el archivo lightning después de obtener region_id
336
- await self.create_lightning_file()
337
-
255
+ # Crear archivos regionales de alertas y lightning
256
+ await self.create_alerts_file()
257
+
338
258
  return await self.async_step_set_api_limits()
339
259
  except Exception as ex:
340
260
  _LOGGER.error("Error al obtener los metadatos de la estación: %s", ex)
@@ -342,22 +262,12 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
342
262
  else:
343
263
  errors["base"] = "station_not_found"
344
264
 
345
- schema = vol.Schema(
346
- {
347
- vol.Required("station"): vol.In(
348
- {station["codi"]: station["nom"] for station in stations_data[0]["variables"][0]["estacions"]}
349
- )
350
- }
351
- )
265
+ schema = vol.Schema({vol.Required("station"): vol.In({station["codi"]: station["nom"] for station in stations_data[0]["variables"][0]["estacions"]})})
266
+ return self.async_show_form(step_id="select_station", data_schema=schema, errors=errors)
352
267
 
353
- return self.async_show_form(
354
- step_id="select_station", data_schema=schema, errors=errors
355
- )
356
-
357
268
  async def async_step_set_api_limits(self, user_input=None):
358
- """Cuarto paso: Introducir los límites de XEMA y PREDICCIO del plan de la API."""
269
+ """Cuarto paso: límites de la API."""
359
270
  errors = {}
360
-
361
271
  if user_input is not None:
362
272
  self.limit_xema = user_input.get(LIMIT_XEMA, 750)
363
273
  self.limit_prediccio = user_input.get(LIMIT_PREDICCIO, 100)
@@ -388,22 +298,17 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
388
298
  LIMIT_XDDE: self.limit_xdde,
389
299
  LIMIT_QUOTA: self.limit_quota,
390
300
  LIMIT_BASIC: self.limit_basic,
391
- },
301
+ }
392
302
  )
393
303
 
394
- schema = vol.Schema(
395
- {
396
- vol.Required(LIMIT_XEMA, default=750): cv.positive_int,
397
- vol.Required(LIMIT_PREDICCIO, default=100): cv.positive_int,
398
- vol.Required(LIMIT_XDDE, default=250): cv.positive_int,
399
- vol.Required(LIMIT_QUOTA, default=300): cv.positive_int,
400
- vol.Required(LIMIT_BASIC, default=2000): cv.positive_int,
401
- }
402
- )
403
-
404
- return self.async_show_form(
405
- step_id="set_api_limits", data_schema=schema, errors=errors
406
- )
304
+ schema = vol.Schema({
305
+ vol.Required(LIMIT_XEMA, default=750): cv.positive_int,
306
+ vol.Required(LIMIT_PREDICCIO, default=100): cv.positive_int,
307
+ vol.Required(LIMIT_XDDE, default=250): cv.positive_int,
308
+ vol.Required(LIMIT_QUOTA, default=300): cv.positive_int,
309
+ vol.Required(LIMIT_BASIC, default=2000): cv.positive_int,
310
+ })
311
+ return self.async_show_form(step_id="set_api_limits", data_schema=schema, errors=errors)
407
312
 
408
313
  @staticmethod
409
314
  @callback
@@ -1268,6 +1268,18 @@ class MeteocatAlertsCoordinator(DataUpdateCoordinator):
1268
1268
  if now - last_update > validity_duration:
1269
1269
  return await self._fetch_and_save_new_data()
1270
1270
  else:
1271
+ # Comprobar si el archivo regional sigue con INITIAL_TEMPLATE o sin datos válidos
1272
+ region_data = await load_json_from_file(self.alerts_region_file)
1273
+ if (
1274
+ not region_data
1275
+ or region_data.get("actualitzat", {}).get("dataUpdate") in [None, "1970-01-01T00:00:00+00:00"]
1276
+ ):
1277
+ _LOGGER.info(
1278
+ "El archivo regional %s sigue con plantilla inicial. Regenerando a partir de alerts.json",
1279
+ self.alerts_region_file,
1280
+ )
1281
+ await self._filter_alerts_by_region()
1282
+
1271
1283
  # Devolver los datos del archivo existente
1272
1284
  _LOGGER.debug("Usando datos existentes de alertas: %s", existing_data)
1273
1285
  return {
@@ -9,5 +9,5 @@
9
9
  "issue_tracker": "https://github.com/figorr/meteocat/issues",
10
10
  "loggers": ["meteocatpy"],
11
11
  "requirements": ["meteocatpy==1.0.1", "packaging>=20.3", "wrapt>=1.14.0"],
12
- "version": "2.2.5"
12
+ "version": "2.2.6"
13
13
  }
@@ -1,2 +1,2 @@
1
1
  # version.py
2
- __version__ = "2.2.5"
2
+ __version__ = "2.2.6"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meteocat",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
4
4
  "description": "[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\r [![Python version compatibility](https://img.shields.io/pypi/pyversions/meteocat)](https://pypi.org/project/meteocat)\r [![pipeline status](https://gitlab.com/figorr/meteocat/badges/master/pipeline.svg)](https://gitlab.com/figorr/meteocat/commits/master)",
5
5
  "main": "index.js",
6
6
  "directories": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "meteocat"
3
- version = "2.2.5"
3
+ version = "2.2.6"
4
4
  description = "Script para obtener datos meteorológicos de la API de Meteocat"
5
5
  authors = ["figorr <jdcuartero@yahoo.es>"]
6
6
  license = "Apache-2.0"