meteocat 1.1.0 → 1.1.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.
@@ -1,8 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from datetime import datetime, timezone, time
5
- from zoneinfo import ZoneInfo
4
+ from datetime import datetime, timezone, time, timedelta
5
+ from zoneinfo import ZoneInfo
6
+ import os
7
+ import json
8
+ import aiofiles
9
+ import asyncio
6
10
  import logging
7
11
  from homeassistant.helpers.entity import (
8
12
  DeviceInfo,
@@ -15,6 +19,7 @@ from homeassistant.components.sensor import (
15
19
  SensorStateClass,
16
20
  )
17
21
  from homeassistant.core import callback
22
+ from homeassistant.helpers.aiohttp_client import async_get_clientsession
18
23
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
19
24
  from homeassistant.helpers.update_coordinator import CoordinatorEntity
20
25
  from homeassistant.const import (
@@ -55,6 +60,16 @@ from .const import (
55
60
  HOURLY_FORECAST_FILE_STATUS,
56
61
  DAILY_FORECAST_FILE_STATUS,
57
62
  UVI_FILE_STATUS,
63
+ ALERTS,
64
+ ALERT_FILE_STATUS,
65
+ ALERT_WIND,
66
+ ALERT_RAIN_INTENSITY,
67
+ ALERT_RAIN,
68
+ ALERT_SEA,
69
+ ALERT_COLD,
70
+ ALERT_WARM,
71
+ ALERT_WARM_NIGHT,
72
+ ALERT_SNOW,
58
73
  WIND_SPEED_CODE,
59
74
  WIND_DIRECTION_CODE,
60
75
  TEMPERATURE_CODE,
@@ -70,6 +85,7 @@ from .const import (
70
85
  DEFAULT_VALIDITY_DAYS,
71
86
  DEFAULT_VALIDITY_HOURS,
72
87
  DEFAULT_VALIDITY_MINUTES,
88
+ DEFAULT_ALERT_VALIDITY_TIME,
73
89
  )
74
90
 
75
91
  from .coordinator import (
@@ -81,6 +97,8 @@ from .coordinator import (
81
97
  MeteocatEntityCoordinator,
82
98
  DailyForecastCoordinator,
83
99
  MeteocatUviCoordinator,
100
+ MeteocatAlertsCoordinator,
101
+ MeteocatAlertsRegionCoordinator,
84
102
  )
85
103
 
86
104
  # Definir la zona horaria local
@@ -275,6 +293,57 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
275
293
  translation_key="uvi_file_status",
276
294
  icon="mdi:update",
277
295
  entity_category=EntityCategory.DIAGNOSTIC,
296
+ ),
297
+ MeteocatSensorEntityDescription(
298
+ key=ALERTS,
299
+ translation_key="alerts",
300
+ icon="mdi:alert-outline",
301
+ ),
302
+ MeteocatSensorEntityDescription(
303
+ key=ALERT_FILE_STATUS,
304
+ translation_key="alert_file_status",
305
+ icon="mdi:update",
306
+ entity_category=EntityCategory.DIAGNOSTIC,
307
+ ),
308
+ MeteocatSensorEntityDescription(
309
+ key=ALERT_WIND,
310
+ translation_key="alert_wind",
311
+ icon="mdi:alert-outline",
312
+ ),
313
+ MeteocatSensorEntityDescription(
314
+ key=ALERT_RAIN_INTENSITY,
315
+ translation_key="alert_rain_intensity",
316
+ icon="mdi:alert-outline",
317
+ ),
318
+ MeteocatSensorEntityDescription(
319
+ key=ALERT_RAIN,
320
+ translation_key="alert_rain",
321
+ icon="mdi:alert-outline",
322
+ ),
323
+ MeteocatSensorEntityDescription(
324
+ key=ALERT_SEA,
325
+ translation_key="alert_sea",
326
+ icon="mdi:alert-outline",
327
+ ),
328
+ MeteocatSensorEntityDescription(
329
+ key=ALERT_COLD,
330
+ translation_key="alert_cold",
331
+ icon="mdi:alert-outline",
332
+ ),
333
+ MeteocatSensorEntityDescription(
334
+ key=ALERT_WARM,
335
+ translation_key="alert_warm",
336
+ icon="mdi:alert-outline",
337
+ ),
338
+ MeteocatSensorEntityDescription(
339
+ key=ALERT_WARM_NIGHT,
340
+ translation_key="alert_warm_night",
341
+ icon="mdi:alert-outline",
342
+ ),
343
+ MeteocatSensorEntityDescription(
344
+ key=ALERT_SNOW,
345
+ translation_key="alert_snow",
346
+ icon="mdi:alert-outline",
278
347
  )
279
348
  )
280
349
 
@@ -292,6 +361,8 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
292
361
  temp_forecast_coordinator = entry_data.get("temp_forecast_coordinator")
293
362
  entity_coordinator = entry_data.get("entity_coordinator")
294
363
  uvi_coordinator = entry_data.get("uvi_coordinator")
364
+ alerts_coordinator = entry_data.get("alerts_coordinator")
365
+ alerts_region_coordinator = entry_data.get("alerts_region_coordinator")
295
366
 
296
367
  # Sensores generales
297
368
  async_add_entities(
@@ -356,6 +427,27 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
356
427
  if description.key == UVI_FILE_STATUS
357
428
  )
358
429
 
430
+ # Sensores de alertas
431
+ async_add_entities(
432
+ MeteocatAlertStatusSensor(alerts_coordinator, description, entry_data)
433
+ for description in SENSOR_TYPES
434
+ if description.key == ALERT_FILE_STATUS
435
+ )
436
+
437
+ # Sensores de alertas para la comarca
438
+ async_add_entities(
439
+ MeteocatAlertRegionSensor(alerts_region_coordinator, description, entry_data)
440
+ for description in SENSOR_TYPES
441
+ if description.key == ALERTS
442
+ )
443
+
444
+ # Sensores de alertas para cada meteor
445
+ async_add_entities(
446
+ MeteocatAlertMeteorSensor(alerts_region_coordinator, description, entry_data)
447
+ for description in SENSOR_TYPES
448
+ if description.key in {ALERT_WIND, ALERT_RAIN_INTENSITY, ALERT_RAIN, ALERT_SEA, ALERT_COLD, ALERT_WARM, ALERT_WARM_NIGHT, ALERT_SNOW}
449
+ )
450
+
359
451
  # Cambiar UTC a la zona horaria local
360
452
  def convert_to_local_time(utc_time: str, local_tz: str = "Europe/Madrid") -> datetime | None:
361
453
  """
@@ -1032,4 +1124,184 @@ class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorE
1032
1124
  name="Meteocat " + self._station_id + " " + self._town_name,
1033
1125
  manufacturer="Meteocat",
1034
1126
  model="Meteocat API",
1035
- )
1127
+ )
1128
+
1129
+ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], SensorEntity):
1130
+ _attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
1131
+
1132
+ def __init__(self, alerts_coordinator, description, entry_data):
1133
+ super().__init__(alerts_coordinator)
1134
+ self.entity_description = description
1135
+ self._town_name = entry_data["town_name"]
1136
+ self._town_id = entry_data["town_id"]
1137
+ self._station_id = entry_data["station_id"]
1138
+ self._region_id = entry_data["region_id"]
1139
+
1140
+ # Unique ID for the entity
1141
+ self._attr_unique_id = f"sensor.{DOMAIN}_{self._region_id}_alert_status"
1142
+
1143
+ # Assign entity_category if defined in the description
1144
+ self._attr_entity_category = getattr(description, "entity_category", None)
1145
+
1146
+ def _get_data_update(self):
1147
+ """Obtiene la fecha de actualización directamente desde el coordinador."""
1148
+ data_update = self.coordinator.data.get("actualizado")
1149
+ if data_update:
1150
+ try:
1151
+ return datetime.fromisoformat(data_update.rstrip("Z"))
1152
+ except ValueError:
1153
+ _LOGGER.error("Formato de fecha de actualización inválido: %s", data_update)
1154
+ return None
1155
+
1156
+ @property
1157
+ def native_value(self):
1158
+ """Devuelve el estado actual de las alertas basado en la fecha de actualización."""
1159
+ data_update = self._get_data_update()
1160
+ if not data_update:
1161
+ return "unknown"
1162
+
1163
+ current_time = datetime.now(ZoneInfo("UTC"))
1164
+
1165
+ # Comprobar si el archivo de alertas está obsoleto
1166
+ if current_time - data_update >= timedelta(hours=DEFAULT_ALERT_VALIDITY_TIME):
1167
+ return "obsolete"
1168
+
1169
+ return "updated"
1170
+
1171
+ @property
1172
+ def extra_state_attributes(self):
1173
+ """Devuelve los atributos adicionales del estado."""
1174
+ attributes = super().extra_state_attributes or {}
1175
+ data_update = self._get_data_update()
1176
+ if data_update:
1177
+ attributes["update_date"] = data_update.isoformat()
1178
+ return attributes
1179
+
1180
+ @property
1181
+ def device_info(self) -> DeviceInfo:
1182
+ """Devuelve la información del dispositivo."""
1183
+ return DeviceInfo(
1184
+ identifiers={(DOMAIN, self._town_id)},
1185
+ name=f"Meteocat {self._station_id} {self._town_name}",
1186
+ manufacturer="Meteocat",
1187
+ model="Meteocat API",
1188
+ )
1189
+
1190
+ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinator], SensorEntity):
1191
+ """Sensor dinámico que muestra el estado de las alertas por región."""
1192
+ _attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
1193
+
1194
+ def __init__(self, alerts_region_coordinator, description, entry_data):
1195
+ super().__init__(alerts_region_coordinator)
1196
+ self.entity_description = description
1197
+ self._town_name = entry_data["town_name"]
1198
+ self._town_id = entry_data["town_id"]
1199
+ self._station_id = entry_data["station_id"]
1200
+ self._region_id = entry_data["region_id"]
1201
+
1202
+ # Unique ID for the entity
1203
+ self._attr_unique_id = f"sensor.{DOMAIN}_{self._region_id}_alerts"
1204
+
1205
+ # Assign entity_category if defined in the description
1206
+ self._attr_entity_category = getattr(description, "entity_category", None)
1207
+
1208
+ @property
1209
+ def native_value(self):
1210
+ """Devuelve el número de alertas activas."""
1211
+ return self.coordinator.data.get("activas", 0)
1212
+
1213
+ @property
1214
+ def extra_state_attributes(self):
1215
+ """Devuelve los atributos extra del sensor."""
1216
+ meteor_details = self.coordinator.data.get("detalles", {}).get("meteor", {})
1217
+ attributes = {f"alert_{i+1}": meteor for i, meteor in enumerate(meteor_details.keys())}
1218
+ _LOGGER.info("Atributos simplificados del sensor: %s", attributes)
1219
+ return attributes
1220
+
1221
+ @property
1222
+ def device_info(self) -> DeviceInfo:
1223
+ """Devuelve la información del dispositivo."""
1224
+ return DeviceInfo(
1225
+ identifiers={(DOMAIN, self._town_id)},
1226
+ name="Meteocat " + self._station_id + " " + self._town_name,
1227
+ manufacturer="Meteocat",
1228
+ model="Meteocat API",
1229
+ )
1230
+
1231
+ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinator], SensorEntity):
1232
+ """Sensor dinámico que muestra el estado de las alertas por cada meteor para una región."""
1233
+ METEOR_MAPPING = {
1234
+ ALERT_WIND: "Vent",
1235
+ ALERT_RAIN_INTENSITY: "Intensitat de pluja",
1236
+ ALERT_RAIN: "Acumulació de pluja",
1237
+ ALERT_SEA: "Estat de la mar",
1238
+ ALERT_COLD: "Fred",
1239
+ ALERT_WARM: "Calor",
1240
+ ALERT_WARM_NIGHT: "Calor nocturna",
1241
+ ALERT_SNOW: "Neu acumulada en 24 hores",
1242
+ }
1243
+
1244
+ _attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
1245
+
1246
+ def __init__(self, alerts_region_coordinator, description, entry_data):
1247
+ super().__init__(alerts_region_coordinator)
1248
+ self.entity_description = description
1249
+ self._town_name = entry_data["town_name"]
1250
+ self._town_id = entry_data["town_id"]
1251
+ self._station_id = entry_data["station_id"]
1252
+ self._region_id = entry_data["region_id"]
1253
+
1254
+ # Unique ID for the entity
1255
+ self._attr_unique_id = f"sensor.{DOMAIN}_{self._region_id}_{self.entity_description.key}"
1256
+
1257
+ # Assign entity_category if defined in the description
1258
+ self._attr_entity_category = getattr(description, "entity_category", None)
1259
+
1260
+ # Log para depuración
1261
+ _LOGGER.debug(
1262
+ "Inicializando sensor: %s, Unique ID: %s",
1263
+ self.entity_description.name,
1264
+ self._attr_unique_id,
1265
+ )
1266
+
1267
+ @property
1268
+ def native_value(self):
1269
+ """Devuelve el estado de la alerta específica."""
1270
+ meteor_type = self.METEOR_MAPPING.get(self.entity_description.key)
1271
+ if not meteor_type:
1272
+ return "Desconocido"
1273
+
1274
+ meteor_data = self.coordinator.data.get("detalles", {}).get("meteor", {}).get(meteor_type, {})
1275
+ return meteor_data.get("estado", "Tancat")
1276
+
1277
+ @property
1278
+ def extra_state_attributes(self):
1279
+ """Devuelve los atributos específicos de la alerta."""
1280
+ meteor_type = self.METEOR_MAPPING.get(self.entity_description.key)
1281
+ if not meteor_type:
1282
+ return {}
1283
+
1284
+ meteor_data = self.coordinator.data.get("detalles", {}).get("meteor", {}).get(meteor_type, {})
1285
+ if not meteor_data:
1286
+ return {}
1287
+
1288
+ return {
1289
+ "inicio": meteor_data.get("inicio"),
1290
+ "fin": meteor_data.get("fin"),
1291
+ "fecha": meteor_data.get("fecha"),
1292
+ "periodo": meteor_data.get("periodo"),
1293
+ "umbral": meteor_data.get("umbral"),
1294
+ "nivel": meteor_data.get("nivel"),
1295
+ "peligro": meteor_data.get("peligro"),
1296
+ "comentario": meteor_data.get("comentario"),
1297
+ }
1298
+
1299
+ @property
1300
+ def device_info(self) -> DeviceInfo:
1301
+ """Devuelve la información del dispositivo."""
1302
+ return DeviceInfo(
1303
+ identifiers={(DOMAIN, self._town_id)},
1304
+ name="Meteocat " + self._station_id + " " + self._town_name,
1305
+ manufacturer="Meteocat",
1306
+ model="Meteocat API",
1307
+ )