meteocat 3.2.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +1 -1
- package/custom_components/meteocat/__init__.py +2 -2
- package/custom_components/meteocat/coordinator.py +248 -68
- package/custom_components/meteocat/manifest.json +1 -1
- package/custom_components/meteocat/sensor.py +3 -1
- package/custom_components/meteocat/strings.json +2 -0
- package/custom_components/meteocat/translations/ca.json +2 -0
- package/custom_components/meteocat/translations/en.json +2 -0
- package/custom_components/meteocat/translations/es.json +2 -0
- package/custom_components/meteocat/version.py +1 -1
- package/filetree.txt +1 -0
- package/hacs.json +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
## [4.0.1](https://github.com/figorr/meteocat/compare/v4.0.0...v4.0.1) (2026-01-04)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add translation for new snow thresholds ([e57a0b6](https://github.com/figorr/meteocat/commit/e57a0b618a241e9df89855a211e0f8b4a68750cf))
|
|
7
|
+
* fix snow meteor name and include new threshold ([fbec1e2](https://github.com/figorr/meteocat/commit/fbec1e23d6b5157a1e02f30bcd4f461b54ee519c))
|
|
8
|
+
* improve cache ([d36c43a](https://github.com/figorr/meteocat/commit/d36c43a831a4515d1fade109fc5b5154b2d425e4))
|
|
9
|
+
|
|
10
|
+
# [4.0.0](https://github.com/figorr/meteocat/compare/v3.2.0...v4.0.0) (2025-11-07)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## 🚀 Release Meteocat integration v4.0.0 — major improvements and new solar/lunar system
|
|
14
|
+
My own custom Python package `Solarmoonpy` is introduced in this major release.
|
|
15
|
+
`Solarmoonpy` added support for new native sun and moon sensors in the Meteocat integration.
|
|
16
|
+
|
|
17
|
+
### ✨ Highlights:
|
|
18
|
+
- Introduced new Python package `Solarmoonpy` to handle all solar and lunar data and event calculations.
|
|
19
|
+
→ Replaces the `astral` dependency completely.
|
|
20
|
+
- Added new solar and lunar sensors to provide richer astronomical data with many extra attributes.
|
|
21
|
+
- Added configuration option to manually adjust default coordinates and elevation.
|
|
22
|
+
→ This allows users to fine-tune their position for more accurate solar and lunar event calculations instead of using the default station coordinates and elevation.
|
|
23
|
+
|
|
24
|
+
### 🛠 Changes:
|
|
25
|
+
- Major refactor and optimization of coordinator logic for better performance and stability.
|
|
26
|
+
- The default validation hour for forecasts is set to 6.
|
|
27
|
+
|
|
28
|
+
### ⚠️ Note:
|
|
29
|
+
- The new v4.0.0 is required for using Meteocat Card v3.0.0 and later.
|
|
30
|
+
- Upgrade is recommended if you plan to use the updated card.
|
|
31
|
+
|
|
1
32
|
# [3.2.0](https://github.com/figorr/meteocat/compare/v3.1.0...v3.2.0) (2025-11-07)
|
|
2
33
|
|
|
3
34
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Meteocat for Home Assistant
|
|
2
2
|

|
|
3
3
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
-
|
|
4
|
+

|
|
5
5
|
[](https://github.com/hacs/integration)
|
|
6
6
|
[](https://github.com/figorr/meteocat/actions/workflows/validate.yaml)
|
|
7
7
|
[](https://github.com/figorr/meteocat/actions/workflows/release.yml)
|
|
@@ -24,7 +24,7 @@ from .const import DOMAIN, PLATFORMS
|
|
|
24
24
|
_LOGGER = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
# Versión
|
|
27
|
-
__version__ = "
|
|
27
|
+
__version__ = "4.0.1"
|
|
28
28
|
|
|
29
29
|
# Definir el esquema de configuración CONFIG_SCHEMA
|
|
30
30
|
CONFIG_SCHEMA = vol.Schema(
|
|
@@ -295,4 +295,4 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
|
295
295
|
|
|
296
296
|
# Intentar eliminar carpetas vacías
|
|
297
297
|
for folder in [assets_folder, files_folder, base_folder]:
|
|
298
|
-
safe_remove(folder, is_folder=True)
|
|
298
|
+
safe_remove(folder, is_folder=True)
|
|
@@ -290,27 +290,54 @@ class MeteocatSensorCoordinator(DataUpdateCoordinator):
|
|
|
290
290
|
raise
|
|
291
291
|
except Exception as err:
|
|
292
292
|
if isinstance(err, ConfigEntryNotReady):
|
|
293
|
-
_LOGGER.exception(
|
|
294
|
-
"No se pudo inicializar el dispositivo (Station ID: %s): %s",
|
|
295
|
-
self.station_id,
|
|
296
|
-
err,
|
|
297
|
-
)
|
|
293
|
+
_LOGGER.exception("No se pudo inicializar el dispositivo (Station ID: %s): %s", self.station_id, err)
|
|
298
294
|
raise
|
|
299
295
|
else:
|
|
300
|
-
_LOGGER.exception(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
err,
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
# Cargar datos en caché si la API falla
|
|
296
|
+
_LOGGER.exception("Error inesperado al obtener datos de sensores (Station ID: %s): %s", self.station_id, err)
|
|
297
|
+
|
|
298
|
+
# === FALLBACK SEGURO ===
|
|
307
299
|
cached_data = await load_json_from_file(self.station_file)
|
|
308
|
-
if cached_data:
|
|
309
|
-
|
|
300
|
+
if cached_data and isinstance(cached_data, list) and cached_data:
|
|
301
|
+
# Buscar la última lectura (cualquier variable)
|
|
302
|
+
last_reading = None
|
|
303
|
+
last_time_str = "unknown"
|
|
304
|
+
for var_block in cached_data:
|
|
305
|
+
for variable in var_block.get("variables", []):
|
|
306
|
+
lectures = variable.get("lectures", [])
|
|
307
|
+
if lectures:
|
|
308
|
+
candidate = lectures[-1].get("data")
|
|
309
|
+
if candidate and (last_reading is None or candidate > last_time_str):
|
|
310
|
+
last_reading = candidate
|
|
311
|
+
last_time_str = candidate
|
|
312
|
+
|
|
313
|
+
# Formatear hora legible
|
|
314
|
+
try:
|
|
315
|
+
if last_time_str != "unknown":
|
|
316
|
+
dt = datetime.fromisoformat(last_time_str.replace("Z", "+00:00"))
|
|
317
|
+
local_dt = dt.astimezone(TIMEZONE)
|
|
318
|
+
display_time = local_dt.strftime("%d/%m/%Y %H:%M")
|
|
319
|
+
else:
|
|
320
|
+
display_time = "unknown"
|
|
321
|
+
except (ValueError, TypeError, AttributeError):
|
|
322
|
+
display_time = last_time_str.split("T")[0] if "T" in last_time_str else last_time_str
|
|
323
|
+
|
|
324
|
+
_LOGGER.warning(
|
|
325
|
+
"SENSOR: API falló → usando caché local:\n"
|
|
326
|
+
" • Estación: %s (%s)\n"
|
|
327
|
+
" • Archivo: %s\n"
|
|
328
|
+
" • Última lectura: %s",
|
|
329
|
+
self.station_name,
|
|
330
|
+
self.station_id,
|
|
331
|
+
self.station_file.name,
|
|
332
|
+
display_time
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
self.async_set_updated_data(cached_data)
|
|
310
336
|
return cached_data
|
|
311
|
-
|
|
312
|
-
_LOGGER.error("No
|
|
313
|
-
|
|
337
|
+
|
|
338
|
+
_LOGGER.error("SENSOR: No hay caché disponible para los datos de la estación %s.", self.station_id)
|
|
339
|
+
self.async_set_updated_data([])
|
|
340
|
+
return []
|
|
314
341
|
|
|
315
342
|
class MeteocatStaticSensorCoordinator(DataUpdateCoordinator):
|
|
316
343
|
"""Coordinator to manage and update static sensor data."""
|
|
@@ -490,13 +517,30 @@ class MeteocatUviCoordinator(DataUpdateCoordinator):
|
|
|
490
517
|
raise
|
|
491
518
|
except Exception as err:
|
|
492
519
|
_LOGGER.exception("Error inesperado al obtener datos del índice UV para %s: %s", self.town_id, err)
|
|
493
|
-
|
|
494
|
-
#
|
|
520
|
+
|
|
521
|
+
# === FALLBACK SEGURO ===
|
|
495
522
|
cached_data = await load_json_from_file(self.uvi_file)
|
|
496
|
-
if cached_data:
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
523
|
+
if cached_data and "uvi" in cached_data and cached_data["uvi"]:
|
|
524
|
+
raw_date = cached_data["uvi"][0].get("date", "unknown")
|
|
525
|
+
# Formatear fecha para log
|
|
526
|
+
try:
|
|
527
|
+
first_date = datetime.strptime(raw_date, "%Y-%m-%d").strftime("%d/%m/%Y")
|
|
528
|
+
except (ValueError, TypeError):
|
|
529
|
+
first_date = raw_date
|
|
530
|
+
|
|
531
|
+
_LOGGER.warning(
|
|
532
|
+
"API UVI falló → usando caché local:\n"
|
|
533
|
+
" • Archivo: %s\n"
|
|
534
|
+
" • Datos desde: %s",
|
|
535
|
+
self.uvi_file.name,
|
|
536
|
+
first_date
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
self.async_set_updated_data(cached_data["uvi"])
|
|
540
|
+
return cached_data["uvi"]
|
|
541
|
+
|
|
542
|
+
_LOGGER.error("No hay datos UVI ni en caché para %s", self.town_id)
|
|
543
|
+
self.async_set_updated_data([])
|
|
500
544
|
return []
|
|
501
545
|
|
|
502
546
|
class MeteocatUviFileCoordinator(BaseFileCoordinator):
|
|
@@ -753,20 +797,36 @@ class MeteocatEntityCoordinator(DataUpdateCoordinator):
|
|
|
753
797
|
raise
|
|
754
798
|
except Exception as err:
|
|
755
799
|
_LOGGER.exception("Error inesperado al obtener datos de predicción: %s", err)
|
|
756
|
-
|
|
757
|
-
#
|
|
758
|
-
# Fallback: usar caché local si todo falla
|
|
759
|
-
# -----------------------------------------------------------------
|
|
800
|
+
|
|
801
|
+
# === FALLBACK SEGURO ===
|
|
760
802
|
hourly_cache = await load_json_from_file(self.hourly_file) or {}
|
|
761
803
|
daily_cache = await load_json_from_file(self.daily_file) or {}
|
|
762
|
-
|
|
804
|
+
|
|
805
|
+
# --- Fecha horaria ---
|
|
806
|
+
h_raw = hourly_cache.get("dies", [{}])[0].get("data", "")
|
|
807
|
+
try:
|
|
808
|
+
h_date = h_raw.replace("Z", "").split("T")[0]
|
|
809
|
+
h_display = datetime.strptime(h_date, "%Y-%m-%d").strftime("%d/%m/%Y")
|
|
810
|
+
except (ValueError, AttributeError, IndexError):
|
|
811
|
+
h_display = "unknown"
|
|
812
|
+
|
|
813
|
+
# --- Fecha diaria ---
|
|
814
|
+
d_raw = daily_cache.get("dies", [{}])[0].get("data", "")
|
|
815
|
+
try:
|
|
816
|
+
d_date = d_raw.replace("Z", "").split("T")[0]
|
|
817
|
+
d_display = datetime.strptime(d_date, "%Y-%m-%d").strftime("%d/%m/%Y")
|
|
818
|
+
except (ValueError, AttributeError, IndexError):
|
|
819
|
+
d_display = "unknown"
|
|
820
|
+
|
|
763
821
|
_LOGGER.warning(
|
|
764
|
-
"
|
|
765
|
-
|
|
766
|
-
"
|
|
767
|
-
|
|
822
|
+
"API falló → usando caché local:\n"
|
|
823
|
+
" • %s → %s\n"
|
|
824
|
+
" • %s → %s",
|
|
825
|
+
self.hourly_file.name, h_display,
|
|
826
|
+
self.daily_file.name, d_display
|
|
768
827
|
)
|
|
769
|
-
|
|
828
|
+
|
|
829
|
+
self.async_set_updated_data({"hourly": hourly_cache, "daily": daily_cache})
|
|
770
830
|
return {"hourly": hourly_cache, "daily": daily_cache}
|
|
771
831
|
|
|
772
832
|
def get_condition_from_code(code: int) -> str:
|
|
@@ -1381,20 +1441,36 @@ class MeteocatAlertsCoordinator(DataUpdateCoordinator):
|
|
|
1381
1441
|
_LOGGER.error("Error al obtener datos de alertas: %s", err)
|
|
1382
1442
|
raise
|
|
1383
1443
|
except Exception as err:
|
|
1384
|
-
_LOGGER.exception("Error
|
|
1385
|
-
|
|
1386
|
-
#
|
|
1444
|
+
_LOGGER.exception("Error al obtener alertas: %s", err)
|
|
1445
|
+
|
|
1446
|
+
# === FALLBACK SEGURO ===
|
|
1387
1447
|
cached_data = await load_json_from_file(self.alerts_file)
|
|
1388
1448
|
if self._is_valid_alert_data(cached_data):
|
|
1449
|
+
update_str = cached_data["actualitzat"]["dataUpdate"]
|
|
1450
|
+
try:
|
|
1451
|
+
update_dt = datetime.fromisoformat(update_str)
|
|
1452
|
+
local_dt = update_dt.astimezone(TIMEZONE)
|
|
1453
|
+
display_time = local_dt.strftime("%d/%m/%Y %H:%M")
|
|
1454
|
+
except (ValueError, TypeError):
|
|
1455
|
+
display_time = update_str
|
|
1456
|
+
|
|
1389
1457
|
_LOGGER.warning(
|
|
1390
|
-
"
|
|
1391
|
-
|
|
1458
|
+
"ALERTAS: API falló → usando caché local:\n"
|
|
1459
|
+
" • Archivo: %s\n"
|
|
1460
|
+
" • Última actualización: %s\n"
|
|
1461
|
+
" • Alertas activas: %d",
|
|
1462
|
+
self.alerts_file.name,
|
|
1463
|
+
display_time
|
|
1392
1464
|
)
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1465
|
+
|
|
1466
|
+
self.async_set_updated_data({
|
|
1467
|
+
"actualizado": cached_data["actualitzat"]["dataUpdate"]
|
|
1468
|
+
})
|
|
1469
|
+
return {"actualizado": cached_data["actualitzat"]["dataUpdate"]}
|
|
1470
|
+
|
|
1471
|
+
_LOGGER.error("ALERTAS: No hay caché disponible. Sin datos de alertas.")
|
|
1472
|
+
self.async_set_updated_data({})
|
|
1473
|
+
return {}
|
|
1398
1474
|
|
|
1399
1475
|
@staticmethod
|
|
1400
1476
|
def _is_valid_alert_data(data: dict) -> bool:
|
|
@@ -1822,16 +1898,40 @@ class MeteocatQuotesCoordinator(DataUpdateCoordinator):
|
|
|
1822
1898
|
_LOGGER.error("Error al obtener cuotas de la API de Meteocat: %s", err)
|
|
1823
1899
|
raise
|
|
1824
1900
|
except Exception as err:
|
|
1825
|
-
_LOGGER.exception("Error
|
|
1826
|
-
|
|
1827
|
-
#
|
|
1901
|
+
_LOGGER.exception("Error al obtener cuotas: %s", err)
|
|
1902
|
+
|
|
1903
|
+
# === FALLBACK SEGURO ===
|
|
1828
1904
|
cached_data = await load_json_from_file(self.quotes_file)
|
|
1829
|
-
if cached_data:
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1905
|
+
if cached_data and "actualitzat" in cached_data and "dataUpdate" in cached_data["actualitzat"]:
|
|
1906
|
+
update_str = cached_data["actualitzat"]["dataUpdate"]
|
|
1907
|
+
try:
|
|
1908
|
+
update_dt = datetime.fromisoformat(update_str)
|
|
1909
|
+
local_dt = update_dt.astimezone(TIMEZONE)
|
|
1910
|
+
display_time = local_dt.strftime("%d/%m/%Y %H:%M")
|
|
1911
|
+
except (ValueError, TypeError):
|
|
1912
|
+
display_time = update_str.split("T")[0]
|
|
1913
|
+
|
|
1914
|
+
# Contar planes activos
|
|
1915
|
+
plans_count = len(cached_data.get("plans", []))
|
|
1916
|
+
|
|
1917
|
+
_LOGGER.warning(
|
|
1918
|
+
"CUOTAS: API falló → usando caché local:\n"
|
|
1919
|
+
" • Archivo: %s\n"
|
|
1920
|
+
" • Última actualización: %s\n"
|
|
1921
|
+
" • Planes registrados: %d",
|
|
1922
|
+
self.quotes_file.name,
|
|
1923
|
+
display_time,
|
|
1924
|
+
plans_count
|
|
1925
|
+
)
|
|
1926
|
+
|
|
1927
|
+
self.async_set_updated_data({
|
|
1928
|
+
"actualizado": cached_data["actualitzat"]["dataUpdate"]
|
|
1929
|
+
})
|
|
1930
|
+
return {"actualizado": cached_data["actualitzat"]["dataUpdate"]}
|
|
1931
|
+
|
|
1932
|
+
_LOGGER.error("CUOTAS: No hay caché disponible. Sin información de consumo.")
|
|
1933
|
+
self.async_set_updated_data({})
|
|
1934
|
+
return {}
|
|
1835
1935
|
|
|
1836
1936
|
class MeteocatQuotesFileCoordinator(BaseFileCoordinator):
|
|
1837
1937
|
"""Coordinator para manejar la actualización de las cuotas desde quotes.json."""
|
|
@@ -1993,16 +2093,36 @@ class MeteocatLightningCoordinator(DataUpdateCoordinator):
|
|
|
1993
2093
|
_LOGGER.warning("Tiempo de espera agotado al obtener los datos de rayos de la API de Meteocat.")
|
|
1994
2094
|
raise ConfigEntryNotReady from err
|
|
1995
2095
|
except Exception as err:
|
|
1996
|
-
_LOGGER.exception("Error
|
|
1997
|
-
|
|
1998
|
-
#
|
|
2096
|
+
_LOGGER.exception("Error al obtener datos de rayos: %s", err)
|
|
2097
|
+
|
|
2098
|
+
# === FALLBACK SEGURO ===
|
|
1999
2099
|
cached_data = await load_json_from_file(self.lightning_file)
|
|
2000
|
-
if cached_data:
|
|
2001
|
-
|
|
2002
|
-
|
|
2100
|
+
if cached_data and "actualitzat" in cached_data:
|
|
2101
|
+
update_str = cached_data["actualitzat"]["dataUpdate"]
|
|
2102
|
+
try:
|
|
2103
|
+
update_dt = datetime.fromisoformat(update_str)
|
|
2104
|
+
# Convertir a hora local para mostrar
|
|
2105
|
+
local_dt = update_dt.astimezone(TIMEZONE)
|
|
2106
|
+
display_time = local_dt.strftime("%d/%m/%Y %H:%M")
|
|
2107
|
+
except (ValueError, TypeError):
|
|
2108
|
+
display_time = update_str
|
|
2109
|
+
|
|
2110
|
+
_LOGGER.warning(
|
|
2111
|
+
"API rayos falló → usando caché local:\n"
|
|
2112
|
+
" • Archivo: %s\n"
|
|
2113
|
+
" • Última actualización: %s",
|
|
2114
|
+
self.lightning_file.name,
|
|
2115
|
+
display_time
|
|
2116
|
+
)
|
|
2003
2117
|
|
|
2004
|
-
|
|
2005
|
-
|
|
2118
|
+
self.async_set_updated_data({
|
|
2119
|
+
"actualizado": cached_data["actualitzat"]["dataUpdate"]
|
|
2120
|
+
})
|
|
2121
|
+
return {"actualizado": cached_data["actualitzat"]["dataUpdate"]}
|
|
2122
|
+
|
|
2123
|
+
_LOGGER.error("No hay caché de rayos disponible.")
|
|
2124
|
+
self.async_set_updated_data({})
|
|
2125
|
+
return {}
|
|
2006
2126
|
|
|
2007
2127
|
class MeteocatLightningFileCoordinator(BaseFileCoordinator):
|
|
2008
2128
|
"""Coordinator para manejar la actualización de los datos de rayos desde lightning_{region_id}.json."""
|
|
@@ -2440,11 +2560,39 @@ class MeteocatSunCoordinator(DataUpdateCoordinator):
|
|
|
2440
2560
|
|
|
2441
2561
|
except Exception as err:
|
|
2442
2562
|
_LOGGER.exception("Error al calcular/guardar los datos solares: %s", err)
|
|
2563
|
+
|
|
2564
|
+
# === FALLBACK SEGURO ===
|
|
2443
2565
|
cached = await load_json_from_file(self.sun_file)
|
|
2444
|
-
if cached:
|
|
2445
|
-
|
|
2566
|
+
if cached and "actualitzat" in cached and "dades" in cached and cached["dades"]:
|
|
2567
|
+
update_str = cached["actualitzat"]["dataUpdate"]
|
|
2568
|
+
try:
|
|
2569
|
+
update_dt = datetime.fromisoformat(update_str)
|
|
2570
|
+
local_dt = update_dt.astimezone(ZoneInfo(self.timezone_str))
|
|
2571
|
+
display_time = local_dt.strftime("%d/%m/%Y %H:%M")
|
|
2572
|
+
except (ValueError, TypeError):
|
|
2573
|
+
display_time = update_str.split("T")[0]
|
|
2574
|
+
|
|
2575
|
+
sunrise = cached["dades"][0].get("sunrise", "unknown")
|
|
2576
|
+
sunset = cached["dades"][0].get("sunset", "unknown")
|
|
2577
|
+
|
|
2578
|
+
_LOGGER.warning(
|
|
2579
|
+
"SOL: Cálculo falló → usando caché local:\n"
|
|
2580
|
+
" • Archivo: %s\n"
|
|
2581
|
+
" • Última actualización: %s\n"
|
|
2582
|
+
" • Amanecer: %s\n"
|
|
2583
|
+
" • Atardecer: %s",
|
|
2584
|
+
self.sun_file.name,
|
|
2585
|
+
display_time,
|
|
2586
|
+
sunrise.split("T")[1][:5] if "T" in sunrise else sunrise,
|
|
2587
|
+
sunset.split("T")[1][:5] if "T" in sunset else sunset
|
|
2588
|
+
)
|
|
2589
|
+
|
|
2590
|
+
self.async_set_updated_data(cached)
|
|
2446
2591
|
return cached
|
|
2447
|
-
|
|
2592
|
+
|
|
2593
|
+
_LOGGER.error("SOL: No hay caché disponible. Sin datos solares.")
|
|
2594
|
+
self.async_set_updated_data({})
|
|
2595
|
+
return {}
|
|
2448
2596
|
|
|
2449
2597
|
class MeteocatSunFileCoordinator(BaseFileCoordinator):
|
|
2450
2598
|
"""Coordinator para manejar la actualización de los datos de sol desde sun_{town_id}.json."""
|
|
@@ -2868,13 +3016,45 @@ class MeteocatMoonCoordinator(DataUpdateCoordinator):
|
|
|
2868
3016
|
return {"actualizado": data_with_timestamp["actualitzat"]["dataUpdate"]}
|
|
2869
3017
|
|
|
2870
3018
|
except Exception as err:
|
|
2871
|
-
_LOGGER.exception("
|
|
3019
|
+
_LOGGER.exception("Error al calcular datos de la luna: %s", err)
|
|
3020
|
+
|
|
3021
|
+
# === FALLBACK SEGURO ===
|
|
2872
3022
|
cached_data = await load_json_from_file(self.moon_file)
|
|
2873
|
-
if cached_data:
|
|
2874
|
-
|
|
3023
|
+
if cached_data and "actualitzat" in cached_data and "dades" in cached_data:
|
|
3024
|
+
update_str = cached_data["actualitzat"]["dataUpdate"]
|
|
3025
|
+
try:
|
|
3026
|
+
update_dt = datetime.fromisoformat(update_str)
|
|
3027
|
+
local_dt = update_dt.astimezone(ZoneInfo(self.timezone_str))
|
|
3028
|
+
display_time = local_dt.strftime("%d/%m/%Y %H:%M")
|
|
3029
|
+
except (ValueError, TypeError):
|
|
3030
|
+
display_time = update_str.split("T")[0]
|
|
3031
|
+
|
|
3032
|
+
moonrise = cached_data["dades"][0].get("moonrise", "unknown")
|
|
3033
|
+
moonset = cached_data["dades"][0].get("moonset", "unkwnown")
|
|
3034
|
+
phase = cached_data["dades"][0].get("moon_phase_name", "unknown")
|
|
3035
|
+
|
|
3036
|
+
_LOGGER.warning(
|
|
3037
|
+
"LUNA: Cálculo falló → usando caché local:\n"
|
|
3038
|
+
" • Archivo: %s\n"
|
|
3039
|
+
" • Última actualización: %s\n"
|
|
3040
|
+
" • Fase: %s\n"
|
|
3041
|
+
" • Salida: %s\n"
|
|
3042
|
+
" • Atardecer: %s",
|
|
3043
|
+
self.moon_file.name,
|
|
3044
|
+
display_time,
|
|
3045
|
+
phase.title().replace("_", " "),
|
|
3046
|
+
moonrise.split("T")[1][:5] if "T" in moonrise else "—",
|
|
3047
|
+
moonset.split("T")[1][:5] if "T" in moonset else "—"
|
|
3048
|
+
)
|
|
3049
|
+
|
|
3050
|
+
self.async_set_updated_data({
|
|
3051
|
+
"actualizado": cached_data["actualitzat"]["dataUpdate"]
|
|
3052
|
+
})
|
|
2875
3053
|
return {"actualizado": cached_data["actualitzat"]["dataUpdate"]}
|
|
2876
|
-
|
|
2877
|
-
|
|
3054
|
+
|
|
3055
|
+
_LOGGER.error("LUNA: No hay caché disponible. Sin datos lunares.")
|
|
3056
|
+
self.async_set_updated_data({})
|
|
3057
|
+
return {}
|
|
2878
3058
|
|
|
2879
3059
|
class MeteocatMoonFileCoordinator(BaseFileCoordinator):
|
|
2880
3060
|
"""Coordinator para manejar la actualización de los datos de la luna desde moon_{town_id}.json."""
|
|
@@ -1477,7 +1477,7 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1477
1477
|
ALERT_COLD: "Fred",
|
|
1478
1478
|
ALERT_WARM: "Calor",
|
|
1479
1479
|
ALERT_WARM_NIGHT: "Calor nocturna",
|
|
1480
|
-
ALERT_SNOW: "Neu
|
|
1480
|
+
ALERT_SNOW: "Neu",
|
|
1481
1481
|
}
|
|
1482
1482
|
STATE_MAPPING = {
|
|
1483
1483
|
"Obert": "opened",
|
|
@@ -1515,6 +1515,8 @@ class MeteocatAlertMeteorSensor(CoordinatorEntity[MeteocatAlertsRegionCoordinato
|
|
|
1515
1515
|
"gruix > 5 cm a cotes superiors a 600 metres fins a 800 metres": "thickness_5_at_600",
|
|
1516
1516
|
"gruix > 2 cm a cotes superiors a 300 metres fins a 600 metres": "thickness_2_at_300",
|
|
1517
1517
|
"gruix ≥ 0 cm a cotes inferiors a 300 metres": "thickness_0_at_300",
|
|
1518
|
+
"gruix >= 0 cm a cotes > 200 metres": "thickness_0_at_200",
|
|
1519
|
+
"gruix > 2 cm a cotes > 400 metres": "thickness_2_at_400",
|
|
1518
1520
|
}
|
|
1519
1521
|
_attr_has_entity_name = True
|
|
1520
1522
|
|
|
@@ -824,8 +824,10 @@
|
|
|
824
824
|
"thickness_10_at_800": "thickness > 10 cm at altitudes above 800 meters up to 1000 meters",
|
|
825
825
|
"thickness_5_at_300": "thickness > 5 cm at altitudes below 300 meters",
|
|
826
826
|
"thickness_5_at_600": "thickness > 5 cm at altitudes above 600 meters up to 800 meters",
|
|
827
|
+
"thickness_2_at_400": "thickness > 2 cm at altitudes above 400 meters",
|
|
827
828
|
"thickness_2_at_300": "thickness > 2 cm at altitudes above 300 meters up to 600 meters",
|
|
828
829
|
"thickness_0_at_300": "thickness ≥ 0 cm at altitudes below 300 meters",
|
|
830
|
+
"thickness_0_at_200": "thickness ≥ 0 cm at altitudes above 200 meters",
|
|
829
831
|
"unknown": "Unknown"
|
|
830
832
|
}
|
|
831
833
|
},
|
|
@@ -824,8 +824,10 @@
|
|
|
824
824
|
"thickness_10_at_800": "gruix > 10 cm a cotes superiors a 800 metres fins a 1000 metres",
|
|
825
825
|
"thickness_5_at_300": "gruix > 5 cm a cotes inferiors a 300 metres",
|
|
826
826
|
"thickness_5_at_600": "gruix > 5 cm a cotes superiors a 600 metres fins a 800 metres",
|
|
827
|
+
"thickness_2_at_400": "gruix > 2 cm a cotes superiors a 400 metres",
|
|
827
828
|
"thickness_2_at_300": "gruix > 2 cm a cotes superiors a 300 metres fins a 600 metres",
|
|
828
829
|
"thickness_0_at_300": "gruix ≥ 0 cm a cotes inferiors a 300 metres",
|
|
830
|
+
"thickness_0_at_200": "gruix ≥ 0 cm a cotes superiors a 200 metres",
|
|
829
831
|
"unknown": "Desconegut"
|
|
830
832
|
}
|
|
831
833
|
},
|
|
@@ -824,8 +824,10 @@
|
|
|
824
824
|
"thickness_10_at_800": "thickness > 10 cm at altitudes above 800 meters up to 1000 meters",
|
|
825
825
|
"thickness_5_at_300": "thickness > 5 cm at altitudes below 300 meters",
|
|
826
826
|
"thickness_5_at_600": "thickness > 5 cm at altitudes above 600 meters up to 800 meters",
|
|
827
|
+
"thickness_2_at_400": "thickness > 2 cm at altitudes above 400 meters",
|
|
827
828
|
"thickness_2_at_300": "thickness > 2 cm at altitudes above 300 meters up to 600 meters",
|
|
828
829
|
"thickness_0_at_300": "thickness ≥ 0 cm at altitudes below 300 meters",
|
|
830
|
+
"thickness_0_at_200": "thickness ≥ 0 cm at altitudes above 200 meters",
|
|
829
831
|
"unknown": "Unknown"
|
|
830
832
|
}
|
|
831
833
|
},
|
|
@@ -824,8 +824,10 @@
|
|
|
824
824
|
"thickness_10_at_800": "grosor > 10 cm en cotas superiores a 800 metros hasta 1000 metros",
|
|
825
825
|
"thickness_5_at_300": "grosor > 5 cm en cotas inferiores a 300 metros",
|
|
826
826
|
"thickness_5_at_600": "grosor > 5 cm en cotas superiores a 600 metros hasta 800 metros",
|
|
827
|
+
"thickness_2_at_400": "grosor > 2 cm en cotas superiores a 400 metros",
|
|
827
828
|
"thickness_2_at_300": "grosor > 2 cm en cotas superiores a 300 metros hasta 600 metros",
|
|
828
829
|
"thickness_0_at_300": "grosor ≥ 0 cm en cotas inferiores a 300 metros",
|
|
830
|
+
"thickness_0_at_200": "grosor ≥ 0 cm en cotas superiores a 200 metros",
|
|
829
831
|
"unknown": "Desconocido"
|
|
830
832
|
}
|
|
831
833
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "4.0.1"
|
package/filetree.txt
CHANGED
package/hacs.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meteocat",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "[](https://opensource.org/licenses/Apache-2.0)\r [](https://pypi.org/project/meteocat)\r [](https://gitlab.com/figorr/meteocat/commits/master)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|