meteocat 3.1.0 → 4.0.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 +45 -45
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/ISSUE_TEMPLATE/improvement.md +39 -39
- package/.github/ISSUE_TEMPLATE/new_function.md +41 -41
- package/.github/labels.yml +63 -63
- package/.github/workflows/autocloser.yaml +27 -27
- package/.github/workflows/close-on-label.yml +48 -48
- package/.github/workflows/force-sync-labels.yml +18 -18
- package/.github/workflows/hassfest.yaml +13 -13
- package/.github/workflows/publish-zip.yml +67 -67
- package/.github/workflows/release.yml +41 -41
- package/.github/workflows/stale.yml +63 -63
- package/.github/workflows/sync-gitlab.yml +107 -107
- package/.github/workflows/sync-labels.yml +21 -21
- package/.github/workflows/validate.yaml +16 -16
- package/.pre-commit-config.yaml +37 -37
- package/.releaserc +37 -37
- package/AUTHORS.md +13 -13
- package/CHANGELOG.md +954 -898
- package/README.md +207 -204
- package/conftest.py +11 -11
- package/custom_components/meteocat/__init__.py +298 -293
- package/custom_components/meteocat/condition.py +63 -59
- package/custom_components/meteocat/config_flow.py +613 -435
- package/custom_components/meteocat/const.py +132 -120
- package/custom_components/meteocat/coordinator.py +1040 -205
- package/custom_components/meteocat/helpers.py +58 -63
- package/custom_components/meteocat/manifest.json +25 -24
- package/custom_components/meteocat/options_flow.py +287 -277
- package/custom_components/meteocat/sensor.py +366 -4
- package/custom_components/meteocat/strings.json +1058 -867
- package/custom_components/meteocat/translations/ca.json +1058 -867
- package/custom_components/meteocat/translations/en.json +1058 -867
- package/custom_components/meteocat/translations/es.json +1058 -867
- package/custom_components/meteocat/version.py +1 -1
- package/custom_components/meteocat/weather.py +218 -218
- package/filetree.py +48 -48
- package/filetree.txt +79 -70
- package/hacs.json +8 -8
- 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/info.md +11 -11
- package/package.json +22 -22
- package/poetry.lock +3222 -3222
- package/pyproject.toml +68 -68
- package/requirements.test.txt +3 -3
- package/setup.cfg +64 -64
- package/setup.py +10 -10
- package/tests/bandit.yaml +17 -17
- package/tests/conftest.py +19 -19
- package/tests/test_init.py +9 -9
|
@@ -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
|
|
@@ -105,9 +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,
|
|
108
110
|
SUNRISE,
|
|
109
111
|
SUNSET,
|
|
110
112
|
SUN_FILE_STATUS,
|
|
113
|
+
MOON_PHASE,
|
|
114
|
+
MOON_FILE_STATUS,
|
|
115
|
+
MOONRISE,
|
|
116
|
+
MOONSET,
|
|
111
117
|
)
|
|
112
118
|
|
|
113
119
|
from .coordinator import (
|
|
@@ -127,6 +133,8 @@ from .coordinator import (
|
|
|
127
133
|
MeteocatLightningFileCoordinator,
|
|
128
134
|
MeteocatSunCoordinator,
|
|
129
135
|
MeteocatSunFileCoordinator,
|
|
136
|
+
MeteocatMoonCoordinator,
|
|
137
|
+
MeteocatMoonFileCoordinator,
|
|
130
138
|
)
|
|
131
139
|
|
|
132
140
|
# Definir la zona horaria local
|
|
@@ -437,7 +445,13 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
437
445
|
icon="mdi:counter",
|
|
438
446
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
439
447
|
),
|
|
440
|
-
|
|
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
|
+
),
|
|
441
455
|
MeteocatSensorEntityDescription(
|
|
442
456
|
key=SUNRISE,
|
|
443
457
|
translation_key="sunrise",
|
|
@@ -456,6 +470,41 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
|
456
470
|
icon="mdi:update",
|
|
457
471
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
458
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
|
+
),
|
|
459
508
|
)
|
|
460
509
|
|
|
461
510
|
@callback
|
|
@@ -480,6 +529,8 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
480
529
|
lightning_file_coordinator = entry_data.get("lightning_file_coordinator")
|
|
481
530
|
sun_coordinator = entry_data.get("sun_coordinator")
|
|
482
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")
|
|
483
534
|
|
|
484
535
|
# Sensores generales
|
|
485
536
|
async_add_entities(
|
|
@@ -624,6 +675,13 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
624
675
|
if description.key == SUN_FILE_STATUS
|
|
625
676
|
)
|
|
626
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
|
+
|
|
627
685
|
# Sensores de sol
|
|
628
686
|
async_add_entities(
|
|
629
687
|
MeteocatSunSensor(sun_file_coordinator, description, entry_data)
|
|
@@ -631,6 +689,26 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
631
689
|
if description.key in {SUNRISE, SUNSET}
|
|
632
690
|
)
|
|
633
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
|
+
)
|
|
634
712
|
|
|
635
713
|
# Cambiar UTC a la zona horaria local
|
|
636
714
|
def convert_to_local_time(utc_time: str, local_tz: str = "Europe/Madrid") -> datetime | None:
|
|
@@ -1376,7 +1454,7 @@ class MeteocatAlertRegionSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1376
1454
|
_LOGGER.warning("Meteor desconocido sin mapeo: '%s'. Añadirlo a 'METEOR_MAPPING' del coordinador 'MeteocatAlertRegionSensor' si es necesario.", meteor)
|
|
1377
1455
|
mapped_name = "unknown"
|
|
1378
1456
|
attributes[f"alert_{i+1}"] = mapped_name
|
|
1379
|
-
_LOGGER.
|
|
1457
|
+
_LOGGER.debug("Atributos traducidos del sensor: %s", attributes)
|
|
1380
1458
|
return attributes
|
|
1381
1459
|
|
|
1382
1460
|
@property
|
|
@@ -1770,6 +1848,83 @@ class MeteocatLightningSensor(CoordinatorEntity[MeteocatLightningFileCoordinator
|
|
|
1770
1848
|
model="Meteocat API",
|
|
1771
1849
|
)
|
|
1772
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
|
+
|
|
1773
1928
|
class MeteocatSunSensor(CoordinatorEntity[MeteocatSunFileCoordinator], SensorEntity):
|
|
1774
1929
|
"""Representation of Meteocat Sun sensors (sunrise/sunset)."""
|
|
1775
1930
|
_attr_has_entity_name = True
|
|
@@ -1785,7 +1940,7 @@ class MeteocatSunSensor(CoordinatorEntity[MeteocatSunFileCoordinator], SensorEnt
|
|
|
1785
1940
|
self._attr_entity_category = getattr(description, "entity_category", None)
|
|
1786
1941
|
|
|
1787
1942
|
_LOGGER.debug(
|
|
1788
|
-
"Inicializando sensor: %s, Unique ID: %s",
|
|
1943
|
+
"Inicializando sensor solar: %s, Unique ID: %s",
|
|
1789
1944
|
self.entity_description.name,
|
|
1790
1945
|
self._attr_unique_id,
|
|
1791
1946
|
)
|
|
@@ -1813,10 +1968,20 @@ class MeteocatSunSensor(CoordinatorEntity[MeteocatSunFileCoordinator], SensorEnt
|
|
|
1813
1968
|
try:
|
|
1814
1969
|
dt = datetime.fromisoformat(time_str)
|
|
1815
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
|
+
|
|
1816
1977
|
except ValueError:
|
|
1817
1978
|
attributes["friendly_time"] = None
|
|
1979
|
+
attributes["friendly_date"] = None
|
|
1980
|
+
attributes["friendly_day"] = None
|
|
1818
1981
|
else:
|
|
1819
1982
|
attributes["friendly_time"] = None
|
|
1983
|
+
attributes["friendly_date"] = None
|
|
1984
|
+
attributes["friendly_day"] = None
|
|
1820
1985
|
return attributes
|
|
1821
1986
|
|
|
1822
1987
|
@property
|
|
@@ -1851,7 +2016,7 @@ class MeteocatSunStatusSensor(CoordinatorEntity[MeteocatSunCoordinator], SensorE
|
|
|
1851
2016
|
|
|
1852
2017
|
def _get_data_update(self):
|
|
1853
2018
|
"""Obtain the update date from the coordinator and convert to UTC."""
|
|
1854
|
-
data_update = self.coordinator.data.get("
|
|
2019
|
+
data_update = self.coordinator.data.get("actualitzat", {}).get("dataUpdate")
|
|
1855
2020
|
if data_update:
|
|
1856
2021
|
try:
|
|
1857
2022
|
local_time = datetime.fromisoformat(data_update)
|
|
@@ -1889,3 +2054,200 @@ class MeteocatSunStatusSensor(CoordinatorEntity[MeteocatSunCoordinator], SensorE
|
|
|
1889
2054
|
manufacturer="Meteocat",
|
|
1890
2055
|
model="Meteocat API",
|
|
1891
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
|
+
)
|