meteocat 3.0.0 → 3.1.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 +46 -11
- package/README.md +13 -4
- package/custom_components/meteocat/__init__.py +51 -41
- package/custom_components/meteocat/config_flow.py +52 -3
- package/custom_components/meteocat/const.py +3 -0
- package/custom_components/meteocat/coordinator.py +188 -2
- package/custom_components/meteocat/manifest.json +1 -1
- package/custom_components/meteocat/options_flow.py +61 -3
- package/custom_components/meteocat/sensor.py +297 -246
- package/custom_components/meteocat/strings.json +61 -15
- package/custom_components/meteocat/translations/ca.json +58 -13
- package/custom_components/meteocat/translations/en.json +61 -15
- package/custom_components/meteocat/translations/es.json +61 -15
- package/custom_components/meteocat/version.py +1 -1
- package/hacs.json +1 -1
- package/images/daily_forecast_2_alerts.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
|
@@ -71,13 +71,13 @@ from .const import (
|
|
|
71
71
|
ALERTS,
|
|
72
72
|
ALERT_FILE_STATUS,
|
|
73
73
|
ALERT_WIND,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
ALERT_RAIN_INTENSITY,
|
|
75
|
+
ALERT_RAIN,
|
|
76
|
+
ALERT_SEA,
|
|
77
|
+
ALERT_COLD,
|
|
78
|
+
ALERT_WARM,
|
|
79
|
+
ALERT_WARM_NIGHT,
|
|
80
|
+
ALERT_SNOW,
|
|
81
81
|
LIGHTNING_FILE_STATUS,
|
|
82
82
|
LIGHTNING_REGION,
|
|
83
83
|
LIGHTNING_TOWN,
|
|
@@ -105,6 +105,9 @@ from .const import (
|
|
|
105
105
|
DEFAULT_LIGHTNING_VALIDITY_TIME,
|
|
106
106
|
DEFAULT_LIGHTNING_VALIDITY_HOURS,
|
|
107
107
|
DEFAULT_LIGHTNING_VALIDITY_MINUTES,
|
|
108
|
+
SUNRISE,
|
|
109
|
+
SUNSET,
|
|
110
|
+
SUN_FILE_STATUS,
|
|
108
111
|
)
|
|
109
112
|
|
|
110
113
|
from .coordinator import (
|
|
@@ -122,6 +125,8 @@ from .coordinator import (
|
|
|
122
125
|
MeteocatQuotesFileCoordinator,
|
|
123
126
|
MeteocatLightningCoordinator,
|
|
124
127
|
MeteocatLightningFileCoordinator,
|
|
128
|
+
MeteocatSunCoordinator,
|
|
129
|
+
MeteocatSunFileCoordinator,
|
|
125
130
|
)
|
|
126
131
|
|
|
127
132
|
# Definir la zona horaria local
|
|
@@ -208,7 +213,7 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
208
213
|
icon="mdi:weather-sunny",
|
|
209
214
|
device_class=SensorDeviceClass.IRRADIANCE,
|
|
210
215
|
state_class=SensorStateClass.MEASUREMENT,
|
|
211
|
-
native_unit_of_measurement
|
|
216
|
+
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
|
212
217
|
),
|
|
213
218
|
MeteocatSensorEntityDescription(
|
|
214
219
|
key=UV_INDEX,
|
|
@@ -305,7 +310,7 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
305
310
|
translation_key="condition",
|
|
306
311
|
icon="mdi:weather-partly-cloudy",
|
|
307
312
|
),
|
|
308
|
-
|
|
313
|
+
MeteocatSensorEntityDescription(
|
|
309
314
|
key=MAX_TEMPERATURE_FORECAST,
|
|
310
315
|
translation_key="max_temperature_forecast",
|
|
311
316
|
icon="mdi:thermometer-plus",
|
|
@@ -367,37 +372,37 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
367
372
|
translation_key="alert_wind",
|
|
368
373
|
icon="mdi:alert-outline",
|
|
369
374
|
),
|
|
370
|
-
|
|
375
|
+
MeteocatSensorEntityDescription(
|
|
371
376
|
key=ALERT_RAIN_INTENSITY,
|
|
372
377
|
translation_key="alert_rain_intensity",
|
|
373
378
|
icon="mdi:alert-outline",
|
|
374
379
|
),
|
|
375
|
-
|
|
380
|
+
MeteocatSensorEntityDescription(
|
|
376
381
|
key=ALERT_RAIN,
|
|
377
382
|
translation_key="alert_rain",
|
|
378
383
|
icon="mdi:alert-outline",
|
|
379
384
|
),
|
|
380
|
-
|
|
385
|
+
MeteocatSensorEntityDescription(
|
|
381
386
|
key=ALERT_SEA,
|
|
382
387
|
translation_key="alert_sea",
|
|
383
388
|
icon="mdi:alert-outline",
|
|
384
389
|
),
|
|
385
|
-
|
|
390
|
+
MeteocatSensorEntityDescription(
|
|
386
391
|
key=ALERT_COLD,
|
|
387
392
|
translation_key="alert_cold",
|
|
388
393
|
icon="mdi:alert-outline",
|
|
389
394
|
),
|
|
390
|
-
|
|
395
|
+
MeteocatSensorEntityDescription(
|
|
391
396
|
key=ALERT_WARM,
|
|
392
397
|
translation_key="alert_warm",
|
|
393
398
|
icon="mdi:alert-outline",
|
|
394
399
|
),
|
|
395
|
-
|
|
400
|
+
MeteocatSensorEntityDescription(
|
|
396
401
|
key=ALERT_WARM_NIGHT,
|
|
397
402
|
translation_key="alert_warm_night",
|
|
398
403
|
icon="mdi:alert-outline",
|
|
399
404
|
),
|
|
400
|
-
|
|
405
|
+
MeteocatSensorEntityDescription(
|
|
401
406
|
key=ALERT_SNOW,
|
|
402
407
|
translation_key="alert_snow",
|
|
403
408
|
icon="mdi:alert-outline",
|
|
@@ -431,7 +436,26 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
431
436
|
translation_key="quota_queries",
|
|
432
437
|
icon="mdi:counter",
|
|
433
438
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
434
|
-
)
|
|
439
|
+
),
|
|
440
|
+
# Nuevos sensores de sol
|
|
441
|
+
MeteocatSensorEntityDescription(
|
|
442
|
+
key=SUNRISE,
|
|
443
|
+
translation_key="sunrise",
|
|
444
|
+
icon="mdi:weather-sunset-up",
|
|
445
|
+
device_class=SensorDeviceClass.TIMESTAMP,
|
|
446
|
+
),
|
|
447
|
+
MeteocatSensorEntityDescription(
|
|
448
|
+
key=SUNSET,
|
|
449
|
+
translation_key="sunset",
|
|
450
|
+
icon="mdi:weather-sunset-down",
|
|
451
|
+
device_class=SensorDeviceClass.TIMESTAMP,
|
|
452
|
+
),
|
|
453
|
+
MeteocatSensorEntityDescription(
|
|
454
|
+
key=SUN_FILE_STATUS,
|
|
455
|
+
translation_key="sun_file_status",
|
|
456
|
+
icon="mdi:update",
|
|
457
|
+
entity_category=EntityCategory.DIAGNOSTIC,
|
|
458
|
+
),
|
|
435
459
|
)
|
|
436
460
|
|
|
437
461
|
@callback
|
|
@@ -454,12 +478,29 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
454
478
|
quotes_file_coordinator = entry_data.get("quotes_file_coordinator")
|
|
455
479
|
lightning_coordinator = entry_data.get("lightning_coordinator")
|
|
456
480
|
lightning_file_coordinator = entry_data.get("lightning_file_coordinator")
|
|
481
|
+
sun_coordinator = entry_data.get("sun_coordinator")
|
|
482
|
+
sun_file_coordinator = entry_data.get("sun_file_coordinator")
|
|
457
483
|
|
|
458
484
|
# Sensores generales
|
|
459
485
|
async_add_entities(
|
|
460
486
|
MeteocatSensor(sensor_coordinator, description, entry_data)
|
|
461
487
|
for description in SENSOR_TYPES
|
|
462
|
-
if description.key in {
|
|
488
|
+
if description.key in {
|
|
489
|
+
WIND_SPEED,
|
|
490
|
+
WIND_DIRECTION,
|
|
491
|
+
WIND_DIRECTION_CARDINAL,
|
|
492
|
+
TEMPERATURE,
|
|
493
|
+
HUMIDITY,
|
|
494
|
+
PRESSURE,
|
|
495
|
+
PRECIPITATION,
|
|
496
|
+
PRECIPITATION_ACCUMULATED,
|
|
497
|
+
SOLAR_GLOBAL_IRRADIANCE,
|
|
498
|
+
MAX_TEMPERATURE,
|
|
499
|
+
MIN_TEMPERATURE,
|
|
500
|
+
FEELS_LIKE,
|
|
501
|
+
WIND_GUST,
|
|
502
|
+
STATION_TIMESTAMP,
|
|
503
|
+
}
|
|
463
504
|
)
|
|
464
505
|
|
|
465
506
|
# Sensores estáticos
|
|
@@ -473,14 +514,14 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
473
514
|
async_add_entities(
|
|
474
515
|
MeteocatUviSensor(uvi_file_coordinator, description, entry_data)
|
|
475
516
|
for description in SENSOR_TYPES
|
|
476
|
-
if description.key == UV_INDEX
|
|
517
|
+
if description.key == UV_INDEX
|
|
477
518
|
)
|
|
478
519
|
|
|
479
520
|
# Sensor CONDITION para estado del cielo
|
|
480
521
|
async_add_entities(
|
|
481
522
|
MeteocatConditionSensor(condition_coordinator, description, entry_data)
|
|
482
523
|
for description in SENSOR_TYPES
|
|
483
|
-
if description.key == CONDITION
|
|
524
|
+
if description.key == CONDITION
|
|
484
525
|
)
|
|
485
526
|
|
|
486
527
|
# Sensores temperatura previsión
|
|
@@ -536,7 +577,16 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
536
577
|
async_add_entities(
|
|
537
578
|
MeteocatAlertMeteorSensor(alerts_region_coordinator, description, entry_data)
|
|
538
579
|
for description in SENSOR_TYPES
|
|
539
|
-
if description.key in {
|
|
580
|
+
if description.key in {
|
|
581
|
+
ALERT_WIND,
|
|
582
|
+
ALERT_RAIN_INTENSITY,
|
|
583
|
+
ALERT_RAIN,
|
|
584
|
+
ALERT_SEA,
|
|
585
|
+
ALERT_COLD,
|
|
586
|
+
ALERT_WARM,
|
|
587
|
+
ALERT_WARM_NIGHT,
|
|
588
|
+
ALERT_SNOW,
|
|
589
|
+
}
|
|
540
590
|
)
|
|
541
591
|
|
|
542
592
|
# Sensores de estado de cuotas
|
|
@@ -567,6 +617,21 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
567
617
|
if description.key in {LIGHTNING_REGION, LIGHTNING_TOWN}
|
|
568
618
|
)
|
|
569
619
|
|
|
620
|
+
# Sensor de estado de archivo de sol
|
|
621
|
+
async_add_entities(
|
|
622
|
+
MeteocatSunStatusSensor(sun_coordinator, description, entry_data)
|
|
623
|
+
for description in SENSOR_TYPES
|
|
624
|
+
if description.key == SUN_FILE_STATUS
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# Sensores de sol
|
|
628
|
+
async_add_entities(
|
|
629
|
+
MeteocatSunSensor(sun_file_coordinator, description, entry_data)
|
|
630
|
+
for description in SENSOR_TYPES
|
|
631
|
+
if description.key in {SUNRISE, SUNSET}
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
|
|
570
635
|
# Cambiar UTC a la zona horaria local
|
|
571
636
|
def convert_to_local_time(utc_time: str, local_tz: str = "Europe/Madrid") -> datetime | None:
|
|
572
637
|
"""
|
|
@@ -594,7 +659,7 @@ class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], S
|
|
|
594
659
|
"""Representation of a static Meteocat sensor."""
|
|
595
660
|
STATIC_KEYS = {TOWN_NAME, TOWN_ID, STATION_NAME, STATION_ID, REGION_NAME, REGION_ID}
|
|
596
661
|
|
|
597
|
-
_attr_has_entity_name = True
|
|
662
|
+
_attr_has_entity_name = True
|
|
598
663
|
|
|
599
664
|
def __init__(self, static_sensor_coordinator, description, entry_data):
|
|
600
665
|
"""Initialize the static sensor."""
|
|
@@ -607,13 +672,9 @@ class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], S
|
|
|
607
672
|
self._region_name = entry_data["region_name"]
|
|
608
673
|
self._region_id = entry_data["region_id"]
|
|
609
674
|
|
|
610
|
-
# Unique ID for the entity
|
|
611
675
|
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
676
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
615
677
|
|
|
616
|
-
# Log para depuración
|
|
617
678
|
_LOGGER.debug(
|
|
618
679
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
619
680
|
self.entity_description.name,
|
|
@@ -623,9 +684,7 @@ class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], S
|
|
|
623
684
|
@property
|
|
624
685
|
def native_value(self):
|
|
625
686
|
"""Return the state of the sensor."""
|
|
626
|
-
# Información estática
|
|
627
687
|
if self.entity_description.key in self.STATIC_KEYS:
|
|
628
|
-
# Información estática del `entry_data`
|
|
629
688
|
if self.entity_description.key == TOWN_NAME:
|
|
630
689
|
return self._town_name
|
|
631
690
|
if self.entity_description.key == TOWN_ID:
|
|
@@ -644,15 +703,14 @@ class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], S
|
|
|
644
703
|
"""Return the device info."""
|
|
645
704
|
return DeviceInfo(
|
|
646
705
|
identifiers={(DOMAIN, self._town_id)},
|
|
647
|
-
name="Meteocat
|
|
706
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
648
707
|
manufacturer="Meteocat",
|
|
649
708
|
model="Meteocat API",
|
|
650
709
|
)
|
|
651
710
|
|
|
652
711
|
class MeteocatUviSensor(CoordinatorEntity[MeteocatUviFileCoordinator], SensorEntity):
|
|
653
712
|
"""Representation of a Meteocat UV Index sensor."""
|
|
654
|
-
|
|
655
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
713
|
+
_attr_has_entity_name = True
|
|
656
714
|
|
|
657
715
|
def __init__(self, uvi_file_coordinator, description, entry_data):
|
|
658
716
|
"""Initialize the UV Index sensor."""
|
|
@@ -661,14 +719,9 @@ class MeteocatUviSensor(CoordinatorEntity[MeteocatUviFileCoordinator], SensorEnt
|
|
|
661
719
|
self._town_name = entry_data["town_name"]
|
|
662
720
|
self._town_id = entry_data["town_id"]
|
|
663
721
|
self._station_id = entry_data["station_id"]
|
|
664
|
-
|
|
665
|
-
# Unique ID for the entity
|
|
666
722
|
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
723
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
670
724
|
|
|
671
|
-
# Log para depuración
|
|
672
725
|
_LOGGER.debug(
|
|
673
726
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
674
727
|
self.entity_description.name,
|
|
@@ -688,7 +741,6 @@ class MeteocatUviSensor(CoordinatorEntity[MeteocatUviFileCoordinator], SensorEnt
|
|
|
688
741
|
attributes = super().extra_state_attributes or {}
|
|
689
742
|
if self.entity_description.key == UV_INDEX:
|
|
690
743
|
uvi_data = self.coordinator.data or {}
|
|
691
|
-
# Add the "hour" attribute if it exists
|
|
692
744
|
attributes["hour"] = uvi_data.get("hour")
|
|
693
745
|
return attributes
|
|
694
746
|
|
|
@@ -697,31 +749,25 @@ class MeteocatUviSensor(CoordinatorEntity[MeteocatUviFileCoordinator], SensorEnt
|
|
|
697
749
|
"""Return the device info."""
|
|
698
750
|
return DeviceInfo(
|
|
699
751
|
identifiers={(DOMAIN, self._town_id)},
|
|
700
|
-
name="Meteocat
|
|
752
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
701
753
|
manufacturer="Meteocat",
|
|
702
754
|
model="Meteocat API",
|
|
703
755
|
)
|
|
704
756
|
|
|
705
757
|
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
|
|
758
|
+
"""Representation of a Meteocat Condition sensor."""
|
|
759
|
+
_attr_has_entity_name = True
|
|
709
760
|
|
|
710
761
|
def __init__(self, condition_coordinator, description, entry_data):
|
|
711
|
-
"""Initialize the
|
|
762
|
+
"""Initialize the Condition sensor."""
|
|
712
763
|
super().__init__(condition_coordinator)
|
|
713
764
|
self.entity_description = description
|
|
714
765
|
self._town_name = entry_data["town_name"]
|
|
715
766
|
self._town_id = entry_data["town_id"]
|
|
716
767
|
self._station_id = entry_data["station_id"]
|
|
717
|
-
|
|
718
|
-
# Unique ID for the entity
|
|
719
768
|
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
769
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
723
770
|
|
|
724
|
-
# Log para depuración
|
|
725
771
|
_LOGGER.debug(
|
|
726
772
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
727
773
|
self.entity_description.name,
|
|
@@ -730,7 +776,7 @@ class MeteocatConditionSensor(CoordinatorEntity[MeteocatConditionCoordinator], S
|
|
|
730
776
|
|
|
731
777
|
@property
|
|
732
778
|
def native_value(self):
|
|
733
|
-
"""Return the current
|
|
779
|
+
"""Return the current condition value."""
|
|
734
780
|
if self.entity_description.key == CONDITION:
|
|
735
781
|
condition_data = self.coordinator.data or {}
|
|
736
782
|
return condition_data.get("condition", None)
|
|
@@ -741,7 +787,6 @@ class MeteocatConditionSensor(CoordinatorEntity[MeteocatConditionCoordinator], S
|
|
|
741
787
|
attributes = super().extra_state_attributes or {}
|
|
742
788
|
if self.entity_description.key == CONDITION:
|
|
743
789
|
condition_data = self.coordinator.data or {}
|
|
744
|
-
# Add the "hour" attribute if it exists
|
|
745
790
|
attributes["hour"] = condition_data.get("hour", None)
|
|
746
791
|
return attributes
|
|
747
792
|
|
|
@@ -750,14 +795,59 @@ class MeteocatConditionSensor(CoordinatorEntity[MeteocatConditionCoordinator], S
|
|
|
750
795
|
"""Return the device info."""
|
|
751
796
|
return DeviceInfo(
|
|
752
797
|
identifiers={(DOMAIN, self._town_id)},
|
|
753
|
-
name="Meteocat
|
|
798
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
799
|
+
manufacturer="Meteocat",
|
|
800
|
+
model="Meteocat API",
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
class MeteocatSunSensor(CoordinatorEntity[MeteocatSunCoordinator], SensorEntity):
|
|
804
|
+
"""Representation of a Meteocat Sun sensor (sunrise/sunset)."""
|
|
805
|
+
_attr_has_entity_name = True
|
|
806
|
+
|
|
807
|
+
def __init__(self, sun_coordinator, description, entry_data):
|
|
808
|
+
"""Initialize the Sun sensor."""
|
|
809
|
+
super().__init__(sun_coordinator)
|
|
810
|
+
self.entity_description = description
|
|
811
|
+
self._town_name = entry_data["town_name"]
|
|
812
|
+
self._town_id = entry_data["town_id"]
|
|
813
|
+
self._station_id = entry_data["station_id"]
|
|
814
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
815
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
816
|
+
|
|
817
|
+
_LOGGER.debug(
|
|
818
|
+
"Inicializando sensor: %s, Unique ID: %s",
|
|
819
|
+
self.entity_description.name,
|
|
820
|
+
self._attr_unique_id,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
@property
|
|
824
|
+
def native_value(self):
|
|
825
|
+
"""Return the sunrise or sunset time as a datetime."""
|
|
826
|
+
if self.entity_description.key in {SUNRISE, SUNSET}:
|
|
827
|
+
return self.coordinator.data.get(self.entity_description.key)
|
|
828
|
+
return None
|
|
829
|
+
|
|
830
|
+
@property
|
|
831
|
+
def extra_state_attributes(self):
|
|
832
|
+
"""Return additional attributes for the sensor."""
|
|
833
|
+
attributes = super().extra_state_attributes or {}
|
|
834
|
+
if self.entity_description.key in {SUNRISE, SUNSET}:
|
|
835
|
+
dt = self.coordinator.data.get(self.entity_description.key)
|
|
836
|
+
attributes["friendly_time"] = dt.strftime("%H:%M") if dt else None
|
|
837
|
+
return attributes
|
|
838
|
+
|
|
839
|
+
@property
|
|
840
|
+
def device_info(self) -> DeviceInfo:
|
|
841
|
+
"""Return the device info."""
|
|
842
|
+
return DeviceInfo(
|
|
843
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
844
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
754
845
|
manufacturer="Meteocat",
|
|
755
846
|
model="Meteocat API",
|
|
756
847
|
)
|
|
757
848
|
|
|
758
849
|
class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity):
|
|
759
850
|
"""Representation of a Meteocat sensor."""
|
|
760
|
-
|
|
761
851
|
CODE_MAPPING = {
|
|
762
852
|
WIND_SPEED: WIND_SPEED_CODE,
|
|
763
853
|
WIND_DIRECTION: WIND_DIRECTION_CODE,
|
|
@@ -766,13 +856,11 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
766
856
|
PRESSURE: PRESSURE_CODE,
|
|
767
857
|
PRECIPITATION: PRECIPITATION_CODE,
|
|
768
858
|
SOLAR_GLOBAL_IRRADIANCE: SOLAR_GLOBAL_IRRADIANCE_CODE,
|
|
769
|
-
# UV_INDEX: UV_INDEX_CODE,
|
|
770
859
|
MAX_TEMPERATURE: MAX_TEMPERATURE_CODE,
|
|
771
860
|
MIN_TEMPERATURE: MIN_TEMPERATURE_CODE,
|
|
772
861
|
WIND_GUST: WIND_GUST_CODE,
|
|
773
862
|
}
|
|
774
|
-
|
|
775
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
863
|
+
_attr_has_entity_name = True
|
|
776
864
|
|
|
777
865
|
def __init__(self, sensor_coordinator, description, entry_data):
|
|
778
866
|
"""Initialize the sensor."""
|
|
@@ -783,14 +871,8 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
783
871
|
self._town_id = entry_data["town_id"]
|
|
784
872
|
self._station_name = entry_data["station_name"]
|
|
785
873
|
self._station_id = entry_data["station_id"]
|
|
786
|
-
|
|
787
|
-
# Unique ID for the entity
|
|
788
874
|
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
875
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
792
|
-
|
|
793
|
-
# Log para depuración
|
|
794
876
|
_LOGGER.debug(
|
|
795
877
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
796
878
|
self.entity_description.name,
|
|
@@ -800,16 +882,11 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
800
882
|
@property
|
|
801
883
|
def native_value(self):
|
|
802
884
|
"""Return the state of the sensor."""
|
|
803
|
-
# Información dinámica
|
|
804
885
|
if self.entity_description.key == FEELS_LIKE:
|
|
805
886
|
stations = self.coordinator.data or []
|
|
806
|
-
|
|
807
|
-
# Variables necesarias
|
|
808
887
|
temperature = None
|
|
809
888
|
humidity = None
|
|
810
889
|
wind_speed = None
|
|
811
|
-
|
|
812
|
-
# Obtener valores de las variables
|
|
813
890
|
for station in stations:
|
|
814
891
|
variables = station.get("variables", [])
|
|
815
892
|
for var in variables:
|
|
@@ -818,25 +895,19 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
818
895
|
if not lectures:
|
|
819
896
|
continue
|
|
820
897
|
latest_reading = lectures[-1].get("valor")
|
|
821
|
-
|
|
822
898
|
if code == TEMPERATURE_CODE:
|
|
823
899
|
temperature = float(latest_reading)
|
|
824
900
|
elif code == HUMIDITY_CODE:
|
|
825
901
|
humidity = float(latest_reading)
|
|
826
902
|
elif code == WIND_SPEED_CODE:
|
|
827
903
|
wind_speed = float(latest_reading)
|
|
828
|
-
|
|
829
|
-
# Verificar que todas las variables necesarias están presentes
|
|
830
904
|
if temperature is not None and humidity is not None and wind_speed is not None:
|
|
831
|
-
# Cálculo del windchill
|
|
832
905
|
windchill = (
|
|
833
906
|
13.1267 +
|
|
834
907
|
0.6215 * temperature -
|
|
835
908
|
11.37 * (wind_speed ** 0.16) +
|
|
836
909
|
0.3965 * temperature * (wind_speed ** 0.16)
|
|
837
910
|
)
|
|
838
|
-
|
|
839
|
-
# Cálculo del heat_index
|
|
840
911
|
heat_index = (
|
|
841
912
|
-8.78469476 +
|
|
842
913
|
1.61139411 * temperature +
|
|
@@ -848,8 +919,6 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
848
919
|
0.00072546 * temperature * (humidity ** 2) -
|
|
849
920
|
0.000003582 * (temperature ** 2) * (humidity ** 2)
|
|
850
921
|
)
|
|
851
|
-
|
|
852
|
-
# Lógica de selección
|
|
853
922
|
if -50 <= temperature <= 10 and wind_speed > 4.8:
|
|
854
923
|
_LOGGER.debug(f"Sensación térmica por frío, calculada según la fórmula de Wind Chill: {windchill} ºC")
|
|
855
924
|
return round(windchill, 1)
|
|
@@ -861,50 +930,36 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
861
930
|
return round(temperature, 1)
|
|
862
931
|
|
|
863
932
|
sensor_code = self.CODE_MAPPING.get(self.entity_description.key)
|
|
864
|
-
|
|
865
933
|
if sensor_code is not None:
|
|
866
|
-
# Accedemos a las estaciones en el JSON recibido
|
|
867
934
|
stations = self.coordinator.data or []
|
|
868
935
|
for station in stations:
|
|
869
936
|
variables = station.get("variables", [])
|
|
870
|
-
|
|
871
|
-
# Filtramos por código
|
|
872
937
|
variable_data = next(
|
|
873
938
|
(var for var in variables if var.get("codi") == sensor_code),
|
|
874
939
|
None,
|
|
875
940
|
)
|
|
876
|
-
|
|
877
941
|
if variable_data:
|
|
878
|
-
# Obtenemos la última lectura
|
|
879
942
|
lectures = variable_data.get("lectures", [])
|
|
880
943
|
if lectures:
|
|
881
944
|
latest_reading = lectures[-1]
|
|
882
945
|
value = latest_reading.get("valor")
|
|
883
|
-
|
|
884
946
|
return value
|
|
885
|
-
|
|
886
|
-
# Para el sensor WIND_DIRECTION_CARDINAL, convertir grados a dirección cardinal
|
|
947
|
+
|
|
887
948
|
if self.entity_description.key == WIND_DIRECTION_CARDINAL:
|
|
888
949
|
stations = self.coordinator.data or []
|
|
889
950
|
for station in stations:
|
|
890
951
|
variables = station.get("variables", [])
|
|
891
|
-
|
|
892
|
-
# Filtramos por código
|
|
893
952
|
variable_data = next(
|
|
894
953
|
(var for var in variables if var.get("codi") == WIND_DIRECTION_CODE),
|
|
895
954
|
None,
|
|
896
955
|
)
|
|
897
|
-
|
|
898
956
|
if variable_data:
|
|
899
|
-
# Obtenemos la última lectura
|
|
900
957
|
lectures = variable_data.get("lectures", [])
|
|
901
958
|
if lectures:
|
|
902
959
|
latest_reading = lectures[-1]
|
|
903
960
|
value = latest_reading.get("valor")
|
|
904
|
-
|
|
905
961
|
return self._convert_degrees_to_cardinal(value)
|
|
906
|
-
|
|
907
|
-
# Lógica específica para el sensor de timestamp
|
|
962
|
+
|
|
908
963
|
if self.entity_description.key == STATION_TIMESTAMP:
|
|
909
964
|
stations = self.coordinator.data or []
|
|
910
965
|
for station in stations:
|
|
@@ -912,42 +967,30 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
912
967
|
for variable in variables:
|
|
913
968
|
lectures = variable.get("lectures", [])
|
|
914
969
|
if lectures:
|
|
915
|
-
# Obtenemos el campo `data` de la última lectura
|
|
916
970
|
latest_reading = lectures[-1]
|
|
917
971
|
raw_timestamp = latest_reading.get("data")
|
|
918
|
-
|
|
919
972
|
if raw_timestamp:
|
|
920
|
-
# Convertir el timestamp a un objeto datetime
|
|
921
973
|
try:
|
|
922
|
-
# Convertimos raw_timestamp a hora local
|
|
923
974
|
local_time = convert_to_local_time(raw_timestamp)
|
|
924
975
|
_LOGGER.debug("Hora UTC: %s convertida a hora local: %s", raw_timestamp, local_time)
|
|
925
976
|
return local_time
|
|
926
977
|
except ValueError:
|
|
927
|
-
# Manejo de errores si el formato no es válido
|
|
928
978
|
_LOGGER.error(f"Error al convertir el timestamp '{raw_timestamp}' a hora local.")
|
|
929
979
|
return None
|
|
930
980
|
|
|
931
|
-
# Nuevo sensor para la precipitación acumulada
|
|
932
981
|
if self.entity_description.key == PRECIPITATION_ACCUMULATED:
|
|
933
982
|
stations = self.coordinator.data or []
|
|
934
|
-
total_precipitation = 0.0
|
|
935
|
-
|
|
983
|
+
total_precipitation = 0.0
|
|
936
984
|
for station in stations:
|
|
937
985
|
variables = station.get("variables", [])
|
|
938
|
-
|
|
939
|
-
# Filtramos por código de precipitación
|
|
940
986
|
variable_data = next(
|
|
941
987
|
(var for var in variables if var.get("codi") == PRECIPITATION_CODE),
|
|
942
988
|
None,
|
|
943
989
|
)
|
|
944
|
-
|
|
945
990
|
if variable_data:
|
|
946
|
-
# Sumamos las lecturas de precipitación
|
|
947
991
|
lectures = variable_data.get("lectures", [])
|
|
948
992
|
for lecture in lectures:
|
|
949
|
-
total_precipitation += float(lecture.get("valor", 0.0))
|
|
950
|
-
|
|
993
|
+
total_precipitation += float(lecture.get("valor", 0.0))
|
|
951
994
|
_LOGGER.debug(f"Total precipitación acumulada: {total_precipitation} mm")
|
|
952
995
|
return total_precipitation
|
|
953
996
|
|
|
@@ -957,11 +1000,10 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
957
1000
|
def _convert_degrees_to_cardinal(degree: float) -> str:
|
|
958
1001
|
"""Convert degrees to cardinal direction."""
|
|
959
1002
|
if not isinstance(degree, (int, float)):
|
|
960
|
-
return "Unknown"
|
|
961
|
-
|
|
1003
|
+
return "Unknown"
|
|
962
1004
|
directions = [
|
|
963
|
-
|
|
964
|
-
|
|
1005
|
+
"north", "north_northeast", "northeast", "east_northeast", "east", "east_southeast", "southeast", "south_southeast",
|
|
1006
|
+
"south", "south_southwest", "southwest", "west_southwest", "west", "west_northwest", "northwest", "north_northwest"
|
|
965
1007
|
]
|
|
966
1008
|
index = round(degree / 22.5) % 16
|
|
967
1009
|
return directions[index]
|
|
@@ -971,31 +1013,24 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
971
1013
|
"""Return the device info."""
|
|
972
1014
|
return DeviceInfo(
|
|
973
1015
|
identifiers={(DOMAIN, self._town_id)},
|
|
974
|
-
name="Meteocat
|
|
1016
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
975
1017
|
manufacturer="Meteocat",
|
|
976
1018
|
model="Meteocat API",
|
|
977
1019
|
)
|
|
978
1020
|
|
|
979
1021
|
class MeteocatTempForecast(CoordinatorEntity[MeteocatTempForecastCoordinator], SensorEntity):
|
|
980
1022
|
"""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
|
|
1023
|
+
_attr_has_entity_name = True
|
|
983
1024
|
|
|
984
1025
|
def __init__(self, temp_forecast_coordinator, description, entry_data):
|
|
985
|
-
"""Initialize the
|
|
1026
|
+
"""Initialize the Min and Max Temperature sensors."""
|
|
986
1027
|
super().__init__(temp_forecast_coordinator)
|
|
987
1028
|
self.entity_description = description
|
|
988
1029
|
self._town_name = entry_data["town_name"]
|
|
989
1030
|
self._town_id = entry_data["town_id"]
|
|
990
1031
|
self._station_id = entry_data["station_id"]
|
|
991
|
-
|
|
992
|
-
# Unique ID for the entity
|
|
993
1032
|
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
1033
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
997
|
-
|
|
998
|
-
# Log para depuración
|
|
999
1034
|
_LOGGER.debug(
|
|
1000
1035
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
1001
1036
|
self.entity_description.name,
|
|
@@ -1006,7 +1041,6 @@ class MeteocatTempForecast(CoordinatorEntity[MeteocatTempForecastCoordinator], S
|
|
|
1006
1041
|
def native_value(self):
|
|
1007
1042
|
"""Return the Max and Min Temp Forecast value."""
|
|
1008
1043
|
temp_forecast_data = self.coordinator.data or {}
|
|
1009
|
-
|
|
1010
1044
|
if self.entity_description.key == MAX_TEMPERATURE_FORECAST:
|
|
1011
1045
|
return temp_forecast_data.get("max_temp_forecast", None)
|
|
1012
1046
|
if self.entity_description.key == MIN_TEMPERATURE_FORECAST:
|
|
@@ -1018,15 +1052,14 @@ class MeteocatTempForecast(CoordinatorEntity[MeteocatTempForecastCoordinator], S
|
|
|
1018
1052
|
"""Return the device info."""
|
|
1019
1053
|
return DeviceInfo(
|
|
1020
1054
|
identifiers={(DOMAIN, self._town_id)},
|
|
1021
|
-
name="Meteocat
|
|
1055
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1022
1056
|
manufacturer="Meteocat",
|
|
1023
1057
|
model="Meteocat API",
|
|
1024
1058
|
)
|
|
1025
1059
|
|
|
1026
1060
|
class MeteocatPrecipitationProbabilitySensor(CoordinatorEntity[DailyForecastCoordinator], SensorEntity):
|
|
1027
1061
|
"""Representation of a Meteocat precipitation probability sensor."""
|
|
1028
|
-
|
|
1029
|
-
_attr_has_entity_name = True # Enable device-based naming
|
|
1062
|
+
_attr_has_entity_name = True
|
|
1030
1063
|
|
|
1031
1064
|
def __init__(self, daily_forecast_coordinator, description, entry_data):
|
|
1032
1065
|
super().__init__(daily_forecast_coordinator)
|
|
@@ -1034,10 +1067,8 @@ class MeteocatPrecipitationProbabilitySensor(CoordinatorEntity[DailyForecastCoor
|
|
|
1034
1067
|
self._town_name = entry_data["town_name"]
|
|
1035
1068
|
self._town_id = entry_data["town_id"]
|
|
1036
1069
|
self._station_id = entry_data["station_id"]
|
|
1037
|
-
|
|
1038
1070
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
1039
1071
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1040
|
-
|
|
1041
1072
|
_LOGGER.debug(
|
|
1042
1073
|
"Initializing sensor: %s, Unique ID: %s",
|
|
1043
1074
|
self.entity_description.name,
|
|
@@ -1058,14 +1089,13 @@ class MeteocatPrecipitationProbabilitySensor(CoordinatorEntity[DailyForecastCoor
|
|
|
1058
1089
|
def device_info(self) -> DeviceInfo:
|
|
1059
1090
|
return DeviceInfo(
|
|
1060
1091
|
identifiers={(DOMAIN, self._town_id)},
|
|
1061
|
-
name="Meteocat
|
|
1092
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1062
1093
|
manufacturer="Meteocat",
|
|
1063
1094
|
model="Meteocat API",
|
|
1064
1095
|
)
|
|
1065
1096
|
|
|
1066
1097
|
class MeteocatHourlyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordinator], SensorEntity):
|
|
1067
|
-
|
|
1068
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1098
|
+
_attr_has_entity_name = True
|
|
1069
1099
|
|
|
1070
1100
|
def __init__(self, entity_coordinator, description, entry_data):
|
|
1071
1101
|
super().__init__(entity_coordinator)
|
|
@@ -1073,11 +1103,7 @@ class MeteocatHourlyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordin
|
|
|
1073
1103
|
self._town_name = entry_data["town_name"]
|
|
1074
1104
|
self._town_id = entry_data["town_id"]
|
|
1075
1105
|
self._station_id = entry_data["station_id"]
|
|
1076
|
-
|
|
1077
|
-
# Unique ID for the entity
|
|
1078
1106
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_hourly_status"
|
|
1079
|
-
|
|
1080
|
-
# Assign entity_category if defined in the description
|
|
1081
1107
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1082
1108
|
|
|
1083
1109
|
def _get_first_date(self):
|
|
@@ -1101,8 +1127,6 @@ class MeteocatHourlyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordin
|
|
|
1101
1127
|
f"hora de contacto a la API >= {DEFAULT_VALIDITY_HOURS}, "
|
|
1102
1128
|
f"minutos de contacto a la API >= {DEFAULT_VALIDITY_MINUTES}."
|
|
1103
1129
|
)
|
|
1104
|
-
|
|
1105
|
-
# Validar fecha y hora según la lógica del coordinador
|
|
1106
1130
|
if days_difference > DEFAULT_VALIDITY_DAYS and current_time >= time(DEFAULT_VALIDITY_HOURS, DEFAULT_VALIDITY_MINUTES):
|
|
1107
1131
|
return "obsolete"
|
|
1108
1132
|
return "updated"
|
|
@@ -1121,14 +1145,13 @@ class MeteocatHourlyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordin
|
|
|
1121
1145
|
"""Return the device info."""
|
|
1122
1146
|
return DeviceInfo(
|
|
1123
1147
|
identifiers={(DOMAIN, self._town_id)},
|
|
1124
|
-
name="Meteocat
|
|
1148
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1125
1149
|
manufacturer="Meteocat",
|
|
1126
1150
|
model="Meteocat API",
|
|
1127
1151
|
)
|
|
1128
1152
|
|
|
1129
1153
|
class MeteocatDailyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordinator], SensorEntity):
|
|
1130
|
-
|
|
1131
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1154
|
+
_attr_has_entity_name = True
|
|
1132
1155
|
|
|
1133
1156
|
def __init__(self, entity_coordinator, description, entry_data):
|
|
1134
1157
|
super().__init__(entity_coordinator)
|
|
@@ -1136,11 +1159,7 @@ class MeteocatDailyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordina
|
|
|
1136
1159
|
self._town_name = entry_data["town_name"]
|
|
1137
1160
|
self._town_id = entry_data["town_id"]
|
|
1138
1161
|
self._station_id = entry_data["station_id"]
|
|
1139
|
-
|
|
1140
|
-
# Unique ID for the entity
|
|
1141
1162
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_daily_status"
|
|
1142
|
-
|
|
1143
|
-
# Assign entity_category if defined in the description
|
|
1144
1163
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1145
1164
|
|
|
1146
1165
|
def _get_first_date(self):
|
|
@@ -1164,8 +1183,6 @@ class MeteocatDailyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordina
|
|
|
1164
1183
|
f"hora de contacto a la API >= {DEFAULT_VALIDITY_HOURS}, "
|
|
1165
1184
|
f"minutos de contacto a la API >= {DEFAULT_VALIDITY_MINUTES}."
|
|
1166
1185
|
)
|
|
1167
|
-
|
|
1168
|
-
# Validar fecha y hora según la lógica del coordinador
|
|
1169
1186
|
if days_difference > DEFAULT_VALIDITY_DAYS and current_time >= time(DEFAULT_VALIDITY_HOURS, DEFAULT_VALIDITY_MINUTES):
|
|
1170
1187
|
return "obsolete"
|
|
1171
1188
|
return "updated"
|
|
@@ -1184,14 +1201,13 @@ class MeteocatDailyForecastStatusSensor(CoordinatorEntity[MeteocatEntityCoordina
|
|
|
1184
1201
|
"""Return the device info."""
|
|
1185
1202
|
return DeviceInfo(
|
|
1186
1203
|
identifiers={(DOMAIN, self._town_id)},
|
|
1187
|
-
name="Meteocat
|
|
1204
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1188
1205
|
manufacturer="Meteocat",
|
|
1189
1206
|
model="Meteocat API",
|
|
1190
1207
|
)
|
|
1191
1208
|
|
|
1192
1209
|
class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorEntity):
|
|
1193
|
-
|
|
1194
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1210
|
+
_attr_has_entity_name = True
|
|
1195
1211
|
|
|
1196
1212
|
def __init__(self, uvi_coordinator, description, entry_data):
|
|
1197
1213
|
super().__init__(uvi_coordinator)
|
|
@@ -1199,11 +1215,7 @@ class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorE
|
|
|
1199
1215
|
self._town_name = entry_data["town_name"]
|
|
1200
1216
|
self._town_id = entry_data["town_id"]
|
|
1201
1217
|
self._station_id = entry_data["station_id"]
|
|
1202
|
-
|
|
1203
|
-
# Unique ID for the entity
|
|
1204
1218
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_uvi_status"
|
|
1205
|
-
|
|
1206
|
-
# Assign entity_category if defined in the description
|
|
1207
1219
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1208
1220
|
|
|
1209
1221
|
def _get_first_date(self):
|
|
@@ -1226,8 +1238,6 @@ class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorE
|
|
|
1226
1238
|
f"hora de contacto a la API >= {DEFAULT_VALIDITY_HOURS}, "
|
|
1227
1239
|
f"minutos de contacto a la API >= {DEFAULT_VALIDITY_MINUTES}."
|
|
1228
1240
|
)
|
|
1229
|
-
|
|
1230
|
-
# Validar fecha y hora según la lógica del coordinador
|
|
1231
1241
|
if days_difference > DEFAULT_VALIDITY_DAYS and current_time >= time(DEFAULT_VALIDITY_HOURS, DEFAULT_VALIDITY_MINUTES):
|
|
1232
1242
|
return "obsolete"
|
|
1233
1243
|
return "updated"
|
|
@@ -1246,13 +1256,13 @@ class MeteocatUviStatusSensor(CoordinatorEntity[MeteocatUviCoordinator], SensorE
|
|
|
1246
1256
|
"""Return the device info."""
|
|
1247
1257
|
return DeviceInfo(
|
|
1248
1258
|
identifiers={(DOMAIN, self._town_id)},
|
|
1249
|
-
name="Meteocat
|
|
1259
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1250
1260
|
manufacturer="Meteocat",
|
|
1251
1261
|
model="Meteocat API",
|
|
1252
1262
|
)
|
|
1253
1263
|
|
|
1254
1264
|
class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], SensorEntity):
|
|
1255
|
-
_attr_has_entity_name = True
|
|
1265
|
+
_attr_has_entity_name = True
|
|
1256
1266
|
|
|
1257
1267
|
def __init__(self, alerts_coordinator, description, entry_data):
|
|
1258
1268
|
super().__init__(alerts_coordinator)
|
|
@@ -1262,11 +1272,7 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1262
1272
|
self._station_id = entry_data["station_id"]
|
|
1263
1273
|
self._region_id = entry_data["region_id"]
|
|
1264
1274
|
self._limit_prediccio = entry_data["limit_prediccio"]
|
|
1265
|
-
|
|
1266
|
-
# Unique ID for the entity
|
|
1267
1275
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_alert_status"
|
|
1268
|
-
|
|
1269
|
-
# Assign entity_category if defined in the description
|
|
1270
1276
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1271
1277
|
|
|
1272
1278
|
def _get_data_update(self):
|
|
@@ -1289,7 +1295,6 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1289
1295
|
multiplier = ALERT_VALIDITY_MULTIPLIER_500
|
|
1290
1296
|
else:
|
|
1291
1297
|
multiplier = ALERT_VALIDITY_MULTIPLIER_DEFAULT
|
|
1292
|
-
|
|
1293
1298
|
return timedelta(minutes=DEFAULT_ALERT_VALIDITY_TIME * multiplier)
|
|
1294
1299
|
|
|
1295
1300
|
@property
|
|
@@ -1298,11 +1303,8 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1298
1303
|
data_update = self._get_data_update()
|
|
1299
1304
|
if not data_update:
|
|
1300
1305
|
return "unknown"
|
|
1301
|
-
|
|
1302
1306
|
current_time = datetime.now(ZoneInfo("UTC"))
|
|
1303
1307
|
validity_duration = self._get_validity_duration()
|
|
1304
|
-
|
|
1305
|
-
# Comprobar si el archivo de alertas está obsoleto
|
|
1306
1308
|
if current_time - data_update >= validity_duration:
|
|
1307
1309
|
return "obsolete"
|
|
1308
1310
|
return "updated"
|
|
@@ -1328,7 +1330,6 @@ class MeteocatAlertStatusSensor(CoordinatorEntity[MeteocatAlertsCoordinator], Se
|
|
|
1328
1330
|
|
|
1329
1331
|
class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinator], SensorEntity):
|
|
1330
1332
|
"""Sensor dinámico que muestra el estado de las alertas por región."""
|
|
1331
|
-
|
|
1332
1333
|
METEOR_MAPPING = {
|
|
1333
1334
|
"Temps violent": "violent_weather",
|
|
1334
1335
|
"Intensitat de pluja": "rain_intensity",
|
|
@@ -1340,8 +1341,7 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1340
1341
|
"Calor": "heat",
|
|
1341
1342
|
"Calor nocturna": "night_heat",
|
|
1342
1343
|
}
|
|
1343
|
-
|
|
1344
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1344
|
+
_attr_has_entity_name = True
|
|
1345
1345
|
|
|
1346
1346
|
def __init__(self, alerts_region_coordinator, description, entry_data):
|
|
1347
1347
|
super().__init__(alerts_region_coordinator)
|
|
@@ -1350,11 +1350,7 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1350
1350
|
self._town_id = entry_data["town_id"]
|
|
1351
1351
|
self._station_id = entry_data["station_id"]
|
|
1352
1352
|
self._region_id = entry_data["region_id"]
|
|
1353
|
-
|
|
1354
|
-
# Unique ID for the entity
|
|
1355
1353
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_alerts"
|
|
1356
|
-
|
|
1357
|
-
# Assign entity_category if defined in the description
|
|
1358
1354
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1359
1355
|
|
|
1360
1356
|
def _map_meteor_case_insensitive(self, meteor: str) -> str:
|
|
@@ -1373,8 +1369,6 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1373
1369
|
def extra_state_attributes(self):
|
|
1374
1370
|
"""Devuelve los atributos extra del sensor con los nombres traducidos."""
|
|
1375
1371
|
meteor_details = self.coordinator.data.get("detalles", {}).get("meteor", {})
|
|
1376
|
-
|
|
1377
|
-
# Convertimos las claves al formato deseado usando el mapping
|
|
1378
1372
|
attributes = {}
|
|
1379
1373
|
for i, meteor in enumerate(meteor_details.keys()):
|
|
1380
1374
|
mapped_name = self._map_meteor_case_insensitive(meteor)
|
|
@@ -1382,7 +1376,6 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1382
1376
|
_LOGGER.warning("Meteor desconocido sin mapeo: '%s'. Añadirlo a 'METEOR_MAPPING' del coordinador 'MeteocatAlertRegionSensor' si es necesario.", meteor)
|
|
1383
1377
|
mapped_name = "unknown"
|
|
1384
1378
|
attributes[f"alert_{i+1}"] = mapped_name
|
|
1385
|
-
|
|
1386
1379
|
_LOGGER.info("Atributos traducidos del sensor: %s", attributes)
|
|
1387
1380
|
return attributes
|
|
1388
1381
|
|
|
@@ -1391,7 +1384,7 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1391
1384
|
"""Devuelve la información del dispositivo."""
|
|
1392
1385
|
return DeviceInfo(
|
|
1393
1386
|
identifiers={(DOMAIN, self._town_id)},
|
|
1394
|
-
name="Meteocat
|
|
1387
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1395
1388
|
manufacturer="Meteocat",
|
|
1396
1389
|
model="Meteocat API",
|
|
1397
1390
|
)
|
|
@@ -1408,12 +1401,10 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1408
1401
|
ALERT_WARM_NIGHT: "Calor nocturna",
|
|
1409
1402
|
ALERT_SNOW: "Neu acumulada en 24 hores",
|
|
1410
1403
|
}
|
|
1411
|
-
|
|
1412
1404
|
STATE_MAPPING = {
|
|
1413
1405
|
"Obert": "opened",
|
|
1414
1406
|
"Tancat": "closed",
|
|
1415
1407
|
}
|
|
1416
|
-
|
|
1417
1408
|
UMBRAL_MAPPING = {
|
|
1418
1409
|
"Ratxes de vent > 25 m/s": "wind_gusts_25",
|
|
1419
1410
|
"Esclafits": "microburst",
|
|
@@ -1447,8 +1438,7 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1447
1438
|
"gruix > 2 cm a cotes superiors a 300 metres fins a 600 metres": "thickness_2_at_300",
|
|
1448
1439
|
"gruix ≥ 0 cm a cotes inferiors a 300 metres": "thickness_0_at_300",
|
|
1449
1440
|
}
|
|
1450
|
-
|
|
1451
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1441
|
+
_attr_has_entity_name = True
|
|
1452
1442
|
|
|
1453
1443
|
def __init__(self, alerts_region_coordinator, description, entry_data):
|
|
1454
1444
|
super().__init__(alerts_region_coordinator)
|
|
@@ -1457,14 +1447,8 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1457
1447
|
self._town_id = entry_data["town_id"]
|
|
1458
1448
|
self._station_id = entry_data["station_id"]
|
|
1459
1449
|
self._region_id = entry_data["region_id"]
|
|
1460
|
-
|
|
1461
|
-
# Unique ID for the entity
|
|
1462
1450
|
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
1451
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1466
|
-
|
|
1467
|
-
# Log para depuración
|
|
1468
1452
|
_LOGGER.debug(
|
|
1469
1453
|
"Inicializando sensor: %s, Unique ID: %s",
|
|
1470
1454
|
self.entity_description.name,
|
|
@@ -1494,10 +1478,7 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1494
1478
|
meteor_type = self.METEOR_MAPPING.get(self.entity_description.key)
|
|
1495
1479
|
if not meteor_type:
|
|
1496
1480
|
return "Desconocido"
|
|
1497
|
-
|
|
1498
1481
|
meteor_data = self._get_meteor_data_case_insensitive(meteor_type)
|
|
1499
|
-
|
|
1500
|
-
# Convertir estado para translation_key
|
|
1501
1482
|
estado_original = meteor_data.get("estado", "Tancat")
|
|
1502
1483
|
return self.STATE_MAPPING.get(estado_original, "unknown")
|
|
1503
1484
|
|
|
@@ -1512,22 +1493,17 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1512
1493
|
self.coordinator.data.get("detalles", {}).get("meteor", {}).keys(),
|
|
1513
1494
|
)
|
|
1514
1495
|
return "unknown"
|
|
1515
|
-
|
|
1516
1496
|
meteor_data = self._get_meteor_data_case_insensitive(meteor_type)
|
|
1517
1497
|
if not meteor_data:
|
|
1518
1498
|
return {}
|
|
1519
|
-
|
|
1520
|
-
# Convertir umbral para translation_key
|
|
1521
1499
|
umbral_original = meteor_data.get("umbral")
|
|
1522
1500
|
umbral_convertido = self._get_umbral_case_insensitive(umbral_original)
|
|
1523
|
-
|
|
1524
1501
|
if umbral_convertido == "unknown" and umbral_original is not None:
|
|
1525
1502
|
_LOGGER.warning(
|
|
1526
1503
|
"Umbral desconocido para sensor %s: '%s'. Añadirlo a 'UMBRAL_MAPPING' del coordinador 'MeteocatAlertMeteorSensor' si es necesario.",
|
|
1527
1504
|
self.entity_description.key,
|
|
1528
1505
|
umbral_original
|
|
1529
1506
|
)
|
|
1530
|
-
|
|
1531
1507
|
return {
|
|
1532
1508
|
"inicio": meteor_data.get("inicio"),
|
|
1533
1509
|
"fin": meteor_data.get("fin"),
|
|
@@ -1544,13 +1520,13 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1544
1520
|
"""Devuelve la información del dispositivo."""
|
|
1545
1521
|
return DeviceInfo(
|
|
1546
1522
|
identifiers={(DOMAIN, self._town_id)},
|
|
1547
|
-
name="Meteocat
|
|
1523
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1548
1524
|
manufacturer="Meteocat",
|
|
1549
1525
|
model="Meteocat API",
|
|
1550
1526
|
)
|
|
1551
1527
|
|
|
1552
1528
|
class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], SensorEntity):
|
|
1553
|
-
_attr_has_entity_name = True
|
|
1529
|
+
_attr_has_entity_name = True
|
|
1554
1530
|
|
|
1555
1531
|
def __init__(self, quotes_coordinator, description, entry_data):
|
|
1556
1532
|
super().__init__(quotes_coordinator)
|
|
@@ -1558,11 +1534,7 @@ class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], Se
|
|
|
1558
1534
|
self._town_name = entry_data["town_name"]
|
|
1559
1535
|
self._town_id = entry_data["town_id"]
|
|
1560
1536
|
self._station_id = entry_data["station_id"]
|
|
1561
|
-
|
|
1562
|
-
# Unique ID for the entity
|
|
1563
1537
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_quota_status"
|
|
1564
|
-
|
|
1565
|
-
# Assign entity_category if defined in the description
|
|
1566
1538
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1567
1539
|
|
|
1568
1540
|
def _get_data_update(self):
|
|
@@ -1581,13 +1553,9 @@ class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], Se
|
|
|
1581
1553
|
data_update = self._get_data_update()
|
|
1582
1554
|
if not data_update:
|
|
1583
1555
|
return "unknown"
|
|
1584
|
-
|
|
1585
1556
|
current_time = datetime.now(ZoneInfo("UTC"))
|
|
1586
|
-
|
|
1587
|
-
# Comprobar si el archivo de alertas está obsoleto
|
|
1588
1557
|
if current_time - data_update >= timedelta(minutes=DEFAULT_QUOTES_VALIDITY_TIME):
|
|
1589
1558
|
return "obsolete"
|
|
1590
|
-
|
|
1591
1559
|
return "updated"
|
|
1592
1560
|
|
|
1593
1561
|
@property
|
|
@@ -1611,8 +1579,6 @@ class MeteocatQuotaStatusSensor(CoordinatorEntity[MeteocatQuotesCoordinator], Se
|
|
|
1611
1579
|
|
|
1612
1580
|
class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], SensorEntity):
|
|
1613
1581
|
"""Representation of Meteocat Quota sensors."""
|
|
1614
|
-
|
|
1615
|
-
# Mapeo de claves en sensor.py a nombres en quotes.json
|
|
1616
1582
|
QUOTA_MAPPING = {
|
|
1617
1583
|
"quota_xdde": "XDDE",
|
|
1618
1584
|
"quota_prediccio": "Prediccio",
|
|
@@ -1620,15 +1586,12 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1620
1586
|
"quota_xema": "XEMA",
|
|
1621
1587
|
"quota_queries": "Quota",
|
|
1622
1588
|
}
|
|
1623
|
-
|
|
1624
|
-
# Mapeo de periodos para facilitar la traducción del estado
|
|
1625
1589
|
PERIOD_STATE_MAPPING = {
|
|
1626
1590
|
"Setmanal": "weekly",
|
|
1627
1591
|
"Mensual": "monthly",
|
|
1628
1592
|
"Anual": "annual",
|
|
1629
1593
|
}
|
|
1630
|
-
|
|
1631
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1594
|
+
_attr_has_entity_name = True
|
|
1632
1595
|
|
|
1633
1596
|
def __init__(self, quotes_file_coordinator, description, entry_data):
|
|
1634
1597
|
super().__init__(quotes_file_coordinator)
|
|
@@ -1636,28 +1599,20 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1636
1599
|
self._town_name = entry_data["town_name"]
|
|
1637
1600
|
self._town_id = entry_data["town_id"]
|
|
1638
1601
|
self._station_id = entry_data["station_id"]
|
|
1639
|
-
|
|
1640
|
-
# Unique ID for the entity
|
|
1641
1602
|
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
1603
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1645
1604
|
|
|
1646
1605
|
def _get_plan_data(self):
|
|
1647
1606
|
"""Encuentra los datos del plan correspondiente al sensor actual."""
|
|
1648
1607
|
if not self.coordinator.data:
|
|
1649
1608
|
return None
|
|
1650
|
-
|
|
1651
1609
|
plan_name = self.QUOTA_MAPPING.get(self.entity_description.key)
|
|
1652
|
-
|
|
1653
1610
|
if not plan_name:
|
|
1654
1611
|
_LOGGER.error(f"No se encontró un mapeo para la clave: {self.entity_description.key}")
|
|
1655
1612
|
return None
|
|
1656
|
-
|
|
1657
1613
|
for plan in self.coordinator.data.get("plans", []):
|
|
1658
1614
|
if plan.get("nom") == plan_name:
|
|
1659
|
-
return plan
|
|
1660
|
-
|
|
1615
|
+
return plan
|
|
1661
1616
|
_LOGGER.warning(f"No se encontró el plan '{plan_name}' en los datos del coordinador.")
|
|
1662
1617
|
return None
|
|
1663
1618
|
|
|
@@ -1665,18 +1620,14 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1665
1620
|
def native_value(self):
|
|
1666
1621
|
"""Devuelve el estado de la cuota: 'ok' si no se ha excedido, 'exceeded' si se ha superado."""
|
|
1667
1622
|
plan = self._get_plan_data()
|
|
1668
|
-
|
|
1669
1623
|
if not plan:
|
|
1670
1624
|
return None
|
|
1671
|
-
|
|
1672
1625
|
max_consultes = plan.get("maxConsultes")
|
|
1673
1626
|
consultes_realitzades = plan.get("consultesRealitzades")
|
|
1674
|
-
|
|
1675
1627
|
if max_consultes is None or consultes_realitzades is None or \
|
|
1676
1628
|
not isinstance(max_consultes, (int, float)) or not isinstance(consultes_realitzades, (int, float)):
|
|
1677
1629
|
_LOGGER.warning(f"Datos inválidos para el plan '{plan.get('nom', 'unknown')}': {plan}")
|
|
1678
1630
|
return None
|
|
1679
|
-
|
|
1680
1631
|
return "ok" if consultes_realitzades <= max_consultes else "exceeded"
|
|
1681
1632
|
|
|
1682
1633
|
@property
|
|
@@ -1684,21 +1635,16 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1684
1635
|
"""Devuelve atributos adicionales del estado del sensor."""
|
|
1685
1636
|
attributes = super().extra_state_attributes or {}
|
|
1686
1637
|
plan = self._get_plan_data()
|
|
1687
|
-
|
|
1688
1638
|
if not plan:
|
|
1689
1639
|
return {}
|
|
1690
|
-
|
|
1691
|
-
# Aplicar el mapeo de periodos
|
|
1692
1640
|
period = plan.get("periode", "desconocido")
|
|
1693
|
-
translated_period = self.PERIOD_STATE_MAPPING.get(period, period)
|
|
1694
|
-
|
|
1641
|
+
translated_period = self.PERIOD_STATE_MAPPING.get(period, period)
|
|
1695
1642
|
attributes.update({
|
|
1696
1643
|
"period": translated_period,
|
|
1697
1644
|
"max_queries": plan.get("maxConsultes"),
|
|
1698
1645
|
"made_queries": plan.get("consultesRealitzades"),
|
|
1699
1646
|
"remain_queries": plan.get("consultesRestants"),
|
|
1700
1647
|
})
|
|
1701
|
-
|
|
1702
1648
|
return attributes
|
|
1703
1649
|
|
|
1704
1650
|
@property
|
|
@@ -1712,7 +1658,7 @@ class MeteocatQuotaSensor(CoordinatorEntity[MeteocatQuotesFileCoordinator], Sens
|
|
|
1712
1658
|
)
|
|
1713
1659
|
|
|
1714
1660
|
class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinator], SensorEntity):
|
|
1715
|
-
_attr_has_entity_name = True
|
|
1661
|
+
_attr_has_entity_name = True
|
|
1716
1662
|
|
|
1717
1663
|
def __init__(self, lightning_coordinator, description, entry_data):
|
|
1718
1664
|
super().__init__(lightning_coordinator)
|
|
@@ -1720,11 +1666,7 @@ class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinat
|
|
|
1720
1666
|
self._town_name = entry_data["town_name"]
|
|
1721
1667
|
self._town_id = entry_data["town_id"]
|
|
1722
1668
|
self._station_id = entry_data["station_id"]
|
|
1723
|
-
|
|
1724
|
-
# Unique ID for the entity
|
|
1725
1669
|
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_lightning_status"
|
|
1726
|
-
|
|
1727
|
-
# Assign entity_category if defined in the description
|
|
1728
1670
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1729
1671
|
|
|
1730
1672
|
def _get_data_update(self):
|
|
@@ -1732,8 +1674,8 @@ class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinat
|
|
|
1732
1674
|
data_update = self.coordinator.data.get("actualizado")
|
|
1733
1675
|
if data_update:
|
|
1734
1676
|
try:
|
|
1735
|
-
local_time = datetime.fromisoformat(data_update)
|
|
1736
|
-
return local_time.astimezone(ZoneInfo("UTC"))
|
|
1677
|
+
local_time = datetime.fromisoformat(data_update)
|
|
1678
|
+
return local_time.astimezone(ZoneInfo("UTC"))
|
|
1737
1679
|
except ValueError:
|
|
1738
1680
|
_LOGGER.error("Formato de fecha de actualización inválido: %s", data_update)
|
|
1739
1681
|
return None
|
|
@@ -1752,15 +1694,11 @@ class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinat
|
|
|
1752
1694
|
data_update = self._get_data_update()
|
|
1753
1695
|
if not data_update:
|
|
1754
1696
|
return "unknown"
|
|
1755
|
-
|
|
1756
1697
|
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)
|
|
1698
|
+
current_time = now.time()
|
|
1699
|
+
offset = now.utcoffset().total_seconds() / 3600
|
|
1761
1700
|
validity_start_time = time(int(DEFAULT_LIGHTNING_VALIDITY_HOURS + offset), DEFAULT_LIGHTNING_VALIDITY_MINUTES)
|
|
1762
1701
|
validity_duration = timedelta(minutes=DEFAULT_LIGHTNING_VALIDITY_TIME)
|
|
1763
|
-
|
|
1764
1702
|
return self._determine_status(now, data_update, current_time, validity_start_time, validity_duration)
|
|
1765
1703
|
|
|
1766
1704
|
@property
|
|
@@ -1784,8 +1722,7 @@ class MeteocatLightningStatusSensor(CoordinatorEntity[MeteocatLightningCoordinat
|
|
|
1784
1722
|
|
|
1785
1723
|
class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator], SensorEntity):
|
|
1786
1724
|
"""Representation of Meteocat Lightning sensors."""
|
|
1787
|
-
|
|
1788
|
-
_attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
|
|
1725
|
+
_attr_has_entity_name = True
|
|
1789
1726
|
|
|
1790
1727
|
def __init__(self, lightning_file_coordinator, description, entry_data):
|
|
1791
1728
|
super().__init__(lightning_file_coordinator)
|
|
@@ -1794,11 +1731,7 @@ class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator
|
|
|
1794
1731
|
self._town_id = entry_data["town_id"]
|
|
1795
1732
|
self._station_id = entry_data["station_id"]
|
|
1796
1733
|
self._region_id = entry_data["region_id"]
|
|
1797
|
-
|
|
1798
|
-
# Unique ID for the entity
|
|
1799
1734
|
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
1735
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1803
1736
|
|
|
1804
1737
|
@property
|
|
@@ -1820,8 +1753,6 @@ class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator
|
|
|
1820
1753
|
data = self.coordinator.data.get("town", {})
|
|
1821
1754
|
else:
|
|
1822
1755
|
return attributes
|
|
1823
|
-
|
|
1824
|
-
# Agregar atributos específicos
|
|
1825
1756
|
attributes.update({
|
|
1826
1757
|
"cloud_cloud": data.get("cc", 0),
|
|
1827
1758
|
"cloud_ground_neg": data.get("cg-", 0),
|
|
@@ -1837,4 +1768,124 @@ class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator
|
|
|
1837
1768
|
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1838
1769
|
manufacturer="Meteocat",
|
|
1839
1770
|
model="Meteocat API",
|
|
1840
|
-
)
|
|
1771
|
+
)
|
|
1772
|
+
|
|
1773
|
+
class MeteocatSunSensor(CoordinatorEntity[MeteocatSunFileCoordinator], SensorEntity):
|
|
1774
|
+
"""Representation of Meteocat Sun sensors (sunrise/sunset)."""
|
|
1775
|
+
_attr_has_entity_name = True
|
|
1776
|
+
|
|
1777
|
+
def __init__(self, sun_file_coordinator, description, entry_data):
|
|
1778
|
+
"""Initialize the Sun sensor."""
|
|
1779
|
+
super().__init__(sun_file_coordinator)
|
|
1780
|
+
self.entity_description = description
|
|
1781
|
+
self._town_name = entry_data["town_name"]
|
|
1782
|
+
self._town_id = entry_data["town_id"]
|
|
1783
|
+
self._station_id = entry_data["station_id"]
|
|
1784
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
|
|
1785
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1786
|
+
|
|
1787
|
+
_LOGGER.debug(
|
|
1788
|
+
"Inicializando sensor: %s, Unique ID: %s",
|
|
1789
|
+
self.entity_description.name,
|
|
1790
|
+
self._attr_unique_id,
|
|
1791
|
+
)
|
|
1792
|
+
|
|
1793
|
+
@property
|
|
1794
|
+
def native_value(self):
|
|
1795
|
+
"""Return the sunrise or sunset time as a datetime."""
|
|
1796
|
+
if self.entity_description.key in {SUNRISE, SUNSET}:
|
|
1797
|
+
time_str = self.coordinator.data.get(self.entity_description.key)
|
|
1798
|
+
if time_str:
|
|
1799
|
+
try:
|
|
1800
|
+
return datetime.fromisoformat(time_str)
|
|
1801
|
+
except ValueError:
|
|
1802
|
+
_LOGGER.error("Formato de fecha inválido para %s: %s", self.entity_description.key, time_str)
|
|
1803
|
+
return None
|
|
1804
|
+
return None
|
|
1805
|
+
|
|
1806
|
+
@property
|
|
1807
|
+
def extra_state_attributes(self):
|
|
1808
|
+
"""Return additional attributes for the sensor."""
|
|
1809
|
+
attributes = super().extra_state_attributes or {}
|
|
1810
|
+
if self.entity_description.key in {SUNRISE, SUNSET}:
|
|
1811
|
+
time_str = self.coordinator.data.get(self.entity_description.key)
|
|
1812
|
+
if time_str:
|
|
1813
|
+
try:
|
|
1814
|
+
dt = datetime.fromisoformat(time_str)
|
|
1815
|
+
attributes["friendly_time"] = dt.strftime("%H:%M")
|
|
1816
|
+
except ValueError:
|
|
1817
|
+
attributes["friendly_time"] = None
|
|
1818
|
+
else:
|
|
1819
|
+
attributes["friendly_time"] = None
|
|
1820
|
+
return attributes
|
|
1821
|
+
|
|
1822
|
+
@property
|
|
1823
|
+
def device_info(self) -> DeviceInfo:
|
|
1824
|
+
"""Return the device info."""
|
|
1825
|
+
return DeviceInfo(
|
|
1826
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
1827
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1828
|
+
manufacturer="Meteocat",
|
|
1829
|
+
model="Meteocat API",
|
|
1830
|
+
)
|
|
1831
|
+
|
|
1832
|
+
class MeteocatSunStatusSensor(CoordinatorEntity[MeteocatSunCoordinator], SensorEntity):
|
|
1833
|
+
"""Representation of Meteocat Sun file status sensor."""
|
|
1834
|
+
_attr_has_entity_name = True
|
|
1835
|
+
|
|
1836
|
+
def __init__(self, sun_coordinator, description, entry_data):
|
|
1837
|
+
"""Initialize the Sun status sensor."""
|
|
1838
|
+
super().__init__(sun_coordinator)
|
|
1839
|
+
self.entity_description = description
|
|
1840
|
+
self._town_name = entry_data["town_name"]
|
|
1841
|
+
self._town_id = entry_data["town_id"]
|
|
1842
|
+
self._station_id = entry_data["station_id"]
|
|
1843
|
+
self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_sun_status"
|
|
1844
|
+
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1845
|
+
|
|
1846
|
+
_LOGGER.debug(
|
|
1847
|
+
"Inicializando sensor: %s, Unique ID: %s",
|
|
1848
|
+
self.entity_description.name,
|
|
1849
|
+
self._attr_unique_id,
|
|
1850
|
+
)
|
|
1851
|
+
|
|
1852
|
+
def _get_data_update(self):
|
|
1853
|
+
"""Obtain the update date from the coordinator and convert to UTC."""
|
|
1854
|
+
data_update = self.coordinator.data.get("actualizado")
|
|
1855
|
+
if data_update:
|
|
1856
|
+
try:
|
|
1857
|
+
local_time = datetime.fromisoformat(data_update)
|
|
1858
|
+
return local_time.astimezone(ZoneInfo("UTC"))
|
|
1859
|
+
except ValueError:
|
|
1860
|
+
_LOGGER.error("Formato de fecha de actualización inválido: %s", data_update)
|
|
1861
|
+
return None
|
|
1862
|
+
|
|
1863
|
+
@property
|
|
1864
|
+
def native_value(self):
|
|
1865
|
+
"""Return the status of the sun file based on the update date."""
|
|
1866
|
+
data_update = self._get_data_update()
|
|
1867
|
+
if not data_update:
|
|
1868
|
+
return "unknown"
|
|
1869
|
+
now = datetime.now(timezone.utc).astimezone(TIMEZONE)
|
|
1870
|
+
if (now - data_update) > timedelta(days=1):
|
|
1871
|
+
return "obsolete"
|
|
1872
|
+
return "updated"
|
|
1873
|
+
|
|
1874
|
+
@property
|
|
1875
|
+
def extra_state_attributes(self):
|
|
1876
|
+
"""Return additional attributes for the sensor."""
|
|
1877
|
+
attributes = super().extra_state_attributes or {}
|
|
1878
|
+
data_update = self._get_data_update()
|
|
1879
|
+
if data_update:
|
|
1880
|
+
attributes["update_date"] = data_update.isoformat()
|
|
1881
|
+
return attributes
|
|
1882
|
+
|
|
1883
|
+
@property
|
|
1884
|
+
def device_info(self) -> DeviceInfo:
|
|
1885
|
+
"""Return the device info."""
|
|
1886
|
+
return DeviceInfo(
|
|
1887
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
1888
|
+
name=f"Meteocat {self._station_id} {self._town_name}",
|
|
1889
|
+
manufacturer="Meteocat",
|
|
1890
|
+
model="Meteocat API",
|
|
1891
|
+
)
|