meteocat 3.1.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 -898
- package/README.md +207 -204
- package/conftest.py +11 -11
- package/custom_components/meteocat/__init__.py +298 -293
- package/custom_components/meteocat/condition.py +63 -59
- package/custom_components/meteocat/config_flow.py +613 -435
- package/custom_components/meteocat/const.py +132 -120
- package/custom_components/meteocat/coordinator.py +1040 -205
- package/custom_components/meteocat/helpers.py +58 -63
- package/custom_components/meteocat/manifest.json +25 -24
- package/custom_components/meteocat/options_flow.py +287 -277
- package/custom_components/meteocat/sensor.py +366 -4
- package/custom_components/meteocat/strings.json +1058 -867
- package/custom_components/meteocat/translations/ca.json +1058 -867
- package/custom_components/meteocat/translations/en.json +1058 -867
- package/custom_components/meteocat/translations/es.json +1058 -867
- 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 -70
- package/hacs.json +8 -8
- package/images/daily_forecast_2_alerts.png +0 -0
- package/images/daily_forecast_no_alerts.png +0 -0
- package/images/diagnostic_sensors.png +0 -0
- package/images/dynamic_sensors.png +0 -0
- package/images/options.png +0 -0
- package/images/regenerate_assets.png +0 -0
- package/images/setup_options.png +0 -0
- package/images/system_options.png +0 -0
- 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,293 +1,298 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import voluptuous as vol
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
import aiofiles
|
|
7
|
-
import json
|
|
8
|
-
import importlib # Para importaciones lazy
|
|
9
|
-
|
|
10
|
-
from homeassistant import core
|
|
11
|
-
from homeassistant.config_entries import ConfigEntry
|
|
12
|
-
from homeassistant.core import HomeAssistant
|
|
13
|
-
from homeassistant.exceptions import HomeAssistantError
|
|
14
|
-
from homeassistant.helpers.entity_platform import async_get_platforms
|
|
15
|
-
from homeassistant.helpers import config_validation as cv
|
|
16
|
-
|
|
17
|
-
from .helpers import get_storage_dir
|
|
18
|
-
from meteocatpy.town import MeteocatTown
|
|
19
|
-
from meteocatpy.symbols import MeteocatSymbols
|
|
20
|
-
from meteocatpy.variables import MeteocatVariables
|
|
21
|
-
from meteocatpy.townstations import MeteocatTownStations
|
|
22
|
-
from .const import DOMAIN, PLATFORMS
|
|
23
|
-
|
|
24
|
-
_LOGGER = logging.getLogger(__name__)
|
|
25
|
-
|
|
26
|
-
# Versión
|
|
27
|
-
__version__ = "
|
|
28
|
-
|
|
29
|
-
# Definir el esquema de configuración CONFIG_SCHEMA
|
|
30
|
-
CONFIG_SCHEMA = vol.Schema(
|
|
31
|
-
{
|
|
32
|
-
DOMAIN: vol.Schema(
|
|
33
|
-
{
|
|
34
|
-
vol.Required("api_key"): cv.string,
|
|
35
|
-
vol.Required("town_name"): cv.string,
|
|
36
|
-
vol.Required("town_id"): cv.string,
|
|
37
|
-
vol.Optional("variable_name", default="temperature"): cv.string,
|
|
38
|
-
vol.Required("variable_id"): cv.string,
|
|
39
|
-
vol.Optional("station_name"): cv.string,
|
|
40
|
-
vol.Optional("station_id"): cv.string,
|
|
41
|
-
vol.Optional("province_name"): cv.string,
|
|
42
|
-
vol.Optional("province_id"): cv.string,
|
|
43
|
-
vol.Optional("region_name"): cv.string,
|
|
44
|
-
vol.Optional("region_id"): cv.string,
|
|
45
|
-
vol.Required("latitude"): cv.latitude,
|
|
46
|
-
vol.Required("longitude"): cv.longitude,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
assets_dir
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
assets
|
|
74
|
-
|
|
75
|
-
("
|
|
76
|
-
("
|
|
77
|
-
("
|
|
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
|
-
if
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
latitude, longitude
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
f"
|
|
155
|
-
f"
|
|
156
|
-
f"
|
|
157
|
-
f"
|
|
158
|
-
f"
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
("
|
|
166
|
-
("
|
|
167
|
-
("
|
|
168
|
-
("
|
|
169
|
-
("
|
|
170
|
-
("
|
|
171
|
-
("
|
|
172
|
-
("
|
|
173
|
-
("
|
|
174
|
-
("
|
|
175
|
-
("
|
|
176
|
-
("
|
|
177
|
-
("
|
|
178
|
-
("
|
|
179
|
-
("
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import voluptuous as vol
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import aiofiles
|
|
7
|
+
import json
|
|
8
|
+
import importlib # Para importaciones lazy
|
|
9
|
+
|
|
10
|
+
from homeassistant import core
|
|
11
|
+
from homeassistant.config_entries import ConfigEntry
|
|
12
|
+
from homeassistant.core import HomeAssistant
|
|
13
|
+
from homeassistant.exceptions import HomeAssistantError
|
|
14
|
+
from homeassistant.helpers.entity_platform import async_get_platforms
|
|
15
|
+
from homeassistant.helpers import config_validation as cv
|
|
16
|
+
|
|
17
|
+
from .helpers import get_storage_dir
|
|
18
|
+
from meteocatpy.town import MeteocatTown
|
|
19
|
+
from meteocatpy.symbols import MeteocatSymbols
|
|
20
|
+
from meteocatpy.variables import MeteocatVariables
|
|
21
|
+
from meteocatpy.townstations import MeteocatTownStations
|
|
22
|
+
from .const import DOMAIN, PLATFORMS
|
|
23
|
+
|
|
24
|
+
_LOGGER = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# Versión
|
|
27
|
+
__version__ = "4.0.0"
|
|
28
|
+
|
|
29
|
+
# Definir el esquema de configuración CONFIG_SCHEMA
|
|
30
|
+
CONFIG_SCHEMA = vol.Schema(
|
|
31
|
+
{
|
|
32
|
+
DOMAIN: vol.Schema(
|
|
33
|
+
{
|
|
34
|
+
vol.Required("api_key"): cv.string,
|
|
35
|
+
vol.Required("town_name"): cv.string,
|
|
36
|
+
vol.Required("town_id"): cv.string,
|
|
37
|
+
vol.Optional("variable_name", default="temperature"): cv.string,
|
|
38
|
+
vol.Required("variable_id"): cv.string,
|
|
39
|
+
vol.Optional("station_name"): cv.string,
|
|
40
|
+
vol.Optional("station_id"): cv.string,
|
|
41
|
+
vol.Optional("province_name"): cv.string,
|
|
42
|
+
vol.Optional("province_id"): cv.string,
|
|
43
|
+
vol.Optional("region_name"): cv.string,
|
|
44
|
+
vol.Optional("region_id"): cv.string,
|
|
45
|
+
vol.Required("latitude"): cv.latitude,
|
|
46
|
+
vol.Required("longitude"): cv.longitude,
|
|
47
|
+
vol.Required("altitude"): vol.Coerce(float),
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
},
|
|
51
|
+
extra=vol.ALLOW_EXTRA,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def safe_remove(path: Path, is_folder: bool = False) -> None:
|
|
55
|
+
"""Elimina un archivo o carpeta vacía de forma segura."""
|
|
56
|
+
try:
|
|
57
|
+
if is_folder:
|
|
58
|
+
if path.exists() and path.is_dir():
|
|
59
|
+
path.rmdir() # Solo elimina si está vacía
|
|
60
|
+
_LOGGER.info("Carpeta eliminada: %s", path)
|
|
61
|
+
else:
|
|
62
|
+
if path.exists():
|
|
63
|
+
path.unlink()
|
|
64
|
+
_LOGGER.info("Archivo eliminado: %s", path)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
_LOGGER.error("Error eliminando %s: %s", path, e)
|
|
67
|
+
|
|
68
|
+
async def ensure_assets_exist(hass, api_key, town_id=None, variable_id=None):
|
|
69
|
+
"""Comprueba y crea los assets básicos si faltan."""
|
|
70
|
+
assets_dir = get_storage_dir(hass, "assets")
|
|
71
|
+
assets_dir.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
|
|
73
|
+
# Lista de assets: (nombre_archivo, fetch_func, clave_json, args)
|
|
74
|
+
assets = [
|
|
75
|
+
("towns.json", MeteocatTown(api_key).get_municipis, "towns", []),
|
|
76
|
+
("stations.json", MeteocatTownStations(api_key).stations_service.get_stations, "stations", []),
|
|
77
|
+
("variables.json", MeteocatVariables(api_key).get_variables, "variables", []),
|
|
78
|
+
("symbols.json", MeteocatSymbols(api_key).fetch_symbols, "symbols", []),
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
# Si tenemos town_id y variable_id, agregamos stations_<town_id>.json
|
|
82
|
+
if town_id and variable_id:
|
|
83
|
+
assets.append(
|
|
84
|
+
(f"stations_{town_id}.json", MeteocatTownStations(api_key).get_town_stations, "town_stations", [town_id, variable_id])
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
for filename, fetch_func, key, args in assets:
|
|
88
|
+
file_path = assets_dir / filename
|
|
89
|
+
if not file_path.exists():
|
|
90
|
+
_LOGGER.debug("Intentando descargar datos para %s desde la API con args: %s", key, args)
|
|
91
|
+
try:
|
|
92
|
+
data = await fetch_func(*args)
|
|
93
|
+
except Exception as ex:
|
|
94
|
+
_LOGGER.warning(
|
|
95
|
+
"No se pudieron obtener los datos para %s. Intenta regenerarlo más adelante desde las opciones de la integración. Detalle: %s",
|
|
96
|
+
key,
|
|
97
|
+
ex,
|
|
98
|
+
)
|
|
99
|
+
data = []
|
|
100
|
+
async with aiofiles.open(file_path, "w", encoding="utf-8") as file:
|
|
101
|
+
await file.write(json.dumps({key: data}, ensure_ascii=False, indent=4))
|
|
102
|
+
_LOGGER.info("Archivo creado: %s", file_path)
|
|
103
|
+
|
|
104
|
+
async def async_setup(hass: core.HomeAssistant, config: dict) -> bool:
|
|
105
|
+
"""Configuración inicial del componente Meteocat."""
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
def _get_coordinator_module(cls_name: str):
|
|
109
|
+
"""Importa dinámicamente un coordinador para evitar blocking imports."""
|
|
110
|
+
module = importlib.import_module(".coordinator", "custom_components.meteocat")
|
|
111
|
+
return getattr(module, cls_name)
|
|
112
|
+
|
|
113
|
+
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
114
|
+
"""Configura una entrada de configuración para Meteocat."""
|
|
115
|
+
_LOGGER.info("Configurando la integración de Meteocat...")
|
|
116
|
+
|
|
117
|
+
# Extraer los datos necesarios de la entrada de configuración
|
|
118
|
+
entry_data = entry.data
|
|
119
|
+
|
|
120
|
+
# Validar campos requeridos
|
|
121
|
+
required_fields = [
|
|
122
|
+
"api_key", "town_name", "town_id", "variable_name",
|
|
123
|
+
"variable_id", "station_name", "station_id", "province_name",
|
|
124
|
+
"province_id", "region_name", "region_id", "latitude", "longitude", "altitude"
|
|
125
|
+
]
|
|
126
|
+
missing_fields = [field for field in required_fields if field not in entry_data]
|
|
127
|
+
if missing_fields:
|
|
128
|
+
_LOGGER.error(f"Faltan los siguientes campos en la configuración: {missing_fields}")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
# Validar coordenadas válidas para Cataluña
|
|
132
|
+
latitude = entry_data.get("latitude")
|
|
133
|
+
longitude = entry_data.get("longitude")
|
|
134
|
+
if not (40.5 <= latitude <= 42.5 and 0.1 <= longitude <= 3.3): # Rango aproximado para Cataluña
|
|
135
|
+
_LOGGER.warning(
|
|
136
|
+
"Coordenadas inválidas (latitude: %s, longitude: %s). Usando coordenadas de Barcelona por defecto para MeteocatSunCoordinator.",
|
|
137
|
+
latitude, longitude
|
|
138
|
+
)
|
|
139
|
+
entry_data = {
|
|
140
|
+
**entry_data,
|
|
141
|
+
"latitude": 41.38879,
|
|
142
|
+
"longitude": 2.15899
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Crear los assets básicos si faltan
|
|
146
|
+
await ensure_assets_exist(
|
|
147
|
+
hass,
|
|
148
|
+
api_key=entry_data["api_key"],
|
|
149
|
+
town_id=entry_data.get("town_id"),
|
|
150
|
+
variable_id=entry_data.get("variable_id"),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
_LOGGER.debug(
|
|
154
|
+
f"Datos de configuración: Municipio '{entry_data['town_name']}' (ID: {entry_data['town_id']}), "
|
|
155
|
+
f"Variable '{entry_data['variable_name']}' (ID: {entry_data['variable_id']}), "
|
|
156
|
+
f"Estación '{entry_data['station_name']}' (ID: {entry_data['station_id']}), "
|
|
157
|
+
f"Provincia '{entry_data['province_name']}' (ID: {entry_data['province_id']}), "
|
|
158
|
+
f"Comarca '{entry_data['region_name']}' (ID: {entry_data['region_id']}), "
|
|
159
|
+
f"Coordenadas: ({entry_data['latitude']}, {entry_data['longitude']})."
|
|
160
|
+
f"Altitud: ({entry_data['altitude']})."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Lista de coordinadores con sus clases
|
|
164
|
+
coordinator_configs = [
|
|
165
|
+
("sensor_coordinator", "MeteocatSensorCoordinator"),
|
|
166
|
+
("static_sensor_coordinator", "MeteocatStaticSensorCoordinator"),
|
|
167
|
+
("entity_coordinator", "MeteocatEntityCoordinator"),
|
|
168
|
+
("uvi_coordinator", "MeteocatUviCoordinator"),
|
|
169
|
+
("uvi_file_coordinator", "MeteocatUviFileCoordinator"),
|
|
170
|
+
("hourly_forecast_coordinator", "HourlyForecastCoordinator"),
|
|
171
|
+
("daily_forecast_coordinator", "DailyForecastCoordinator"),
|
|
172
|
+
("condition_coordinator", "MeteocatConditionCoordinator"),
|
|
173
|
+
("temp_forecast_coordinator", "MeteocatTempForecastCoordinator"),
|
|
174
|
+
("alerts_coordinator", "MeteocatAlertsCoordinator"),
|
|
175
|
+
("alerts_region_coordinator", "MeteocatAlertsRegionCoordinator"),
|
|
176
|
+
("quotes_coordinator", "MeteocatQuotesCoordinator"),
|
|
177
|
+
("quotes_file_coordinator", "MeteocatQuotesFileCoordinator"),
|
|
178
|
+
("lightning_coordinator", "MeteocatLightningCoordinator"),
|
|
179
|
+
("lightning_file_coordinator", "MeteocatLightningFileCoordinator"),
|
|
180
|
+
("sun_coordinator", "MeteocatSunCoordinator"),
|
|
181
|
+
("sun_file_coordinator", "MeteocatSunFileCoordinator"),
|
|
182
|
+
("moon_coordinator", "MeteocatMoonCoordinator"),
|
|
183
|
+
("moon_file_coordinator", "MeteocatMoonFileCoordinator"),
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
hass.data.setdefault(DOMAIN, {})
|
|
187
|
+
hass.data[DOMAIN][entry.entry_id] = {}
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
for key, cls_name in coordinator_configs:
|
|
191
|
+
# Importación lazy: importa la clase solo cuando sea necesario
|
|
192
|
+
cls = await hass.async_add_executor_job(_get_coordinator_module, cls_name)
|
|
193
|
+
coordinator = cls(hass=hass, entry_data=entry_data)
|
|
194
|
+
await coordinator.async_config_entry_first_refresh()
|
|
195
|
+
hass.data[DOMAIN][entry.entry_id][key] = coordinator
|
|
196
|
+
|
|
197
|
+
except Exception as err:
|
|
198
|
+
_LOGGER.exception("Error al inicializar los coordinadores: %s", err)
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
hass.data[DOMAIN][entry.entry_id].update(entry_data)
|
|
202
|
+
|
|
203
|
+
_LOGGER.debug(f"Cargando plataformas: {PLATFORMS}")
|
|
204
|
+
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
|
205
|
+
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
209
|
+
"""Desactiva una entrada de configuración para Meteocat."""
|
|
210
|
+
platforms = async_get_platforms(hass, DOMAIN)
|
|
211
|
+
_LOGGER.info(f"Descargando plataformas: {[p.domain for p in platforms]}")
|
|
212
|
+
|
|
213
|
+
if entry.entry_id in hass.data.get(DOMAIN, {}):
|
|
214
|
+
hass.data[DOMAIN].pop(entry.entry_id, None)
|
|
215
|
+
if not hass.data[DOMAIN]:
|
|
216
|
+
hass.data.pop(DOMAIN)
|
|
217
|
+
|
|
218
|
+
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
|
219
|
+
|
|
220
|
+
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
221
|
+
"""Limpia cualquier dato adicional al desinstalar la integración."""
|
|
222
|
+
_LOGGER.info(f"Eliminando datos residuales de la integración: {entry.entry_id}")
|
|
223
|
+
|
|
224
|
+
# Rutas persistentes en /config/meteocat_files
|
|
225
|
+
base_folder = get_storage_dir(hass)
|
|
226
|
+
assets_folder = get_storage_dir(hass, "assets")
|
|
227
|
+
files_folder = get_storage_dir(hass, "files")
|
|
228
|
+
|
|
229
|
+
# Archivos comunes (solo se eliminan si no queda ninguna entrada)
|
|
230
|
+
common_files = [
|
|
231
|
+
assets_folder / "towns.json",
|
|
232
|
+
assets_folder / "symbols.json",
|
|
233
|
+
assets_folder / "variables.json",
|
|
234
|
+
assets_folder / "stations.json",
|
|
235
|
+
files_folder / "alerts.json",
|
|
236
|
+
files_folder / "quotes.json",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
# Identificadores de la entrada eliminada
|
|
240
|
+
station_id = entry.data.get("station_id")
|
|
241
|
+
town_id = entry.data.get("town_id")
|
|
242
|
+
region_id = entry.data.get("region_id")
|
|
243
|
+
|
|
244
|
+
specific_files = []
|
|
245
|
+
|
|
246
|
+
# 1. Archivos de estación
|
|
247
|
+
if station_id:
|
|
248
|
+
other_entries_with_station = [
|
|
249
|
+
e for e in hass.config_entries.async_entries(DOMAIN)
|
|
250
|
+
if e.entry_id != entry.entry_id and e.data.get("station_id") == station_id
|
|
251
|
+
]
|
|
252
|
+
if not other_entries_with_station:
|
|
253
|
+
specific_files.append(files_folder / f"station_{station_id.lower()}_data.json")
|
|
254
|
+
|
|
255
|
+
# 2. Archivos de municipio
|
|
256
|
+
if town_id:
|
|
257
|
+
other_entries_with_town = [
|
|
258
|
+
e for e in hass.config_entries.async_entries(DOMAIN)
|
|
259
|
+
if e.entry_id != entry.entry_id and e.data.get("town_id") == town_id
|
|
260
|
+
]
|
|
261
|
+
if not other_entries_with_town:
|
|
262
|
+
specific_files.extend([
|
|
263
|
+
assets_folder / f"stations_{town_id.lower()}.json",
|
|
264
|
+
files_folder / f"uvi_{town_id.lower()}_data.json",
|
|
265
|
+
files_folder / f"forecast_{town_id.lower()}_hourly_data.json",
|
|
266
|
+
files_folder / f"forecast_{town_id.lower()}_daily_data.json",
|
|
267
|
+
files_folder / f"sun_{town_id.lower()}_data.json",
|
|
268
|
+
files_folder / f"moon_{town_id.lower()}_data.json",
|
|
269
|
+
])
|
|
270
|
+
|
|
271
|
+
# 3. Archivos de comarca (region_id)
|
|
272
|
+
if region_id:
|
|
273
|
+
other_entries_with_region = [
|
|
274
|
+
e for e in hass.config_entries.async_entries(DOMAIN)
|
|
275
|
+
if e.entry_id != entry.entry_id and e.data.get("region_id") == region_id
|
|
276
|
+
]
|
|
277
|
+
if not other_entries_with_region:
|
|
278
|
+
specific_files.extend([
|
|
279
|
+
files_folder / f"alerts_{region_id}.json",
|
|
280
|
+
files_folder / f"lightning_{region_id}.json",
|
|
281
|
+
])
|
|
282
|
+
|
|
283
|
+
# Eliminar archivos específicos (solo si ya no los necesita nadie más)
|
|
284
|
+
for f in specific_files:
|
|
285
|
+
safe_remove(f)
|
|
286
|
+
|
|
287
|
+
# Comprobar si quedan entradas activas de la integración
|
|
288
|
+
remaining_entries = [
|
|
289
|
+
e for e in hass.config_entries.async_entries(DOMAIN)
|
|
290
|
+
if e.entry_id != entry.entry_id
|
|
291
|
+
]
|
|
292
|
+
if not remaining_entries:
|
|
293
|
+
for f in common_files:
|
|
294
|
+
safe_remove(f)
|
|
295
|
+
|
|
296
|
+
# Intentar eliminar carpetas vacías
|
|
297
|
+
for folder in [assets_folder, files_folder, base_folder]:
|
|
298
|
+
safe_remove(folder, is_folder=True)
|