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 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
  ![Meteocat Banner](images/banner.png)
3
3
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
- [![Python version compatibility](https://img.shields.io/pypi/pyversions/meteocat)](https://pypi.org/project/meteocat)
4
+ ![Python](https://img.shields.io/badge/Language-Python-blue)
5
5
  [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration)
6
6
  [![Validate](https://github.com/figorr/meteocat/actions/workflows/validate.yaml/badge.svg)](https://github.com/figorr/meteocat/actions/workflows/validate.yaml)
7
7
  [![Release](https://github.com/figorr/meteocat/actions/workflows/release.yml/badge.svg)](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__ = "3.2.0"
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
- "Error inesperado al obtener datos de los sensores (Station ID: %s): %s",
302
- self.station_id,
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
- _LOGGER.warning("Usando datos en caché para la estación %s.", self.station_id)
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 se pudo obtener datos actualizados ni cargar datos en caché.")
313
- return None
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
- # Fallback a caché en disco
520
+
521
+ # === FALLBACK SEGURO ===
495
522
  cached_data = await load_json_from_file(self.uvi_file)
496
- if cached_data:
497
- _LOGGER.warning("Usando datos en caché para la ciudad %s.", self.town_id)
498
- return cached_data.get("uvi", [])
499
- _LOGGER.error("No se pudo obtener datos UVI ni cargar caché.")
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
- "Cargando datos desde caché para %s. Datos horarios: %s, Datos diarios: %s",
765
- self.town_id,
766
- "Encontrados" if hourly_cache else "No encontrados",
767
- "Encontrados" if daily_cache else "No encontrados",
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 inesperado al obtener datos de alertas: %s", err)
1385
-
1386
- # Intentar cargar datos en caché si hay un error
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
- "Usando datos en caché para las alertas. Última actualización: %s",
1391
- cached_data["actualitzat"]["dataUpdate"],
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
- return {"actualizado": cached_data['actualitzat']['dataUpdate']}
1394
-
1395
- # Si no se puede actualizar ni cargar datos en caché, retornar None
1396
- _LOGGER.error("No se pudo obtener datos actualizados ni cargar datos en caché de alertas.")
1397
- return None
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 inesperado al obtener cuotas de la API de Meteocat: %s", err)
1826
-
1827
- # Intentar cargar datos en caché si hay un error
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
- _LOGGER.warning("Usando datos en caché para las cuotas de la API de Meteocat.")
1831
- return {"actualizado": cached_data['actualitzat']['dataUpdate']}
1832
-
1833
- _LOGGER.error("No se pudo obtener datos actualizados ni cargar datos en caché.")
1834
- return None
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 inesperado al obtener los datos de rayos de la API de Meteocat: %s", err)
1997
-
1998
- # Intentar cargar datos en caché si la API falla
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
- _LOGGER.warning("Usando datos en caché para los datos de rayos de la API de Meteocat.")
2002
- return {"actualizado": cached_data['actualitzat']['dataUpdate']}
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
- _LOGGER.error("No se pudo obtener datos actualizados ni cargar datos en caché.")
2005
- return None
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
- _LOGGER.warning("Usando datos solares en caché por error.")
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
- return None
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("🌙 Error al calcular datos de la luna: %s", err)
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
- _LOGGER.warning("🌙 Se usaron datos en caché por error de cálculo.")
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
- _LOGGER.error("🌙 No se pudo calcular ni cargar datos en caché de luna.")
2877
- return None
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."""
@@ -21,5 +21,5 @@
21
21
  "packaging>=20.3",
22
22
  "wrapt>=1.14.0"
23
23
  ],
24
- "version": "3.2.0"
24
+ "version": "4.0.1"
25
25
  }
@@ -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 acumulada en 24 hores",
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__ = "3.2.0"
1
+ __version__ = "4.0.1"
package/filetree.txt CHANGED
@@ -51,6 +51,7 @@
51
51
  ├── banner.png
52
52
  ├── change_units.png
53
53
  ├── daily_forecast_2_alerts.png
54
+ ├── daily_forecast_no_alerts.png
54
55
  ├── devices.png
55
56
  ├── diagnostic_sensors.png
56
57
  ├── dynamic_sensors.png
package/hacs.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Meteocat",
3
- "homeassistant": "2025.9.4",
3
+ "homeassistant": "2025.11.0",
4
4
  "hide_default_branch": true,
5
5
  "render_readme": true,
6
6
  "zip_release": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meteocat",
3
- "version": "3.2.0",
3
+ "version": "4.0.1",
4
4
  "description": "[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\r [![Python version compatibility](https://img.shields.io/pypi/pyversions/meteocat)](https://pypi.org/project/meteocat)\r [![pipeline status](https://gitlab.com/figorr/meteocat/badges/master/pipeline.svg)](https://gitlab.com/figorr/meteocat/commits/master)",
5
5
  "main": "index.js",
6
6
  "directories": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "meteocat"
3
- version = "3.2.0"
3
+ version = "4.0.1"
4
4
  description = "Script para obtener datos meteorológicos de la API de Meteocat"
5
5
  authors = ["figorr <jdcuartero@yahoo.es>"]
6
6
  license = "Apache-2.0"