meteocat 3.2.0 → 4.0.0
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/ISSUE_TEMPLATE/bug_report.md +45 -45
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/ISSUE_TEMPLATE/improvement.md +39 -39
- package/.github/ISSUE_TEMPLATE/new_function.md +41 -41
- package/.github/labels.yml +63 -63
- package/.github/workflows/autocloser.yaml +27 -27
- package/.github/workflows/close-on-label.yml +48 -48
- package/.github/workflows/force-sync-labels.yml +18 -18
- package/.github/workflows/hassfest.yaml +13 -13
- package/.github/workflows/publish-zip.yml +67 -67
- package/.github/workflows/release.yml +41 -41
- package/.github/workflows/stale.yml +63 -63
- package/.github/workflows/sync-gitlab.yml +107 -107
- package/.github/workflows/sync-labels.yml +21 -21
- package/.github/workflows/validate.yaml +16 -16
- package/.pre-commit-config.yaml +37 -37
- package/.releaserc +37 -37
- package/AUTHORS.md +13 -13
- package/CHANGELOG.md +954 -932
- package/README.md +207 -207
- package/conftest.py +11 -11
- package/custom_components/meteocat/__init__.py +298 -298
- package/custom_components/meteocat/condition.py +63 -63
- package/custom_components/meteocat/config_flow.py +613 -613
- package/custom_components/meteocat/const.py +132 -132
- package/custom_components/meteocat/helpers.py +58 -58
- package/custom_components/meteocat/manifest.json +25 -25
- package/custom_components/meteocat/options_flow.py +287 -287
- package/custom_components/meteocat/strings.json +1058 -1058
- package/custom_components/meteocat/translations/ca.json +1058 -1058
- package/custom_components/meteocat/translations/en.json +1058 -1058
- package/custom_components/meteocat/translations/es.json +1058 -1058
- package/custom_components/meteocat/version.py +1 -1
- package/custom_components/meteocat/weather.py +218 -218
- package/filetree.py +48 -48
- package/filetree.txt +79 -79
- package/hacs.json +8 -8
- package/info.md +11 -11
- package/package.json +22 -22
- package/poetry.lock +3222 -3222
- package/pyproject.toml +68 -68
- 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,288 +1,288 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from homeassistant.config_entries import ConfigEntry, OptionsFlow
|
|
5
|
-
from homeassistant.exceptions import HomeAssistantError
|
|
6
|
-
from homeassistant.helpers import config_validation as cv
|
|
7
|
-
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
|
|
8
|
-
import voluptuous as vol
|
|
9
|
-
|
|
10
|
-
from .const import (
|
|
11
|
-
CONF_API_KEY,
|
|
12
|
-
LIMIT_XEMA,
|
|
13
|
-
LIMIT_PREDICCIO,
|
|
14
|
-
LIMIT_XDDE,
|
|
15
|
-
LIMIT_QUOTA,
|
|
16
|
-
LIMIT_BASIC,
|
|
17
|
-
LATITUDE,
|
|
18
|
-
LONGITUDE,
|
|
19
|
-
ALTITUDE,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
from meteocatpy.town import MeteocatTown
|
|
23
|
-
from meteocatpy.exceptions import (
|
|
24
|
-
BadRequestError,
|
|
25
|
-
ForbiddenError,
|
|
26
|
-
TooManyRequestsError,
|
|
27
|
-
InternalServerError,
|
|
28
|
-
UnknownAPIError,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
_LOGGER = logging.getLogger(__name__)
|
|
32
|
-
|
|
33
|
-
class MeteocatOptionsFlowHandler(OptionsFlow):
|
|
34
|
-
"""Manejo del flujo de opciones para Meteocat."""
|
|
35
|
-
|
|
36
|
-
def __init__(self, config_entry: ConfigEntry):
|
|
37
|
-
"""Inicializa el flujo de opciones."""
|
|
38
|
-
self._config_entry = config_entry
|
|
39
|
-
self.api_key: str | None = None
|
|
40
|
-
self.limit_xema: int | None = None
|
|
41
|
-
self.limit_prediccio: int | None = None
|
|
42
|
-
self.limit_xdde: int | None = None
|
|
43
|
-
self.limit_quota: int | None = None
|
|
44
|
-
self.limit_basic: int | None = None
|
|
45
|
-
self.latitude: float | None = None
|
|
46
|
-
self.longitude: float | None = None
|
|
47
|
-
self.altitude: float | None = None
|
|
48
|
-
|
|
49
|
-
async def async_step_init(self, user_input: dict | None = None):
|
|
50
|
-
"""Paso inicial del flujo de opciones."""
|
|
51
|
-
if user_input is not None:
|
|
52
|
-
if user_input["option"] == "update_api_and_limits":
|
|
53
|
-
return await self.async_step_update_api_and_limits()
|
|
54
|
-
elif user_input["option"] == "update_limits_only":
|
|
55
|
-
return await self.async_step_update_limits_only()
|
|
56
|
-
elif user_input["option"] == "regenerate_assets":
|
|
57
|
-
return await self.async_step_confirm_regenerate_assets()
|
|
58
|
-
elif user_input["option"] == "update_coordinates":
|
|
59
|
-
return await self.async_step_update_coordinates()
|
|
60
|
-
|
|
61
|
-
return self.async_show_form(
|
|
62
|
-
step_id="init",
|
|
63
|
-
data_schema=vol.Schema({
|
|
64
|
-
vol.Required("option"): SelectSelector(
|
|
65
|
-
SelectSelectorConfig(
|
|
66
|
-
options=[
|
|
67
|
-
"update_api_and_limits",
|
|
68
|
-
"update_limits_only",
|
|
69
|
-
"regenerate_assets",
|
|
70
|
-
"update_coordinates"
|
|
71
|
-
],
|
|
72
|
-
translation_key="option"
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
})
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
async def async_step_update_api_and_limits(self, user_input: dict | None = None):
|
|
79
|
-
"""Permite al usuario actualizar la API Key y los límites."""
|
|
80
|
-
errors = {}
|
|
81
|
-
|
|
82
|
-
if user_input is not None:
|
|
83
|
-
self.api_key = user_input.get(CONF_API_KEY)
|
|
84
|
-
self.limit_xema = user_input.get(LIMIT_XEMA)
|
|
85
|
-
self.limit_prediccio = user_input.get(LIMIT_PREDICCIO)
|
|
86
|
-
self.limit_xdde = user_input.get(LIMIT_XDDE)
|
|
87
|
-
self.limit_quota = user_input.get(LIMIT_QUOTA)
|
|
88
|
-
self.limit_basic = user_input.get(LIMIT_BASIC)
|
|
89
|
-
|
|
90
|
-
# Validar la nueva API Key utilizando MeteocatTown
|
|
91
|
-
if self.api_key:
|
|
92
|
-
town_client = MeteocatTown(self.api_key)
|
|
93
|
-
try:
|
|
94
|
-
await town_client.get_municipis() # Verificar que la API Key sea válida
|
|
95
|
-
except (
|
|
96
|
-
BadRequestError,
|
|
97
|
-
ForbiddenError,
|
|
98
|
-
TooManyRequestsError,
|
|
99
|
-
InternalServerError,
|
|
100
|
-
UnknownAPIError,
|
|
101
|
-
) as ex:
|
|
102
|
-
_LOGGER.error("Error al validar la nueva API Key: %s", ex)
|
|
103
|
-
errors["base"] = "cannot_connect"
|
|
104
|
-
except Exception as ex:
|
|
105
|
-
_LOGGER.error("Error inesperado al validar la nueva API Key: %s", ex)
|
|
106
|
-
errors["base"] = "unknown"
|
|
107
|
-
|
|
108
|
-
# Validar que los límites sean números positivos
|
|
109
|
-
limits_to_validate = [self.limit_xema, self.limit_prediccio, self.limit_xdde, self.limit_quota, self.limit_basic]
|
|
110
|
-
if not all(cv.positive_int(limit) for limit in limits_to_validate if limit is not None):
|
|
111
|
-
errors["base"] = "invalid_limit"
|
|
112
|
-
|
|
113
|
-
if not errors:
|
|
114
|
-
# Actualizar la configuración de la entrada con la nueva API Key y límites
|
|
115
|
-
data_update = {}
|
|
116
|
-
if self.api_key:
|
|
117
|
-
data_update[CONF_API_KEY] = self.api_key
|
|
118
|
-
if self.limit_xema:
|
|
119
|
-
data_update[LIMIT_XEMA] = self.limit_xema
|
|
120
|
-
if self.limit_prediccio:
|
|
121
|
-
data_update[LIMIT_PREDICCIO] = self.limit_prediccio
|
|
122
|
-
if self.limit_xdde:
|
|
123
|
-
data_update[LIMIT_XDDE] = self.limit_xdde
|
|
124
|
-
if self.limit_quota:
|
|
125
|
-
data_update[LIMIT_QUOTA] = self.limit_quota
|
|
126
|
-
if self.limit_basic:
|
|
127
|
-
data_update[LIMIT_BASIC] = self.limit_basic
|
|
128
|
-
|
|
129
|
-
self.hass.config_entries.async_update_entry(
|
|
130
|
-
self._config_entry,
|
|
131
|
-
data={**self._config_entry.data, **data_update},
|
|
132
|
-
)
|
|
133
|
-
# Recargar la integración para aplicar los cambios dinámicamente
|
|
134
|
-
await self.hass.config_entries.async_reload(self._config_entry.entry_id)
|
|
135
|
-
|
|
136
|
-
return self.async_create_entry(title="", data={})
|
|
137
|
-
|
|
138
|
-
schema = vol.Schema({
|
|
139
|
-
vol.Required(CONF_API_KEY): str,
|
|
140
|
-
vol.Required(LIMIT_XEMA, default=self._config_entry.data.get(LIMIT_XEMA)): cv.positive_int,
|
|
141
|
-
vol.Required(LIMIT_PREDICCIO, default=self._config_entry.data.get(LIMIT_PREDICCIO)): cv.positive_int,
|
|
142
|
-
vol.Required(LIMIT_XDDE, default=self._config_entry.data.get(LIMIT_XDDE)): cv.positive_int,
|
|
143
|
-
vol.Required(LIMIT_QUOTA, default=self._config_entry.data.get(LIMIT_QUOTA)): cv.positive_int,
|
|
144
|
-
vol.Required(LIMIT_BASIC, default=self._config_entry.data.get(LIMIT_BASIC)): cv.positive_int,
|
|
145
|
-
})
|
|
146
|
-
return self.async_show_form(
|
|
147
|
-
step_id="update_api_and_limits", data_schema=schema, errors=errors
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
async def async_step_update_limits_only(self, user_input: dict | None = None):
|
|
151
|
-
"""Permite al usuario actualizar solo los límites de la API."""
|
|
152
|
-
errors = {}
|
|
153
|
-
|
|
154
|
-
if user_input is not None:
|
|
155
|
-
self.limit_xema = user_input.get(LIMIT_XEMA)
|
|
156
|
-
self.limit_prediccio = user_input.get(LIMIT_PREDICCIO)
|
|
157
|
-
self.limit_xdde = user_input.get(LIMIT_XDDE)
|
|
158
|
-
self.limit_quota = user_input.get(LIMIT_QUOTA)
|
|
159
|
-
self.limit_basic = user_input.get(LIMIT_BASIC)
|
|
160
|
-
|
|
161
|
-
# Validar que los límites sean números positivos
|
|
162
|
-
limits_to_validate = [self.limit_xema, self.limit_prediccio, self.limit_xdde, self.limit_quota, self.limit_basic]
|
|
163
|
-
if not all(cv.positive_int(limit) for limit in limits_to_validate if limit is not None):
|
|
164
|
-
errors["base"] = "invalid_limit"
|
|
165
|
-
|
|
166
|
-
if not errors:
|
|
167
|
-
self.hass.config_entries.async_update_entry(
|
|
168
|
-
self._config_entry,
|
|
169
|
-
data={
|
|
170
|
-
**self._config_entry.data,
|
|
171
|
-
LIMIT_XEMA: self.limit_xema,
|
|
172
|
-
LIMIT_PREDICCIO: self.limit_prediccio,
|
|
173
|
-
LIMIT_XDDE: self.limit_xdde,
|
|
174
|
-
LIMIT_QUOTA: self.limit_quota,
|
|
175
|
-
LIMIT_BASIC: self.limit_basic
|
|
176
|
-
},
|
|
177
|
-
)
|
|
178
|
-
# Recargar la integración para aplicar los cambios dinámicamente
|
|
179
|
-
await self.hass.config_entries.async_reload(self._config_entry.entry_id)
|
|
180
|
-
|
|
181
|
-
return self.async_create_entry(title="", data={})
|
|
182
|
-
|
|
183
|
-
schema = vol.Schema({
|
|
184
|
-
vol.Required(LIMIT_XEMA, default=self._config_entry.data.get(LIMIT_XEMA)): cv.positive_int,
|
|
185
|
-
vol.Required(LIMIT_PREDICCIO, default=self._config_entry.data.get(LIMIT_PREDICCIO)): cv.positive_int,
|
|
186
|
-
vol.Required(LIMIT_XDDE, default=self._config_entry.data.get(LIMIT_XDDE)): cv.positive_int,
|
|
187
|
-
vol.Required(LIMIT_QUOTA, default=self._config_entry.data.get(LIMIT_QUOTA)): cv.positive_int,
|
|
188
|
-
vol.Required(LIMIT_BASIC, default=self._config_entry.data.get(LIMIT_BASIC)): cv.positive_int,
|
|
189
|
-
})
|
|
190
|
-
return self.async_show_form(
|
|
191
|
-
step_id="update_limits_only", data_schema=schema, errors=errors
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
async def async_step_update_coordinates(self, user_input: dict | None = None):
|
|
195
|
-
"""Permite al usuario actualizar las coordenadas (latitude, longitude)."""
|
|
196
|
-
errors = {}
|
|
197
|
-
|
|
198
|
-
if user_input is not None:
|
|
199
|
-
self.latitude = user_input.get(LATITUDE)
|
|
200
|
-
self.longitude = user_input.get(LONGITUDE)
|
|
201
|
-
self.altitude = user_input.get(ALTITUDE)
|
|
202
|
-
|
|
203
|
-
# Validar que las coordenadas estén dentro del rango de Cataluña
|
|
204
|
-
if not (40.5 <= self.latitude <= 42.5 and 0.1 <= self.longitude <= 3.3):
|
|
205
|
-
_LOGGER.error(
|
|
206
|
-
"Coordenadas fuera del rango de Cataluña (latitude: %s, longitude: %s).",
|
|
207
|
-
self.latitude, self.longitude
|
|
208
|
-
)
|
|
209
|
-
errors["base"] = "invalid_coordinates"
|
|
210
|
-
# Validar que la altitud sea positiva
|
|
211
|
-
elif self.altitude < 0:
|
|
212
|
-
_LOGGER.error("Altitud inválida: %s. Debe ser >= 0.", self.altitude)
|
|
213
|
-
errors["base"] = "invalid_altitude"
|
|
214
|
-
else:
|
|
215
|
-
# Actualizar la configuración con las nuevas coordenadas
|
|
216
|
-
self.hass.config_entries.async_update_entry(
|
|
217
|
-
self._config_entry,
|
|
218
|
-
data={
|
|
219
|
-
**self._config_entry.data,
|
|
220
|
-
LATITUDE: self.latitude,
|
|
221
|
-
LONGITUDE: self.longitude,
|
|
222
|
-
ALTITUDE: self.altitude
|
|
223
|
-
},
|
|
224
|
-
)
|
|
225
|
-
# Recargar la integración para aplicar los cambios
|
|
226
|
-
await self.hass.config_entries.async_reload(self._config_entry.entry_id)
|
|
227
|
-
_LOGGER.info(
|
|
228
|
-
"Coordenadas actualizadas a latitude: %s, longitude: %s, altitude=%s.",
|
|
229
|
-
self.latitude, self.longitude, self.altitude
|
|
230
|
-
)
|
|
231
|
-
return self.async_create_entry(title="", data={})
|
|
232
|
-
|
|
233
|
-
schema = vol.Schema({
|
|
234
|
-
vol.Required(LATITUDE, default=self._config_entry.data.get(LATITUDE)): cv.latitude,
|
|
235
|
-
vol.Required(LONGITUDE, default=self._config_entry.data.get(LONGITUDE)): cv.longitude,
|
|
236
|
-
vol.Required(ALTITUDE, default=self._config_entry.data.get(ALTITUDE)): vol.Coerce(float),
|
|
237
|
-
})
|
|
238
|
-
return self.async_show_form(
|
|
239
|
-
step_id="update_coordinates",
|
|
240
|
-
data_schema=schema,
|
|
241
|
-
errors=errors,
|
|
242
|
-
description_placeholders={
|
|
243
|
-
"current_latitude": self._config_entry.data.get(LATITUDE),
|
|
244
|
-
"current_longitude": self._config_entry.data.get(LONGITUDE),
|
|
245
|
-
"current_altitude": self._config_entry.data.get(ALTITUDE, 0.0)
|
|
246
|
-
}
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
async def async_step_confirm_regenerate_assets(self, user_input: dict | None = None):
|
|
250
|
-
"""Confirma si el usuario realmente quiere regenerar los assets."""
|
|
251
|
-
if user_input is not None:
|
|
252
|
-
if user_input.get("confirm") is True:
|
|
253
|
-
return await self.async_step_regenerate_assets()
|
|
254
|
-
else:
|
|
255
|
-
# Volver al menú inicial si el usuario cancela
|
|
256
|
-
return await self.async_step_init()
|
|
257
|
-
|
|
258
|
-
schema = vol.Schema({
|
|
259
|
-
vol.Required("confirm", default=False): bool
|
|
260
|
-
})
|
|
261
|
-
return self.async_show_form(
|
|
262
|
-
step_id="confirm_regenerate_assets",
|
|
263
|
-
data_schema=schema,
|
|
264
|
-
description_placeholders={
|
|
265
|
-
"warning": "Esto regenerará los archivos faltantes de towns.json, stations.json, variables.json, symbols.json y stations_<town_id>.json. ¿Desea continuar?"
|
|
266
|
-
}
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
async def async_step_regenerate_assets(self, user_input: dict | None = None):
|
|
270
|
-
"""Regenera los archivos de assets."""
|
|
271
|
-
from . import ensure_assets_exist # importamos la función desde __init__.py
|
|
272
|
-
|
|
273
|
-
errors = {}
|
|
274
|
-
try:
|
|
275
|
-
# Llamar a la función que garantiza que los assets existan
|
|
276
|
-
await ensure_assets_exist(self.hass, self._config_entry.data)
|
|
277
|
-
|
|
278
|
-
_LOGGER.info("Archivos de assets regenerados correctamente.")
|
|
279
|
-
# Forzar recarga de la integración
|
|
280
|
-
await self.hass.config_entries.async_reload(self._config_entry.entry_id)
|
|
281
|
-
|
|
282
|
-
return self.async_create_entry(title="", data={})
|
|
283
|
-
|
|
284
|
-
except Exception as ex:
|
|
285
|
-
_LOGGER.error("Error al regenerar assets: %s", ex)
|
|
286
|
-
errors["base"] = "regenerate_failed"
|
|
287
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from homeassistant.config_entries import ConfigEntry, OptionsFlow
|
|
5
|
+
from homeassistant.exceptions import HomeAssistantError
|
|
6
|
+
from homeassistant.helpers import config_validation as cv
|
|
7
|
+
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
|
|
8
|
+
import voluptuous as vol
|
|
9
|
+
|
|
10
|
+
from .const import (
|
|
11
|
+
CONF_API_KEY,
|
|
12
|
+
LIMIT_XEMA,
|
|
13
|
+
LIMIT_PREDICCIO,
|
|
14
|
+
LIMIT_XDDE,
|
|
15
|
+
LIMIT_QUOTA,
|
|
16
|
+
LIMIT_BASIC,
|
|
17
|
+
LATITUDE,
|
|
18
|
+
LONGITUDE,
|
|
19
|
+
ALTITUDE,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from meteocatpy.town import MeteocatTown
|
|
23
|
+
from meteocatpy.exceptions import (
|
|
24
|
+
BadRequestError,
|
|
25
|
+
ForbiddenError,
|
|
26
|
+
TooManyRequestsError,
|
|
27
|
+
InternalServerError,
|
|
28
|
+
UnknownAPIError,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_LOGGER = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
class MeteocatOptionsFlowHandler(OptionsFlow):
|
|
34
|
+
"""Manejo del flujo de opciones para Meteocat."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, config_entry: ConfigEntry):
|
|
37
|
+
"""Inicializa el flujo de opciones."""
|
|
38
|
+
self._config_entry = config_entry
|
|
39
|
+
self.api_key: str | None = None
|
|
40
|
+
self.limit_xema: int | None = None
|
|
41
|
+
self.limit_prediccio: int | None = None
|
|
42
|
+
self.limit_xdde: int | None = None
|
|
43
|
+
self.limit_quota: int | None = None
|
|
44
|
+
self.limit_basic: int | None = None
|
|
45
|
+
self.latitude: float | None = None
|
|
46
|
+
self.longitude: float | None = None
|
|
47
|
+
self.altitude: float | None = None
|
|
48
|
+
|
|
49
|
+
async def async_step_init(self, user_input: dict | None = None):
|
|
50
|
+
"""Paso inicial del flujo de opciones."""
|
|
51
|
+
if user_input is not None:
|
|
52
|
+
if user_input["option"] == "update_api_and_limits":
|
|
53
|
+
return await self.async_step_update_api_and_limits()
|
|
54
|
+
elif user_input["option"] == "update_limits_only":
|
|
55
|
+
return await self.async_step_update_limits_only()
|
|
56
|
+
elif user_input["option"] == "regenerate_assets":
|
|
57
|
+
return await self.async_step_confirm_regenerate_assets()
|
|
58
|
+
elif user_input["option"] == "update_coordinates":
|
|
59
|
+
return await self.async_step_update_coordinates()
|
|
60
|
+
|
|
61
|
+
return self.async_show_form(
|
|
62
|
+
step_id="init",
|
|
63
|
+
data_schema=vol.Schema({
|
|
64
|
+
vol.Required("option"): SelectSelector(
|
|
65
|
+
SelectSelectorConfig(
|
|
66
|
+
options=[
|
|
67
|
+
"update_api_and_limits",
|
|
68
|
+
"update_limits_only",
|
|
69
|
+
"regenerate_assets",
|
|
70
|
+
"update_coordinates"
|
|
71
|
+
],
|
|
72
|
+
translation_key="option"
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def async_step_update_api_and_limits(self, user_input: dict | None = None):
|
|
79
|
+
"""Permite al usuario actualizar la API Key y los límites."""
|
|
80
|
+
errors = {}
|
|
81
|
+
|
|
82
|
+
if user_input is not None:
|
|
83
|
+
self.api_key = user_input.get(CONF_API_KEY)
|
|
84
|
+
self.limit_xema = user_input.get(LIMIT_XEMA)
|
|
85
|
+
self.limit_prediccio = user_input.get(LIMIT_PREDICCIO)
|
|
86
|
+
self.limit_xdde = user_input.get(LIMIT_XDDE)
|
|
87
|
+
self.limit_quota = user_input.get(LIMIT_QUOTA)
|
|
88
|
+
self.limit_basic = user_input.get(LIMIT_BASIC)
|
|
89
|
+
|
|
90
|
+
# Validar la nueva API Key utilizando MeteocatTown
|
|
91
|
+
if self.api_key:
|
|
92
|
+
town_client = MeteocatTown(self.api_key)
|
|
93
|
+
try:
|
|
94
|
+
await town_client.get_municipis() # Verificar que la API Key sea válida
|
|
95
|
+
except (
|
|
96
|
+
BadRequestError,
|
|
97
|
+
ForbiddenError,
|
|
98
|
+
TooManyRequestsError,
|
|
99
|
+
InternalServerError,
|
|
100
|
+
UnknownAPIError,
|
|
101
|
+
) as ex:
|
|
102
|
+
_LOGGER.error("Error al validar la nueva API Key: %s", ex)
|
|
103
|
+
errors["base"] = "cannot_connect"
|
|
104
|
+
except Exception as ex:
|
|
105
|
+
_LOGGER.error("Error inesperado al validar la nueva API Key: %s", ex)
|
|
106
|
+
errors["base"] = "unknown"
|
|
107
|
+
|
|
108
|
+
# Validar que los límites sean números positivos
|
|
109
|
+
limits_to_validate = [self.limit_xema, self.limit_prediccio, self.limit_xdde, self.limit_quota, self.limit_basic]
|
|
110
|
+
if not all(cv.positive_int(limit) for limit in limits_to_validate if limit is not None):
|
|
111
|
+
errors["base"] = "invalid_limit"
|
|
112
|
+
|
|
113
|
+
if not errors:
|
|
114
|
+
# Actualizar la configuración de la entrada con la nueva API Key y límites
|
|
115
|
+
data_update = {}
|
|
116
|
+
if self.api_key:
|
|
117
|
+
data_update[CONF_API_KEY] = self.api_key
|
|
118
|
+
if self.limit_xema:
|
|
119
|
+
data_update[LIMIT_XEMA] = self.limit_xema
|
|
120
|
+
if self.limit_prediccio:
|
|
121
|
+
data_update[LIMIT_PREDICCIO] = self.limit_prediccio
|
|
122
|
+
if self.limit_xdde:
|
|
123
|
+
data_update[LIMIT_XDDE] = self.limit_xdde
|
|
124
|
+
if self.limit_quota:
|
|
125
|
+
data_update[LIMIT_QUOTA] = self.limit_quota
|
|
126
|
+
if self.limit_basic:
|
|
127
|
+
data_update[LIMIT_BASIC] = self.limit_basic
|
|
128
|
+
|
|
129
|
+
self.hass.config_entries.async_update_entry(
|
|
130
|
+
self._config_entry,
|
|
131
|
+
data={**self._config_entry.data, **data_update},
|
|
132
|
+
)
|
|
133
|
+
# Recargar la integración para aplicar los cambios dinámicamente
|
|
134
|
+
await self.hass.config_entries.async_reload(self._config_entry.entry_id)
|
|
135
|
+
|
|
136
|
+
return self.async_create_entry(title="", data={})
|
|
137
|
+
|
|
138
|
+
schema = vol.Schema({
|
|
139
|
+
vol.Required(CONF_API_KEY): str,
|
|
140
|
+
vol.Required(LIMIT_XEMA, default=self._config_entry.data.get(LIMIT_XEMA)): cv.positive_int,
|
|
141
|
+
vol.Required(LIMIT_PREDICCIO, default=self._config_entry.data.get(LIMIT_PREDICCIO)): cv.positive_int,
|
|
142
|
+
vol.Required(LIMIT_XDDE, default=self._config_entry.data.get(LIMIT_XDDE)): cv.positive_int,
|
|
143
|
+
vol.Required(LIMIT_QUOTA, default=self._config_entry.data.get(LIMIT_QUOTA)): cv.positive_int,
|
|
144
|
+
vol.Required(LIMIT_BASIC, default=self._config_entry.data.get(LIMIT_BASIC)): cv.positive_int,
|
|
145
|
+
})
|
|
146
|
+
return self.async_show_form(
|
|
147
|
+
step_id="update_api_and_limits", data_schema=schema, errors=errors
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
async def async_step_update_limits_only(self, user_input: dict | None = None):
|
|
151
|
+
"""Permite al usuario actualizar solo los límites de la API."""
|
|
152
|
+
errors = {}
|
|
153
|
+
|
|
154
|
+
if user_input is not None:
|
|
155
|
+
self.limit_xema = user_input.get(LIMIT_XEMA)
|
|
156
|
+
self.limit_prediccio = user_input.get(LIMIT_PREDICCIO)
|
|
157
|
+
self.limit_xdde = user_input.get(LIMIT_XDDE)
|
|
158
|
+
self.limit_quota = user_input.get(LIMIT_QUOTA)
|
|
159
|
+
self.limit_basic = user_input.get(LIMIT_BASIC)
|
|
160
|
+
|
|
161
|
+
# Validar que los límites sean números positivos
|
|
162
|
+
limits_to_validate = [self.limit_xema, self.limit_prediccio, self.limit_xdde, self.limit_quota, self.limit_basic]
|
|
163
|
+
if not all(cv.positive_int(limit) for limit in limits_to_validate if limit is not None):
|
|
164
|
+
errors["base"] = "invalid_limit"
|
|
165
|
+
|
|
166
|
+
if not errors:
|
|
167
|
+
self.hass.config_entries.async_update_entry(
|
|
168
|
+
self._config_entry,
|
|
169
|
+
data={
|
|
170
|
+
**self._config_entry.data,
|
|
171
|
+
LIMIT_XEMA: self.limit_xema,
|
|
172
|
+
LIMIT_PREDICCIO: self.limit_prediccio,
|
|
173
|
+
LIMIT_XDDE: self.limit_xdde,
|
|
174
|
+
LIMIT_QUOTA: self.limit_quota,
|
|
175
|
+
LIMIT_BASIC: self.limit_basic
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
# Recargar la integración para aplicar los cambios dinámicamente
|
|
179
|
+
await self.hass.config_entries.async_reload(self._config_entry.entry_id)
|
|
180
|
+
|
|
181
|
+
return self.async_create_entry(title="", data={})
|
|
182
|
+
|
|
183
|
+
schema = vol.Schema({
|
|
184
|
+
vol.Required(LIMIT_XEMA, default=self._config_entry.data.get(LIMIT_XEMA)): cv.positive_int,
|
|
185
|
+
vol.Required(LIMIT_PREDICCIO, default=self._config_entry.data.get(LIMIT_PREDICCIO)): cv.positive_int,
|
|
186
|
+
vol.Required(LIMIT_XDDE, default=self._config_entry.data.get(LIMIT_XDDE)): cv.positive_int,
|
|
187
|
+
vol.Required(LIMIT_QUOTA, default=self._config_entry.data.get(LIMIT_QUOTA)): cv.positive_int,
|
|
188
|
+
vol.Required(LIMIT_BASIC, default=self._config_entry.data.get(LIMIT_BASIC)): cv.positive_int,
|
|
189
|
+
})
|
|
190
|
+
return self.async_show_form(
|
|
191
|
+
step_id="update_limits_only", data_schema=schema, errors=errors
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
async def async_step_update_coordinates(self, user_input: dict | None = None):
|
|
195
|
+
"""Permite al usuario actualizar las coordenadas (latitude, longitude)."""
|
|
196
|
+
errors = {}
|
|
197
|
+
|
|
198
|
+
if user_input is not None:
|
|
199
|
+
self.latitude = user_input.get(LATITUDE)
|
|
200
|
+
self.longitude = user_input.get(LONGITUDE)
|
|
201
|
+
self.altitude = user_input.get(ALTITUDE)
|
|
202
|
+
|
|
203
|
+
# Validar que las coordenadas estén dentro del rango de Cataluña
|
|
204
|
+
if not (40.5 <= self.latitude <= 42.5 and 0.1 <= self.longitude <= 3.3):
|
|
205
|
+
_LOGGER.error(
|
|
206
|
+
"Coordenadas fuera del rango de Cataluña (latitude: %s, longitude: %s).",
|
|
207
|
+
self.latitude, self.longitude
|
|
208
|
+
)
|
|
209
|
+
errors["base"] = "invalid_coordinates"
|
|
210
|
+
# Validar que la altitud sea positiva
|
|
211
|
+
elif self.altitude < 0:
|
|
212
|
+
_LOGGER.error("Altitud inválida: %s. Debe ser >= 0.", self.altitude)
|
|
213
|
+
errors["base"] = "invalid_altitude"
|
|
214
|
+
else:
|
|
215
|
+
# Actualizar la configuración con las nuevas coordenadas
|
|
216
|
+
self.hass.config_entries.async_update_entry(
|
|
217
|
+
self._config_entry,
|
|
218
|
+
data={
|
|
219
|
+
**self._config_entry.data,
|
|
220
|
+
LATITUDE: self.latitude,
|
|
221
|
+
LONGITUDE: self.longitude,
|
|
222
|
+
ALTITUDE: self.altitude
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
# Recargar la integración para aplicar los cambios
|
|
226
|
+
await self.hass.config_entries.async_reload(self._config_entry.entry_id)
|
|
227
|
+
_LOGGER.info(
|
|
228
|
+
"Coordenadas actualizadas a latitude: %s, longitude: %s, altitude=%s.",
|
|
229
|
+
self.latitude, self.longitude, self.altitude
|
|
230
|
+
)
|
|
231
|
+
return self.async_create_entry(title="", data={})
|
|
232
|
+
|
|
233
|
+
schema = vol.Schema({
|
|
234
|
+
vol.Required(LATITUDE, default=self._config_entry.data.get(LATITUDE)): cv.latitude,
|
|
235
|
+
vol.Required(LONGITUDE, default=self._config_entry.data.get(LONGITUDE)): cv.longitude,
|
|
236
|
+
vol.Required(ALTITUDE, default=self._config_entry.data.get(ALTITUDE)): vol.Coerce(float),
|
|
237
|
+
})
|
|
238
|
+
return self.async_show_form(
|
|
239
|
+
step_id="update_coordinates",
|
|
240
|
+
data_schema=schema,
|
|
241
|
+
errors=errors,
|
|
242
|
+
description_placeholders={
|
|
243
|
+
"current_latitude": self._config_entry.data.get(LATITUDE),
|
|
244
|
+
"current_longitude": self._config_entry.data.get(LONGITUDE),
|
|
245
|
+
"current_altitude": self._config_entry.data.get(ALTITUDE, 0.0)
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
async def async_step_confirm_regenerate_assets(self, user_input: dict | None = None):
|
|
250
|
+
"""Confirma si el usuario realmente quiere regenerar los assets."""
|
|
251
|
+
if user_input is not None:
|
|
252
|
+
if user_input.get("confirm") is True:
|
|
253
|
+
return await self.async_step_regenerate_assets()
|
|
254
|
+
else:
|
|
255
|
+
# Volver al menú inicial si el usuario cancela
|
|
256
|
+
return await self.async_step_init()
|
|
257
|
+
|
|
258
|
+
schema = vol.Schema({
|
|
259
|
+
vol.Required("confirm", default=False): bool
|
|
260
|
+
})
|
|
261
|
+
return self.async_show_form(
|
|
262
|
+
step_id="confirm_regenerate_assets",
|
|
263
|
+
data_schema=schema,
|
|
264
|
+
description_placeholders={
|
|
265
|
+
"warning": "Esto regenerará los archivos faltantes de towns.json, stations.json, variables.json, symbols.json y stations_<town_id>.json. ¿Desea continuar?"
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
async def async_step_regenerate_assets(self, user_input: dict | None = None):
|
|
270
|
+
"""Regenera los archivos de assets."""
|
|
271
|
+
from . import ensure_assets_exist # importamos la función desde __init__.py
|
|
272
|
+
|
|
273
|
+
errors = {}
|
|
274
|
+
try:
|
|
275
|
+
# Llamar a la función que garantiza que los assets existan
|
|
276
|
+
await ensure_assets_exist(self.hass, self._config_entry.data)
|
|
277
|
+
|
|
278
|
+
_LOGGER.info("Archivos de assets regenerados correctamente.")
|
|
279
|
+
# Forzar recarga de la integración
|
|
280
|
+
await self.hass.config_entries.async_reload(self._config_entry.entry_id)
|
|
281
|
+
|
|
282
|
+
return self.async_create_entry(title="", data={})
|
|
283
|
+
|
|
284
|
+
except Exception as ex:
|
|
285
|
+
_LOGGER.error("Error al regenerar assets: %s", ex)
|
|
286
|
+
errors["base"] = "regenerate_failed"
|
|
287
|
+
|
|
288
288
|
return self.async_show_form(step_id="regenerate_assets", errors=errors)
|