meteocat 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +33 -33
- package/.pre-commit-config.yaml +37 -37
- package/.releaserc +23 -23
- package/.releaserc.toml +14 -14
- package/AUTHORS.md +12 -12
- package/CHANGELOG.md +401 -381
- package/README.md +40 -40
- package/custom_components/meteocat/__init__.py +107 -107
- package/custom_components/meteocat/condition.py +28 -28
- package/custom_components/meteocat/config_flow.py +192 -192
- package/custom_components/meteocat/const.py +55 -54
- package/custom_components/meteocat/coordinator.py +195 -171
- package/custom_components/meteocat/entity.py +98 -98
- package/custom_components/meteocat/helpers.py +42 -42
- package/custom_components/meteocat/manifest.json +12 -12
- package/custom_components/meteocat/options_flow.py +71 -71
- package/custom_components/meteocat/sensor.py +303 -200
- package/custom_components/meteocat/strings.json +25 -25
- package/custom_components/meteocat/translations/ca.json +25 -25
- package/custom_components/meteocat/translations/en.json +25 -25
- package/custom_components/meteocat/translations/es.json +25 -25
- package/custom_components/meteocat/version.py +2 -2
- package/filetree.py +48 -48
- package/filetree.txt +46 -46
- package/hacs.json +5 -5
- package/package.json +22 -22
- package/poetry.lock +3216 -3216
- package/pyproject.toml +64 -64
- package/releaserc.json +17 -17
- package/requirements.test.txt +3 -3
- package/setup.cfg +64 -64
- package/setup.py +10 -10
- package/tests/bandit.yaml +17 -17
- package/tests/conftest.py +19 -19
- package/tests/test_init.py +9 -9
|
@@ -1,171 +1,195 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
from homeassistant.
|
|
10
|
-
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
from meteocatpy.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
_LOGGER
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
from typing import Dict
|
|
8
|
+
|
|
9
|
+
from homeassistant.core import HomeAssistant
|
|
10
|
+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
11
|
+
from homeassistant.exceptions import ConfigEntryNotReady
|
|
12
|
+
|
|
13
|
+
from meteocatpy.data import MeteocatStationData
|
|
14
|
+
from meteocatpy.forecast import MeteocatForecast
|
|
15
|
+
from meteocatpy.exceptions import (
|
|
16
|
+
BadRequestError,
|
|
17
|
+
ForbiddenError,
|
|
18
|
+
TooManyRequestsError,
|
|
19
|
+
InternalServerError,
|
|
20
|
+
UnknownAPIError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from .const import DOMAIN
|
|
24
|
+
|
|
25
|
+
_LOGGER = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# Valores predeterminados para los intervalos de actualización
|
|
28
|
+
DEFAULT_SENSOR_UPDATE_INTERVAL = timedelta(minutes=90)
|
|
29
|
+
DEFAULT_ENTITY_UPDATE_INTERVAL = timedelta(hours=12)
|
|
30
|
+
|
|
31
|
+
def save_json_to_file(data: dict, filename="station_data.json"):
|
|
32
|
+
"""Guardar datos JSON en un archivo para análisis."""
|
|
33
|
+
try:
|
|
34
|
+
# Crear la carpeta 'files' si no existe
|
|
35
|
+
output_folder = os.path.join(os.path.dirname(__file__), "files")
|
|
36
|
+
os.makedirs(output_folder, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
# Ruta completa del archivo
|
|
39
|
+
output_file = os.path.join(output_folder, filename)
|
|
40
|
+
|
|
41
|
+
# Guardar los datos en el archivo
|
|
42
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
43
|
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
44
|
+
|
|
45
|
+
_LOGGER.info(f"Archivo JSON guardado en: {output_file}")
|
|
46
|
+
except Exception as e:
|
|
47
|
+
_LOGGER.error(f"Error al guardar el archivo JSON: {e}")
|
|
48
|
+
|
|
49
|
+
class MeteocatSensorCoordinator(DataUpdateCoordinator):
|
|
50
|
+
"""Coordinator para manejar la actualización de datos de los sensores."""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
hass: HomeAssistant,
|
|
55
|
+
entry_data: dict,
|
|
56
|
+
update_interval: timedelta = DEFAULT_SENSOR_UPDATE_INTERVAL,
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Inicializa el coordinador de sensores de Meteocat.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
hass (HomeAssistant): Instancia de Home Assistant.
|
|
63
|
+
entry_data (dict): Datos de configuración obtenidos de core.config_entries.
|
|
64
|
+
update_interval (timedelta): Intervalo de actualización.
|
|
65
|
+
"""
|
|
66
|
+
self.api_key = entry_data["api_key"] # Usamos la API key de la configuración
|
|
67
|
+
self.town_name = entry_data["town_name"] # Usamos el nombre del municipio
|
|
68
|
+
self.town_id = entry_data["town_id"] # Usamos el ID del municipio
|
|
69
|
+
self.station_name = entry_data["station_name"] # Usamos el nombre de la estación
|
|
70
|
+
self.station_id = entry_data["station_id"] # Usamos el ID de la estación
|
|
71
|
+
self.variable_name = entry_data["variable_name"] # Usamos el nombre de la variable
|
|
72
|
+
self.variable_id = entry_data["variable_id"] # Usamos el ID de la variable
|
|
73
|
+
self.meteocat_station_data = MeteocatStationData(self.api_key)
|
|
74
|
+
|
|
75
|
+
super().__init__(
|
|
76
|
+
hass,
|
|
77
|
+
_LOGGER,
|
|
78
|
+
name=f"{DOMAIN} Sensor Coordinator",
|
|
79
|
+
update_interval=update_interval,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
async def _async_update_data(self) -> Dict:
|
|
83
|
+
"""Actualiza los datos de los sensores desde la API de Meteocat."""
|
|
84
|
+
try:
|
|
85
|
+
# Obtener datos desde la API
|
|
86
|
+
data = await self.meteocat_station_data.get_station_data(self.station_id)
|
|
87
|
+
_LOGGER.debug("Datos de sensores actualizados exitosamente: %s", data)
|
|
88
|
+
|
|
89
|
+
# Guardar los datos en un archivo JSON
|
|
90
|
+
save_json_to_file(data)
|
|
91
|
+
|
|
92
|
+
return data
|
|
93
|
+
except ForbiddenError as err:
|
|
94
|
+
_LOGGER.error(
|
|
95
|
+
"Acceso denegado al obtener datos de sensores (Station ID: %s): %s",
|
|
96
|
+
self.station_id,
|
|
97
|
+
err,
|
|
98
|
+
)
|
|
99
|
+
raise ConfigEntryNotReady from err
|
|
100
|
+
except TooManyRequestsError as err:
|
|
101
|
+
_LOGGER.warning(
|
|
102
|
+
"Límite de solicitudes alcanzado al obtener datos de sensores (Station ID: %s): %s",
|
|
103
|
+
self.station_id,
|
|
104
|
+
err,
|
|
105
|
+
)
|
|
106
|
+
raise ConfigEntryNotReady from err
|
|
107
|
+
except (BadRequestError, InternalServerError, UnknownAPIError) as err:
|
|
108
|
+
_LOGGER.error(
|
|
109
|
+
"Error al obtener datos de sensores (Station ID: %s): %s",
|
|
110
|
+
self.station_id,
|
|
111
|
+
err,
|
|
112
|
+
)
|
|
113
|
+
raise
|
|
114
|
+
except Exception as err:
|
|
115
|
+
_LOGGER.exception(
|
|
116
|
+
"Error inesperado al obtener datos de sensores (Station ID: %s): %s",
|
|
117
|
+
self.station_id,
|
|
118
|
+
err,
|
|
119
|
+
)
|
|
120
|
+
raise
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class MeteocatEntityCoordinator(DataUpdateCoordinator):
|
|
124
|
+
"""Coordinator para manejar la actualización de datos de las entidades de predicción."""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
hass: HomeAssistant,
|
|
129
|
+
entry_data: dict,
|
|
130
|
+
update_interval: timedelta = DEFAULT_ENTITY_UPDATE_INTERVAL,
|
|
131
|
+
):
|
|
132
|
+
"""
|
|
133
|
+
Inicializa el coordinador de datos para entidades de predicción.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
hass (HomeAssistant): Instancia de Home Assistant.
|
|
137
|
+
entry_data (dict): Datos de configuración obtenidos de core.config_entries.
|
|
138
|
+
update_interval (timedelta): Intervalo de actualización.
|
|
139
|
+
"""
|
|
140
|
+
self.api_key = entry_data["api_key"]
|
|
141
|
+
self.town_name = entry_data["town_name"]
|
|
142
|
+
self.town_id = entry_data["town_id"]
|
|
143
|
+
self.station_name = entry_data["station_name"]
|
|
144
|
+
self.station_id = entry_data["station_id"]
|
|
145
|
+
self.variable_name = entry_data["variable_name"]
|
|
146
|
+
self.variable_id = entry_data["variable_id"]
|
|
147
|
+
self.meteocat_forecast = MeteocatForecast(self.api_key)
|
|
148
|
+
|
|
149
|
+
super().__init__(
|
|
150
|
+
hass,
|
|
151
|
+
_LOGGER,
|
|
152
|
+
name=f"{DOMAIN} Entity Coordinator",
|
|
153
|
+
update_interval=update_interval,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
async def _async_update_data(self) -> Dict:
|
|
157
|
+
"""Actualiza los datos de las entidades de predicción desde la API de Meteocat."""
|
|
158
|
+
try:
|
|
159
|
+
hourly_forecast = await self.meteocat_forecast.get_prediccion_horaria(self.town_id)
|
|
160
|
+
daily_forecast = await self.meteocat_forecast.get_prediccion_diaria(self.town_id)
|
|
161
|
+
_LOGGER.debug(
|
|
162
|
+
"Datos de predicción actualizados exitosamente (Town ID: %s)", self.town_id
|
|
163
|
+
)
|
|
164
|
+
return {
|
|
165
|
+
"hourly_forecast": hourly_forecast,
|
|
166
|
+
"daily_forecast": daily_forecast,
|
|
167
|
+
}
|
|
168
|
+
except ForbiddenError as err:
|
|
169
|
+
_LOGGER.error(
|
|
170
|
+
"Acceso denegado al obtener datos de predicción (Town ID: %s): %s",
|
|
171
|
+
self.town_id,
|
|
172
|
+
err,
|
|
173
|
+
)
|
|
174
|
+
raise ConfigEntryNotReady from err
|
|
175
|
+
except TooManyRequestsError as err:
|
|
176
|
+
_LOGGER.warning(
|
|
177
|
+
"Límite de solicitudes alcanzado al obtener datos de predicción (Town ID: %s): %s",
|
|
178
|
+
self.town_id,
|
|
179
|
+
err,
|
|
180
|
+
)
|
|
181
|
+
raise ConfigEntryNotReady from err
|
|
182
|
+
except (BadRequestError, InternalServerError, UnknownAPIError) as err:
|
|
183
|
+
_LOGGER.error(
|
|
184
|
+
"Error al obtener datos de predicción (Town ID: %s): %s",
|
|
185
|
+
self.town_id,
|
|
186
|
+
err,
|
|
187
|
+
)
|
|
188
|
+
raise
|
|
189
|
+
except Exception as err:
|
|
190
|
+
_LOGGER.exception(
|
|
191
|
+
"Error inesperado al obtener datos de predicción (Town ID: %s): %s",
|
|
192
|
+
self.town_id,
|
|
193
|
+
err,
|
|
194
|
+
)
|
|
195
|
+
raise
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import asyncio
|
|
4
|
-
import logging
|
|
5
|
-
from homeassistant.components.weather import WeatherEntity
|
|
6
|
-
from homeassistant.const import (
|
|
7
|
-
DEGREE,
|
|
8
|
-
PERCENTAGE,
|
|
9
|
-
UnitOfPressure,
|
|
10
|
-
UnitOfSpeed,
|
|
11
|
-
UnitOfTemperature,
|
|
12
|
-
UnitOfVolumetricFlux,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
from .const import (
|
|
16
|
-
DOMAIN,
|
|
17
|
-
CONF_API_KEY,
|
|
18
|
-
TOWN_ID,
|
|
19
|
-
TEMPERATURE,
|
|
20
|
-
HUMIDITY,
|
|
21
|
-
WIND_SPEED,
|
|
22
|
-
WIND_DIRECTION,
|
|
23
|
-
)
|
|
24
|
-
from .condition import get_condition_from_statcel
|
|
25
|
-
from .coordinator import MeteocatEntityCoordinator
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
_LOGGER = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
31
|
-
"""Configura el componente weather basado en una entrada de configuración."""
|
|
32
|
-
api_key = config_entry.data[CONF_API_KEY]
|
|
33
|
-
town_id = config_entry.data[TOWN_ID]
|
|
34
|
-
|
|
35
|
-
# Crear el coordinador
|
|
36
|
-
coordinator = MeteocatEntityCoordinator(hass, api_key, town_id)
|
|
37
|
-
# Asegurarse de que el coordinador esté actualizado antes de agregar la entidad
|
|
38
|
-
await coordinator.async_refresh()
|
|
39
|
-
|
|
40
|
-
async_add_entities([MeteocatWeatherEntity(coordinator)], True)
|
|
41
|
-
|
|
42
|
-
class MeteocatWeatherEntity(WeatherEntity):
|
|
43
|
-
"""Entidad de clima para la integración Meteocat."""
|
|
44
|
-
|
|
45
|
-
def __init__(self, coordinator: MeteocatEntityCoordinator):
|
|
46
|
-
"""Inicializa la entidad MeteocatWeather."""
|
|
47
|
-
self._coordinator = coordinator
|
|
48
|
-
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
|
49
|
-
self._data = {}
|
|
50
|
-
|
|
51
|
-
async def async_update(self):
|
|
52
|
-
"""Actualiza los datos meteorológicos."""
|
|
53
|
-
try:
|
|
54
|
-
# Usamos el coordinador para obtener los datos actualizados
|
|
55
|
-
if self._coordinator.data:
|
|
56
|
-
hourly_forecast = self._coordinator.data["hourly_forecast"]
|
|
57
|
-
current_forecast = hourly_forecast["variables"]
|
|
58
|
-
codi_estatcel = current_forecast.get("estatCel", {}).get("valor")
|
|
59
|
-
is_night = current_forecast.get("is_night", False)
|
|
60
|
-
self._data = {
|
|
61
|
-
"temperature": current_forecast.get(TEMPERATURE, {}).get("valor"),
|
|
62
|
-
"humidity": current_forecast.get(HUMIDITY, {}).get("valor"),
|
|
63
|
-
"wind_speed": current_forecast.get(WIND_SPEED, {}).get("valor"),
|
|
64
|
-
"wind_bearing": current_forecast.get(WIND_DIRECTION, {}).get("valor"),
|
|
65
|
-
"condition": get_condition_from_statcel(codi_estatcel, is_night)["condition"],
|
|
66
|
-
}
|
|
67
|
-
except Exception as err:
|
|
68
|
-
_LOGGER.error("Error al actualizar la predicción de Meteocat: %s", err)
|
|
69
|
-
|
|
70
|
-
@property
|
|
71
|
-
def name(self):
|
|
72
|
-
"""Retorna el nombre de la entidad."""
|
|
73
|
-
return f"Clima {self._coordinator._town_id}"
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
def temperature(self):
|
|
77
|
-
"""Retorna la temperatura actual."""
|
|
78
|
-
return self._data.get("temperature")
|
|
79
|
-
|
|
80
|
-
@property
|
|
81
|
-
def humidity(self):
|
|
82
|
-
"""Retorna la humedad relativa actual."""
|
|
83
|
-
return self._data.get("humidity")
|
|
84
|
-
|
|
85
|
-
@property
|
|
86
|
-
def wind_speed(self):
|
|
87
|
-
"""Retorna la velocidad del viento."""
|
|
88
|
-
return self._data.get("wind_speed")
|
|
89
|
-
|
|
90
|
-
@property
|
|
91
|
-
def wind_bearing(self):
|
|
92
|
-
"""Retorna la dirección del viento."""
|
|
93
|
-
return self._data.get("wind_bearing")
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
def condition(self):
|
|
97
|
-
"""Retorna la condición climática."""
|
|
98
|
-
return self._data.get("condition")
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from homeassistant.components.weather import WeatherEntity
|
|
6
|
+
from homeassistant.const import (
|
|
7
|
+
DEGREE,
|
|
8
|
+
PERCENTAGE,
|
|
9
|
+
UnitOfPressure,
|
|
10
|
+
UnitOfSpeed,
|
|
11
|
+
UnitOfTemperature,
|
|
12
|
+
UnitOfVolumetricFlux,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .const import (
|
|
16
|
+
DOMAIN,
|
|
17
|
+
CONF_API_KEY,
|
|
18
|
+
TOWN_ID,
|
|
19
|
+
TEMPERATURE,
|
|
20
|
+
HUMIDITY,
|
|
21
|
+
WIND_SPEED,
|
|
22
|
+
WIND_DIRECTION,
|
|
23
|
+
)
|
|
24
|
+
from .condition import get_condition_from_statcel
|
|
25
|
+
from .coordinator import MeteocatEntityCoordinator
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_LOGGER = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
31
|
+
"""Configura el componente weather basado en una entrada de configuración."""
|
|
32
|
+
api_key = config_entry.data[CONF_API_KEY]
|
|
33
|
+
town_id = config_entry.data[TOWN_ID]
|
|
34
|
+
|
|
35
|
+
# Crear el coordinador
|
|
36
|
+
coordinator = MeteocatEntityCoordinator(hass, api_key, town_id)
|
|
37
|
+
# Asegurarse de que el coordinador esté actualizado antes de agregar la entidad
|
|
38
|
+
await coordinator.async_refresh()
|
|
39
|
+
|
|
40
|
+
async_add_entities([MeteocatWeatherEntity(coordinator)], True)
|
|
41
|
+
|
|
42
|
+
class MeteocatWeatherEntity(WeatherEntity):
|
|
43
|
+
"""Entidad de clima para la integración Meteocat."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, coordinator: MeteocatEntityCoordinator):
|
|
46
|
+
"""Inicializa la entidad MeteocatWeather."""
|
|
47
|
+
self._coordinator = coordinator
|
|
48
|
+
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
|
49
|
+
self._data = {}
|
|
50
|
+
|
|
51
|
+
async def async_update(self):
|
|
52
|
+
"""Actualiza los datos meteorológicos."""
|
|
53
|
+
try:
|
|
54
|
+
# Usamos el coordinador para obtener los datos actualizados
|
|
55
|
+
if self._coordinator.data:
|
|
56
|
+
hourly_forecast = self._coordinator.data["hourly_forecast"]
|
|
57
|
+
current_forecast = hourly_forecast["variables"]
|
|
58
|
+
codi_estatcel = current_forecast.get("estatCel", {}).get("valor")
|
|
59
|
+
is_night = current_forecast.get("is_night", False)
|
|
60
|
+
self._data = {
|
|
61
|
+
"temperature": current_forecast.get(TEMPERATURE, {}).get("valor"),
|
|
62
|
+
"humidity": current_forecast.get(HUMIDITY, {}).get("valor"),
|
|
63
|
+
"wind_speed": current_forecast.get(WIND_SPEED, {}).get("valor"),
|
|
64
|
+
"wind_bearing": current_forecast.get(WIND_DIRECTION, {}).get("valor"),
|
|
65
|
+
"condition": get_condition_from_statcel(codi_estatcel, is_night)["condition"],
|
|
66
|
+
}
|
|
67
|
+
except Exception as err:
|
|
68
|
+
_LOGGER.error("Error al actualizar la predicción de Meteocat: %s", err)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def name(self):
|
|
72
|
+
"""Retorna el nombre de la entidad."""
|
|
73
|
+
return f"Clima {self._coordinator._town_id}"
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def temperature(self):
|
|
77
|
+
"""Retorna la temperatura actual."""
|
|
78
|
+
return self._data.get("temperature")
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def humidity(self):
|
|
82
|
+
"""Retorna la humedad relativa actual."""
|
|
83
|
+
return self._data.get("humidity")
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def wind_speed(self):
|
|
87
|
+
"""Retorna la velocidad del viento."""
|
|
88
|
+
return self._data.get("wind_speed")
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def wind_bearing(self):
|
|
92
|
+
"""Retorna la dirección del viento."""
|
|
93
|
+
return self._data.get("wind_bearing")
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def condition(self):
|
|
97
|
+
"""Retorna la condición climática."""
|
|
98
|
+
return self._data.get("condition")
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from datetime import datetime, time
|
|
4
|
-
from homeassistant.helpers import entity_registry
|
|
5
|
-
|
|
6
|
-
def get_sun_times(hass) -> tuple:
|
|
7
|
-
"""
|
|
8
|
-
Obtiene las horas de amanecer y atardecer desde la integración sun.
|
|
9
|
-
|
|
10
|
-
:param hass: Instancia de Home Assistant.
|
|
11
|
-
:return: Tupla con las horas de amanecer y atardecer (datetime).
|
|
12
|
-
"""
|
|
13
|
-
sun_entity = hass.states.get("sun.sun")
|
|
14
|
-
if sun_entity:
|
|
15
|
-
sunrise = sun_entity.attributes.get("next_rising")
|
|
16
|
-
sunset = sun_entity.attributes.get("next_setting")
|
|
17
|
-
if sunrise and sunset:
|
|
18
|
-
return (
|
|
19
|
-
hass.util.dt.as_local(hass.util.dt.parse_datetime(sunrise)),
|
|
20
|
-
hass.util.dt.as_local(hass.util.dt.parse_datetime(sunset)),
|
|
21
|
-
)
|
|
22
|
-
raise ValueError("No se pudo obtener las horas de amanecer y atardecer de sun.sun")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def is_night(current_time: datetime, hass) -> bool:
|
|
26
|
-
"""
|
|
27
|
-
Determina si la hora actual está fuera del rango entre el amanecer y el atardecer.
|
|
28
|
-
|
|
29
|
-
:param current_time: Hora actual como objeto datetime.
|
|
30
|
-
:param hass: Instancia de Home Assistant.
|
|
31
|
-
:return: True si es de noche, False si es de día.
|
|
32
|
-
"""
|
|
33
|
-
# Obtener las horas de amanecer y atardecer de la integración sun
|
|
34
|
-
sunrise, sunset = get_sun_times(hass)
|
|
35
|
-
|
|
36
|
-
# Convertimos a objetos time para comparar solo las horas
|
|
37
|
-
current_time_only = current_time.time()
|
|
38
|
-
sunrise_time_only = sunrise.time()
|
|
39
|
-
sunset_time_only = sunset.time()
|
|
40
|
-
|
|
41
|
-
# Devuelve True si la hora está fuera del rango del día
|
|
42
|
-
return current_time_only < sunrise_time_only or current_time_only > sunset_time_only
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, time
|
|
4
|
+
from homeassistant.helpers import entity_registry
|
|
5
|
+
|
|
6
|
+
def get_sun_times(hass) -> tuple:
|
|
7
|
+
"""
|
|
8
|
+
Obtiene las horas de amanecer y atardecer desde la integración sun.
|
|
9
|
+
|
|
10
|
+
:param hass: Instancia de Home Assistant.
|
|
11
|
+
:return: Tupla con las horas de amanecer y atardecer (datetime).
|
|
12
|
+
"""
|
|
13
|
+
sun_entity = hass.states.get("sun.sun")
|
|
14
|
+
if sun_entity:
|
|
15
|
+
sunrise = sun_entity.attributes.get("next_rising")
|
|
16
|
+
sunset = sun_entity.attributes.get("next_setting")
|
|
17
|
+
if sunrise and sunset:
|
|
18
|
+
return (
|
|
19
|
+
hass.util.dt.as_local(hass.util.dt.parse_datetime(sunrise)),
|
|
20
|
+
hass.util.dt.as_local(hass.util.dt.parse_datetime(sunset)),
|
|
21
|
+
)
|
|
22
|
+
raise ValueError("No se pudo obtener las horas de amanecer y atardecer de sun.sun")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_night(current_time: datetime, hass) -> bool:
|
|
26
|
+
"""
|
|
27
|
+
Determina si la hora actual está fuera del rango entre el amanecer y el atardecer.
|
|
28
|
+
|
|
29
|
+
:param current_time: Hora actual como objeto datetime.
|
|
30
|
+
:param hass: Instancia de Home Assistant.
|
|
31
|
+
:return: True si es de noche, False si es de día.
|
|
32
|
+
"""
|
|
33
|
+
# Obtener las horas de amanecer y atardecer de la integración sun
|
|
34
|
+
sunrise, sunset = get_sun_times(hass)
|
|
35
|
+
|
|
36
|
+
# Convertimos a objetos time para comparar solo las horas
|
|
37
|
+
current_time_only = current_time.time()
|
|
38
|
+
sunrise_time_only = sunrise.time()
|
|
39
|
+
sunset_time_only = sunset.time()
|
|
40
|
+
|
|
41
|
+
# Devuelve True si la hora está fuera del rango del día
|
|
42
|
+
return current_time_only < sunrise_time_only or current_time_only > sunset_time_only
|