meteocat 3.0.0 → 3.2.0
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/.github/ISSUE_TEMPLATE/bug_report.md +8 -2
- package/.github/ISSUE_TEMPLATE/config.yml +7 -0
- package/.github/ISSUE_TEMPLATE/improvement.md +39 -0
- package/.github/ISSUE_TEMPLATE/new_function.md +41 -0
- package/.github/labels.yml +63 -0
- package/.github/workflows/autocloser.yaml +11 -9
- package/.github/workflows/close-on-label.yml +48 -0
- package/.github/workflows/force-sync-labels.yml +18 -0
- package/.github/workflows/sync-gitlab.yml +15 -4
- package/.github/workflows/sync-labels.yml +21 -0
- package/CHANGELOG.md +80 -11
- package/README.md +16 -4
- package/custom_components/meteocat/__init__.py +57 -42
- package/custom_components/meteocat/condition.py +6 -2
- package/custom_components/meteocat/config_flow.py +231 -4
- package/custom_components/meteocat/const.py +17 -2
- package/custom_components/meteocat/coordinator.py +1122 -101
- package/custom_components/meteocat/helpers.py +31 -36
- package/custom_components/meteocat/manifest.json +3 -2
- package/custom_components/meteocat/options_flow.py +71 -3
- package/custom_components/meteocat/sensor.py +660 -247
- package/custom_components/meteocat/strings.json +252 -15
- package/custom_components/meteocat/translations/ca.json +249 -13
- package/custom_components/meteocat/translations/en.json +252 -15
- package/custom_components/meteocat/translations/es.json +252 -15
- package/custom_components/meteocat/version.py +1 -1
- package/filetree.txt +12 -3
- package/hacs.json +1 -1
- package/images/daily_forecast_2_alerts.png +0 -0
- package/images/daily_forecast_no_alerts.png +0 -0
- package/images/diagnostic_sensors.png +0 -0
- package/images/dynamic_sensors.png +0 -0
- package/images/options.png +0 -0
- package/images/regenerate_assets.png +0 -0
- package/images/setup_options.png +0 -0
- package/images/system_options.png +0 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/update_version.sh +6 -0
- package/.github/workflows/close-duplicates.yml +0 -57
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from datetime import datetime, timezone, time, timedelta
|
|
5
5
|
from zoneinfo import ZoneInfo
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
6
7
|
import os
|
|
7
8
|
import json
|
|
8
9
|
import aiofiles
|
|
@@ -71,13 +72,13 @@ from .const import (
|
|
|
71
72
|
ALERTS,
|
|
72
73
|
ALERT_FILE_STATUS,
|
|
73
74
|
ALERT_WIND,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
ALERT_RAIN_INTENSITY,
|
|
76
|
+
ALERT_RAIN,
|
|
77
|
+
ALERT_SEA,
|
|
78
|
+
ALERT_COLD,
|
|
79
|
+
ALERT_WARM,
|
|
80
|
+
ALERT_WARM_NIGHT,
|
|
81
|
+
ALERT_SNOW,
|
|
81
82
|
LIGHTNING_FILE_STATUS,
|
|
82
83
|
LIGHTNING_REGION,
|
|
83
84
|
LIGHTNING_TOWN,
|
|
@@ -105,6 +106,14 @@ from .const import (
|
|
|
105
106
|
DEFAULT_LIGHTNING_VALIDITY_TIME,
|
|
106
107
|
DEFAULT_LIGHTNING_VALIDITY_HOURS,
|
|
107
108
|
DEFAULT_LIGHTNING_VALIDITY_MINUTES,
|
|
109
|
+
SUN,
|
|
110
|
+
SUNRISE,
|
|
111
|
+
SUNSET,
|
|
112
|
+
SUN_FILE_STATUS,
|
|
113
|
+
MOON_PHASE,
|
|
114
|
+
MOON_FILE_STATUS,
|
|
115
|
+
MOONRISE,
|
|
116
|
+
MOONSET,
|
|
108
117
|
)
|
|
109
118
|
|
|
110
119
|
from .coordinator import (
|
|
@@ -122,6 +131,10 @@ from .coordinator import (
|
|
|
122
131
|
MeteocatQuotesFileCoordinator,
|
|
123
132
|
MeteocatLightningCoordinator,
|
|
124
133
|
MeteocatLightningFileCoordinator,
|
|
134
|
+
MeteocatSunCoordinator,
|
|
135
|
+
MeteocatSunFileCoordinator,
|
|
136
|
+
MeteocatMoonCoordinator,
|
|
137
|
+
MeteocatMoonFileCoordinator,
|
|
125
138
|
)
|
|
126
139
|
|
|
127
140
|
# Definir la zona horaria local
|
|
@@ -208,7 +221,7 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
208
221
|
icon="mdi:weather-sunny",
|
|
209
222
|
device_class=SensorDeviceClass.IRRADIANCE,
|
|
210
223
|
state_class=SensorStateClass.MEASUREMENT,
|
|
211
|
-
native_unit_of_measurement
|
|
224
|
+
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
|
212
225
|
),
|
|
213
226
|
MeteocatSensorEntityDescription(
|
|
214
227
|
key=UV_INDEX,
|
|
@@ -305,7 +318,7 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
305
318
|
translation_key="condition",
|
|
306
319
|
icon="mdi:weather-partly-cloudy",
|
|
307
320
|
),
|
|
308
|
-
|
|
321
|
+
MeteocatSensorEntityDescription(
|
|
309
322
|
key=MAX_TEMPERATURE_FORECAST,
|
|
310
323
|
translation_key="max_temperature_forecast",
|
|
311
324
|
icon="mdi:thermometer-plus",
|
|
@@ -367,37 +380,37 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
367
380
|
translation_key="alert_wind",
|
|
368
381
|
icon="mdi:alert-outline",
|
|
369
382
|
),
|
|
370
|
-
|
|
383
|
+
MeteocatSensorEntityDescription(
|
|
371
384
|
key=ALERT_RAIN_INTENSITY,
|
|
372
385
|
translation_key="alert_rain_intensity",
|
|
373
386
|
icon="mdi:alert-outline",
|
|
374
387
|
),
|
|
375
|
-
|
|
388
|
+
MeteocatSensorEntityDescription(
|
|
376
389
|
key=ALERT_RAIN,
|
|
377
390
|
translation_key="alert_rain",
|
|
378
391
|
icon="mdi:alert-outline",
|
|
379
392
|
),
|
|
380
|
-
|
|
393
|
+
MeteocatSensorEntityDescription(
|
|
381
394
|
key=ALERT_SEA,
|
|
382
395
|
translation_key="alert_sea",
|
|
383
396
|
icon="mdi:alert-outline",
|
|
384
397
|
),
|
|
385
|
-
|
|
398
|
+
MeteocatSensorEntityDescription(
|
|
386
399
|
key=ALERT_COLD,
|
|
387
400
|
translation_key="alert_cold",
|
|
388
401
|
icon="mdi:alert-outline",
|
|
389
402
|
),
|
|
390
|
-
|
|
403
|
+
MeteocatSensorEntityDescription(
|
|
391
404
|
key=ALERT_WARM,
|
|
392
405
|
translation_key="alert_warm",
|
|
393
406
|
icon="mdi:alert-outline",
|
|
394
407
|
),
|
|
395
|
-
|
|
408
|
+
MeteocatSensorEntityDescription(
|
|
396
409
|
key=ALERT_WARM_NIGHT,
|
|
397
410
|
translation_key="alert_warm_night",
|
|
398
411
|
icon="mdi:alert-outline",
|
|
399
412
|
),
|
|
400
|
-
|
|
413
|
+
MeteocatSensorEntityDescription(
|
|
401
414
|
key=ALERT_SNOW,
|
|
402
415
|
translation_key="alert_snow",
|
|
403
416
|
icon="mdi:alert-outline",
|
|
@@ -431,7 +444,67 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
431
444
|
translation_key="quota_queries",
|
|
432
445
|
icon="mdi:counter",
|
|
433
446
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
434
|
-
)
|
|
447
|
+
),
|
|
448
|
+
MeteocatSensorEntityDescription(
|
|
449
|
+
key=SUN,
|
|
450
|
+
translation_key="sun",
|
|
451
|
+
icon="mdi:weather-sunny",
|
|
452
|
+
device_class=SensorDeviceClass.ENUM,
|
|
453
|
+
options=["above_horizon", "below_horizon"],
|
|
454
|
+
),
|
|
455
|
+
MeteocatSensorEntityDescription(
|
|
456
|
+
key=SUNRISE,
|
|
457
|
+
translation_key="sunrise",
|
|
458
|
+
icon="mdi:weather-sunset-up",
|
|
459
|
+
device_class=SensorDeviceClass.TIMESTAMP,
|
|
460
|
+
),
|
|
461
|
+
MeteocatSensorEntityDescription(
|
|
462
|
+
key=SUNSET,
|
|
463
|
+
translation_key="sunset",
|
|
464
|
+
icon="mdi:weather-sunset-down",
|
|
465
|
+
device_class=SensorDeviceClass.TIMESTAMP,
|
|
466
|
+
),
|
|
467
|
+
MeteocatSensorEntityDescription(
|
|
468
|
+
key=SUN_FILE_STATUS,
|
|
469
|
+
translation_key="sun_file_status",
|
|
470
|
+
icon="mdi:update",
|
|
471
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
472
|
+
),
|
|
473
|
+
MeteocatSensorEntityDescription(
|
|
474
|
+
key=MOON_PHASE,
|
|
475
|
+
translation_key="moon_phase",
|
|
476
|
+
device_class=SensorDeviceClass.ENUM,
|
|
477
|
+
options=[
|
|
478
|
+
"new_moon",
|
|
479
|
+
"waxing_crescent",
|
|
480
|
+
"first_quarter",
|
|
481
|
+
"waxing_gibbous",
|
|
482
|
+
"full_moon",
|
|
483
|
+
"waning_gibbous",
|
|
484
|
+
"last_quarter",
|
|
485
|
+
"waning_crescent",
|
|
486
|
+
"unknown",
|
|
487
|
+
],
|
|
488
|
+
state_class=None,
|
|
489
|
+
),
|
|
490
|
+
MeteocatSensorEntityDescription(
|
|
491
|
+
key=MOON_FILE_STATUS,
|
|
492
|
+
translation_key="moon_file_status",
|
|
493
|
+
icon="mdi:update",
|
|
494
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
495
|
+
),
|
|
496
|
+
MeteocatSensorEntityDescription(
|
|
497
|
+
key=MOONRISE,
|
|
498
|
+
translation_key="moonrise",
|
|
499
|
+
icon="mdi:weather-moonset-up",
|
|
500
|
+
device_class=SensorDeviceClass.TIMESTAMP,
|
|
501
|
+
),
|
|
502
|
+
MeteocatSensorEntityDescription(
|
|
503
|
+
key=MOONSET,
|
|
504
|
+
translation_key="moonset",
|
|
505
|
+
icon="mdi:weather-moonset-down",
|
|
506
|
+
device_class=SensorDeviceClass.TIMESTAMP,
|
|
507
|
+
),
|
|
435
508
|
)
|
|
436
509
|
|
|
437
510
|
@callback
|
|
@@ -454,12 +527,31 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
454
527
|
quotes_file_coordinator = entry_data.get("quotes_file_coordinator")
|
|
455
528
|
lightning_coordinator = entry_data.get("lightning_coordinator")
|
|
456
529
|
lightning_file_coordinator = entry_data.get("lightning_file_coordinator")
|
|
530
|
+
sun_coordinator = entry_data.get("sun_coordinator")
|
|
531
|
+
sun_file_coordinator = entry_data.get("sun_file_coordinator")
|
|
532
|
+
moon_coordinator = entry_data.get("moon_coordinator")
|
|
533
|
+
moon_file_coordinator = entry_data.get("moon_file_coordinator")
|
|
457
534
|
|
|
458
535
|
# Sensores generales
|
|
459
536
|
async_add_entities(
|
|
460
537
|
MeteocatSensor(sensor_coordinator, description, entry_data)
|
|
461
538
|
for description in SENSOR_TYPES
|
|
462
|
-
if description.key in {
|
|
539
|
+
if description.key in {
|
|
540
|
+
WIND_SPEED,
|
|
541
|
+
WIND_DIRECTION,
|
|
542
|
+
WIND_DIRECTION_CARDINAL,
|
|
543
|
+
TEMPERATURE,
|
|
544
|
+
HUMIDITY,
|
|
545
|
+
PRESSURE,
|
|
546
|
+
PRECIPITATION,
|
|
547
|
+
PRECIPITATION_ACCUMULATED,
|
|
548
|
+
SOLAR_GLOBAL_IRRADIANCE,
|
|
549
|
+
MAX_TEMPERATURE,
|
|
550
|
+
MIN_TEMPERATURE,
|
|
551
|
+
FEELS_LIKE,
|
|
552
|
+
WIND_GUST,
|
|
553
|
+
STATION_TIMESTAMP,
|
|
554
|
+
}
|
|
463
555
|
)
|
|
464
556
|
|
|
465
557
|
# Sensores estáticos
|
|
@@ -473,14 +565,14 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
473
565
|
async_add_entities(
|
|
474
566
|
MeteocatUviSensor(uvi_file_coordinator, description, entry_data)
|
|
475
567
|
for description in SENSOR_TYPES
|
|
476
|
-
if description.key == UV_INDEX
|
|
568
|
+
if description.key == UV_INDEX
|
|
477
569
|
)
|
|
478
570
|
|
|
479
571
|
# Sensor CONDITION para estado del cielo
|
|
480
572
|
async_add_entities(
|
|
481
573
|
MeteocatConditionSensor(condition_coordinator, description, entry_data)
|
|
482
574
|
for description in SENSOR_TYPES
|
|
483
|
-
if description.key == CONDITION
|
|
575
|
+
if description.key == CONDITION
|
|
484
576
|
)
|
|
485
577
|
|
|
486
578
|
# Sensores temperatura previsión
|
|
@@ -536,7 +628,16 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
536
628
|
async_add_entities(
|
|
537
629
|
MeteocatAlertMeteorSensor(alerts_region_coordinator, description, entry_data)
|
|
538
630
|
for description in SENSOR_TYPES
|
|
539
|
-
if description.key in {
|
|
631
|
+
if description.key in {
|
|
632
|
+
ALERT_WIND,
|
|
633
|
+
ALERT_RAIN_INTENSITY,
|
|
634
|
+
ALERT_RAIN,
|
|
635
|
+
ALERT_SEA,
|
|
636
|
+
ALERT_COLD,
|
|
637
|
+
ALERT_WARM,
|
|
638
|
+
ALERT_WARM_NIGHT,
|
|
639
|
+
ALERT_SNOW,
|
|
640
|
+
}
|
|
540
641
|
)
|
|
541
642
|
|
|
542
643
|
# Sensores de estado de cuotas
|
|
@@ -567,6 +668,48 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
567
668
|
if description.key in {LIGHTNING_REGION, LIGHTNING_TOWN}
|
|
568
669
|
)
|
|
569
670
|
|
|
671
|
+
# Sensor de estado de archivo de sol
|
|
672
|
+
async_add_entities(
|
|
673
|
+
MeteocatSunStatusSensor(sun_coordinator, description, entry_data)
|
|
674
|
+
for description in SENSOR_TYPES
|
|
675
|
+
if description.key == SUN_FILE_STATUS
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
# Sensor de posición del sol
|
|
679
|
+
async_add_entities(
|
|
680
|
+
MeteocatSunPositionSensor(sun_file_coordinator, description, entry_data)
|
|
681
|
+
for description in SENSOR_TYPES
|
|
682
|
+
if description.key == SUN
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
# Sensores de sol
|
|
686
|
+
async_add_entities(
|
|
687
|
+
MeteocatSunSensor(sun_file_coordinator, description, entry_data)
|
|
688
|
+
for description in SENSOR_TYPES
|
|
689
|
+
if description.key in {SUNRISE, SUNSET}
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
# Sensor de fase lunar
|
|
693
|
+
async_add_entities(
|
|
694
|
+
MeteocatMoonSensor(moon_file_coordinator, description, entry_data)
|
|
695
|
+
for description in SENSOR_TYPES
|
|
696
|
+
if description.key == MOON_PHASE
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
# Sensor de estado de archivo lunar
|
|
700
|
+
async_add_entities(
|
|
701
|
+
MeteocatMoonStatusSensor(moon_coordinator, description, entry_data)
|
|
702
|
+
for description in SENSOR_TYPES
|
|
703
|
+
if description.key == MOON_FILE_STATUS
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
# Sensores de salida y puesta de la luna
|
|
707
|
+
async_add_entities(
|
|
708
|
+
MeteocatMoonTimeSensor(moon_file_coordinator, description, entry_data)
|
|
709
|
+
for description in SENSOR_TYPES
|
|
710
|
+
if description.key in {MOONRISE, MOONSET}
|
|
711
|
+
)
|
|
712
|
+
|
|
570
713
|
# Cambiar UTC a la zona horaria local
|
|
571
714
|
def convert_to_local_time(utc_time: str, local_tz: str = "Europe/Madrid") -> datetime | None:
|
|
572
715
|
"""
|
|
@@ -594,7 +737,7 @@ class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], S
|
|
|
594
737
|
"""Representation of a static Meteocat sensor."""
|
|
595
738
|
STATIC_KEYS = {TOWN_NAME, TOWN_ID, STATION_NAME, STATION_ID, REGION_NAME, REGION_ID}
|
|
596
739
|
|
|
597
|
-
_attr_has_entity_name = True
|
|
740
|
+
_attr_has_entity_name = True
|
|
598
741
|
|
|
599
742
|
def __init__(self, static_sensor_coordinator, description, entry_data):
|
|
600
743
|
"""Initialize the static sensor."""
|
|
@@ -607,13 +750,9 @@ class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], S
|
|
|
607
750
|
self._region_name = entry_data["region_name"]
|
|
608
751
|
self._region_id = entry_data["region_id"]
|
|
609
752
|
|
|
610
|
-
# Unique ID for the entity
|
|
611
753
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
612
|
-
|
|
613
|
-
# Assign entity_category if defined in the description
|
|
614
754
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
615
755
|
|
|
616
|
-
# Log para depuración
|
|
617
756
|
_LOGGER.debug(
|
|
618
757
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
619
758
|
self.entity_description.name,
|
|
@@ -623,9 +762,7 @@ class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], S
|
|
|
623
762
|
@property
|
|
624
763
|
def native_value(self):
|
|
625
764
|
"""Return the state of the sensor."""
|
|
626
|
-
# Información estática
|
|
627
765
|
if self.entity_description.key in self.STATIC_KEYS:
|
|
628
|
-
# Información estática del `entry_data`
|
|
629
766
|
if self.entity_description.key == TOWN_NAME:
|
|
630
767
|
return self._town_name
|
|
631
768
|
if self.entity_description.key == TOWN_ID:
|
|
@@ -644,15 +781,14 @@ class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], S
|
|
|
644
781
|
"""Return the device info."""
|
|
645
782
|
return DeviceInfo(
|
|
646
783
|
identifiers={(DOMAIN, self._town_id)},
|
|
647
|
-
name="Meteocat
|
|
784
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
648
785
|
manufacturer="Meteocat",
|
|
649
786
|
model="Meteocat API",
|
|
650
787
|
)
|
|
651
788
|
|
|
652
789
|
class MeteocatUviSensor(CoordinatorEntity[MeteocatUviFileCoordinator], SensorEntity):
|
|
653
790
|
"""Representation of a Meteocat UV Index sensor."""
|
|
654
|
-
|
|
655
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
791
|
+
_attr_has_entity_name = True
|
|
656
792
|
|
|
657
793
|
def __init__(self, uvi_file_coordinator, description, entry_data):
|
|
658
794
|
"""Initialize the UV Index sensor."""
|
|
@@ -661,14 +797,9 @@ class MeteocatUviSensor(CoordinatorEntity[MeteocatUviFileCoordinator], SensorEnt
|
|
|
661
797
|
self._town_name = entry_data["town_name"]
|
|
662
798
|
self._town_id = entry_data["town_id"]
|
|
663
799
|
self._station_id = entry_data["station_id"]
|
|
664
|
-
|
|
665
|
-
# Unique ID for the entity
|
|
666
800
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
667
|
-
|
|
668
|
-
# Asigna entity_category desde description (si está definido)
|
|
669
801
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
670
802
|
|
|
671
|
-
# Log para depuración
|
|
672
803
|
_LOGGER.debug(
|
|
673
804
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
674
805
|
self.entity_description.name,
|
|
@@ -688,7 +819,6 @@ class MeteocatUviSensor(CoordinatorEntity[MeteocatUviFileCoordinator], SensorEnt
|
|
|
688
819
|
attributes = super().extra_state_attributes or {}
|
|
689
820
|
if self.entity_description.key == UV_INDEX:
|
|
690
821
|
uvi_data = self.coordinator.data or {}
|
|
691
|
-
# Add the "hour" attribute if it exists
|
|
692
822
|
attributes["hour"] = uvi_data.get("hour")
|
|
693
823
|
return attributes
|
|
694
824
|
|
|
@@ -697,31 +827,25 @@ class MeteocatUviSensor(CoordinatorEntity[MeteocatUviFileCoordinator], SensorEnt
|
|
|
697
827
|
"""Return the device info."""
|
|
698
828
|
return DeviceInfo(
|
|
699
829
|
identifiers={(DOMAIN, self._town_id)},
|
|
700
|
-
name="Meteocat
|
|
830
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
701
831
|
manufacturer="Meteocat",
|
|
702
832
|
model="Meteocat API",
|
|
703
833
|
)
|
|
704
834
|
|
|
705
835
|
class MeteocatConditionSensor(CoordinatorEntity[MeteocatConditionCoordinator], SensorEntity):
|
|
706
|
-
"""Representation of a Meteocat
|
|
707
|
-
|
|
708
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
836
|
+
"""Representation of a Meteocat Condition sensor."""
|
|
837
|
+
_attr_has_entity_name = True
|
|
709
838
|
|
|
710
839
|
def __init__(self, condition_coordinator, description, entry_data):
|
|
711
|
-
"""Initialize the
|
|
840
|
+
"""Initialize the Condition sensor."""
|
|
712
841
|
super().__init__(condition_coordinator)
|
|
713
842
|
self.entity_description = description
|
|
714
843
|
self._town_name = entry_data["town_name"]
|
|
715
844
|
self._town_id = entry_data["town_id"]
|
|
716
845
|
self._station_id = entry_data["station_id"]
|
|
717
|
-
|
|
718
|
-
# Unique ID for the entity
|
|
719
846
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
720
|
-
|
|
721
|
-
# Asigna entity_category desde description (si está definido)
|
|
722
847
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
723
848
|
|
|
724
|
-
# Log para depuración
|
|
725
849
|
_LOGGER.debug(
|
|
726
850
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
727
851
|
self.entity_description.name,
|
|
@@ -730,7 +854,7 @@ class MeteocatConditionSensor(CoordinatorEntity[MeteocatConditionCoordinator], S
|
|
|
730
854
|
|
|
731
855
|
@property
|
|
732
856
|
def native_value(self):
|
|
733
|
-
"""Return the current
|
|
857
|
+
"""Return the current condition value."""
|
|
734
858
|
if self.entity_description.key == CONDITION:
|
|
735
859
|
condition_data = self.coordinator.data or {}
|
|
736
860
|
return condition_data.get("condition", None)
|
|
@@ -741,7 +865,6 @@ class MeteocatConditionSensor(CoordinatorEntity[MeteocatConditionCoordinator], S
|
|
|
741
865
|
attributes = super().extra_state_attributes or {}
|
|
742
866
|
if self.entity_description.key == CONDITION:
|
|
743
867
|
condition_data = self.coordinator.data or {}
|
|
744
|
-
# Add the "hour" attribute if it exists
|
|
745
868
|
attributes["hour"] = condition_data.get("hour", None)
|
|
746
869
|
return attributes
|
|
747
870
|
|
|
@@ -750,14 +873,59 @@ class MeteocatConditionSensor(CoordinatorEntity[MeteocatConditionCoordinator], S
|
|
|
750
873
|
"""Return the device info."""
|
|
751
874
|
return DeviceInfo(
|
|
752
875
|
identifiers={(DOMAIN, self._town_id)},
|
|
753
|
-
name="Meteocat
|
|
876
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
877
|
+
manufacturer="Meteocat",
|
|
878
|
+
model="Meteocat API",
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
class MeteocatSunSensor(CoordinatorEntity[MeteocatSunCoordinator], SensorEntity):
|
|
882
|
+
"""Representation of a Meteocat Sun sensor (sunrise/sunset)."""
|
|
883
|
+
_attr_has_entity_name = True
|
|
884
|
+
|
|
885
|
+
def __init__(self, sun_coordinator, description, entry_data):
|
|
886
|
+
"""Initialize the Sun sensor."""
|
|
887
|
+
super().__init__(sun_coordinator)
|
|
888
|
+
self.entity_description = description
|
|
889
|
+
self._town_name = entry_data["town_name"]
|
|
890
|
+
self._town_id = entry_data["town_id"]
|
|
891
|
+
self._station_id = entry_data["station_id"]
|
|
892
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
893
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
894
|
+
|
|
895
|
+
_LOGGER.debug(
|
|
896
|
+
"Inicializando sensor: %s, Unique ID: %s",
|
|
897
|
+
self.entity_description.name,
|
|
898
|
+
self._attr_unique_id,
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
@property
|
|
902
|
+
def native_value(self):
|
|
903
|
+
"""Return the sunrise or sunset time as a datetime."""
|
|
904
|
+
if self.entity_description.key in {SUNRISE, SUNSET}:
|
|
905
|
+
return self.coordinator.data.get(self.entity_description.key)
|
|
906
|
+
return None
|
|
907
|
+
|
|
908
|
+
@property
|
|
909
|
+
def extra_state_attributes(self):
|
|
910
|
+
"""Return additional attributes for the sensor."""
|
|
911
|
+
attributes = super().extra_state_attributes or {}
|
|
912
|
+
if self.entity_description.key in {SUNRISE, SUNSET}:
|
|
913
|
+
dt = self.coordinator.data.get(self.entity_description.key)
|
|
914
|
+
attributes["friendly_time"] = dt.strftime("%H:%M") if dt else None
|
|
915
|
+
return attributes
|
|
916
|
+
|
|
917
|
+
@property
|
|
918
|
+
def device_info(self) -> DeviceInfo:
|
|
919
|
+
"""Return the device info."""
|
|
920
|
+
return DeviceInfo(
|
|
921
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
922
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
754
923
|
manufacturer="Meteocat",
|
|
755
924
|
model="Meteocat API",
|
|
756
925
|
)
|
|
757
926
|
|
|
758
927
|
class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity):
|
|
759
928
|
"""Representation of a Meteocat sensor."""
|
|
760
|
-
|
|
761
929
|
CODE_MAPPING = {
|
|
762
930
|
WIND_SPEED: WIND_SPEED_CODE,
|
|
763
931
|
WIND_DIRECTION: WIND_DIRECTION_CODE,
|
|
@@ -766,13 +934,11 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
766
934
|
PRESSURE: PRESSURE_CODE,
|
|
767
935
|
PRECIPITATION: PRECIPITATION_CODE,
|
|
768
936
|
SOLAR_GLOBAL_IRRADIANCE: SOLAR_GLOBAL_IRRADIANCE_CODE,
|
|
769
|
-
# UV_INDEX: UV_INDEX_CODE,
|
|
770
937
|
MAX_TEMPERATURE: MAX_TEMPERATURE_CODE,
|
|
771
938
|
MIN_TEMPERATURE: MIN_TEMPERATURE_CODE,
|
|
772
939
|
WIND_GUST: WIND_GUST_CODE,
|
|
773
940
|
}
|
|
774
|
-
|
|
775
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
941
|
+
_attr_has_entity_name = True
|
|
776
942
|
|
|
777
943
|
def __init__(self, sensor_coordinator, description, entry_data):
|
|
778
944
|
"""Initialize the sensor."""
|
|
@@ -783,14 +949,8 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
783
949
|
self._town_id = entry_data["town_id"]
|
|
784
950
|
self._station_name = entry_data["station_name"]
|
|
785
951
|
self._station_id = entry_data["station_id"]
|
|
786
|
-
|
|
787
|
-
# Unique ID for the entity
|
|
788
952
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
789
|
-
|
|
790
|
-
# Asigna entity_category desde description (si está definido)
|
|
791
953
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
792
|
-
|
|
793
|
-
# Log para depuración
|
|
794
954
|
_LOGGER.debug(
|
|
795
955
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
796
956
|
self.entity_description.name,
|
|
@@ -800,16 +960,11 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
800
960
|
@property
|
|
801
961
|
def native_value(self):
|
|
802
962
|
"""Return the state of the sensor."""
|
|
803
|
-
# Información dinámica
|
|
804
963
|
if self.entity_description.key == FEELS_LIKE:
|
|
805
964
|
stations = self.coordinator.data or []
|
|
806
|
-
|
|
807
|
-
# Variables necesarias
|
|
808
965
|
temperature = None
|
|
809
966
|
humidity = None
|
|
810
967
|
wind_speed = None
|
|
811
|
-
|
|
812
|
-
# Obtener valores de las variables
|
|
813
968
|
for station in stations:
|
|
814
969
|
variables = station.get("variables", [])
|
|
815
970
|
for var in variables:
|
|
@@ -818,25 +973,19 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
818
973
|
if not lectures:
|
|
819
974
|
continue
|
|
820
975
|
latest_reading = lectures[-1].get("valor")
|
|
821
|
-
|
|
822
976
|
if code == TEMPERATURE_CODE:
|
|
823
977
|
temperature = float(latest_reading)
|
|
824
978
|
elif code == HUMIDITY_CODE:
|
|
825
979
|
humidity = float(latest_reading)
|
|
826
980
|
elif code == WIND_SPEED_CODE:
|
|
827
981
|
wind_speed = float(latest_reading)
|
|
828
|
-
|
|
829
|
-
# Verificar que todas las variables necesarias están presentes
|
|
830
982
|
if temperature is not None and humidity is not None and wind_speed is not None:
|
|
831
|
-
# Cálculo del windchill
|
|
832
983
|
windchill = (
|
|
833
984
|
13.1267 +
|
|
834
985
|
0.6215 * temperature -
|
|
835
986
|
11.37 * (wind_speed ** 0.16) +
|
|
836
987
|
0.3965 * temperature * (wind_speed ** 0.16)
|
|
837
988
|
)
|
|
838
|
-
|
|
839
|
-
# Cálculo del heat_index
|
|
840
989
|
heat_index = (
|
|
841
990
|
-8.78469476 +
|
|
842
991
|
1.61139411 * temperature +
|
|
@@ -848,8 +997,6 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
848
997
|
0.00072546 * temperature * (humidity ** 2) -
|
|
849
998
|
0.000003582 * (temperature ** 2) * (humidity ** 2)
|
|
850
999
|
)
|
|
851
|
-
|
|
852
|
-
# Lógica de selección
|
|
853
1000
|
if -50 <= temperature <= 10 and wind_speed > 4.8:
|
|
854
1001
|
_LOGGER.debug(f"Sensación térmica por frío, calculada según la fórmula de Wind Chill: {windchill} ºC")
|
|
855
1002
|
return round(windchill, 1)
|
|
@@ -861,50 +1008,36 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
861
1008
|
return round(temperature, 1)
|
|
862
1009
|
|
|
863
1010
|
sensor_code = self.CODE_MAPPING.get(self.entity_description.key)
|
|
864
|
-
|
|
865
1011
|
if sensor_code is not None:
|
|
866
|
-
# Accedemos a las estaciones en el JSON recibido
|
|
867
1012
|
stations = self.coordinator.data or []
|
|
868
1013
|
for station in stations:
|
|
869
1014
|
variables = station.get("variables", [])
|
|
870
|
-
|
|
871
|
-
# Filtramos por código
|
|
872
1015
|
variable_data = next(
|
|
873
1016
|
(var for var in variables if var.get("codi") == sensor_code),
|
|
874
1017
|
None,
|
|
875
1018
|
)
|
|
876
|
-
|
|
877
1019
|
if variable_data:
|
|
878
|
-
# Obtenemos la última lectura
|
|
879
1020
|
lectures = variable_data.get("lectures", [])
|
|
880
1021
|
if lectures:
|
|
881
1022
|
latest_reading = lectures[-1]
|
|
882
1023
|
value = latest_reading.get("valor")
|
|
883
|
-
|
|
884
1024
|
return value
|
|
885
|
-
|
|
886
|
-
# Para el sensor WIND_DIRECTION_CARDINAL, convertir grados a dirección cardinal
|
|
1025
|
+
|
|
887
1026
|
if self.entity_description.key == WIND_DIRECTION_CARDINAL:
|
|
888
1027
|
stations = self.coordinator.data or []
|
|
889
1028
|
for station in stations:
|
|
890
1029
|
variables = station.get("variables", [])
|
|
891
|
-
|
|
892
|
-
# Filtramos por código
|
|
893
1030
|
variable_data = next(
|
|
894
1031
|
(var for var in variables if var.get("codi") == WIND_DIRECTION_CODE),
|
|
895
1032
|
None,
|
|
896
1033
|
)
|
|
897
|
-
|
|
898
1034
|
if variable_data:
|
|
899
|
-
# Obtenemos la última lectura
|
|
900
1035
|
lectures = variable_data.get("lectures", [])
|
|
901
1036
|
if lectures:
|
|
902
1037
|
latest_reading = lectures[-1]
|
|
903
1038
|
value = latest_reading.get("valor")
|
|
904
|
-
|
|
905
1039
|
return self._convert_degrees_to_cardinal(value)
|
|
906
|
-
|
|
907
|
-
# Lógica específica para el sensor de timestamp
|
|
1040
|
+
|
|
908
1041
|
if self.entity_description.key == STATION_TIMESTAMP:
|
|
909
1042
|
stations = self.coordinator.data or []
|
|
910
1043
|
for station in stations:
|
|
@@ -912,42 +1045,30 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
912
1045
|
for variable in variables:
|
|
913
1046
|
lectures = variable.get("lectures", [])
|
|
914
1047
|
if lectures:
|
|
915
|
-
# Obtenemos el campo `data` de la última lectura
|
|
916
1048
|
latest_reading = lectures[-1]
|
|
917
1049
|
raw_timestamp = latest_reading.get("data")
|
|
918
|
-
|
|
919
1050
|
if raw_timestamp:
|
|
920
|
-
# Convertir el timestamp a un objeto datetime
|
|
921
1051
|
try:
|
|
922
|
-
# Convertimos raw_timestamp a hora local
|
|
923
1052
|
local_time = convert_to_local_time(raw_timestamp)
|
|
924
1053
|
_LOGGER.debug("Hora UTC: %s convertida a hora local: %s", raw_timestamp, local_time)
|
|
925
1054
|
return local_time
|
|
926
1055
|
except ValueError:
|
|
927
|
-
# Manejo de errores si el formato no es válido
|
|
928
1056
|
_LOGGER.error(f"Error al convertir el timestamp '{raw_timestamp}' a hora local.")
|
|
929
1057
|
return None
|
|
930
1058
|
|
|
931
|
-
# Nuevo sensor para la precipitación acumulada
|
|
932
1059
|
if self.entity_description.key == PRECIPITATION_ACCUMULATED:
|
|
933
1060
|
stations = self.coordinator.data or []
|
|
934
|
-
total_precipitation = 0.0
|
|
935
|
-
|
|
1061
|
+
total_precipitation = 0.0
|
|
936
1062
|
for station in stations:
|
|
937
1063
|
variables = station.get("variables", [])
|
|
938
|
-
|
|
939
|
-
# Filtramos por código de precipitación
|
|
940
1064
|
variable_data = next(
|
|
941
1065
|
(var for var in variables if var.get("codi") == PRECIPITATION_CODE),
|
|
942
1066
|
None,
|
|
943
1067
|
)
|
|
944
|
-
|
|
945
1068
|
if variable_data:
|
|
946
|
-
# Sumamos las lecturas de precipitación
|
|
947
1069
|
lectures = variable_data.get("lectures", [])
|
|
948
1070
|
for lecture in lectures:
|
|
949
|
-
total_precipitation += float(lecture.get("valor", 0.0))
|
|
950
|
-
|
|
1071
|
+
total_precipitation += float(lecture.get("valor", 0.0))
|
|
951
1072
|
_LOGGER.debug(f"Total precipitación acumulada: {total_precipitation} mm")
|
|
952
1073
|
return total_precipitation
|
|
953
1074
|
|
|
@@ -957,11 +1078,10 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
957
1078
|
def _convert_degrees_to_cardinal(degree: float) -> str:
|
|
958
1079
|
"""Convert degrees to cardinal direction."""
|
|
959
1080
|
if not isinstance(degree, (int, float)):
|
|
960
|
-
return "Unknown"
|
|
961
|
-
|
|
1081
|
+
return "Unknown"
|
|
962
1082
|
directions = [
|
|
963
|
-
|
|
964
|
-
|
|
1083
|
+
"north", "north_northeast", "northeast", "east_northeast", "east", "east_southeast", "southeast", "south_southeast",
|
|
1084
|
+
"south", "south_southwest", "southwest", "west_southwest", "west", "west_northwest", "northwest", "north_northwest"
|
|
965
1085
|
]
|
|
966
1086
|
index = round(degree / 22.5) % 16
|
|
967
1087
|
return directions[index]
|
|
@@ -971,31 +1091,24 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
971
1091
|
"""Return the device info."""
|
|
972
1092
|
return DeviceInfo(
|
|
973
1093
|
identifiers={(DOMAIN, self._town_id)},
|
|
974
|
-
name="Meteocat
|
|
1094
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
975
1095
|
manufacturer="Meteocat",
|
|
976
1096
|
model="Meteocat API",
|
|
977
1097
|
)
|
|
978
1098
|
|
|
979
1099
|
class MeteocatTempForecast(CoordinatorEntity[MeteocatTempForecastCoordinator], SensorEntity):
|
|
980
1100
|
"""Representation of a Meteocat Min and Max Temperature sensors."""
|
|
981
|
-
|
|
982
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1101
|
+
_attr_has_entity_name = True
|
|
983
1102
|
|
|
984
1103
|
def __init__(self, temp_forecast_coordinator, description, entry_data):
|
|
985
|
-
"""Initialize the
|
|
1104
|
+
"""Initialize the Min and Max Temperature sensors."""
|
|
986
1105
|
super().__init__(temp_forecast_coordinator)
|
|
987
1106
|
self.entity_description = description
|
|
988
1107
|
self._town_name = entry_data["town_name"]
|
|
989
1108
|
self._town_id = entry_data["town_id"]
|
|
990
1109
|
self._station_id = entry_data["station_id"]
|
|
991
|
-
|
|
992
|
-
# Unique ID for the entity
|
|
993
1110
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
994
|
-
|
|
995
|
-
# Asigna entity_category desde description (si está definido)
|
|
996
1111
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
997
|
-
|
|
998
|
-
# Log para depuración
|
|
999
1112
|
_LOGGER.debug(
|
|
1000
1113
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
1001
1114
|
self.entity_description.name,
|
|
@@ -1006,7 +1119,6 @@ class MeteocatTempForecast(CoordinatorEntity[MeteocatTempForecastCoordinator], S
|
|
|
1006
1119
|
def native_value(self):
|
|
1007
1120
|
"""Return the Max and Min Temp Forecast value."""
|
|
1008
1121
|
temp_forecast_data = self.coordinator.data or {}
|
|
1009
|
-
|
|
1010
1122
|
if self.entity_description.key == MAX_TEMPERATURE_FORECAST:
|
|
1011
1123
|
return temp_forecast_data.get("max_temp_forecast", None)
|
|
1012
1124
|
if self.entity_description.key == MIN_TEMPERATURE_FORECAST:
|
|
@@ -1018,15 +1130,14 @@ class MeteocatTempForecast(CoordinatorEntity[MeteocatTempForecastCoordinator], S
|
|
|
1018
1130
|
"""Return the device info."""
|
|
1019
1131
|
return DeviceInfo(
|
|
1020
1132
|
identifiers={(DOMAIN, self._town_id)},
|
|
1021
|
-
name="Meteocat
|
|
1133
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1022
1134
|
manufacturer="Meteocat",
|
|
1023
1135
|
model="Meteocat API",
|
|
1024
1136
|
)
|
|
1025
1137
|
|
|
1026
1138
|
class MeteocatPrecipitationProbabilitySensor(CoordinatorEntity[DailyForecastCoordinator], SensorEntity):
|
|
1027
1139
|
"""Representation of a Meteocat precipitation probability sensor."""
|
|
1028
|
-
|
|
1029
|
-
_attr_has_entity_name = True # Enable device-based naming
|
|
1140
|
+
_attr_has_entity_name = True
|
|
1030
1141
|
|
|
1031
1142
|
def __init__(self, daily_forecast_coordinator, description, entry_data):
|
|
1032
1143
|
super().__init__(daily_forecast_coordinator)
|
|
@@ -1034,10 +1145,8 @@ class MeteocatPrecipitationProbabilitySensor(CoordinatorEntity[DailyForecastCoor
|
|
|
1034
1145
|
self._town_name = entry_data["town_name"]
|
|
1035
1146
|
self._town_id = entry_data["town_id"]
|
|
1036
1147
|
self._station_id = entry_data["station_id"]
|
|
1037
|
-
|
|
1038
1148
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
1039
1149
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1040
|
-
|
|
1041
1150
|
_LOGGER.debug(
|
|
1042
1151
|
"Initializing sensor: %s, Unique ID: %s",
|
|
1043
1152
|
self.entity_description.name,
|
|
@@ -1058,14 +1167,13 @@ class MeteocatPrecipitationProbabilitySensor(CoordinatorEntity[DailyForecastCoor
|
|
|
1058
1167
|
def device_info(self) -> DeviceInfo:
|
|
1059
1168
|
return DeviceInfo(
|
|
1060
1169
|
identifiers={(DOMAIN, self._town_id)},
|
|
1061
|
-
name="Meteocat
|
|
1170
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1062
1171
|
manufacturer="Meteocat",
|
|
1063
1172
|
model="Meteocat API",
|
|
1064
1173
|
)
|
|
1065
1174
|
|
|
1066
1175
|
class MeteocatHourlyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordinator], SensorEntity):
|
|
1067
|
-
|
|
1068
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1176
|
+
_attr_has_entity_name = True
|
|
1069
1177
|
|
|
1070
1178
|
def __init__(self, entity_coordinator, description, entry_data):
|
|
1071
1179
|
super().__init__(entity_coordinator)
|
|
@@ -1073,11 +1181,7 @@ class MeteocatHourlyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordin
|
|
|
1073
1181
|
self._town_name = entry_data["town_name"]
|
|
1074
1182
|
self._town_id = entry_data["town_id"]
|
|
1075
1183
|
self._station_id = entry_data["station_id"]
|
|
1076
|
-
|
|
1077
|
-
# Unique ID for the entity
|
|
1078
1184
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_hourly_status"
|
|
1079
|
-
|
|
1080
|
-
# Assign entity_category if defined in the description
|
|
1081
1185
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1082
1186
|
|
|
1083
1187
|
def _get_first_date(self):
|
|
@@ -1101,8 +1205,6 @@ class MeteocatHourlyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordin
|
|
|
1101
1205
|
f"hora de contacto a la API >= {DEFAULT_VALIDITY_HOURS}, "
|
|
1102
1206
|
f"minutos de contacto a la API >= {DEFAULT_VALIDITY_MINUTES}."
|
|
1103
1207
|
)
|
|
1104
|
-
|
|
1105
|
-
# Validar fecha y hora según la lógica del coordinador
|
|
1106
1208
|
if days_difference > DEFAULT_VALIDITY_DAYS and current_time >= time(DEFAULT_VALIDITY_HOURS, DEFAULT_VALIDITY_MINUTES):
|
|
1107
1209
|
return "obsolete"
|
|
1108
1210
|
return "updated"
|
|
@@ -1121,14 +1223,13 @@ class MeteocatHourlyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordin
|
|
|
1121
1223
|
"""Return the device info."""
|
|
1122
1224
|
return DeviceInfo(
|
|
1123
1225
|
identifiers={(DOMAIN, self._town_id)},
|
|
1124
|
-
name="Meteocat
|
|
1226
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1125
1227
|
manufacturer="Meteocat",
|
|
1126
1228
|
model="Meteocat API",
|
|
1127
1229
|
)
|
|
1128
1230
|
|
|
1129
1231
|
class MeteocatDailyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordinator], SensorEntity):
|
|
1130
|
-
|
|
1131
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1232
|
+
_attr_has_entity_name = True
|
|
1132
1233
|
|
|
1133
1234
|
def __init__(self, entity_coordinator, description, entry_data):
|
|
1134
1235
|
super().__init__(entity_coordinator)
|
|
@@ -1136,11 +1237,7 @@ class MeteocatDailyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordina
|
|
|
1136
1237
|
self._town_name = entry_data["town_name"]
|
|
1137
1238
|
self._town_id = entry_data["town_id"]
|
|
1138
1239
|
self._station_id = entry_data["station_id"]
|
|
1139
|
-
|
|
1140
|
-
# Unique ID for the entity
|
|
1141
1240
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_daily_status"
|
|
1142
|
-
|
|
1143
|
-
# Assign entity_category if defined in the description
|
|
1144
1241
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1145
1242
|
|
|
1146
1243
|
def _get_first_date(self):
|
|
@@ -1164,8 +1261,6 @@ class MeteocatDailyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordina
|
|
|
1164
1261
|
f"hora de contacto a la API >= {DEFAULT_VALIDITY_HOURS}, "
|
|
1165
1262
|
f"minutos de contacto a la API >= {DEFAULT_VALIDITY_MINUTES}."
|
|
1166
1263
|
)
|
|
1167
|
-
|
|
1168
|
-
# Validar fecha y hora según la lógica del coordinador
|
|
1169
1264
|
if days_difference > DEFAULT_VALIDITY_DAYS and current_time >= time(DEFAULT_VALIDITY_HOURS, DEFAULT_VALIDITY_MINUTES):
|
|
1170
1265
|
return "obsolete"
|
|
1171
1266
|
return "updated"
|
|
@@ -1184,14 +1279,13 @@ class MeteocatDailyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordina
|
|
|
1184
1279
|
"""Return the device info."""
|
|
1185
1280
|
return DeviceInfo(
|
|
1186
1281
|
identifiers={(DOMAIN, self._town_id)},
|
|
1187
|
-
name="Meteocat
|
|
1282
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1188
1283
|
manufacturer="Meteocat",
|
|
1189
1284
|
model="Meteocat API",
|
|
1190
1285
|
)
|
|
1191
1286
|
|
|
1192
1287
|
class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorEntity):
|
|
1193
|
-
|
|
1194
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1288
|
+
_attr_has_entity_name = True
|
|
1195
1289
|
|
|
1196
1290
|
def __init__(self, uvi_coordinator, description, entry_data):
|
|
1197
1291
|
super().__init__(uvi_coordinator)
|
|
@@ -1199,11 +1293,7 @@ class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorE
|
|
|
1199
1293
|
self._town_name = entry_data["town_name"]
|
|
1200
1294
|
self._town_id = entry_data["town_id"]
|
|
1201
1295
|
self._station_id = entry_data["station_id"]
|
|
1202
|
-
|
|
1203
|
-
# Unique ID for the entity
|
|
1204
1296
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_uvi_status"
|
|
1205
|
-
|
|
1206
|
-
# Assign entity_category if defined in the description
|
|
1207
1297
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1208
1298
|
|
|
1209
1299
|
def _get_first_date(self):
|
|
@@ -1226,8 +1316,6 @@ class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorE
|
|
|
1226
1316
|
f"hora de contacto a la API >= {DEFAULT_VALIDITY_HOURS}, "
|
|
1227
1317
|
f"minutos de contacto a la API >= {DEFAULT_VALIDITY_MINUTES}."
|
|
1228
1318
|
)
|
|
1229
|
-
|
|
1230
|
-
# Validar fecha y hora según la lógica del coordinador
|
|
1231
1319
|
if days_difference > DEFAULT_VALIDITY_DAYS and current_time >= time(DEFAULT_VALIDITY_HOURS, DEFAULT_VALIDITY_MINUTES):
|
|
1232
1320
|
return "obsolete"
|
|
1233
1321
|
return "updated"
|
|
@@ -1246,13 +1334,13 @@ class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorE
|
|
|
1246
1334
|
"""Return the device info."""
|
|
1247
1335
|
return DeviceInfo(
|
|
1248
1336
|
identifiers={(DOMAIN, self._town_id)},
|
|
1249
|
-
name="Meteocat
|
|
1337
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1250
1338
|
manufacturer="Meteocat",
|
|
1251
1339
|
model="Meteocat API",
|
|
1252
1340
|
)
|
|
1253
1341
|
|
|
1254
1342
|
class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], SensorEntity):
|
|
1255
|
-
_attr_has_entity_name = True
|
|
1343
|
+
_attr_has_entity_name = True
|
|
1256
1344
|
|
|
1257
1345
|
def __init__(self, alerts_coordinator, description, entry_data):
|
|
1258
1346
|
super().__init__(alerts_coordinator)
|
|
@@ -1262,11 +1350,7 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1262
1350
|
self._station_id = entry_data["station_id"]
|
|
1263
1351
|
self._region_id = entry_data["region_id"]
|
|
1264
1352
|
self._limit_prediccio = entry_data["limit_prediccio"]
|
|
1265
|
-
|
|
1266
|
-
# Unique ID for the entity
|
|
1267
1353
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_alert_status"
|
|
1268
|
-
|
|
1269
|
-
# Assign entity_category if defined in the description
|
|
1270
1354
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1271
1355
|
|
|
1272
1356
|
def _get_data_update(self):
|
|
@@ -1289,7 +1373,6 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1289
1373
|
multiplier = ALERT_VALIDITY_MULTIPLIER_500
|
|
1290
1374
|
else:
|
|
1291
1375
|
multiplier = ALERT_VALIDITY_MULTIPLIER_DEFAULT
|
|
1292
|
-
|
|
1293
1376
|
return timedelta(minutes=DEFAULT_ALERT_VALIDITY_TIME * multiplier)
|
|
1294
1377
|
|
|
1295
1378
|
@property
|
|
@@ -1298,11 +1381,8 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1298
1381
|
data_update = self._get_data_update()
|
|
1299
1382
|
if not data_update:
|
|
1300
1383
|
return "unknown"
|
|
1301
|
-
|
|
1302
1384
|
current_time = datetime.now(ZoneInfo("UTC"))
|
|
1303
1385
|
validity_duration = self._get_validity_duration()
|
|
1304
|
-
|
|
1305
|
-
# Comprobar si el archivo de alertas está obsoleto
|
|
1306
1386
|
if current_time - data_update >= validity_duration:
|
|
1307
1387
|
return "obsolete"
|
|
1308
1388
|
return "updated"
|
|
@@ -1328,7 +1408,6 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1328
1408
|
|
|
1329
1409
|
class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinator], SensorEntity):
|
|
1330
1410
|
"""Sensor dinámico que muestra el estado de las alertas por región."""
|
|
1331
|
-
|
|
1332
1411
|
METEOR_MAPPING = {
|
|
1333
1412
|
"Temps violent": "violent_weather",
|
|
1334
1413
|
"Intensitat de pluja": "rain_intensity",
|
|
@@ -1340,8 +1419,7 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1340
1419
|
"Calor": "heat",
|
|
1341
1420
|
"Calor nocturna": "night_heat",
|
|
1342
1421
|
}
|
|
1343
|
-
|
|
1344
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1422
|
+
_attr_has_entity_name = True
|
|
1345
1423
|
|
|
1346
1424
|
def __init__(self, alerts_region_coordinator, description, entry_data):
|
|
1347
1425
|
super().__init__(alerts_region_coordinator)
|
|
@@ -1350,11 +1428,7 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1350
1428
|
self._town_id = entry_data["town_id"]
|
|
1351
1429
|
self._station_id = entry_data["station_id"]
|
|
1352
1430
|
self._region_id = entry_data["region_id"]
|
|
1353
|
-
|
|
1354
|
-
# Unique ID for the entity
|
|
1355
1431
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_alerts"
|
|
1356
|
-
|
|
1357
|
-
# Assign entity_category if defined in the description
|
|
1358
1432
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1359
1433
|
|
|
1360
1434
|
def _map_meteor_case_insensitive(self, meteor: str) -> str:
|
|
@@ -1373,8 +1447,6 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1373
1447
|
def extra_state_attributes(self):
|
|
1374
1448
|
"""Devuelve los atributos extra del sensor con los nombres traducidos."""
|
|
1375
1449
|
meteor_details = self.coordinator.data.get("detalles", {}).get("meteor", {})
|
|
1376
|
-
|
|
1377
|
-
# Convertimos las claves al formato deseado usando el mapping
|
|
1378
1450
|
attributes = {}
|
|
1379
1451
|
for i, meteor in enumerate(meteor_details.keys()):
|
|
1380
1452
|
mapped_name = self._map_meteor_case_insensitive(meteor)
|
|
@@ -1382,8 +1454,7 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1382
1454
|
_LOGGER.warning("Meteor desconocido sin mapeo: '%s'. Añadirlo a 'METEOR_MAPPING' del coordinador 'MeteocatAlertRegionSensor' si es necesario.", meteor)
|
|
1383
1455
|
mapped_name = "unknown"
|
|
1384
1456
|
attributes[f"alert_{i+1}"] = mapped_name
|
|
1385
|
-
|
|
1386
|
-
_LOGGER.info("Atributos traducidos del sensor: %s", attributes)
|
|
1457
|
+
_LOGGER.debug("Atributos traducidos del sensor: %s", attributes)
|
|
1387
1458
|
return attributes
|
|
1388
1459
|
|
|
1389
1460
|
@property
|
|
@@ -1391,7 +1462,7 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1391
1462
|
"""Devuelve la información del dispositivo."""
|
|
1392
1463
|
return DeviceInfo(
|
|
1393
1464
|
identifiers={(DOMAIN, self._town_id)},
|
|
1394
|
-
name="Meteocat
|
|
1465
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1395
1466
|
manufacturer="Meteocat",
|
|
1396
1467
|
model="Meteocat API",
|
|
1397
1468
|
)
|
|
@@ -1408,12 +1479,10 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1408
1479
|
ALERT_WARM_NIGHT: "Calor nocturna",
|
|
1409
1480
|
ALERT_SNOW: "Neu acumulada en 24 hores",
|
|
1410
1481
|
}
|
|
1411
|
-
|
|
1412
1482
|
STATE_MAPPING = {
|
|
1413
1483
|
"Obert": "opened",
|
|
1414
1484
|
"Tancat": "closed",
|
|
1415
1485
|
}
|
|
1416
|
-
|
|
1417
1486
|
UMBRAL_MAPPING = {
|
|
1418
1487
|
"Ratxes de vent > 25 m/s": "wind_gusts_25",
|
|
1419
1488
|
"Esclafits": "microburst",
|
|
@@ -1447,8 +1516,7 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1447
1516
|
"gruix > 2 cm a cotes superiors a 300 metres fins a 600 metres": "thickness_2_at_300",
|
|
1448
1517
|
"gruix ≥ 0 cm a cotes inferiors a 300 metres": "thickness_0_at_300",
|
|
1449
1518
|
}
|
|
1450
|
-
|
|
1451
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1519
|
+
_attr_has_entity_name = True
|
|
1452
1520
|
|
|
1453
1521
|
def __init__(self, alerts_region_coordinator, description, entry_data):
|
|
1454
1522
|
super().__init__(alerts_region_coordinator)
|
|
@@ -1457,14 +1525,8 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1457
1525
|
self._town_id = entry_data["town_id"]
|
|
1458
1526
|
self._station_id = entry_data["station_id"]
|
|
1459
1527
|
self._region_id = entry_data["region_id"]
|
|
1460
|
-
|
|
1461
|
-
# Unique ID for the entity
|
|
1462
1528
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
1463
|
-
|
|
1464
|
-
# Assign entity_category if defined in the description
|
|
1465
1529
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1466
|
-
|
|
1467
|
-
# Log para depuración
|
|
1468
1530
|
_LOGGER.debug(
|
|
1469
1531
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
1470
1532
|
self.entity_description.name,
|
|
@@ -1494,10 +1556,7 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1494
1556
|
meteor_type = self.METEOR_MAPPING.get(self.entity_description.key)
|
|
1495
1557
|
if not meteor_type:
|
|
1496
1558
|
return "Desconocido"
|
|
1497
|
-
|
|
1498
1559
|
meteor_data = self._get_meteor_data_case_insensitive(meteor_type)
|
|
1499
|
-
|
|
1500
|
-
# Convertir estado para translation_key
|
|
1501
1560
|
estado_original = meteor_data.get("estado", "Tancat")
|
|
1502
1561
|
return self.STATE_MAPPING.get(estado_original, "unknown")
|
|
1503
1562
|
|
|
@@ -1512,22 +1571,17 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1512
1571
|
self.coordinator.data.get("detalles", {}).get("meteor", {}).keys(),
|
|
1513
1572
|
)
|
|
1514
1573
|
return "unknown"
|
|
1515
|
-
|
|
1516
1574
|
meteor_data = self._get_meteor_data_case_insensitive(meteor_type)
|
|
1517
1575
|
if not meteor_data:
|
|
1518
1576
|
return {}
|
|
1519
|
-
|
|
1520
|
-
# Convertir umbral para translation_key
|
|
1521
1577
|
umbral_original = meteor_data.get("umbral")
|
|
1522
1578
|
umbral_convertido = self._get_umbral_case_insensitive(umbral_original)
|
|
1523
|
-
|
|
1524
1579
|
if umbral_convertido == "unknown" and umbral_original is not None:
|
|
1525
1580
|
_LOGGER.warning(
|
|
1526
1581
|
"Umbral desconocido para sensor %s: '%s'. Añadirlo a 'UMBRAL_MAPPING' del coordinador 'MeteocatAlertMeteorSensor' si es necesario.",
|
|
1527
1582
|
self.entity_description.key,
|
|
1528
1583
|
umbral_original
|
|
1529
1584
|
)
|
|
1530
|
-
|
|
1531
1585
|
return {
|
|
1532
1586
|
"inicio": meteor_data.get("inicio"),
|
|
1533
1587
|
"fin": meteor_data.get("fin"),
|
|
@@ -1544,13 +1598,13 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1544
1598
|
"""Devuelve la información del dispositivo."""
|
|
1545
1599
|
return DeviceInfo(
|
|
1546
1600
|
identifiers={(DOMAIN, self._town_id)},
|
|
1547
|
-
name="Meteocat
|
|
1601
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1548
1602
|
manufacturer="Meteocat",
|
|
1549
1603
|
model="Meteocat API",
|
|
1550
1604
|
)
|
|
1551
1605
|
|
|
1552
1606
|
class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], SensorEntity):
|
|
1553
|
-
_attr_has_entity_name = True
|
|
1607
|
+
_attr_has_entity_name = True
|
|
1554
1608
|
|
|
1555
1609
|
def __init__(self, quotes_coordinator, description, entry_data):
|
|
1556
1610
|
super().__init__(quotes_coordinator)
|
|
@@ -1558,11 +1612,7 @@ class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], Se
|
|
|
1558
1612
|
self._town_name = entry_data["town_name"]
|
|
1559
1613
|
self._town_id = entry_data["town_id"]
|
|
1560
1614
|
self._station_id = entry_data["station_id"]
|
|
1561
|
-
|
|
1562
|
-
# Unique ID for the entity
|
|
1563
1615
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_quota_status"
|
|
1564
|
-
|
|
1565
|
-
# Assign entity_category if defined in the description
|
|
1566
1616
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1567
1617
|
|
|
1568
1618
|
def _get_data_update(self):
|
|
@@ -1581,13 +1631,9 @@ class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], Se
|
|
|
1581
1631
|
data_update = self._get_data_update()
|
|
1582
1632
|
if not data_update:
|
|
1583
1633
|
return "unknown"
|
|
1584
|
-
|
|
1585
1634
|
current_time = datetime.now(ZoneInfo("UTC"))
|
|
1586
|
-
|
|
1587
|
-
# Comprobar si el archivo de alertas está obsoleto
|
|
1588
1635
|
if current_time - data_update >= timedelta(minutes=DEFAULT_QUOTES_VALIDITY_TIME):
|
|
1589
1636
|
return "obsolete"
|
|
1590
|
-
|
|
1591
1637
|
return "updated"
|
|
1592
1638
|
|
|
1593
1639
|
@property
|
|
@@ -1611,8 +1657,6 @@ class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], Se
|
|
|
1611
1657
|
|
|
1612
1658
|
class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], SensorEntity):
|
|
1613
1659
|
"""Representation of Meteocat Quota sensors."""
|
|
1614
|
-
|
|
1615
|
-
# Mapeo de claves en sensor.py a nombres en quotes.json
|
|
1616
1660
|
QUOTA_MAPPING = {
|
|
1617
1661
|
"quota_xdde": "XDDE",
|
|
1618
1662
|
"quota_prediccio": "Prediccio",
|
|
@@ -1620,15 +1664,12 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1620
1664
|
"quota_xema": "XEMA",
|
|
1621
1665
|
"quota_queries": "Quota",
|
|
1622
1666
|
}
|
|
1623
|
-
|
|
1624
|
-
# Mapeo de periodos para facilitar la traducción del estado
|
|
1625
1667
|
PERIOD_STATE_MAPPING = {
|
|
1626
1668
|
"Setmanal": "weekly",
|
|
1627
1669
|
"Mensual": "monthly",
|
|
1628
1670
|
"Anual": "annual",
|
|
1629
1671
|
}
|
|
1630
|
-
|
|
1631
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1672
|
+
_attr_has_entity_name = True
|
|
1632
1673
|
|
|
1633
1674
|
def __init__(self, quotes_file_coordinator, description, entry_data):
|
|
1634
1675
|
super().__init__(quotes_file_coordinator)
|
|
@@ -1636,28 +1677,20 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1636
1677
|
self._town_name = entry_data["town_name"]
|
|
1637
1678
|
self._town_id = entry_data["town_id"]
|
|
1638
1679
|
self._station_id = entry_data["station_id"]
|
|
1639
|
-
|
|
1640
|
-
# Unique ID for the entity
|
|
1641
1680
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
1642
|
-
|
|
1643
|
-
# Assign entity_category if defined in the description
|
|
1644
1681
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1645
1682
|
|
|
1646
1683
|
def _get_plan_data(self):
|
|
1647
1684
|
"""Encuentra los datos del plan correspondiente al sensor actual."""
|
|
1648
1685
|
if not self.coordinator.data:
|
|
1649
1686
|
return None
|
|
1650
|
-
|
|
1651
1687
|
plan_name = self.QUOTA_MAPPING.get(self.entity_description.key)
|
|
1652
|
-
|
|
1653
1688
|
if not plan_name:
|
|
1654
1689
|
_LOGGER.error(f"No se encontró un mapeo para la clave: {self.entity_description.key}")
|
|
1655
1690
|
return None
|
|
1656
|
-
|
|
1657
1691
|
for plan in self.coordinator.data.get("plans", []):
|
|
1658
1692
|
if plan.get("nom") == plan_name:
|
|
1659
|
-
return plan
|
|
1660
|
-
|
|
1693
|
+
return plan
|
|
1661
1694
|
_LOGGER.warning(f"No se encontró el plan '{plan_name}' en los datos del coordinador.")
|
|
1662
1695
|
return None
|
|
1663
1696
|
|
|
@@ -1665,18 +1698,14 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1665
1698
|
def native_value(self):
|
|
1666
1699
|
"""Devuelve el estado de la cuota: 'ok' si no se ha excedido, 'exceeded' si se ha superado."""
|
|
1667
1700
|
plan = self._get_plan_data()
|
|
1668
|
-
|
|
1669
1701
|
if not plan:
|
|
1670
1702
|
return None
|
|
1671
|
-
|
|
1672
1703
|
max_consultes = plan.get("maxConsultes")
|
|
1673
1704
|
consultes_realitzades = plan.get("consultesRealitzades")
|
|
1674
|
-
|
|
1675
1705
|
if max_consultes is None or consultes_realitzades is None or \
|
|
1676
1706
|
not isinstance(max_consultes, (int, float)) or not isinstance(consultes_realitzades, (int, float)):
|
|
1677
1707
|
_LOGGER.warning(f"Datos inválidos para el plan '{plan.get('nom', 'unknown')}': {plan}")
|
|
1678
1708
|
return None
|
|
1679
|
-
|
|
1680
1709
|
return "ok" if consultes_realitzades <= max_consultes else "exceeded"
|
|
1681
1710
|
|
|
1682
1711
|
@property
|
|
@@ -1684,21 +1713,16 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1684
1713
|
"""Devuelve atributos adicionales del estado del sensor."""
|
|
1685
1714
|
attributes = super().extra_state_attributes or {}
|
|
1686
1715
|
plan = self._get_plan_data()
|
|
1687
|
-
|
|
1688
1716
|
if not plan:
|
|
1689
1717
|
return {}
|
|
1690
|
-
|
|
1691
|
-
# Aplicar el mapeo de periodos
|
|
1692
1718
|
period = plan.get("periode", "desconocido")
|
|
1693
|
-
translated_period = self.PERIOD_STATE_MAPPING.get(period, period)
|
|
1694
|
-
|
|
1719
|
+
translated_period = self.PERIOD_STATE_MAPPING.get(period, period)
|
|
1695
1720
|
attributes.update({
|
|
1696
1721
|
"period": translated_period,
|
|
1697
1722
|
"max_queries": plan.get("maxConsultes"),
|
|
1698
1723
|
"made_queries": plan.get("consultesRealitzades"),
|
|
1699
1724
|
"remain_queries": plan.get("consultesRestants"),
|
|
1700
1725
|
})
|
|
1701
|
-
|
|
1702
1726
|
return attributes
|
|
1703
1727
|
|
|
1704
1728
|
@property
|
|
@@ -1712,7 +1736,7 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1712
1736
|
)
|
|
1713
1737
|
|
|
1714
1738
|
class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinator], SensorEntity):
|
|
1715
|
-
_attr_has_entity_name = True
|
|
1739
|
+
_attr_has_entity_name = True
|
|
1716
1740
|
|
|
1717
1741
|
def __init__(self, lightning_coordinator, description, entry_data):
|
|
1718
1742
|
super().__init__(lightning_coordinator)
|
|
@@ -1720,11 +1744,7 @@ class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinat
|
|
|
1720
1744
|
self._town_name = entry_data["town_name"]
|
|
1721
1745
|
self._town_id = entry_data["town_id"]
|
|
1722
1746
|
self._station_id = entry_data["station_id"]
|
|
1723
|
-
|
|
1724
|
-
# Unique ID for the entity
|
|
1725
1747
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_lightning_status"
|
|
1726
|
-
|
|
1727
|
-
# Assign entity_category if defined in the description
|
|
1728
1748
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1729
1749
|
|
|
1730
1750
|
def _get_data_update(self):
|
|
@@ -1732,8 +1752,8 @@ class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinat
|
|
|
1732
1752
|
data_update = self.coordinator.data.get("actualizado")
|
|
1733
1753
|
if data_update:
|
|
1734
1754
|
try:
|
|
1735
|
-
local_time = datetime.fromisoformat(data_update)
|
|
1736
|
-
return local_time.astimezone(ZoneInfo("UTC"))
|
|
1755
|
+
local_time = datetime.fromisoformat(data_update)
|
|
1756
|
+
return local_time.astimezone(ZoneInfo("UTC"))
|
|
1737
1757
|
except ValueError:
|
|
1738
1758
|
_LOGGER.error("Formato de fecha de actualización inválido: %s", data_update)
|
|
1739
1759
|
return None
|
|
@@ -1752,15 +1772,11 @@ class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinat
|
|
|
1752
1772
|
data_update = self._get_data_update()
|
|
1753
1773
|
if not data_update:
|
|
1754
1774
|
return "unknown"
|
|
1755
|
-
|
|
1756
1775
|
now = datetime.now(timezone.utc).astimezone(TIMEZONE)
|
|
1757
|
-
current_time = now.time()
|
|
1758
|
-
offset = now.utcoffset().total_seconds() / 3600
|
|
1759
|
-
|
|
1760
|
-
# Determinar la hora de validez considerando el offset horario, el horario de verano (+02:00) o invierno (+01:00)
|
|
1776
|
+
current_time = now.time()
|
|
1777
|
+
offset = now.utcoffset().total_seconds() / 3600
|
|
1761
1778
|
validity_start_time = time(int(DEFAULT_LIGHTNING_VALIDITY_HOURS + offset), DEFAULT_LIGHTNING_VALIDITY_MINUTES)
|
|
1762
1779
|
validity_duration = timedelta(minutes=DEFAULT_LIGHTNING_VALIDITY_TIME)
|
|
1763
|
-
|
|
1764
1780
|
return self._determine_status(now, data_update, current_time, validity_start_time, validity_duration)
|
|
1765
1781
|
|
|
1766
1782
|
@property
|
|
@@ -1784,8 +1800,7 @@ class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinat
|
|
|
1784
1800
|
|
|
1785
1801
|
class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator], SensorEntity):
|
|
1786
1802
|
"""Representation of Meteocat Lightning sensors."""
|
|
1787
|
-
|
|
1788
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1803
|
+
_attr_has_entity_name = True
|
|
1789
1804
|
|
|
1790
1805
|
def __init__(self, lightning_file_coordinator, description, entry_data):
|
|
1791
1806
|
super().__init__(lightning_file_coordinator)
|
|
@@ -1794,11 +1809,7 @@ class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator
|
|
|
1794
1809
|
self._town_id = entry_data["town_id"]
|
|
1795
1810
|
self._station_id = entry_data["station_id"]
|
|
1796
1811
|
self._region_id = entry_data["region_id"]
|
|
1797
|
-
|
|
1798
|
-
# Unique ID for the entity
|
|
1799
1812
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
1800
|
-
|
|
1801
|
-
# Assign entity_category if defined in the description
|
|
1802
1813
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1803
1814
|
|
|
1804
1815
|
@property
|
|
@@ -1820,8 +1831,6 @@ class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator
|
|
|
1820
1831
|
data = self.coordinator.data.get("town", {})
|
|
1821
1832
|
else:
|
|
1822
1833
|
return attributes
|
|
1823
|
-
|
|
1824
|
-
# Agregar atributos específicos
|
|
1825
1834
|
attributes.update({
|
|
1826
1835
|
"cloud_cloud": data.get("cc", 0),
|
|
1827
1836
|
"cloud_ground_neg": data.get("cg-", 0),
|
|
@@ -1837,4 +1846,408 @@ class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator
|
|
|
1837
1846
|
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1838
1847
|
manufacturer="Meteocat",
|
|
1839
1848
|
model="Meteocat API",
|
|
1840
|
-
)
|
|
1849
|
+
)
|
|
1850
|
+
|
|
1851
|
+
class MeteocatSunPositionSensor(CoordinatorEntity[MeteocatSunFileCoordinator], SensorEntity):
|
|
1852
|
+
"""Representation of Meteocat Sun position sensor."""
|
|
1853
|
+
_attr_has_entity_name = True
|
|
1854
|
+
entity_description: MeteocatSensorEntityDescription
|
|
1855
|
+
|
|
1856
|
+
def __init__(self, coordinator, description, entry_data):
|
|
1857
|
+
"""Initialize the Sun position sensor."""
|
|
1858
|
+
super().__init__(coordinator)
|
|
1859
|
+
self.entity_description = description
|
|
1860
|
+
self._town_name = entry_data.get(TOWN_NAME)
|
|
1861
|
+
self._town_id = entry_data.get(TOWN_ID)
|
|
1862
|
+
self._station_id = entry_data.get(STATION_ID)
|
|
1863
|
+
self._attr_unique_id = f"{DOMAIN}_{self._town_id}_{description.key}"
|
|
1864
|
+
|
|
1865
|
+
_LOGGER.debug(
|
|
1866
|
+
"Inicializando sensor de posición solar: %s (ID: %s)",
|
|
1867
|
+
description.translation_key, self._attr_unique_id
|
|
1868
|
+
)
|
|
1869
|
+
|
|
1870
|
+
@property
|
|
1871
|
+
def native_value(self) -> str | None:
|
|
1872
|
+
"""Return 'above_horizon' or 'below_horizon'."""
|
|
1873
|
+
return self.coordinator.data.get("sun_horizon_position")
|
|
1874
|
+
|
|
1875
|
+
@property
|
|
1876
|
+
def extra_state_attributes(self) -> dict[str, Any]:
|
|
1877
|
+
"""Return all sun-related attributes as raw values (datetime strings or numbers)."""
|
|
1878
|
+
data = self.coordinator.data
|
|
1879
|
+
attrs = {}
|
|
1880
|
+
|
|
1881
|
+
# === POSICIÓN ACTUAL ===
|
|
1882
|
+
attrs["elevation"] = data.get("sun_elevation")
|
|
1883
|
+
attrs["azimuth"] = data.get("sun_azimuth")
|
|
1884
|
+
attrs["rising"] = data.get("sun_rising")
|
|
1885
|
+
|
|
1886
|
+
# === DURACIÓN DE LUZ DIURNA EN H:M:S (justo antes de daylight_duration) ===
|
|
1887
|
+
daylight_duration = data.get("daylight_duration")
|
|
1888
|
+
if isinstance(daylight_duration, (int, float)) and daylight_duration >= 0:
|
|
1889
|
+
total_seconds = int(round(daylight_duration * 3600)) # Horas → segundos con redondeo
|
|
1890
|
+
hours = total_seconds // 3600
|
|
1891
|
+
minutes = (total_seconds % 3600) // 60
|
|
1892
|
+
seconds = total_seconds % 60
|
|
1893
|
+
attrs["daylight_duration_hms"] = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
1894
|
+
|
|
1895
|
+
# === EVENTOS DEL DÍA (orden garantizado por inserción) ===
|
|
1896
|
+
event_keys = [
|
|
1897
|
+
"daylight_duration",
|
|
1898
|
+
"dawn_astronomical",
|
|
1899
|
+
"dawn_nautical",
|
|
1900
|
+
"dawn_civil",
|
|
1901
|
+
"sunrise",
|
|
1902
|
+
"noon",
|
|
1903
|
+
"sunset",
|
|
1904
|
+
"dusk_civil",
|
|
1905
|
+
"dusk_nautical",
|
|
1906
|
+
"dusk_astronomical",
|
|
1907
|
+
"midnight",
|
|
1908
|
+
]
|
|
1909
|
+
|
|
1910
|
+
for key in event_keys:
|
|
1911
|
+
if key in data:
|
|
1912
|
+
attrs[key] = data[key]
|
|
1913
|
+
|
|
1914
|
+
attrs["last_updated"] = data.get("sun_position_updated")
|
|
1915
|
+
|
|
1916
|
+
return attrs
|
|
1917
|
+
|
|
1918
|
+
@property
|
|
1919
|
+
def device_info(self) -> DeviceInfo:
|
|
1920
|
+
"""Return the device info."""
|
|
1921
|
+
return DeviceInfo(
|
|
1922
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
1923
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1924
|
+
manufacturer="Meteocat",
|
|
1925
|
+
model="Meteocat API",
|
|
1926
|
+
)
|
|
1927
|
+
|
|
1928
|
+
class MeteocatSunSensor(CoordinatorEntity[MeteocatSunFileCoordinator], SensorEntity):
|
|
1929
|
+
"""Representation of Meteocat Sun sensors (sunrise/sunset)."""
|
|
1930
|
+
_attr_has_entity_name = True
|
|
1931
|
+
|
|
1932
|
+
def __init__(self, sun_file_coordinator, description, entry_data):
|
|
1933
|
+
"""Initialize the Sun sensor."""
|
|
1934
|
+
super().__init__(sun_file_coordinator)
|
|
1935
|
+
self.entity_description = description
|
|
1936
|
+
self._town_name = entry_data["town_name"]
|
|
1937
|
+
self._town_id = entry_data["town_id"]
|
|
1938
|
+
self._station_id = entry_data["station_id"]
|
|
1939
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
1940
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1941
|
+
|
|
1942
|
+
_LOGGER.debug(
|
|
1943
|
+
"Inicializando sensor solar: %s, Unique ID: %s",
|
|
1944
|
+
self.entity_description.name,
|
|
1945
|
+
self._attr_unique_id,
|
|
1946
|
+
)
|
|
1947
|
+
|
|
1948
|
+
@property
|
|
1949
|
+
def native_value(self):
|
|
1950
|
+
"""Return the sunrise or sunset time as a datetime."""
|
|
1951
|
+
if self.entity_description.key in {SUNRISE, SUNSET}:
|
|
1952
|
+
time_str = self.coordinator.data.get(self.entity_description.key)
|
|
1953
|
+
if time_str:
|
|
1954
|
+
try:
|
|
1955
|
+
return datetime.fromisoformat(time_str)
|
|
1956
|
+
except ValueError:
|
|
1957
|
+
_LOGGER.error("Formato de fecha inválido para %s: %s", self.entity_description.key, time_str)
|
|
1958
|
+
return None
|
|
1959
|
+
return None
|
|
1960
|
+
|
|
1961
|
+
@property
|
|
1962
|
+
def extra_state_attributes(self):
|
|
1963
|
+
"""Return additional attributes for the sensor."""
|
|
1964
|
+
attributes = super().extra_state_attributes or {}
|
|
1965
|
+
if self.entity_description.key in {SUNRISE, SUNSET}:
|
|
1966
|
+
time_str = self.coordinator.data.get(self.entity_description.key)
|
|
1967
|
+
if time_str:
|
|
1968
|
+
try:
|
|
1969
|
+
dt = datetime.fromisoformat(time_str)
|
|
1970
|
+
attributes["friendly_time"] = dt.strftime("%H:%M")
|
|
1971
|
+
attributes["friendly_date"] = dt.strftime("%Y-%m-%d")
|
|
1972
|
+
|
|
1973
|
+
# Día base en inglés (minúsculas)
|
|
1974
|
+
day_key = dt.strftime("%A").lower()
|
|
1975
|
+
attributes["friendly_day"] = day_key
|
|
1976
|
+
|
|
1977
|
+
except ValueError:
|
|
1978
|
+
attributes["friendly_time"] = None
|
|
1979
|
+
attributes["friendly_date"] = None
|
|
1980
|
+
attributes["friendly_day"] = None
|
|
1981
|
+
else:
|
|
1982
|
+
attributes["friendly_time"] = None
|
|
1983
|
+
attributes["friendly_date"] = None
|
|
1984
|
+
attributes["friendly_day"] = None
|
|
1985
|
+
return attributes
|
|
1986
|
+
|
|
1987
|
+
@property
|
|
1988
|
+
def device_info(self) -> DeviceInfo:
|
|
1989
|
+
"""Return the device info."""
|
|
1990
|
+
return DeviceInfo(
|
|
1991
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
1992
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1993
|
+
manufacturer="Meteocat",
|
|
1994
|
+
model="Meteocat API",
|
|
1995
|
+
)
|
|
1996
|
+
|
|
1997
|
+
class MeteocatSunStatusSensor(CoordinatorEntity[MeteocatSunCoordinator], SensorEntity):
|
|
1998
|
+
"""Representation of Meteocat Sun file status sensor."""
|
|
1999
|
+
_attr_has_entity_name = True
|
|
2000
|
+
|
|
2001
|
+
def __init__(self, sun_coordinator, description, entry_data):
|
|
2002
|
+
"""Initialize the Sun status sensor."""
|
|
2003
|
+
super().__init__(sun_coordinator)
|
|
2004
|
+
self.entity_description = description
|
|
2005
|
+
self._town_name = entry_data["town_name"]
|
|
2006
|
+
self._town_id = entry_data["town_id"]
|
|
2007
|
+
self._station_id = entry_data["station_id"]
|
|
2008
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_sun_status"
|
|
2009
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
2010
|
+
|
|
2011
|
+
_LOGGER.debug(
|
|
2012
|
+
"Inicializando sensor: %s, Unique ID: %s",
|
|
2013
|
+
self.entity_description.name,
|
|
2014
|
+
self._attr_unique_id,
|
|
2015
|
+
)
|
|
2016
|
+
|
|
2017
|
+
def _get_data_update(self):
|
|
2018
|
+
"""Obtain the update date from the coordinator and convert to UTC."""
|
|
2019
|
+
data_update = self.coordinator.data.get("actualitzat", {}).get("dataUpdate")
|
|
2020
|
+
if data_update:
|
|
2021
|
+
try:
|
|
2022
|
+
local_time = datetime.fromisoformat(data_update)
|
|
2023
|
+
return local_time.astimezone(ZoneInfo("UTC"))
|
|
2024
|
+
except ValueError:
|
|
2025
|
+
_LOGGER.error("Formato de fecha de actualización inválido: %s", data_update)
|
|
2026
|
+
return None
|
|
2027
|
+
|
|
2028
|
+
@property
|
|
2029
|
+
def native_value(self):
|
|
2030
|
+
"""Return the status of the sun file based on the update date."""
|
|
2031
|
+
data_update = self._get_data_update()
|
|
2032
|
+
if not data_update:
|
|
2033
|
+
return "unknown"
|
|
2034
|
+
now = datetime.now(timezone.utc).astimezone(TIMEZONE)
|
|
2035
|
+
if (now - data_update) > timedelta(days=1):
|
|
2036
|
+
return "obsolete"
|
|
2037
|
+
return "updated"
|
|
2038
|
+
|
|
2039
|
+
@property
|
|
2040
|
+
def extra_state_attributes(self):
|
|
2041
|
+
"""Return additional attributes for the sensor."""
|
|
2042
|
+
attributes = super().extra_state_attributes or {}
|
|
2043
|
+
data_update = self._get_data_update()
|
|
2044
|
+
if data_update:
|
|
2045
|
+
attributes["update_date"] = data_update.isoformat()
|
|
2046
|
+
return attributes
|
|
2047
|
+
|
|
2048
|
+
@property
|
|
2049
|
+
def device_info(self) -> DeviceInfo:
|
|
2050
|
+
"""Return the device info."""
|
|
2051
|
+
return DeviceInfo(
|
|
2052
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
2053
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
2054
|
+
manufacturer="Meteocat",
|
|
2055
|
+
model="Meteocat API",
|
|
2056
|
+
)
|
|
2057
|
+
|
|
2058
|
+
class MeteocatMoonSensor(CoordinatorEntity[MeteocatMoonFileCoordinator], SensorEntity):
|
|
2059
|
+
"""Representation of Meteocat Moon sensor (moon phase)."""
|
|
2060
|
+
_attr_has_entity_name = True
|
|
2061
|
+
|
|
2062
|
+
def __init__(self, moon_file_coordinator, description, entry_data):
|
|
2063
|
+
"""Initialize the Moon sensor."""
|
|
2064
|
+
super().__init__(moon_file_coordinator)
|
|
2065
|
+
self.entity_description = description
|
|
2066
|
+
self._town_name = entry_data["town_name"]
|
|
2067
|
+
self._town_id = entry_data["town_id"]
|
|
2068
|
+
self._station_id = entry_data["station_id"]
|
|
2069
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
2070
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
2071
|
+
|
|
2072
|
+
_LOGGER.debug(
|
|
2073
|
+
"Inicializando sensor: %s, Unique ID: %s",
|
|
2074
|
+
self.entity_description.name,
|
|
2075
|
+
self._attr_unique_id,
|
|
2076
|
+
)
|
|
2077
|
+
|
|
2078
|
+
@property
|
|
2079
|
+
def native_value(self):
|
|
2080
|
+
"""Return the moon phase name as the state."""
|
|
2081
|
+
return self.coordinator.data.get("moon_phase_name")
|
|
2082
|
+
|
|
2083
|
+
@property
|
|
2084
|
+
def extra_state_attributes(self):
|
|
2085
|
+
"""Return additional attributes for the sensor."""
|
|
2086
|
+
attributes = super().extra_state_attributes or {}
|
|
2087
|
+
attributes["moon_day"] = self.coordinator.data.get("moon_day")
|
|
2088
|
+
attributes["moon_phase_value"] = self.coordinator.data.get("moon_phase")
|
|
2089
|
+
attributes["illuminated_percentage"] = self.coordinator.data.get("illuminated_percentage")
|
|
2090
|
+
attributes["moon_distance"] = self.coordinator.data.get("moon_distance")
|
|
2091
|
+
attributes["moon_angular_diameter"] = self.coordinator.data.get("moon_angular_diameter")
|
|
2092
|
+
attributes["lunation"] = self.coordinator.data.get("lunation")
|
|
2093
|
+
attributes["lunation_duration"] = self.coordinator.data.get("lunation_duration")
|
|
2094
|
+
attributes["last_updated"] = self.coordinator.data.get("last_lunar_update_date")
|
|
2095
|
+
return attributes
|
|
2096
|
+
|
|
2097
|
+
@property
|
|
2098
|
+
def icon(self):
|
|
2099
|
+
"""Return the icon based on the moon phase."""
|
|
2100
|
+
phase = self.coordinator.data.get("moon_phase_name")
|
|
2101
|
+
icon_map = {
|
|
2102
|
+
"new_moon": "mdi:moon-new",
|
|
2103
|
+
"waxing_crescent": "mdi:moon-waxing-crescent",
|
|
2104
|
+
"first_quarter": "mdi:moon-first-quarter",
|
|
2105
|
+
"waxing_gibbous": "mdi:moon-waxing-gibbous",
|
|
2106
|
+
"full_moon": "mdi:moon-full",
|
|
2107
|
+
"waning_gibbous": "mdi:moon-waning-gibbous",
|
|
2108
|
+
"last_quarter": "mdi:moon-last-quarter",
|
|
2109
|
+
"waning_crescent": "mdi:moon-waning-crescent",
|
|
2110
|
+
"unknown": "mdi:moon",
|
|
2111
|
+
}
|
|
2112
|
+
return icon_map.get(phase, "mdi:moon")
|
|
2113
|
+
|
|
2114
|
+
@property
|
|
2115
|
+
def device_info(self) -> DeviceInfo:
|
|
2116
|
+
"""Return the device info."""
|
|
2117
|
+
return DeviceInfo(
|
|
2118
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
2119
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
2120
|
+
manufacturer="Meteocat",
|
|
2121
|
+
model="Meteocat API",
|
|
2122
|
+
)
|
|
2123
|
+
|
|
2124
|
+
class MeteocatMoonStatusSensor(CoordinatorEntity[MeteocatMoonCoordinator], SensorEntity):
|
|
2125
|
+
"""Representation of Meteocat Moon file status sensor."""
|
|
2126
|
+
_attr_has_entity_name = True
|
|
2127
|
+
|
|
2128
|
+
def __init__(self, moon_coordinator, description, entry_data):
|
|
2129
|
+
"""Initialize the Moon status sensor."""
|
|
2130
|
+
super().__init__(moon_coordinator)
|
|
2131
|
+
self.entity_description = description
|
|
2132
|
+
self._town_name = entry_data["town_name"]
|
|
2133
|
+
self._town_id = entry_data["town_id"]
|
|
2134
|
+
self._station_id = entry_data["station_id"]
|
|
2135
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_moon_status"
|
|
2136
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
2137
|
+
|
|
2138
|
+
_LOGGER.debug(
|
|
2139
|
+
"Inicializando sensor: %s, Unique ID: %s",
|
|
2140
|
+
self.entity_description.name,
|
|
2141
|
+
self._attr_unique_id,
|
|
2142
|
+
)
|
|
2143
|
+
|
|
2144
|
+
def _get_data_update(self):
|
|
2145
|
+
"""Obtain the update date from the coordinator and convert to UTC."""
|
|
2146
|
+
data_update = self.coordinator.data.get("actualizado")
|
|
2147
|
+
if data_update:
|
|
2148
|
+
try:
|
|
2149
|
+
local_time = datetime.fromisoformat(data_update)
|
|
2150
|
+
return local_time.astimezone(ZoneInfo("UTC"))
|
|
2151
|
+
except ValueError:
|
|
2152
|
+
_LOGGER.error("Formato de fecha de actualización inválido: %s", data_update)
|
|
2153
|
+
return None
|
|
2154
|
+
|
|
2155
|
+
@property
|
|
2156
|
+
def native_value(self):
|
|
2157
|
+
"""Return the status of the moon file based on the update date."""
|
|
2158
|
+
data_update = self._get_data_update()
|
|
2159
|
+
if not data_update:
|
|
2160
|
+
return "unknown"
|
|
2161
|
+
now = datetime.now(timezone.utc).astimezone(TIMEZONE)
|
|
2162
|
+
if (now - data_update) > timedelta(days=1):
|
|
2163
|
+
return "obsolete"
|
|
2164
|
+
return "updated"
|
|
2165
|
+
|
|
2166
|
+
@property
|
|
2167
|
+
def extra_state_attributes(self):
|
|
2168
|
+
"""Return additional attributes for the sensor."""
|
|
2169
|
+
attributes = super().extra_state_attributes or {}
|
|
2170
|
+
data_update = self._get_data_update()
|
|
2171
|
+
if data_update:
|
|
2172
|
+
attributes["update_date"] = data_update.isoformat()
|
|
2173
|
+
return attributes
|
|
2174
|
+
|
|
2175
|
+
@property
|
|
2176
|
+
def device_info(self) -> DeviceInfo:
|
|
2177
|
+
"""Return the device info."""
|
|
2178
|
+
return DeviceInfo(
|
|
2179
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
2180
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
2181
|
+
manufacturer="Meteocat",
|
|
2182
|
+
model="Meteocat API",
|
|
2183
|
+
)
|
|
2184
|
+
|
|
2185
|
+
class MeteocatMoonTimeSensor(CoordinatorEntity[MeteocatMoonFileCoordinator], SensorEntity):
|
|
2186
|
+
"""Representation of Meteocat Moon time sensors (moonrise/moonset)."""
|
|
2187
|
+
_attr_has_entity_name = True
|
|
2188
|
+
|
|
2189
|
+
def __init__(self, moon_file_coordinator, description, entry_data):
|
|
2190
|
+
super().__init__(moon_file_coordinator)
|
|
2191
|
+
self.entity_description = description
|
|
2192
|
+
self._town_name = entry_data["town_name"]
|
|
2193
|
+
self._town_id = entry_data["town_id"]
|
|
2194
|
+
self._station_id = entry_data["station_id"]
|
|
2195
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
2196
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
2197
|
+
|
|
2198
|
+
_LOGGER.debug(
|
|
2199
|
+
"Inicializando sensor lunar: %s, Unique ID: %s",
|
|
2200
|
+
self.entity_description.name,
|
|
2201
|
+
self._attr_unique_id,
|
|
2202
|
+
)
|
|
2203
|
+
|
|
2204
|
+
@property
|
|
2205
|
+
def native_value(self):
|
|
2206
|
+
"""Return the moonrise or moonset as a datetime."""
|
|
2207
|
+
time_str = self.coordinator.data.get(self.entity_description.key)
|
|
2208
|
+
if time_str:
|
|
2209
|
+
try:
|
|
2210
|
+
return datetime.fromisoformat(time_str)
|
|
2211
|
+
except ValueError:
|
|
2212
|
+
_LOGGER.error("Formato de fecha inválido para %s: %s", self.entity_description.key, time_str)
|
|
2213
|
+
return None
|
|
2214
|
+
return None
|
|
2215
|
+
|
|
2216
|
+
@property
|
|
2217
|
+
def extra_state_attributes(self):
|
|
2218
|
+
"""Return additional attributes for the sensor."""
|
|
2219
|
+
attributes = super().extra_state_attributes or {}
|
|
2220
|
+
time_str = self.coordinator.data.get(self.entity_description.key)
|
|
2221
|
+
key = self.entity_description.key # "moonrise" o "moonset"
|
|
2222
|
+
|
|
2223
|
+
if time_str:
|
|
2224
|
+
try:
|
|
2225
|
+
dt = datetime.fromisoformat(time_str)
|
|
2226
|
+
# Atributos adicionales amigables
|
|
2227
|
+
attributes["friendly_time"] = dt.strftime("%H:%M")
|
|
2228
|
+
attributes["friendly_date"] = dt.strftime("%Y-%m-%d")
|
|
2229
|
+
|
|
2230
|
+
# Día base en inglés (minúsculas)
|
|
2231
|
+
day_key = dt.strftime("%A").lower()
|
|
2232
|
+
attributes["friendly_day"] = day_key
|
|
2233
|
+
|
|
2234
|
+
# No establecemos "status" aquí para evitar que HA lo muestre como "desconocido"
|
|
2235
|
+
|
|
2236
|
+
except ValueError:
|
|
2237
|
+
attributes["friendly_time"] = None
|
|
2238
|
+
attributes["friendly_date"] = None
|
|
2239
|
+
attributes["friendly_day"] = None
|
|
2240
|
+
else:
|
|
2241
|
+
attributes["friendly_time"] = None
|
|
2242
|
+
attributes["friendly_date"] = None
|
|
2243
|
+
attributes["friendly_day"] = None
|
|
2244
|
+
return attributes
|
|
2245
|
+
|
|
2246
|
+
@property
|
|
2247
|
+
def device_info(self) -> DeviceInfo:
|
|
2248
|
+
return DeviceInfo(
|
|
2249
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
2250
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
2251
|
+
manufacturer="Meteocat",
|
|
2252
|
+
model="Meteocat API",
|
|
2253
|
+
)
|