meteocat 2.1.0 → 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.
- package/CHANGELOG.md +8 -0
- package/custom_components/meteocat/__init__.py +15 -1
- package/custom_components/meteocat/config_flow.py +112 -1
- package/custom_components/meteocat/const.py +10 -0
- package/custom_components/meteocat/coordinator.py +303 -7
- package/custom_components/meteocat/manifest.json +2 -2
- package/custom_components/meteocat/options_flow.py +30 -4
- package/custom_components/meteocat/sensor.py +243 -2
- package/custom_components/meteocat/strings.json +151 -4
- package/custom_components/meteocat/translations/ca.json +151 -4
- package/custom_components/meteocat/translations/en.json +151 -4
- package/custom_components/meteocat/translations/es.json +151 -4
- package/custom_components/meteocat/version.py +1 -1
- package/package.json +1 -1
- package/poetry.lock +4 -4
- package/pyproject.toml +3 -3
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -60,6 +60,12 @@ from .const import (
|
|
|
60
60
|
HOURLY_FORECAST_FILE_STATUS,
|
|
61
61
|
DAILY_FORECAST_FILE_STATUS,
|
|
62
62
|
UVI_FILE_STATUS,
|
|
63
|
+
QUOTA_FILE_STATUS,
|
|
64
|
+
QUOTA_XDDE,
|
|
65
|
+
QUOTA_PREDICCIO,
|
|
66
|
+
QUOTA_BASIC,
|
|
67
|
+
QUOTA_XEMA,
|
|
68
|
+
QUOTA_QUERIES,
|
|
63
69
|
ALERTS,
|
|
64
70
|
ALERT_FILE_STATUS,
|
|
65
71
|
ALERT_WIND,
|
|
@@ -86,6 +92,11 @@ from .const import (
|
|
|
86
92
|
DEFAULT_VALIDITY_HOURS,
|
|
87
93
|
DEFAULT_VALIDITY_MINUTES,
|
|
88
94
|
DEFAULT_ALERT_VALIDITY_TIME,
|
|
95
|
+
DEFAULT_QUOTES_VALIDITY_TIME,
|
|
96
|
+
ALERT_VALIDITY_MULTIPLIER_100,
|
|
97
|
+
ALERT_VALIDITY_MULTIPLIER_200,
|
|
98
|
+
ALERT_VALIDITY_MULTIPLIER_500,
|
|
99
|
+
ALERT_VALIDITY_MULTIPLIER_DEFAULT,
|
|
89
100
|
)
|
|
90
101
|
|
|
91
102
|
from .coordinator import (
|
|
@@ -99,6 +110,8 @@ from .coordinator import (
|
|
|
99
110
|
MeteocatUviCoordinator,
|
|
100
111
|
MeteocatAlertsCoordinator,
|
|
101
112
|
MeteocatAlertsRegionCoordinator,
|
|
113
|
+
MeteocatQuotesCoordinator,
|
|
114
|
+
MeteocatQuotesFileCoordinator,
|
|
102
115
|
)
|
|
103
116
|
|
|
104
117
|
# Definir la zona horaria local
|
|
@@ -294,6 +307,12 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
294
307
|
icon="mdi:update",
|
|
295
308
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
296
309
|
),
|
|
310
|
+
MeteocatSensorEntityDescription(
|
|
311
|
+
key=QUOTA_FILE_STATUS,
|
|
312
|
+
translation_key="quota_file_status",
|
|
313
|
+
icon="mdi:update",
|
|
314
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
315
|
+
),
|
|
297
316
|
MeteocatSensorEntityDescription(
|
|
298
317
|
key=ALERTS,
|
|
299
318
|
translation_key="alerts",
|
|
@@ -344,6 +363,36 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
344
363
|
key=ALERT_SNOW,
|
|
345
364
|
translation_key="alert_snow",
|
|
346
365
|
icon="mdi:alert-outline",
|
|
366
|
+
),
|
|
367
|
+
MeteocatSensorEntityDescription(
|
|
368
|
+
key=QUOTA_XDDE,
|
|
369
|
+
translation_key="quota_xdde",
|
|
370
|
+
icon="mdi:counter",
|
|
371
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
372
|
+
),
|
|
373
|
+
MeteocatSensorEntityDescription(
|
|
374
|
+
key=QUOTA_PREDICCIO,
|
|
375
|
+
translation_key="quota_prediccio",
|
|
376
|
+
icon="mdi:counter",
|
|
377
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
378
|
+
),
|
|
379
|
+
MeteocatSensorEntityDescription(
|
|
380
|
+
key=QUOTA_BASIC,
|
|
381
|
+
translation_key="quota_basic",
|
|
382
|
+
icon="mdi:counter",
|
|
383
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
384
|
+
),
|
|
385
|
+
MeteocatSensorEntityDescription(
|
|
386
|
+
key=QUOTA_XEMA,
|
|
387
|
+
translation_key="quota_xema",
|
|
388
|
+
icon="mdi:counter",
|
|
389
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
390
|
+
),
|
|
391
|
+
MeteocatSensorEntityDescription(
|
|
392
|
+
key=QUOTA_QUERIES,
|
|
393
|
+
translation_key="quota_queries",
|
|
394
|
+
icon="mdi:counter",
|
|
395
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
347
396
|
)
|
|
348
397
|
)
|
|
349
398
|
|
|
@@ -363,6 +412,8 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
363
412
|
uvi_coordinator = entry_data.get("uvi_coordinator")
|
|
364
413
|
alerts_coordinator = entry_data.get("alerts_coordinator")
|
|
365
414
|
alerts_region_coordinator = entry_data.get("alerts_region_coordinator")
|
|
415
|
+
quotes_coordinator = entry_data.get("quotes_coordinator")
|
|
416
|
+
quotes_file_coordinator = entry_data.get("quotes_file_coordinator")
|
|
366
417
|
|
|
367
418
|
# Sensores generales
|
|
368
419
|
async_add_entities(
|
|
@@ -448,6 +499,20 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
448
499
|
if description.key in {ALERT_WIND, ALERT_RAIN_INTENSITY, ALERT_RAIN, ALERT_SEA, ALERT_COLD, ALERT_WARM, ALERT_WARM_NIGHT, ALERT_SNOW}
|
|
449
500
|
)
|
|
450
501
|
|
|
502
|
+
# Sensores de estado de cuotas
|
|
503
|
+
async_add_entities(
|
|
504
|
+
MeteocatQuotaStatusSensor(quotes_coordinator, description, entry_data)
|
|
505
|
+
for description in SENSOR_TYPES
|
|
506
|
+
if description.key == QUOTA_FILE_STATUS
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
# Sensores cuotas
|
|
510
|
+
async_add_entities(
|
|
511
|
+
MeteocatQuotaSensor(quotes_file_coordinator, description, entry_data)
|
|
512
|
+
for description in SENSOR_TYPES
|
|
513
|
+
if description.key in {QUOTA_XDDE, QUOTA_PREDICCIO, QUOTA_BASIC, QUOTA_XEMA, QUOTA_QUERIES}
|
|
514
|
+
)
|
|
515
|
+
|
|
451
516
|
# Cambiar UTC a la zona horaria local
|
|
452
517
|
def convert_to_local_time(utc_time: str, local_tz: str = "Europe/Madrid") -> datetime | None:
|
|
453
518
|
"""
|
|
@@ -1136,6 +1201,7 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1136
1201
|
self._town_id = entry_data["town_id"]
|
|
1137
1202
|
self._station_id = entry_data["station_id"]
|
|
1138
1203
|
self._region_id = entry_data["region_id"]
|
|
1204
|
+
self._limit_prediccio = entry_data["limit_prediccio"]
|
|
1139
1205
|
|
|
1140
1206
|
# Unique ID for the entity
|
|
1141
1207
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._region_id}_alert_status"
|
|
@@ -1152,6 +1218,19 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1152
1218
|
except ValueError:
|
|
1153
1219
|
_LOGGER.error("Formato de fecha de actualización inválido: %s", data_update)
|
|
1154
1220
|
return None
|
|
1221
|
+
|
|
1222
|
+
def _get_validity_duration(self):
|
|
1223
|
+
"""Calcula la duración de validez basada en el límite de predicción."""
|
|
1224
|
+
if self._limit_prediccio <= 100:
|
|
1225
|
+
multiplier = ALERT_VALIDITY_MULTIPLIER_100
|
|
1226
|
+
elif 100 < self._limit_prediccio <= 200:
|
|
1227
|
+
multiplier = ALERT_VALIDITY_MULTIPLIER_200
|
|
1228
|
+
elif 200 < self._limit_prediccio <= 500:
|
|
1229
|
+
multiplier = ALERT_VALIDITY_MULTIPLIER_500
|
|
1230
|
+
else:
|
|
1231
|
+
multiplier = ALERT_VALIDITY_MULTIPLIER_DEFAULT
|
|
1232
|
+
|
|
1233
|
+
return timedelta(minutes=DEFAULT_ALERT_VALIDITY_TIME * multiplier)
|
|
1155
1234
|
|
|
1156
1235
|
@property
|
|
1157
1236
|
def native_value(self):
|
|
@@ -1161,11 +1240,11 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1161
1240
|
return "unknown"
|
|
1162
1241
|
|
|
1163
1242
|
current_time = datetime.now(ZoneInfo("UTC"))
|
|
1243
|
+
validity_duration = self._get_validity_duration()
|
|
1164
1244
|
|
|
1165
1245
|
# Comprobar si el archivo de alertas está obsoleto
|
|
1166
|
-
if current_time - data_update >=
|
|
1246
|
+
if current_time - data_update >= validity_duration:
|
|
1167
1247
|
return "obsolete"
|
|
1168
|
-
|
|
1169
1248
|
return "updated"
|
|
1170
1249
|
|
|
1171
1250
|
@property
|
|
@@ -1370,3 +1449,165 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1370
1449
|
manufacturer="Meteocat",
|
|
1371
1450
|
model="Meteocat API",
|
|
1372
1451
|
)
|
|
1452
|
+
|
|
1453
|
+
class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], SensorEntity):
|
|
1454
|
+
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1455
|
+
|
|
1456
|
+
def __init__(self, quotes_coordinator, description, entry_data):
|
|
1457
|
+
super().__init__(quotes_coordinator)
|
|
1458
|
+
self.entity_description = description
|
|
1459
|
+
self._town_name = entry_data["town_name"]
|
|
1460
|
+
self._town_id = entry_data["town_id"]
|
|
1461
|
+
self._station_id = entry_data["station_id"]
|
|
1462
|
+
|
|
1463
|
+
# Unique ID for the entity
|
|
1464
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_quota_status"
|
|
1465
|
+
|
|
1466
|
+
# Assign entity_category if defined in the description
|
|
1467
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1468
|
+
|
|
1469
|
+
def _get_data_update(self):
|
|
1470
|
+
"""Obtiene la fecha de actualización directamente desde el coordinador."""
|
|
1471
|
+
data_update = self.coordinator.data.get("actualizado")
|
|
1472
|
+
if data_update:
|
|
1473
|
+
try:
|
|
1474
|
+
return datetime.fromisoformat(data_update.rstrip("Z"))
|
|
1475
|
+
except ValueError:
|
|
1476
|
+
_LOGGER.error("Formato de fecha de actualización inválido: %s", data_update)
|
|
1477
|
+
return None
|
|
1478
|
+
|
|
1479
|
+
@property
|
|
1480
|
+
def native_value(self):
|
|
1481
|
+
"""Devuelve el estado actual de las alertas basado en la fecha de actualización."""
|
|
1482
|
+
data_update = self._get_data_update()
|
|
1483
|
+
if not data_update:
|
|
1484
|
+
return "unknown"
|
|
1485
|
+
|
|
1486
|
+
current_time = datetime.now(ZoneInfo("UTC"))
|
|
1487
|
+
|
|
1488
|
+
# Comprobar si el archivo de alertas está obsoleto
|
|
1489
|
+
if current_time - data_update >= timedelta(minutes=DEFAULT_QUOTES_VALIDITY_TIME):
|
|
1490
|
+
return "obsolete"
|
|
1491
|
+
|
|
1492
|
+
return "updated"
|
|
1493
|
+
|
|
1494
|
+
@property
|
|
1495
|
+
def extra_state_attributes(self):
|
|
1496
|
+
"""Devuelve los atributos adicionales del estado."""
|
|
1497
|
+
attributes = super().extra_state_attributes or {}
|
|
1498
|
+
data_update = self._get_data_update()
|
|
1499
|
+
if data_update:
|
|
1500
|
+
attributes["update_date"] = data_update.isoformat()
|
|
1501
|
+
return attributes
|
|
1502
|
+
|
|
1503
|
+
@property
|
|
1504
|
+
def device_info(self) -> DeviceInfo:
|
|
1505
|
+
"""Devuelve la información del dispositivo."""
|
|
1506
|
+
return DeviceInfo(
|
|
1507
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
1508
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1509
|
+
manufacturer="Meteocat",
|
|
1510
|
+
model="Meteocat API",
|
|
1511
|
+
)
|
|
1512
|
+
|
|
1513
|
+
class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], SensorEntity):
|
|
1514
|
+
"""Representation of Meteocat Quota sensors."""
|
|
1515
|
+
|
|
1516
|
+
# Mapeo de claves en sensor.py a nombres en quotes.json
|
|
1517
|
+
QUOTA_MAPPING = {
|
|
1518
|
+
"quota_xdde": "XDDE",
|
|
1519
|
+
"quota_prediccio": "Prediccio",
|
|
1520
|
+
"quota_basic": "Basic",
|
|
1521
|
+
"quota_xema": "XEMA",
|
|
1522
|
+
"quota_queries": "Quota",
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
# Mapeo de periodos para facilitar la traducción del estado
|
|
1526
|
+
PERIOD_STATE_MAPPING = {
|
|
1527
|
+
"Setmanal": "weekly",
|
|
1528
|
+
"Mensual": "monthly",
|
|
1529
|
+
"Anual": "annual",
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1533
|
+
|
|
1534
|
+
def __init__(self, quotes_file_coordinator, description, entry_data):
|
|
1535
|
+
super().__init__(quotes_file_coordinator)
|
|
1536
|
+
self.entity_description = description
|
|
1537
|
+
self._town_name = entry_data["town_name"]
|
|
1538
|
+
self._town_id = entry_data["town_id"]
|
|
1539
|
+
self._station_id = entry_data["station_id"]
|
|
1540
|
+
|
|
1541
|
+
# Unique ID for the entity
|
|
1542
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
1543
|
+
|
|
1544
|
+
# Assign entity_category if defined in the description
|
|
1545
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1546
|
+
|
|
1547
|
+
def _get_plan_data(self):
|
|
1548
|
+
"""Encuentra los datos del plan correspondiente al sensor actual."""
|
|
1549
|
+
if not self.coordinator.data:
|
|
1550
|
+
return None
|
|
1551
|
+
|
|
1552
|
+
plan_name = self.QUOTA_MAPPING.get(self.entity_description.key)
|
|
1553
|
+
|
|
1554
|
+
if not plan_name:
|
|
1555
|
+
_LOGGER.error(f"No se encontró un mapeo para la clave: {self.entity_description.key}")
|
|
1556
|
+
return None
|
|
1557
|
+
|
|
1558
|
+
for plan in self.coordinator.data.get("plans", []):
|
|
1559
|
+
if plan.get("nom") == plan_name:
|
|
1560
|
+
return plan # Retorna el plan encontrado
|
|
1561
|
+
|
|
1562
|
+
_LOGGER.warning(f"No se encontró el plan '{plan_name}' en los datos del coordinador.")
|
|
1563
|
+
return None
|
|
1564
|
+
|
|
1565
|
+
@property
|
|
1566
|
+
def native_value(self):
|
|
1567
|
+
"""Devuelve el estado de la cuota: 'ok' si no se ha excedido, 'exceeded' si se ha superado."""
|
|
1568
|
+
plan = self._get_plan_data()
|
|
1569
|
+
|
|
1570
|
+
if not plan:
|
|
1571
|
+
return None
|
|
1572
|
+
|
|
1573
|
+
max_consultes = plan.get("maxConsultes")
|
|
1574
|
+
consultes_realitzades = plan.get("consultesRealitzades")
|
|
1575
|
+
|
|
1576
|
+
if max_consultes is None or consultes_realitzades is None or \
|
|
1577
|
+
not isinstance(max_consultes, (int, float)) or not isinstance(consultes_realitzades, (int, float)):
|
|
1578
|
+
_LOGGER.warning(f"Datos inválidos para el plan '{plan.get('nom', 'unknown')}': {plan}")
|
|
1579
|
+
return None
|
|
1580
|
+
|
|
1581
|
+
return "ok" if consultes_realitzades <= max_consultes else "exceeded"
|
|
1582
|
+
|
|
1583
|
+
@property
|
|
1584
|
+
def extra_state_attributes(self):
|
|
1585
|
+
"""Devuelve atributos adicionales del estado del sensor."""
|
|
1586
|
+
attributes = super().extra_state_attributes or {}
|
|
1587
|
+
plan = self._get_plan_data()
|
|
1588
|
+
|
|
1589
|
+
if not plan:
|
|
1590
|
+
return {}
|
|
1591
|
+
|
|
1592
|
+
# Aplicar el mapeo de periodos
|
|
1593
|
+
period = plan.get("periode", "desconocido")
|
|
1594
|
+
translated_period = self.PERIOD_STATE_MAPPING.get(period, period) # Si no está en el mapping, dejar el original
|
|
1595
|
+
|
|
1596
|
+
attributes.update({
|
|
1597
|
+
"period": translated_period,
|
|
1598
|
+
"max_queries": plan.get("maxConsultes"),
|
|
1599
|
+
"made_queries": plan.get("consultesRealitzades"),
|
|
1600
|
+
"remain_queries": plan.get("consultesRestants"),
|
|
1601
|
+
})
|
|
1602
|
+
|
|
1603
|
+
return attributes
|
|
1604
|
+
|
|
1605
|
+
@property
|
|
1606
|
+
def device_info(self) -> DeviceInfo:
|
|
1607
|
+
"""Devuelve la información del dispositivo."""
|
|
1608
|
+
return DeviceInfo(
|
|
1609
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
1610
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1611
|
+
manufacturer="Meteocat",
|
|
1612
|
+
model="Meteocat API",
|
|
1613
|
+
)
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"set_api_limits": {
|
|
23
|
-
"description": "Set the API limits
|
|
23
|
+
"description": "Set the API limits.",
|
|
24
24
|
"title": "API limits"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
@@ -36,18 +36,18 @@
|
|
|
36
36
|
"options": {
|
|
37
37
|
"step":{
|
|
38
38
|
"init": {
|
|
39
|
-
"description": "Setup the API Key and the limits
|
|
39
|
+
"description": "Setup the API Key and the API limits.",
|
|
40
40
|
"title": "Setup options",
|
|
41
41
|
"data": {
|
|
42
42
|
"option": "Options"
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"update_api_and_limits": {
|
|
46
|
-
"description": "Setup the API Key and the limits
|
|
46
|
+
"description": "Setup the API Key and the API limits.",
|
|
47
47
|
"title": "API Key and limits"
|
|
48
48
|
},
|
|
49
49
|
"update_limits_only": {
|
|
50
|
-
"description": "Setup the limits
|
|
50
|
+
"description": "Setup the API limits.",
|
|
51
51
|
"title": "API limits"
|
|
52
52
|
}
|
|
53
53
|
},
|
|
@@ -225,6 +225,153 @@
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
},
|
|
228
|
+
"quota_file_status": {
|
|
229
|
+
"name": "Quota File",
|
|
230
|
+
"state": {
|
|
231
|
+
"updated": "Updated",
|
|
232
|
+
"obsolete": "Obsolete"
|
|
233
|
+
},
|
|
234
|
+
"state_attributes": {
|
|
235
|
+
"update_date": {
|
|
236
|
+
"name": "Date"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"quota_xdde": {
|
|
241
|
+
"name": "Quota XDDE",
|
|
242
|
+
"state": {
|
|
243
|
+
"ok": "Ok",
|
|
244
|
+
"exceeded": "Exceeded",
|
|
245
|
+
"unknown": "Unknown"
|
|
246
|
+
},
|
|
247
|
+
"state_attributes": {
|
|
248
|
+
"period": {
|
|
249
|
+
"name": "Period",
|
|
250
|
+
"state": {
|
|
251
|
+
"weekly": "Weekly",
|
|
252
|
+
"monthly": "Monthly",
|
|
253
|
+
"annual": "Annual"
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
"max_queries": {
|
|
257
|
+
"name": "Max Queries"
|
|
258
|
+
},
|
|
259
|
+
"remain_queries": {
|
|
260
|
+
"name": "Remaining"
|
|
261
|
+
},
|
|
262
|
+
"made_queries": {
|
|
263
|
+
"name": "Made"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
"quota_prediccio": {
|
|
268
|
+
"name": "Quota Prediction",
|
|
269
|
+
"state": {
|
|
270
|
+
"ok": "Ok",
|
|
271
|
+
"exceeded": "Exceeded",
|
|
272
|
+
"unknown": "Unknown"
|
|
273
|
+
},
|
|
274
|
+
"state_attributes": {
|
|
275
|
+
"period": {
|
|
276
|
+
"name": "Period",
|
|
277
|
+
"state": {
|
|
278
|
+
"weekly": "Weekly",
|
|
279
|
+
"monthly": "Monthly",
|
|
280
|
+
"annual": "Annual"
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"max_queries": {
|
|
284
|
+
"name": "Max Queries"
|
|
285
|
+
},
|
|
286
|
+
"remain_queries": {
|
|
287
|
+
"name": "Remaining"
|
|
288
|
+
},
|
|
289
|
+
"made_queries": {
|
|
290
|
+
"name": "Made"
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
"quota_basic": {
|
|
295
|
+
"name": "Quota Basic",
|
|
296
|
+
"state": {
|
|
297
|
+
"ok": "Ok",
|
|
298
|
+
"exceeded": "Exceeded",
|
|
299
|
+
"unknown": "Unknown"
|
|
300
|
+
},
|
|
301
|
+
"state_attributes": {
|
|
302
|
+
"period": {
|
|
303
|
+
"name": "Period",
|
|
304
|
+
"state": {
|
|
305
|
+
"weekly": "Weekly",
|
|
306
|
+
"monthly": "Monthly",
|
|
307
|
+
"annual": "Annual"
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
"max_queries": {
|
|
311
|
+
"name": "Max Queries"
|
|
312
|
+
},
|
|
313
|
+
"remain_queries": {
|
|
314
|
+
"name": "Remaining"
|
|
315
|
+
},
|
|
316
|
+
"made_queries": {
|
|
317
|
+
"name": "Made"
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
"quota_xema": {
|
|
322
|
+
"name": "Quota XEMA",
|
|
323
|
+
"state": {
|
|
324
|
+
"ok": "Ok",
|
|
325
|
+
"exceeded": "Exceeded",
|
|
326
|
+
"unknown": "Unknown"
|
|
327
|
+
},
|
|
328
|
+
"state_attributes": {
|
|
329
|
+
"period": {
|
|
330
|
+
"name": "Period",
|
|
331
|
+
"state": {
|
|
332
|
+
"weekly": "Weekly",
|
|
333
|
+
"monthly": "Monthly",
|
|
334
|
+
"annual": "Annual"
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
"max_queries": {
|
|
338
|
+
"name": "Max Queries"
|
|
339
|
+
},
|
|
340
|
+
"remain_queries": {
|
|
341
|
+
"name": "Remaining"
|
|
342
|
+
},
|
|
343
|
+
"made_queries": {
|
|
344
|
+
"name": "Made"
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
"quota_queries": {
|
|
349
|
+
"name": "Quota Queries",
|
|
350
|
+
"state": {
|
|
351
|
+
"ok": "Ok",
|
|
352
|
+
"exceeded": "Exceeded",
|
|
353
|
+
"unknown": "Unknown"
|
|
354
|
+
},
|
|
355
|
+
"state_attributes": {
|
|
356
|
+
"period": {
|
|
357
|
+
"name": "Period",
|
|
358
|
+
"state": {
|
|
359
|
+
"weekly": "Weekly",
|
|
360
|
+
"monthly": "Monthly",
|
|
361
|
+
"annual": "Annual"
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
"max_queries": {
|
|
365
|
+
"name": "Max Queries"
|
|
366
|
+
},
|
|
367
|
+
"remain_queries": {
|
|
368
|
+
"name": "Remaining"
|
|
369
|
+
},
|
|
370
|
+
"made_queries": {
|
|
371
|
+
"name": "Made"
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
},
|
|
228
375
|
"alerts": {
|
|
229
376
|
"name": "Alerts",
|
|
230
377
|
"state_attributes": {
|