meteocat 0.1.23 → 0.1.26

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,36 @@
1
+ ## [0.1.26](https://github.com/figorr/meteocat/compare/v0.1.25...v0.1.26) (2024-12-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * 0.1.26 ([9d6b083](https://github.com/figorr/meteocat/commit/9d6b083205d12b628d9a62b0c8c558c0ca9d39e2))
7
+ * add station timestamp constant ([6bd0569](https://github.com/figorr/meteocat/commit/6bd0569030361b89c532ef49faae4b938c1dcf8a))
8
+ * add station timestamp sensor ([ac8b98b](https://github.com/figorr/meteocat/commit/ac8b98bcb225d1348b450b619879b6f636a213da))
9
+ * fix async_remove_entry ([ba28daa](https://github.com/figorr/meteocat/commit/ba28daa87271b6286cf22cd2bcc39422f71b668a))
10
+ * fix entity_id names ([b670d8d](https://github.com/figorr/meteocat/commit/b670d8d53888dc93f371ba7c0e4ed2cdad7ac64b))
11
+ * fix loop when saving JSON ([151dbdd](https://github.com/figorr/meteocat/commit/151dbddd932313831028e1e3e17780dd33d44640))
12
+ * sensor names ([5244f7f](https://github.com/figorr/meteocat/commit/5244f7f8e9d332bfd6adf9e65c053e4b12d8a109))
13
+
14
+ ## [0.1.25](https://github.com/figorr/meteocat/compare/v0.1.24...v0.1.25) (2024-12-08)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * 0.1.25 ([5ba0823](https://github.com/figorr/meteocat/commit/5ba0823db1da999d61c5a4d43733dff5db238ea4))
20
+ * add town and station sensors ([372383f](https://github.com/figorr/meteocat/commit/372383f1bd17ba8e385fef638cf3c1f58515dbaf))
21
+
22
+ ## [0.1.24](https://github.com/figorr/meteocat/compare/v0.1.23...v0.1.24) (2024-12-08)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * 0.1.24 ([87b8b51](https://github.com/figorr/meteocat/commit/87b8b519f78ba97a3e9fe01ac1ee85c0efd0a879))
28
+ * add function to save station_data.json ([e78d872](https://github.com/figorr/meteocat/commit/e78d872938c002fe363c2634b1d8d171ea1e2d6e))
29
+ * add solar global irradiance ([dc757b0](https://github.com/figorr/meteocat/commit/dc757b0a8df6ee971c01e290568730cdf3eb54b0))
30
+ * add solar global irradiance sensor ([d0e7373](https://github.com/figorr/meteocat/commit/d0e737302f9d7889a698468ba6ffb881d8da17c2))
31
+ * bump meteocatpy to 0.0.8 ([c280c06](https://github.com/figorr/meteocat/commit/c280c06c7c3c0703e12e94b19108537bbd03baa0))
32
+ * fix constants ([5d4e0b7](https://github.com/figorr/meteocat/commit/5d4e0b77336f1e051a04a45ccaace40ada3ed33a))
33
+
1
34
  ## [0.1.23](https://github.com/figorr/meteocat/compare/v0.1.22...v0.1.23) (2024-12-07)
2
35
 
3
36
 
@@ -6,13 +6,15 @@ from homeassistant import core
6
6
  from homeassistant.config_entries import ConfigEntry
7
7
  from homeassistant.core import HomeAssistant
8
8
  from homeassistant.exceptions import HomeAssistantError
9
+ from homeassistant.helpers.entity_platform import async_get_platforms
10
+
9
11
  from .coordinator import MeteocatSensorCoordinator, MeteocatEntityCoordinator
10
12
  from .const import DOMAIN, PLATFORMS
11
13
 
12
14
  _LOGGER = logging.getLogger(__name__)
13
15
 
14
16
  # Versión
15
- __version__ = "0.1.23"
17
+ __version__ = "0.1.26"
16
18
 
17
19
 
18
20
  async def async_setup(hass: core.HomeAssistant, config: dict) -> bool:
@@ -27,44 +29,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
27
29
  # Extraer los datos necesarios de la entrada de configuración
28
30
  entry_data = entry.data
29
31
 
30
- # Validar que todos los campos requeridos estén presentes
32
+ # Validar campos requeridos
31
33
  required_fields = [
32
34
  "api_key", "town_name", "town_id", "variable_name",
33
35
  "variable_id", "station_name", "station_id"
34
36
  ]
35
- if not all(field in entry_data for field in required_fields):
36
- _LOGGER.error("Faltan datos en la configuración. Por favor, reconfigura la integración.")
37
+ missing_fields = [field for field in required_fields if field not in entry_data]
38
+ if missing_fields:
39
+ _LOGGER.error(f"Faltan los siguientes campos en la configuración: {missing_fields}")
37
40
  return False
38
41
 
39
- _LOGGER.info(
40
- f"Integración configurada para el municipio '{entry_data['town_name']}' "
41
- f"(ID: {entry_data['town_id']}), variable '{entry_data['variable_name']}' "
42
- f"(ID: {entry_data['variable_id']}), estación '{entry_data['station_name']}' "
43
- f"(ID: {entry_data['station_id']})."
42
+ _LOGGER.debug(
43
+ f"Datos de configuración: Municipio '{entry_data['town_name']}' (ID: {entry_data['town_id']}), "
44
+ f"Variable '{entry_data['variable_name']}' (ID: {entry_data['variable_id']}), "
45
+ f"Estación '{entry_data['station_name']}' (ID: {entry_data['station_id']})."
44
46
  )
45
47
 
46
- # Inicializa y refresca los coordinadores
48
+ # Inicializar coordinadores
47
49
  try:
48
- # Crear el coordinador para sensores
49
- sensor_coordinator = MeteocatSensorCoordinator(
50
- hass=hass,
51
- entry_data=entry_data, # Pasa los datos como un diccionario
52
- )
53
-
54
- # Crear el coordinador para entidades de predicción
55
- entity_coordinator = MeteocatEntityCoordinator(
56
- hass=hass,
57
- entry_data=entry_data, # Pasa los mismos datos
58
- )
59
-
60
- # Ejecutar la primera actualización
50
+ sensor_coordinator = MeteocatSensorCoordinator(hass=hass, entry_data=entry_data)
51
+ entity_coordinator = MeteocatEntityCoordinator(hass=hass, entry_data=entry_data)
52
+
61
53
  await sensor_coordinator.async_config_entry_first_refresh()
62
54
  await entity_coordinator.async_config_entry_first_refresh()
63
- except HomeAssistantError as err:
64
- _LOGGER.error(f"Error al inicializar los coordinadores: {err}")
55
+ except Exception as err: # Capturar todos los errores
56
+ _LOGGER.exception(f"Error al inicializar los coordinadores: {err}")
65
57
  return False
66
58
 
67
- # Guardar los coordinadores en hass.data
59
+ # Guardar coordinadores y datos en hass.data
68
60
  hass.data.setdefault(DOMAIN, {})
69
61
  hass.data[DOMAIN][entry.entry_id] = {
70
62
  "sensor_coordinator": sensor_coordinator,
@@ -72,7 +64,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
72
64
  **entry_data,
73
65
  }
74
66
 
75
- # Configurar las plataformas
67
+ # Configurar plataformas
68
+ _LOGGER.debug(f"Cargando plataformas: {PLATFORMS}")
76
69
  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
77
70
 
78
71
  return True
@@ -80,12 +73,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
80
73
 
81
74
  async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
82
75
  """Desactiva una entrada de configuración para Meteocat."""
76
+ platforms = async_get_platforms(hass, DOMAIN)
77
+ _LOGGER.info(f"Descargando plataformas: {[p.domain for p in platforms]}")
78
+
83
79
  if entry.entry_id in hass.data.get(DOMAIN, {}):
84
80
  hass.data[DOMAIN].pop(entry.entry_id, None)
85
- if not hass.data[DOMAIN]: # Si no quedan entradas, elimina el dominio
81
+ if not hass.data[DOMAIN]:
86
82
  hass.data.pop(DOMAIN)
87
83
 
88
- # Desinstalar las plataformas
89
84
  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
90
85
 
91
86
 
@@ -93,10 +88,14 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
93
88
  """Limpia cualquier dato adicional al desinstalar la integración."""
94
89
  _LOGGER.info(f"Eliminando datos residuales de la integración: {entry.entry_id}")
95
90
 
96
- # Definir la ruta del archivo de símbolos
97
- assets_folder = hass.config.path("custom_components", DOMAIN, "assets")
91
+ # Definir las rutas de los archivos y carpetas a eliminar
92
+ custom_components_path = hass.config.path("custom_components", DOMAIN)
93
+ assets_folder = os.path.join(custom_components_path, "assets")
94
+ files_folder = os.path.join(custom_components_path, "files")
98
95
  symbols_file = os.path.join(assets_folder, "symbols.json")
96
+ station_data_file = os.path.join(files_folder, "station_data.json")
99
97
 
98
+ # Eliminar el archivo symbols.json si existe
100
99
  try:
101
100
  if os.path.exists(symbols_file):
102
101
  os.remove(symbols_file)
@@ -105,3 +104,33 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
105
104
  _LOGGER.info("El archivo symbols.json no se encontró.")
106
105
  except OSError as e:
107
106
  _LOGGER.error(f"Error al intentar eliminar el archivo symbols.json: {e}")
107
+
108
+ # Eliminar el archivo station_data.json si existe
109
+ try:
110
+ if os.path.exists(station_data_file):
111
+ os.remove(station_data_file)
112
+ _LOGGER.info("Archivo station_data.json eliminado correctamente.")
113
+ else:
114
+ _LOGGER.info("El archivo station_data.json no se encontró.")
115
+ except OSError as e:
116
+ _LOGGER.error(f"Error al intentar eliminar el archivo station_data.json: {e}")
117
+
118
+ # Eliminar la carpeta assets si está vacía
119
+ try:
120
+ if os.path.exists(assets_folder) and not os.listdir(assets_folder):
121
+ os.rmdir(assets_folder)
122
+ _LOGGER.info("Carpeta assets eliminada correctamente.")
123
+ elif os.path.exists(assets_folder):
124
+ _LOGGER.warning("La carpeta assets no está vacía y no se pudo eliminar.")
125
+ except OSError as e:
126
+ _LOGGER.error(f"Error al intentar eliminar la carpeta assets: {e}")
127
+
128
+ # Eliminar la carpeta files si está vacía
129
+ try:
130
+ if os.path.exists(files_folder) and not os.listdir(files_folder):
131
+ os.rmdir(files_folder)
132
+ _LOGGER.info("Carpeta files eliminada correctamente.")
133
+ elif os.path.exists(files_folder):
134
+ _LOGGER.warning("La carpeta files no está vacía y no se pudo eliminar.")
135
+ except OSError as e:
136
+ _LOGGER.error(f"Error al intentar eliminar la carpeta files: {e}")
@@ -16,10 +16,25 @@ TEMPERATURE = "temperature" # Temperatura
16
16
  HUMIDITY = "humidity" # Humedad relativa
17
17
  PRESSURE = "pressure" # Presión atmosférica
18
18
  PRECIPITATION = "precipitation" # Precipitación
19
+ SOLAR_GLOBAL_IRRADIANCE = "solar_global_irradiance" # Irradiación solar global
19
20
  UV_INDEX = "uv_index" # UV
20
21
  MAX_TEMPERATURE = "max_temperature" # Temperatura máxima
21
22
  MIN_TEMPERATURE = "min_temperature" # Temperatura mínima
22
23
  WIND_GUST = "wind_gust" # Racha de viento
24
+ STATION_TIMESTAMP = "station_timestamp" # Código de tiempo de la estación
25
+
26
+ # Definición de códigos para variables
27
+ WIND_SPEED_CODE = 30
28
+ WIND_DIRECTION_CODE = 31
29
+ TEMPERATURE_CODE = 32
30
+ HUMIDITY_CODE = 33
31
+ PRESSURE_CODE = 34
32
+ PRECIPITATION_CODE = 35
33
+ SOLAR_GLOBAL_IRRADIANCE_CODE = 36
34
+ UV_INDEX_CODE = 39
35
+ MAX_TEMPERATURE_CODE = 40
36
+ MIN_TEMPERATURE_CODE = 42
37
+ WIND_GUST_CODE = 50
23
38
 
24
39
  # Mapeo de códigos 'estatCel' a condiciones de Home Assistant
25
40
  CONDITION_MAPPING = {
@@ -36,19 +51,5 @@ CONDITION_MAPPING = {
36
51
  "snow-rainy": [27, 29, 30],
37
52
  }
38
53
 
39
- # Mapeo de códigos a claves de sensores
40
- VARIABLE_CODE_MAPPING = {
41
- 30: WIND_SPEED,
42
- 31: WIND_DIRECTION,
43
- 32: TEMPERATURE,
44
- 33: HUMIDITY,
45
- 34: PRESSURE,
46
- 35: PRECIPITATION,
47
- 39: UV_INDEX,
48
- 40: MAX_TEMPERATURE,
49
- 42: MIN_TEMPERATURE,
50
- 50: WIND_GUST,
51
- }
52
-
53
54
  # Platforms
54
55
  PLATFORMS = ["sensor"]
@@ -1,5 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
4
+ import json
5
+ import aiofiles
3
6
  import logging
4
7
  from datetime import timedelta
5
8
  from typing import Dict
@@ -26,6 +29,17 @@ _LOGGER = logging.getLogger(__name__)
26
29
  DEFAULT_SENSOR_UPDATE_INTERVAL = timedelta(minutes=90)
27
30
  DEFAULT_ENTITY_UPDATE_INTERVAL = timedelta(hours=12)
28
31
 
32
+ async def save_json_to_file(data: dict, output_file: str) -> None:
33
+ """Save the JSON data to a file asynchronously."""
34
+ try:
35
+ # Crea el directorio si no existe
36
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
37
+
38
+ # Escribe los datos JSON de forma asíncrona
39
+ async with aiofiles.open(output_file, mode="w", encoding="utf-8") as f:
40
+ await f.write(json.dumps(data, indent=4, ensure_ascii=False))
41
+ except Exception as e:
42
+ raise RuntimeError(f"Error saving JSON to {output_file}: {e}")
29
43
 
30
44
  class MeteocatSensorCoordinator(DataUpdateCoordinator):
31
45
  """Coordinator para manejar la actualización de datos de los sensores."""
@@ -63,8 +77,18 @@ class MeteocatSensorCoordinator(DataUpdateCoordinator):
63
77
  async def _async_update_data(self) -> Dict:
64
78
  """Actualiza los datos de los sensores desde la API de Meteocat."""
65
79
  try:
66
- data = await self.meteocat_station_data.get_station_data_with_variables(self.station_id)
80
+ # Obtener datos desde la API
81
+ data = await self.meteocat_station_data.get_station_data(self.station_id)
67
82
  _LOGGER.debug("Datos de sensores actualizados exitosamente: %s", data)
83
+
84
+ # Determinar la ruta al archivo en la carpeta raíz del repositorio
85
+ output_file = os.path.join(
86
+ self.hass.config.path(), "custom_components", "meteocat", "files", "station_data.json"
87
+ )
88
+
89
+ # Guardar los datos en un archivo JSON
90
+ await save_json_to_file(data, output_file)
91
+
68
92
  return data
69
93
  except ForbiddenError as err:
70
94
  _LOGGER.error(
@@ -95,7 +119,6 @@ class MeteocatSensorCoordinator(DataUpdateCoordinator):
95
119
  )
96
120
  raise
97
121
 
98
-
99
122
  class MeteocatEntityCoordinator(DataUpdateCoordinator):
100
123
  """Coordinator para manejar la actualización de datos de las entidades de predicción."""
101
124
 
@@ -7,6 +7,6 @@
7
7
  "iot_class": "cloud_polling",
8
8
  "documentation": "https://gitlab.com/figorr/meteocat",
9
9
  "loggers": ["meteocatpy"],
10
- "requirements": ["meteocatpy==0.0.7", "packaging>=20.3", "wrapt>=1.14.0"],
11
- "version": "0.1.23"
10
+ "requirements": ["meteocatpy==0.0.8", "packaging>=20.3", "wrapt>=1.14.0"],
11
+ "version": "0.1.26"
12
12
  }
@@ -1,7 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from homeassistant.helpers.entity import DeviceInfo
4
+ from datetime import datetime
5
+ import logging
6
+ from homeassistant.helpers.entity import (
7
+ DeviceInfo,
8
+ EntityCategory,
9
+ )
5
10
  from homeassistant.components.sensor import (
6
11
  SensorDeviceClass,
7
12
  SensorEntity,
@@ -18,39 +23,57 @@ from homeassistant.const import (
18
23
  UnitOfSpeed,
19
24
  UnitOfTemperature,
20
25
  UnitOfVolumetricFlux,
26
+ UnitOfIrradiance,
21
27
  )
22
28
 
23
29
  from .const import (
24
30
  DOMAIN,
31
+ TOWN_NAME,
32
+ TOWN_ID,
33
+ STATION_NAME,
34
+ STATION_ID,
25
35
  WIND_SPEED,
26
36
  WIND_DIRECTION,
27
37
  TEMPERATURE,
28
38
  HUMIDITY,
29
39
  PRESSURE,
30
40
  PRECIPITATION,
41
+ SOLAR_GLOBAL_IRRADIANCE,
31
42
  UV_INDEX,
32
43
  MAX_TEMPERATURE,
33
44
  MIN_TEMPERATURE,
34
45
  WIND_GUST,
35
- VARIABLE_CODE_MAPPING,
46
+ STATION_TIMESTAMP,
47
+ WIND_SPEED_CODE,
48
+ WIND_DIRECTION_CODE,
49
+ TEMPERATURE_CODE,
50
+ HUMIDITY_CODE,
51
+ PRESSURE_CODE,
52
+ PRECIPITATION_CODE,
53
+ SOLAR_GLOBAL_IRRADIANCE_CODE,
54
+ UV_INDEX_CODE,
55
+ MAX_TEMPERATURE_CODE,
56
+ MIN_TEMPERATURE_CODE,
57
+ WIND_GUST_CODE,
36
58
  )
37
59
 
38
60
  from .coordinator import MeteocatSensorCoordinator
39
61
 
62
+ _LOGGER = logging.getLogger(__name__)
40
63
 
41
64
  @dataclass
42
65
  class MeteocatSensorEntityDescription(SensorEntityDescription):
43
66
  """A class that describes Meteocat sensor entities."""
44
67
 
45
-
46
68
  SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
69
+ # Sensores dinámicos
47
70
  MeteocatSensorEntityDescription(
48
71
  key=WIND_SPEED,
49
72
  name="Wind Speed",
50
73
  icon="mdi:weather-windy",
51
74
  device_class=SensorDeviceClass.WIND_SPEED,
52
75
  state_class=SensorStateClass.MEASUREMENT,
53
- native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
76
+ native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
54
77
  ),
55
78
  MeteocatSensorEntityDescription(
56
79
  key=WIND_DIRECTION,
@@ -90,6 +113,14 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
90
113
  state_class=SensorStateClass.MEASUREMENT,
91
114
  native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
92
115
  ),
116
+ MeteocatSensorEntityDescription(
117
+ key=SOLAR_GLOBAL_IRRADIANCE,
118
+ name="Solar Global Irradiance",
119
+ icon="mdi:weather-sunny",
120
+ device_class=SensorDeviceClass.IRRADIANCE,
121
+ state_class=SensorStateClass.MEASUREMENT,
122
+ native_unit_of_measurement = UnitOfIrradiance.WATTS_PER_SQUARE_METER,
123
+ ),
93
124
  MeteocatSensorEntityDescription(
94
125
  key=UV_INDEX,
95
126
  name="UV Index",
@@ -117,8 +148,39 @@ SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
117
148
  icon="mdi:weather-windy",
118
149
  device_class=SensorDeviceClass.WIND_SPEED,
119
150
  state_class=SensorStateClass.MEASUREMENT,
120
- native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
151
+ native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
152
+ ),
153
+ # Sensores estáticos
154
+ MeteocatSensorEntityDescription(
155
+ key=TOWN_NAME,
156
+ name="Town Name",
157
+ icon="mdi:home-city",
158
+ entity_category=EntityCategory.DIAGNOSTIC,
121
159
  ),
160
+ MeteocatSensorEntityDescription(
161
+ key=TOWN_ID,
162
+ name="Town ID",
163
+ icon="mdi:identifier",
164
+ entity_category=EntityCategory.DIAGNOSTIC,
165
+ ),
166
+ MeteocatSensorEntityDescription(
167
+ key=STATION_NAME,
168
+ name="Station Name",
169
+ icon="mdi:broadcast",
170
+ entity_category=EntityCategory.DIAGNOSTIC,
171
+ ),
172
+ MeteocatSensorEntityDescription(
173
+ key=STATION_ID,
174
+ name="Station ID",
175
+ icon="mdi:identifier",
176
+ entity_category=EntityCategory.DIAGNOSTIC,
177
+ ),
178
+ MeteocatSensorEntityDescription(
179
+ key=STATION_TIMESTAMP,
180
+ name="Station Timestamp",
181
+ icon="mdi:calendar-clock",
182
+ device_class=SensorDeviceClass.TIMESTAMP,
183
+ )
122
184
  )
123
185
 
124
186
 
@@ -133,62 +195,125 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
133
195
  for description in SENSOR_TYPES
134
196
  )
135
197
 
136
-
137
198
  class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity):
138
199
  """Representation of a Meteocat sensor."""
200
+ STATIC_KEYS = {TOWN_NAME, TOWN_ID, STATION_NAME, STATION_ID}
201
+
202
+ CODE_MAPPING = {
203
+ WIND_SPEED: WIND_SPEED_CODE,
204
+ WIND_DIRECTION: WIND_DIRECTION_CODE,
205
+ TEMPERATURE: TEMPERATURE_CODE,
206
+ HUMIDITY: HUMIDITY_CODE,
207
+ PRESSURE: PRESSURE_CODE,
208
+ PRECIPITATION: PRECIPITATION_CODE,
209
+ SOLAR_GLOBAL_IRRADIANCE: SOLAR_GLOBAL_IRRADIANCE_CODE,
210
+ UV_INDEX: UV_INDEX_CODE,
211
+ MAX_TEMPERATURE: MAX_TEMPERATURE_CODE,
212
+ MIN_TEMPERATURE: MIN_TEMPERATURE_CODE,
213
+ WIND_GUST: WIND_GUST_CODE,
214
+ }
215
+
216
+ _attr_has_entity_name = True # Activa el uso de nombres basados en el dispositivo
139
217
 
140
218
  def __init__(self, coordinator, description, entry_data):
141
219
  """Initialize the sensor."""
142
220
  super().__init__(coordinator)
143
221
  self.entity_description = description
144
- self.api_key = entry_data["api_key"] # Usamos la API key de la configuración
145
- self._town_name = entry_data["town_name"] # Usamos el nombre del municipio
146
- self._town_id = entry_data["town_id"] # Usamos el ID del municipio
147
- self._variable_name = entry_data["variable_name"] # Usamos el nombre de la variable
148
- self._variable_id = entry_data["variable_id"] # Usamos el ID de la variable
149
- self._station_name = entry_data["station_name"] # Usamos el nombre de la estación
150
- self._station_id = entry_data["station_id"] # Usamos el ID de la estación
222
+ self.api_key = entry_data["api_key"]
223
+ self._town_name = entry_data["town_name"]
224
+ self._town_id = entry_data["town_id"]
225
+ self._station_name = entry_data["station_name"]
226
+ self._station_id = entry_data["station_id"]
151
227
 
152
228
  # Unique ID for the entity
153
- self._attr_unique_id = f"{self._town_id}_{self.entity_description.key}"
229
+ self._attr_unique_id = f"sensor.{DOMAIN}_{self._station_id}_{self.entity_description.key}"
230
+
231
+ # Asigna entity_category desde description (si está definido)
232
+ self._attr_entity_category = getattr(description, "entity_category", None)
233
+
234
+ # Log para depuración
235
+ _LOGGER.debug(
236
+ "Inicializando sensor: %s, Unique ID: %s",
237
+ self.entity_description.name,
238
+ self._attr_unique_id,
239
+ )
154
240
 
155
241
  @property
156
242
  def native_value(self):
157
243
  """Return the state of the sensor."""
158
- sensor_code = next(
159
- (code for code, key in VARIABLE_CODE_MAPPING.items() if key == self.entity_description.key),
160
- None,
161
- )
162
-
244
+ # Información estática
245
+ if self.entity_description.key in self.STATIC_KEYS:
246
+ # Información estática del `entry_data`
247
+ if self.entity_description.key == TOWN_NAME:
248
+ return self._town_name
249
+ if self.entity_description.key == TOWN_ID:
250
+ return self._town_id
251
+ if self.entity_description.key == STATION_NAME:
252
+ return self._station_name
253
+ if self.entity_description.key == STATION_ID:
254
+ return self._station_id
255
+ # Información dinámica
256
+ sensor_code = self.CODE_MAPPING.get(self.entity_description.key)
257
+
163
258
  if sensor_code is not None:
164
- variable_data = next(
165
- (var for var in self.coordinator.data.get("variables", []) if var["codi"] == sensor_code),
166
- None,
167
- )
168
- if variable_data:
169
- # Asume que quieres el último valor registrado
170
- latest_reading = variable_data["lectures"][-1]
171
- value = latest_reading.get("valor")
259
+ # Accedemos a las estaciones en el JSON recibido
260
+ stations = self.coordinator.data or []
261
+ for station in stations:
262
+ variables = station.get("variables", [])
263
+
264
+ # Filtramos por código
265
+ variable_data = next(
266
+ (var for var in variables if var.get("codi") == sensor_code),
267
+ None,
268
+ )
269
+
270
+ if variable_data:
271
+ # Obtenemos la última lectura
272
+ lectures = variable_data.get("lectures", [])
273
+ if lectures:
274
+ latest_reading = lectures[-1]
275
+ value = latest_reading.get("valor")
172
276
 
173
- # Convertir grados a direcciones cardinales para WIND_DIRECTION
174
- if self.entity_description.key == WIND_DIRECTION and isinstance(value, (int, float)):
175
- return self._convert_degrees_to_cardinal(value)
277
+ # Convertimos grados a dirección cardinal para WIND_DIRECTION
278
+ if self.entity_description.key == WIND_DIRECTION and isinstance(value, (int, float)):
279
+ return self._convert_degrees_to_cardinal(value)
176
280
 
177
- return value
281
+ return value
282
+ # Lógica específica para el sensor de timestamp
283
+ if self.entity_description.key == "station_timestamp":
284
+ stations = self.coordinator.data or []
285
+ for station in stations:
286
+ variables = station.get("variables", [])
287
+ for variable in variables:
288
+ lectures = variable.get("lectures", [])
289
+ if lectures:
290
+ # Obtenemos el campo `data` de la última lectura
291
+ latest_reading = lectures[-1]
292
+ raw_timestamp = latest_reading.get("data")
293
+
294
+ if raw_timestamp:
295
+ # Convertir el timestamp a un objeto datetime
296
+ try:
297
+ return datetime.fromisoformat(raw_timestamp.replace("Z", "+00:00"))
298
+ except ValueError:
299
+ # Manejo de errores si el formato no es válido
300
+ return None
178
301
 
179
302
  return None
180
303
 
181
304
  @staticmethod
182
305
  def _convert_degrees_to_cardinal(degree: float) -> str:
183
306
  """Convert degrees to cardinal direction."""
307
+ if not isinstance(degree, (int, float)):
308
+ return "Unknown" # Retorna "Unknown" si el valor no es un número válido
309
+
184
310
  directions = [
185
311
  "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
186
- "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N",
312
+ "S", "SSO", "SO", "OSO", "O", "ONO", "NO", "NNO", "N",
187
313
  ]
188
314
  index = round(degree / 22.5) % 16
189
315
  return directions[index]
190
316
 
191
-
192
317
  @property
193
318
  def device_info(self) -> DeviceInfo:
194
319
  """Return the device info."""
@@ -196,5 +321,5 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
196
321
  identifiers={(DOMAIN, self._town_id)},
197
322
  name=self._town_name,
198
323
  manufacturer="Meteocat",
199
- model="Meteocat API"
324
+ model="Meteocat API",
200
325
  )
@@ -1,2 +1,2 @@
1
1
  # version.py
2
- __version__ = "0.1.23"
2
+ __version__ = "0.1.26"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meteocat",
3
- "version": "0.1.23",
3
+ "version": "0.1.26",
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.23"
3
+ version = "0.1.26"
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"