meteocat 0.1.40 → 0.1.41

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,17 @@
1
+ ## [0.1.41](https://github.com/figorr/meteocat/compare/v0.1.40...v0.1.41) (2024-12-27)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * 0.1.41 ([ba2c800](https://github.com/figorr/meteocat/commit/ba2c80048cc2da6efe5f950a5d8fb958a53a7ef6))
7
+ * add new Max and Min Today Temperature sensors ([6cc726d](https://github.com/figorr/meteocat/commit/6cc726d127432ddc13a54638157f1f9566967ed4))
8
+ * add Today temp max and min translations ([8e1d31e](https://github.com/figorr/meteocat/commit/8e1d31e78c1d5df61c37635a19d95b6339f0b0a5))
9
+ * add Today temp max and min translations ([041c3ab](https://github.com/figorr/meteocat/commit/041c3ab877b269f3b3d3359792ecf13b14969466))
10
+ * add Today temp max and min translations ([be25fc4](https://github.com/figorr/meteocat/commit/be25fc43bef58e1cecc2afaff8e0b8d3288399fb))
11
+ * add Today temp max and min translations ([e236182](https://github.com/figorr/meteocat/commit/e236182dfe29b15c3eb06d6a8fd3e02712837858))
12
+ * fix condition when is night ([fb64e0b](https://github.com/figorr/meteocat/commit/fb64e0b754eb5a39afe9135a0be842d5e8bdeae0))
13
+ * fix sunset and sunrise events for night flag ([4770e56](https://github.com/figorr/meteocat/commit/4770e5633707933c0bdd2aae0f74ada363da86bb))
14
+
1
15
  ## [0.1.40](https://github.com/figorr/meteocat/compare/v0.1.39...v0.1.40) (2024-12-26)
2
16
 
3
17
 
@@ -17,6 +17,7 @@ from .coordinator import (
17
17
  HourlyForecastCoordinator,
18
18
  DailyForecastCoordinator,
19
19
  MeteocatConditionCoordinator,
20
+ MeteocatTempForecastCoordinator,
20
21
  )
21
22
 
22
23
  from .const import DOMAIN, PLATFORMS
@@ -24,7 +25,7 @@ from .const import DOMAIN, PLATFORMS
24
25
  _LOGGER = logging.getLogger(__name__)
25
26
 
26
27
  # Versión
27
- __version__ = "0.1.40"
28
+ __version__ = "0.1.41"
28
29
 
29
30
  def safe_remove(path: Path, is_folder: bool = False):
30
31
  """Elimina de forma segura un archivo o carpeta si existe."""
@@ -91,6 +92,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
91
92
  condition_coordinator = MeteocatConditionCoordinator(hass=hass, entry_data=entry_data)
92
93
  await condition_coordinator.async_config_entry_first_refresh()
93
94
 
95
+ temp_forecast_coordinator = MeteocatTempForecastCoordinator(hass=hass, entry_data=entry_data)
96
+ await temp_forecast_coordinator.async_config_entry_first_refresh()
97
+
94
98
  except Exception as err: # Capturar todos los errores
95
99
  _LOGGER.exception(f"Error al inicializar los coordinadores: {err}")
96
100
  return False
@@ -106,6 +110,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
106
110
  "hourly_forecast_coordinator": hourly_forecast_coordinator,
107
111
  "daily_forecast_coordinator": daily_forecast_coordinator,
108
112
  "condition_coordinator": condition_coordinator,
113
+ "temp_forecast_coordinator": temp_forecast_coordinator,
109
114
  **entry_data,
110
115
  }
111
116
 
@@ -3,7 +3,9 @@ from __future__ import annotations
3
3
  from datetime import datetime
4
4
  from .const import CONDITION_MAPPING
5
5
  from .helpers import is_night
6
+ import logging
6
7
 
8
+ _LOGGER = logging.getLogger(__name__)
7
9
 
8
10
  def get_condition_from_statcel(
9
11
  codi_estatcel, current_time: datetime, hass, is_hourly: bool = True
@@ -17,6 +19,13 @@ def get_condition_from_statcel(
17
19
  :param is_hourly: Indica si los datos son de predicción horaria (True) o diaria (False).
18
20
  :return: Diccionario con la condición y el icono.
19
21
  """
22
+
23
+ _LOGGER.debug(
24
+ "Entrando en get_condition_from_statcel con codi_estatcel: %s, is_hourly: %s",
25
+ codi_estatcel,
26
+ is_hourly,
27
+ )
28
+
20
29
  # Asegurarse de que codi_estatcel sea una lista válida
21
30
  if codi_estatcel is None:
22
31
  codi_estatcel = []
@@ -31,7 +40,19 @@ def get_condition_from_statcel(
31
40
  if any(code in codes for code in codi_estatcel):
32
41
  # Ajustar para condiciones nocturnas si aplica
33
42
  if condition == "sunny" and is_night_flag:
43
+ _LOGGER.debug(
44
+ "Códigos EstatCel: %s, Es Noche: %s, Condición Devuelta: clear-night",
45
+ codi_estatcel,
46
+ is_night_flag,
47
+ )
34
48
  return {"condition": "clear-night", "icon": None}
49
+
50
+ _LOGGER.debug(
51
+ "Códigos EstatCel: %s, Es Noche: %s, Condición Devuelta: %s",
52
+ codi_estatcel,
53
+ is_night_flag,
54
+ condition,
55
+ )
35
56
  return {"condition": condition, "icon": None}
36
57
 
37
58
  # Si no coincide ningún código, devolver condición desconocida
@@ -32,6 +32,8 @@ FEELS_LIKE = "feels_like" # Sensación térmica
32
32
  WIND_GUST = "wind_gust" # Racha de viento
33
33
  STATION_TIMESTAMP = "station_timestamp" # Código de tiempo de la estación
34
34
  CONDITION = "condition" # Estado del cielo
35
+ MAX_TEMPERATURE_FORECAST = "max_temperature_forecast" # Temperatura máxima prevista
36
+ MIN_TEMPERATURE_FORECAST = "min_temperature_forecast" # Temperatura mínima prevista
35
37
 
36
38
  # Definición de códigos para variables
37
39
  WIND_SPEED_CODE = 30
@@ -36,12 +36,13 @@ _LOGGER = logging.getLogger(__name__)
36
36
  # Valores predeterminados para los intervalos de actualización
37
37
  DEFAULT_SENSOR_UPDATE_INTERVAL = timedelta(minutes=90)
38
38
  DEFAULT_STATIC_SENSOR_UPDATE_INTERVAL = timedelta(hours=24)
39
- DEFAULT_ENTITY_UPDATE_INTERVAL = timedelta(hours=48)
39
+ DEFAULT_ENTITY_UPDATE_INTERVAL = timedelta(minutes=60)
40
40
  DEFAULT_HOURLY_FORECAST_UPDATE_INTERVAL = timedelta(minutes=5)
41
41
  DEFAULT_DAILY_FORECAST_UPDATE_INTERVAL = timedelta(minutes=15)
42
- DEFAULT_UVI_UPDATE_INTERVAL = timedelta(hours=48)
42
+ DEFAULT_UVI_UPDATE_INTERVAL = timedelta(minutes=60)
43
43
  DEFAULT_UVI_SENSOR_UPDATE_INTERVAL = timedelta(minutes=5)
44
44
  DEFAULT_CONDITION_SENSOR_UPDATE_INTERVAL = timedelta(minutes=5)
45
+ DEFAULT_TEMP_FORECAST_UPDATE_INTERVAL = timedelta(minutes=5)
45
46
 
46
47
  async def save_json_to_file(data: dict, output_file: str) -> None:
47
48
  """Guarda datos JSON en un archivo de forma asíncrona."""
@@ -291,9 +292,9 @@ class MeteocatUviCoordinator(DataUpdateCoordinator):
291
292
  if not isinstance(uvi_data, list) or not uvi_data:
292
293
  return None
293
294
 
294
- # Validar la fecha del primer elemento
295
- first_date = uvi_data[0].get("date")
296
- if first_date != datetime.now(timezone.utc).strftime("%Y-%m-%d"):
295
+ # Validar la fecha del primer elemento superior a 1 día
296
+ first_date = datetime.strptime(uvi_data[0].get("date"), "%Y-%m-%d").date()
297
+ if (datetime.now(timezone.utc).date() - first_date).days > 1:
297
298
  return None
298
299
 
299
300
  return data
@@ -489,7 +490,10 @@ class MeteocatEntityCoordinator(DataUpdateCoordinator):
489
490
 
490
491
  # Obtener la fecha del primer día
491
492
  first_date = datetime.fromisoformat(data["dies"][0]["data"].rstrip("Z")).date()
492
- return first_date == datetime.now(timezone.utc).date()
493
+ today = datetime.now(timezone.utc).date()
494
+
495
+ # Verificar si la antigüedad es mayor a un día
496
+ return (today - first_date).days <= 1
493
497
  except Exception as e:
494
498
  _LOGGER.warning("Error validando datos en %s: %s", file_path, e)
495
499
  return False
@@ -646,7 +650,15 @@ class HourlyForecastCoordinator(DataUpdateCoordinator):
646
650
  datetime.fromisoformat(item["data"].rstrip("Z")).replace(tzinfo=timezone.utc) == forecast_time),
647
651
  -1,
648
652
  )
649
- condition = get_condition_from_code(int(condition_code))
653
+
654
+ # Determinar la condición usando `get_condition_from_statcel`
655
+ condition_data = get_condition_from_statcel(
656
+ codi_estatcel=condition_code,
657
+ current_time=forecast_time,
658
+ hass=self.hass,
659
+ is_hourly=True
660
+ )
661
+ condition = condition_data["condition"]
650
662
 
651
663
  return {
652
664
  "datetime": forecast_time.isoformat(),
@@ -842,6 +854,8 @@ class MeteocatConditionCoordinator(DataUpdateCoordinator):
842
854
 
843
855
  async def _async_update_data(self):
844
856
  """Read and process condition data for the current hour from the file asynchronously."""
857
+ _LOGGER.debug("Iniciando actualización de datos desde el archivo: %s", self._file_path)
858
+
845
859
  try:
846
860
  async with aiofiles.open(self._file_path, "r", encoding="utf-8") as file:
847
861
  raw_data = await file.read()
@@ -889,6 +903,12 @@ class MeteocatConditionCoordinator(DataUpdateCoordinator):
889
903
  "hour": current_hour,
890
904
  "date": current_date,
891
905
  })
906
+ _LOGGER.debug(
907
+ "Hora actual: %s, Código estatCel: %s, Condición procesada: %s",
908
+ current_datetime,
909
+ codi_estatcel,
910
+ condition,
911
+ )
892
912
  return condition
893
913
  break # Sale del bucle una vez encontrada la fecha actual
894
914
 
@@ -900,3 +920,102 @@ class MeteocatConditionCoordinator(DataUpdateCoordinator):
900
920
  )
901
921
  return {"condition": "unknown", "hour": current_hour, "icon": None, "date": current_date}
902
922
 
923
+ class MeteocatTempForecastCoordinator(DataUpdateCoordinator):
924
+ """Coordinator para manejar las predicciones diarias desde archivos locales."""
925
+
926
+ def __init__(
927
+ self,
928
+ hass: HomeAssistant,
929
+ entry_data: dict,
930
+ update_interval: timedelta = DEFAULT_TEMP_FORECAST_UPDATE_INTERVAL,
931
+ ):
932
+ """Inicializa el coordinador para predicciones diarias."""
933
+ self.town_name = entry_data["town_name"]
934
+ self.town_id = entry_data["town_id"]
935
+ self.station_name = entry_data["station_name"]
936
+ self.station_id = entry_data["station_id"]
937
+ self.file_path = os.path.join(
938
+ hass.config.path(),
939
+ "custom_components",
940
+ "meteocat",
941
+ "files",
942
+ f"forecast_{self.town_id.lower()}_daily_data.json",
943
+ )
944
+ super().__init__(
945
+ hass,
946
+ _LOGGER,
947
+ name=f"{DOMAIN} Daily Forecast Coordinator",
948
+ update_interval=update_interval,
949
+ )
950
+
951
+ async def _is_data_valid(self) -> bool:
952
+ """Verifica si hay datos válidos y actuales en el archivo JSON."""
953
+ if not os.path.exists(self.file_path):
954
+ return False
955
+
956
+ try:
957
+ async with aiofiles.open(self.file_path, "r", encoding="utf-8") as f:
958
+ content = await f.read()
959
+ data = json.loads(content)
960
+
961
+ if not data or "dies" not in data or not data["dies"]:
962
+ return False
963
+
964
+ today = datetime.now(timezone.utc).date()
965
+ for dia in data["dies"]:
966
+ forecast_date = datetime.fromisoformat(dia["data"].rstrip("Z")).date()
967
+ if forecast_date >= today:
968
+ return True
969
+
970
+ return False
971
+ except Exception as e:
972
+ _LOGGER.warning("Error validando datos diarios en %s: %s", self.file_path, e)
973
+ return False
974
+
975
+ async def _async_update_data(self) -> dict:
976
+ """Lee y filtra los datos de predicción diaria desde el archivo local."""
977
+ if await self._is_data_valid():
978
+ try:
979
+ async with aiofiles.open(self.file_path, "r", encoding="utf-8") as f:
980
+ content = await f.read()
981
+ data = json.loads(content)
982
+
983
+ # Filtrar días pasados
984
+ today = datetime.now(timezone.utc).date()
985
+ data["dies"] = [
986
+ dia for dia in data["dies"]
987
+ if datetime.fromisoformat(dia["data"].rstrip("Z")).date() >= today
988
+ ]
989
+
990
+ # Usar datos del día actual si están disponibles
991
+ today_temp_forecast = self.get_temp_forecast_for_today(data)
992
+ if today_temp_forecast:
993
+ parsed_data = self.parse_forecast(today_temp_forecast)
994
+ return parsed_data
995
+ except Exception as e:
996
+ _LOGGER.warning("Error leyendo archivo de predicción diaria: %s", e)
997
+
998
+ return {}
999
+
1000
+ def get_temp_forecast_for_today(self, data: dict) -> dict | None:
1001
+ """Obtiene los datos diarios para el día actual."""
1002
+ if not data or "dies" not in data or not data["dies"]:
1003
+ return None
1004
+
1005
+ today = datetime.now(timezone.utc).date()
1006
+ for dia in data["dies"]:
1007
+ forecast_date = datetime.fromisoformat(dia["data"].rstrip("Z")).date()
1008
+ if forecast_date == today:
1009
+ return dia
1010
+ return None
1011
+
1012
+ def parse_forecast(self, dia: dict) -> dict:
1013
+ """Convierte un día de predicción en un diccionario con los datos necesarios."""
1014
+ variables = dia.get("variables", {})
1015
+
1016
+ temp_forecast_data = {
1017
+ "date": datetime.fromisoformat(dia["data"].rstrip("Z")).date(),
1018
+ "max_temp_forecast": float(variables.get("tmax", {}).get("valor", 0.0)),
1019
+ "min_temp_forecast": float(variables.get("tmin", {}).get("valor", 0.0)),
1020
+ }
1021
+ return temp_forecast_data
@@ -1,41 +1,51 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from datetime import datetime, timedelta
5
- from homeassistant.core import HomeAssistant
6
- from homeassistant.util.dt import as_local, as_utc
7
- from homeassistant.helpers.sun import get_astral_event_next
4
+ from datetime import datetime
5
+ from homeassistant.util.dt import as_local, as_utc, start_of_local_day
6
+ from homeassistant.helpers.sun import get_astral_event_date
8
7
 
9
8
  _LOGGER = logging.getLogger(__name__)
10
9
 
11
- def get_sun_times(hass: HomeAssistant, current_time: datetime | None = None):
12
- """Get the sunrise and sunset times."""
13
- # Usa la hora actual si no se proporciona una hora específica
10
+ def get_sun_times(hass, current_time=None):
11
+ """Obtén las horas de amanecer y atardecer para el día actual."""
14
12
  if current_time is None:
15
13
  current_time = datetime.now()
16
14
 
17
- # Asegúrate de que current_time es aware (UTC con offset)
15
+ # Asegúrate de que current_time es aware (UTC)
18
16
  current_time = as_utc(current_time)
17
+ today = start_of_local_day(as_local(current_time))
19
18
 
20
- # Obtén los tiempos de amanecer y atardecer desde el helper
21
- sunrise = get_astral_event_next(hass, "sunrise", utc_point_in_time=current_time)
22
- sunset = get_astral_event_next(hass, "sunset", utc_point_in_time=current_time)
19
+ # Obtén los eventos de amanecer y atardecer del día actual
20
+ sunrise = get_astral_event_date(hass, "sunrise", today)
21
+ sunset = get_astral_event_date(hass, "sunset", today)
22
+
23
+ _LOGGER.debug(
24
+ "Sunrise: %s, Sunset: %s, Current Time: %s",
25
+ sunrise,
26
+ sunset,
27
+ as_local(current_time),
28
+ )
23
29
 
24
- # Asegúrate de que no sean None y conviértelos a la zona horaria local
25
30
  if sunrise and sunset:
26
- return as_local(sunrise), as_local(sunset)
31
+ return sunrise, sunset
27
32
 
28
- # Lanza un error si no se pudieron determinar los eventos
29
- raise ValueError("Sunrise or sunset data is unavailable.")
33
+ raise ValueError("No se pudieron determinar los datos de amanecer y atardecer.")
30
34
 
31
- def is_night(current_time: datetime, hass: HomeAssistant) -> bool:
32
- """Determine if it is currently night based on sunrise and sunset times."""
33
- # Convierte current_time a UTC si no tiene información de zona horaria
35
+ def is_night(current_time, hass):
36
+ """Determina si actualmente es de noche."""
37
+ # Asegúrate de que current_time es aware (UTC)
34
38
  if current_time.tzinfo is None:
35
39
  current_time = as_utc(current_time)
36
40
 
37
- # Obtén los tiempos de amanecer y atardecer
38
41
  sunrise, sunset = get_sun_times(hass, current_time)
39
42
 
40
- # Compara las horas
43
+ _LOGGER.debug(
44
+ "Hora actual: %s, Amanecer: %s, Atardecer: %s",
45
+ as_local(current_time),
46
+ as_local(sunrise),
47
+ as_local(sunset),
48
+ )
49
+
50
+ # Es de noche si es antes del amanecer o después del atardecer
41
51
  return current_time < sunrise or current_time > sunset
@@ -8,5 +8,5 @@
8
8
  "documentation": "https://gitlab.com/figorr/meteocat",
9
9
  "loggers": ["meteocatpy"],
10
10
  "requirements": ["meteocatpy==0.0.15", "packaging>=20.3", "wrapt>=1.14.0"],
11
- "version": "0.1.40"
11
+ "version": "0.1.41"
12
12
  }
@@ -46,6 +46,8 @@ from .const import (
46
46
  WIND_GUST,
47
47
  STATION_TIMESTAMP,
48
48
  CONDITION,
49
+ MAX_TEMPERATURE_FORECAST,
50
+ MIN_TEMPERATURE_FORECAST,
49
51
  WIND_SPEED_CODE,
50
52
  WIND_DIRECTION_CODE,
51
53
  TEMPERATURE_CODE,
@@ -65,6 +67,7 @@ from .coordinator import (
65
67
  MeteocatStaticSensorCoordinator,
66
68
  MeteocatUviFileCoordinator,
67
69
  MeteocatConditionCoordinator,
70
+ MeteocatTempForecastCoordinator,
68
71
  )
69
72
 
70
73
  _LOGGER = logging.getLogger(__name__)
@@ -209,7 +212,23 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
209
212
  key=CONDITION,
210
213
  translation_key="condition",
211
214
  icon="mdi:weather-partly-cloudy",
212
- )
215
+ ),
216
+ MeteocatSensorEntityDescription(
217
+ key=MAX_TEMPERATURE_FORECAST,
218
+ translation_key="max_temperature_forecast",
219
+ icon="mdi:thermometer-plus",
220
+ device_class=SensorDeviceClass.TEMPERATURE,
221
+ state_class=SensorStateClass.MEASUREMENT,
222
+ native_unit_of_measurement=UnitOfTemperature.CELSIUS,
223
+ ),
224
+ MeteocatSensorEntityDescription(
225
+ key=MIN_TEMPERATURE_FORECAST,
226
+ translation_key="min_temperature_forecast",
227
+ icon="mdi:thermometer-minus",
228
+ device_class=SensorDeviceClass.TEMPERATURE,
229
+ state_class=SensorStateClass.MEASUREMENT,
230
+ native_unit_of_measurement=UnitOfTemperature.CELSIUS,
231
+ ),
213
232
  )
214
233
 
215
234
  @callback
@@ -222,12 +241,13 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
222
241
  uvi_file_coordinator = entry_data.get("uvi_file_coordinator")
223
242
  static_sensor_coordinator = entry_data.get("static_sensor_coordinator")
224
243
  condition_coordinator = entry_data.get("condition_coordinator")
244
+ temp_forecast_coordinator = entry_data.get("temp_forecast_coordinator")
225
245
 
226
246
  # Sensores generales
227
247
  async_add_entities(
228
248
  MeteocatSensor(coordinator, description, entry_data)
229
249
  for description in SENSOR_TYPES
230
- if description.key not in {TOWN_NAME, TOWN_ID, STATION_NAME, STATION_ID, UV_INDEX, CONDITION} # Excluir estáticos y UVI
250
+ if description.key not in {TOWN_NAME, TOWN_ID, STATION_NAME, STATION_ID, UV_INDEX, CONDITION, MAX_TEMPERATURE_FORECAST, MIN_TEMPERATURE_FORECAST} # Excluir estáticos y UVI
231
251
  )
232
252
 
233
253
  # Sensores estáticos
@@ -251,6 +271,13 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
251
271
  if description.key == CONDITION # Incluir CONDITION en el coordinador CONDITION COORDINATOR
252
272
  )
253
273
 
274
+ # Sensores temperatura previsión
275
+ async_add_entities(
276
+ MeteocatTempForecast(temp_forecast_coordinator, description, entry_data)
277
+ for description in SENSOR_TYPES
278
+ if description.key in {MAX_TEMPERATURE_FORECAST, MIN_TEMPERATURE_FORECAST}
279
+ )
280
+
254
281
  class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], SensorEntity):
255
282
  """Representation of a static Meteocat sensor."""
256
283
  STATIC_KEYS = {TOWN_NAME, TOWN_ID, STATION_NAME, STATION_ID}
@@ -543,7 +570,7 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
543
570
  return value
544
571
 
545
572
  # Lógica específica para el sensor de timestamp
546
- if self.entity_description.key == "station_timestamp":
573
+ if self.entity_description.key == STATION_TIMESTAMP:
547
574
  stations = self.coordinator.data or []
548
575
  for station in stations:
549
576
  variables = station.get("variables", [])
@@ -563,7 +590,7 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
563
590
  return None
564
591
 
565
592
  # Nuevo sensor para la precipitación acumulada
566
- if self.entity_description.key == "precipitation_accumulated":
593
+ if self.entity_description.key == PRECIPITATION_ACCUMULATED:
567
594
  stations = self.coordinator.data or []
568
595
  total_precipitation = 0.0 # Usa float para permitir acumulación de decimales
569
596
 
@@ -644,3 +671,50 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
644
671
  manufacturer="Meteocat",
645
672
  model="Meteocat API",
646
673
  )
674
+
675
+ class MeteocatTempForecast(CoordinatorEntity[MeteocatTempForecastCoordinator], SensorEntity):
676
+ """Representation of a Meteocat UV Index sensor."""
677
+
678
+ _attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
679
+
680
+ def __init__(self, temp_forecast_coordinator, description, entry_data):
681
+ """Initialize the UV Index sensor."""
682
+ super().__init__(temp_forecast_coordinator)
683
+ self.entity_description = description
684
+ self._town_name = entry_data["town_name"]
685
+ self._town_id = entry_data["town_id"]
686
+ self._station_id = entry_data["station_id"]
687
+
688
+ # Unique ID for the entity
689
+ self._attr_unique_id = f"sensor.{DOMAIN}_{self._town_id}_{self.entity_description.key}"
690
+
691
+ # Asigna entity_category desde description (si está definido)
692
+ self._attr_entity_category = getattr(description, "entity_category", None)
693
+
694
+ # Log para depuración
695
+ _LOGGER.debug(
696
+ "Inicializando sensor: %s, Unique ID: %s",
697
+ self.entity_description.name,
698
+ self._attr_unique_id,
699
+ )
700
+
701
+ @property
702
+ def native_value(self):
703
+ """Return the Max and Min Temp Forecast value."""
704
+ temp_forecast_data = self.coordinator.data or {}
705
+
706
+ if self.entity_description.key == MAX_TEMPERATURE_FORECAST:
707
+ return temp_forecast_data.get("max_temp_forecast", None)
708
+ if self.entity_description.key == MIN_TEMPERATURE_FORECAST:
709
+ return temp_forecast_data.get("min_temp_forecast", None)
710
+ return None
711
+
712
+ @property
713
+ def device_info(self) -> DeviceInfo:
714
+ """Return the device info."""
715
+ return DeviceInfo(
716
+ identifiers={(DOMAIN, self._town_id)},
717
+ name="Meteocat " + self._station_id + " " + self._town_name,
718
+ manufacturer="Meteocat",
719
+ model="Meteocat API",
720
+ )
@@ -127,6 +127,12 @@
127
127
  "name": "Hour"
128
128
  }
129
129
  }
130
+ },
131
+ "max_temperature_forecast": {
132
+ "name": "Max Temperature Today"
133
+ },
134
+ "min_temperature_forecast": {
135
+ "name": "Min Temperature Today"
130
136
  }
131
137
  }
132
138
  }
@@ -127,7 +127,14 @@
127
127
  "name": "Hora"
128
128
  }
129
129
  }
130
+ },
131
+ "max_temperature_forecast": {
132
+ "name": "Temperatura Max Avui"
133
+ },
134
+ "min_temperature_forecast": {
135
+ "name": "Temperatura Min Avui"
130
136
  }
131
137
  }
132
138
  }
133
- }
139
+ }
140
+
@@ -127,8 +127,14 @@
127
127
  "name": "Hour"
128
128
  }
129
129
  }
130
+ },
131
+ "max_temperature_forecast": {
132
+ "name": "Max Temperature Today"
133
+ },
134
+ "min_temperature_forecast": {
135
+ "name": "Min Temperature Today"
130
136
  }
131
137
  }
132
138
  }
133
139
  }
134
-
140
+
@@ -127,6 +127,12 @@
127
127
  "name": "Hora"
128
128
  }
129
129
  }
130
+ },
131
+ "max_temperature_forecast": {
132
+ "name": "Temperatura Max Hoy"
133
+ },
134
+ "min_temperature_forecast": {
135
+ "name": "Temperatura Min Hoy"
130
136
  }
131
137
  }
132
138
  }
@@ -1,2 +1,2 @@
1
1
  # version.py
2
- __version__ = "0.1.40"
2
+ __version__ = "0.1.41"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meteocat",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
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 = "0.1.40"
3
+ version = "0.1.41"
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"