meteocat 3.1.0 → 3.2.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/CHANGELOG.md +34 -0
- package/README.md +4 -1
- package/custom_components/meteocat/__init__.py +8 -3
- package/custom_components/meteocat/condition.py +6 -2
- package/custom_components/meteocat/config_flow.py +206 -28
- package/custom_components/meteocat/const.py +14 -2
- package/custom_components/meteocat/coordinator.py +1040 -205
- package/custom_components/meteocat/helpers.py +31 -36
- package/custom_components/meteocat/manifest.json +3 -2
- package/custom_components/meteocat/options_flow.py +15 -5
- package/custom_components/meteocat/sensor.py +366 -4
- package/custom_components/meteocat/strings.json +195 -4
- package/custom_components/meteocat/translations/ca.json +195 -4
- package/custom_components/meteocat/translations/en.json +195 -4
- package/custom_components/meteocat/translations/es.json +195 -4
- package/custom_components/meteocat/version.py +1 -1
- package/filetree.txt +12 -3
- package/hacs.json +1 -1
- 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/package.json +1 -1
- package/pyproject.toml +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
# [3.2.0](https://github.com/figorr/meteocat/compare/v3.1.0...v3.2.0) (2025-11-07)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add validation date for moon coordinator ([8644f56](https://github.com/figorr/meteocat/commit/8644f56d8f8b6b607ba8809499afbfb81eb70b28))
|
|
7
|
+
* adjust moon update interval ([ea51d90](https://github.com/figorr/meteocat/commit/ea51d905a232ab947607333ea4abaf449cd2e786))
|
|
8
|
+
* fix friendly time translation ([9081a34](https://github.com/figorr/meteocat/commit/9081a343fd1562cb001f8a4b69948be3e26cbe00))
|
|
9
|
+
* fix reset data moon file coordinator ([f3cf22f](https://github.com/figorr/meteocat/commit/f3cf22f1fb1d1a19d6ee9fefd2be304850be1180))
|
|
10
|
+
* more accurate moon calculation ([dcee6e0](https://github.com/figorr/meteocat/commit/dcee6e0040d1cf77860fc7cbd06a5518145d3a72))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add new moon day data ([77edf68](https://github.com/figorr/meteocat/commit/77edf681af037f00748b2b3cf2d281655d225718))
|
|
16
|
+
* add new moon day data ([d2be96e](https://github.com/figorr/meteocat/commit/d2be96ec7f4f9d48c1f018ba2c642f29558d05fe))
|
|
17
|
+
* add translations for lunation attribute ([10e3409](https://github.com/figorr/meteocat/commit/10e3409f82fa6530f0edf81c5e8406d019aeeaf1))
|
|
18
|
+
* bump solarmoonpy to v1.0.0 ([65c2a04](https://github.com/figorr/meteocat/commit/65c2a0495250bac284c060b575256f95f4fb0be0))
|
|
19
|
+
* bump solarmoonpy to v1.0.1 ([aa86747](https://github.com/figorr/meteocat/commit/aa86747ed41d69a80ac009b75b596746762548fe))
|
|
20
|
+
* bump solarmoonpy to v1.0.3 ([246bd83](https://github.com/figorr/meteocat/commit/246bd835589978940efc11722b79e420f838ecab))
|
|
21
|
+
* include latitude, longitude and altitude as new required variables ([5ef864c](https://github.com/figorr/meteocat/commit/5ef864ca38fa17ed05b36a19a96b8c7182f66be7))
|
|
22
|
+
* new latitude, longitude and altitude update options setup ([1aeb968](https://github.com/figorr/meteocat/commit/1aeb9682c8e65b3cb0b29a7b7467edcc455304c9))
|
|
23
|
+
* new lunation attribute for moon and moon phase name code ([60c7129](https://github.com/figorr/meteocat/commit/60c7129757916908baf26439819458ea3a173ad4))
|
|
24
|
+
* new lunation attribute for moon sensor ([9bb32ff](https://github.com/figorr/meteocat/commit/9bb32ff03bd7eba291a1ee1aa578e2838dcf9bd5))
|
|
25
|
+
* new lunation data and moon phase name rework ([0616401](https://github.com/figorr/meteocat/commit/0616401243334c21736d0877e5e50e435279e002))
|
|
26
|
+
* new moon day attribute ([86ec6e3](https://github.com/figorr/meteocat/commit/86ec6e311fc098174027f17d3502e8be740d7e86))
|
|
27
|
+
* new moon sensors ([7c7c1d3](https://github.com/figorr/meteocat/commit/7c7c1d38393a40fd2e69eeba2c24bb185ae349ef))
|
|
28
|
+
* new solarmoonpy requirement for sun and moon events calculation ([1d25a62](https://github.com/figorr/meteocat/commit/1d25a6252cf568e1242f654c389aeada2b5d73f6))
|
|
29
|
+
* new sun and moon coordinators ([af2290e](https://github.com/figorr/meteocat/commit/af2290e7384a9ad5416afdf4d6767ad69099813c))
|
|
30
|
+
* new sun and moon files ([b489d7a](https://github.com/figorr/meteocat/commit/b489d7a9abc5af747fc50a2e0d7b0d661408b824))
|
|
31
|
+
* new sun and moon sensors ([91265f3](https://github.com/figorr/meteocat/commit/91265f3c6cbd512b3706c29d466f9bb01df6d45e))
|
|
32
|
+
* translation for new moon day attribute ([162859e](https://github.com/figorr/meteocat/commit/162859e2822374b81835cb1af07325dceaf5a55e))
|
|
33
|
+
* translations for the new moon and sun sensors ([91a33b6](https://github.com/figorr/meteocat/commit/91a33b6b52e34ed69eed4eb5fbfaf9dffd20df57))
|
|
34
|
+
|
|
1
35
|
# [3.1.0](https://github.com/figorr/meteocat/compare/v3.0.0...v3.1.0) (2025-09-27)
|
|
2
36
|
|
|
3
37
|
|
package/README.md
CHANGED
|
@@ -135,6 +135,9 @@ You will see three available options:
|
|
|
135
135
|
If for any reason some files in the `assets` folder (`towns.json`, `stations.json`, `variables.json`, `symbols.json`, or `stations_<town_id>.json`) are missing or outdated, you can regenerate them directly from the options menu.
|
|
136
136
|
> ℹ️ If the Meteocat API is not available at that moment, the integration will still start, and you can retry regeneration later.
|
|
137
137
|
|
|
138
|
+
- **Update coordinates and elevation**
|
|
139
|
+
Change the default station coordinates and elevation that were set during the first setup. So you can use your location coordinates and elevation for more accurate sun and moon data.
|
|
140
|
+
|
|
138
141
|
### Accessing the Options Menu
|
|
139
142
|
|
|
140
143
|
You can access the Options Menu in two ways, both inside the integration:
|
|
@@ -168,7 +171,7 @@ Meteocat integration has its own weather card.
|
|
|
168
171
|
|
|
169
172
|
To install the card, please follow the instructions from its own repository, [🎫 Meteocat Card](https://github.com/figorr/meteocat-card).
|
|
170
173
|
|
|
171
|
-

|
|
172
175
|
|
|
173
176
|
# Documentation
|
|
174
177
|
|
|
@@ -24,7 +24,7 @@ from .const import DOMAIN, PLATFORMS
|
|
|
24
24
|
_LOGGER = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
# Versión
|
|
27
|
-
__version__ = "3.
|
|
27
|
+
__version__ = "3.2.0"
|
|
28
28
|
|
|
29
29
|
# Definir el esquema de configuración CONFIG_SCHEMA
|
|
30
30
|
CONFIG_SCHEMA = vol.Schema(
|
|
@@ -44,6 +44,7 @@ CONFIG_SCHEMA = vol.Schema(
|
|
|
44
44
|
vol.Optional("region_id"): cv.string,
|
|
45
45
|
vol.Required("latitude"): cv.latitude,
|
|
46
46
|
vol.Required("longitude"): cv.longitude,
|
|
47
|
+
vol.Required("altitude"): vol.Coerce(float),
|
|
47
48
|
}
|
|
48
49
|
)
|
|
49
50
|
},
|
|
@@ -120,7 +121,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
120
121
|
required_fields = [
|
|
121
122
|
"api_key", "town_name", "town_id", "variable_name",
|
|
122
123
|
"variable_id", "station_name", "station_id", "province_name",
|
|
123
|
-
"province_id", "region_name", "region_id", "latitude", "longitude"
|
|
124
|
+
"province_id", "region_name", "region_id", "latitude", "longitude", "altitude"
|
|
124
125
|
]
|
|
125
126
|
missing_fields = [field for field in required_fields if field not in entry_data]
|
|
126
127
|
if missing_fields:
|
|
@@ -156,6 +157,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
156
157
|
f"Provincia '{entry_data['province_name']}' (ID: {entry_data['province_id']}), "
|
|
157
158
|
f"Comarca '{entry_data['region_name']}' (ID: {entry_data['region_id']}), "
|
|
158
159
|
f"Coordenadas: ({entry_data['latitude']}, {entry_data['longitude']})."
|
|
160
|
+
f"Altitud: ({entry_data['altitude']})."
|
|
159
161
|
)
|
|
160
162
|
|
|
161
163
|
# Lista de coordinadores con sus clases
|
|
@@ -177,6 +179,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
177
179
|
("lightning_file_coordinator", "MeteocatLightningFileCoordinator"),
|
|
178
180
|
("sun_coordinator", "MeteocatSunCoordinator"),
|
|
179
181
|
("sun_file_coordinator", "MeteocatSunFileCoordinator"),
|
|
182
|
+
("moon_coordinator", "MeteocatMoonCoordinator"),
|
|
183
|
+
("moon_file_coordinator", "MeteocatMoonFileCoordinator"),
|
|
180
184
|
]
|
|
181
185
|
|
|
182
186
|
hass.data.setdefault(DOMAIN, {})
|
|
@@ -261,6 +265,7 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
|
261
265
|
files_folder / f"forecast_{town_id.lower()}_hourly_data.json",
|
|
262
266
|
files_folder / f"forecast_{town_id.lower()}_daily_data.json",
|
|
263
267
|
files_folder / f"sun_{town_id.lower()}_data.json",
|
|
268
|
+
files_folder / f"moon_{town_id.lower()}_data.json",
|
|
264
269
|
])
|
|
265
270
|
|
|
266
271
|
# 3. Archivos de comarca (region_id)
|
|
@@ -290,4 +295,4 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
|
290
295
|
|
|
291
296
|
# Intentar eliminar carpetas vacías
|
|
292
297
|
for folder in [assets_folder, files_folder, base_folder]:
|
|
293
|
-
safe_remove(folder, is_folder=True)
|
|
298
|
+
safe_remove(folder, is_folder=True)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
4
5
|
from .const import CONDITION_MAPPING
|
|
5
6
|
from .helpers import is_night
|
|
6
7
|
import logging
|
|
@@ -8,7 +9,10 @@ import logging
|
|
|
8
9
|
_LOGGER = logging.getLogger(__name__)
|
|
9
10
|
|
|
10
11
|
def get_condition_from_statcel(
|
|
11
|
-
codi_estatcel
|
|
12
|
+
codi_estatcel: Any,
|
|
13
|
+
current_time: datetime,
|
|
14
|
+
location,
|
|
15
|
+
is_hourly: bool = True
|
|
12
16
|
) -> dict:
|
|
13
17
|
"""
|
|
14
18
|
Convierte el código 'estatCel' en condición de Home Assistant.
|
|
@@ -33,7 +37,7 @@ def get_condition_from_statcel(
|
|
|
33
37
|
codi_estatcel = [codi_estatcel]
|
|
34
38
|
|
|
35
39
|
# Determinar si es de noche
|
|
36
|
-
is_night_flag = is_night(current_time,
|
|
40
|
+
is_night_flag = is_night(current_time, location)
|
|
37
41
|
|
|
38
42
|
# Identificar la condición basada en el código
|
|
39
43
|
for condition, codes in CONDITION_MAPPING.items():
|
|
@@ -5,15 +5,25 @@ import json
|
|
|
5
5
|
import logging
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any
|
|
8
|
-
from datetime import datetime, timezone
|
|
8
|
+
from datetime import date, datetime, timezone, timedelta
|
|
9
9
|
from zoneinfo import ZoneInfo
|
|
10
10
|
|
|
11
11
|
import voluptuous as vol
|
|
12
12
|
import aiofiles
|
|
13
13
|
import unicodedata
|
|
14
14
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
15
|
+
from solarmoonpy.location import Location, LocationInfo
|
|
16
|
+
from solarmoonpy.moon import (
|
|
17
|
+
moon_phase,
|
|
18
|
+
moon_day,
|
|
19
|
+
moon_rise_set,
|
|
20
|
+
illuminated_percentage,
|
|
21
|
+
moon_distance,
|
|
22
|
+
moon_angular_diameter,
|
|
23
|
+
lunation_number,
|
|
24
|
+
get_moon_phase_name,
|
|
25
|
+
get_lunation_duration
|
|
26
|
+
)
|
|
17
27
|
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
|
18
28
|
from homeassistant.core import callback
|
|
19
29
|
from homeassistant.exceptions import HomeAssistantError
|
|
@@ -90,6 +100,8 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
|
90
100
|
self.longitude: float | None = None
|
|
91
101
|
self.altitude: float | None = None
|
|
92
102
|
self.station_status: str | None = None
|
|
103
|
+
self.location: Location | None = None
|
|
104
|
+
self.timezone_str: str | None = None
|
|
93
105
|
|
|
94
106
|
async def fetch_and_save_quotes(self, api_key: str):
|
|
95
107
|
"""Obtiene las cuotas de la API de Meteocat y las guarda en quotes.json."""
|
|
@@ -186,49 +198,214 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
|
186
198
|
)
|
|
187
199
|
|
|
188
200
|
async def create_sun_file(self):
|
|
189
|
-
"""Crea el archivo sun_{town_id}_data.json con
|
|
190
|
-
if not self.selected_municipi or
|
|
201
|
+
"""Crea el archivo sun_{town_id}_data.json con eventos solares + posición inicial del sol."""
|
|
202
|
+
if not self.selected_municipi or self.latitude is None or self.longitude is None:
|
|
191
203
|
_LOGGER.warning("No se puede crear sun_{town_id}_data.json: faltan municipio o coordenadas")
|
|
192
204
|
return
|
|
193
205
|
|
|
194
206
|
town_id = self.selected_municipi["codi"]
|
|
195
207
|
files_dir = get_storage_dir(self.hass, "files")
|
|
196
|
-
sun_file = files_dir / f"sun_{town_id}_data.json"
|
|
208
|
+
sun_file = files_dir / f"sun_{town_id.lower()}_data.json"
|
|
197
209
|
|
|
198
|
-
if
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
210
|
+
if sun_file.exists():
|
|
211
|
+
_LOGGER.debug("El archivo %s ya existe, no se crea de nuevo.", sun_file)
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
# ZONA HORARIA DEL HASS
|
|
216
|
+
self.timezone_str = self.hass.config.time_zone or "Europe/Madrid"
|
|
217
|
+
tz = ZoneInfo(self.timezone_str)
|
|
218
|
+
|
|
219
|
+
# CREAR UBICACIÓN
|
|
220
|
+
self.location = Location(LocationInfo(
|
|
221
|
+
name=self.selected_municipi.get("nom", "Municipio"),
|
|
222
|
+
region="Spain",
|
|
223
|
+
timezone=self.timezone_str,
|
|
224
|
+
latitude=self.latitude,
|
|
225
|
+
longitude=self.longitude,
|
|
226
|
+
elevation=self.altitude or 0.0,
|
|
227
|
+
))
|
|
228
|
+
|
|
229
|
+
now = datetime.now(tz)
|
|
230
|
+
today = now.date()
|
|
231
|
+
tomorrow = today + timedelta(days=1)
|
|
232
|
+
|
|
233
|
+
# EVENTOS HOY Y MAÑANA
|
|
234
|
+
events_today = self.location.sun_events(date=today, local=True)
|
|
235
|
+
events_tomorrow = self.location.sun_events(date=tomorrow, local=True)
|
|
236
|
+
|
|
237
|
+
# LÓGICA DE EVENTOS (igual que en el coordinador)
|
|
238
|
+
expected = {}
|
|
239
|
+
events_list = [
|
|
240
|
+
"dawn_astronomical", "dawn_nautical", "dawn_civil",
|
|
241
|
+
"sunrise", "noon", "sunset",
|
|
242
|
+
"dusk_civil", "dusk_nautical", "dusk_astronomical",
|
|
243
|
+
"midnight",
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
for event in events_list:
|
|
247
|
+
event_time = events_today.get(event)
|
|
248
|
+
if event_time and now >= event_time:
|
|
249
|
+
expected[event] = events_tomorrow.get(event)
|
|
250
|
+
else:
|
|
251
|
+
expected[event] = event_time
|
|
252
|
+
|
|
253
|
+
# daylight_duration según sunrise
|
|
254
|
+
sunrise = expected["sunrise"]
|
|
255
|
+
expected["daylight_duration"] = (
|
|
256
|
+
events_tomorrow["daylight_duration"]
|
|
257
|
+
if sunrise == events_tomorrow["sunrise"]
|
|
258
|
+
else events_today["daylight_duration"]
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# POSICIÓN ACTUAL DEL SOL
|
|
262
|
+
sun_pos = self.location.sun_position(dt=now, local=True)
|
|
263
|
+
|
|
264
|
+
# CONSTRUIR DADES
|
|
265
|
+
dades_dict = {
|
|
266
|
+
"dawn_civil": expected["dawn_civil"].isoformat() if expected["dawn_civil"] else None,
|
|
267
|
+
"dawn_nautical": expected["dawn_nautical"].isoformat() if expected["dawn_nautical"] else None,
|
|
268
|
+
"dawn_astronomical": expected["dawn_astronomical"].isoformat() if expected["dawn_astronomical"] else None,
|
|
269
|
+
"sunrise": expected["sunrise"].isoformat() if expected["sunrise"] else None,
|
|
270
|
+
"noon": expected["noon"].isoformat() if expected["noon"] else None,
|
|
271
|
+
"sunset": expected["sunset"].isoformat() if expected["sunset"] else None,
|
|
272
|
+
"dusk_civil": expected["dusk_civil"].isoformat() if expected["dusk_civil"] else None,
|
|
273
|
+
"dusk_nautical": expected["dusk_nautical"].isoformat() if expected["dusk_nautical"] else None,
|
|
274
|
+
"dusk_astronomical": expected["dusk_astronomical"].isoformat() if expected["dusk_astronomical"] else None,
|
|
275
|
+
"midnight": expected["midnight"].isoformat() if expected["midnight"] else None,
|
|
276
|
+
"daylight_duration": expected["daylight_duration"],
|
|
277
|
+
|
|
278
|
+
# CAMPOS DE POSICIÓN SOLAR
|
|
279
|
+
"sun_elevation": round(sun_pos["elevation"], 2),
|
|
280
|
+
"sun_azimuth": round(sun_pos["azimuth"], 2),
|
|
281
|
+
"sun_horizon_position": sun_pos["horizon_position"],
|
|
282
|
+
"sun_rising": sun_pos["rising"],
|
|
283
|
+
"sun_position_updated": now.isoformat(),
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# JSON FINAL
|
|
287
|
+
data_with_timestamp = {
|
|
288
|
+
"actualitzat": {"dataUpdate": now.isoformat()},
|
|
289
|
+
"dades": [dades_dict],
|
|
290
|
+
}
|
|
208
291
|
|
|
209
|
-
|
|
292
|
+
# GUARDAR
|
|
293
|
+
sun_file.parent.mkdir(parents=True, exist_ok=True)
|
|
294
|
+
async with aiofiles.open(sun_file, "w", encoding="utf-8") as file:
|
|
295
|
+
await file.write(json.dumps(data_with_timestamp, ensure_ascii=False, indent=4))
|
|
296
|
+
|
|
297
|
+
_LOGGER.info(
|
|
298
|
+
"Archivo sun_%s_data.json creado con eventos + posición solar inicial (elev=%.2f°, az=%.2f°)",
|
|
299
|
+
town_id, sun_pos["elevation"], sun_pos["azimuth"]
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
except Exception as ex:
|
|
303
|
+
_LOGGER.error("Error al crear sun_%s_data.json: %s", town_id, ex)
|
|
304
|
+
|
|
305
|
+
async def create_moon_file(self):
|
|
306
|
+
"""Crea el archivo moon_{town_id}_data.json con datos iniciales de la fase lunar, moonrise y moonset."""
|
|
307
|
+
if not self.selected_municipi or not self.latitude or not self.longitude:
|
|
308
|
+
_LOGGER.warning("No se puede crear moon_{town_id}_data.json: faltan municipio o coordenadas")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
town_id = self.selected_municipi["codi"]
|
|
312
|
+
files_dir = get_storage_dir(self.hass, "files")
|
|
313
|
+
moon_file = files_dir / f"moon_{town_id}_data.json"
|
|
314
|
+
|
|
315
|
+
if not moon_file.exists():
|
|
316
|
+
try:
|
|
317
|
+
# Fecha actual en UTC
|
|
210
318
|
current_time = datetime.now(timezone.utc).astimezone(TIMEZONE)
|
|
211
|
-
|
|
319
|
+
today = current_time.date()
|
|
320
|
+
|
|
321
|
+
# Inicializar parámetros con valores por defecto
|
|
322
|
+
phase = None
|
|
323
|
+
moon_day_today = None
|
|
324
|
+
lunation = None
|
|
325
|
+
illuminated = None
|
|
326
|
+
distance = None
|
|
327
|
+
angular_diameter = None
|
|
328
|
+
moon_phase_name = None
|
|
329
|
+
lunation_duration = None
|
|
330
|
+
|
|
331
|
+
# Calcular parámetros con manejo de errores individual
|
|
332
|
+
try:
|
|
333
|
+
phase = round(moon_phase(today), 2)
|
|
334
|
+
except Exception as ex:
|
|
335
|
+
_LOGGER.error("Error al calcular moon_phase: %s", ex)
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
moon_day_today = moon_day(today)
|
|
339
|
+
except Exception as ex:
|
|
340
|
+
_LOGGER.error("Error al calcular moon_day: %s", ex)
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
lunation = lunation_number(today)
|
|
344
|
+
except Exception as ex:
|
|
345
|
+
_LOGGER.error("Error al calcular lunation_number: %s", ex)
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
illuminated = round(illuminated_percentage(today), 2)
|
|
349
|
+
except Exception as ex:
|
|
350
|
+
_LOGGER.error("Error al calcular illuminated_percentage: %s", ex)
|
|
351
|
+
|
|
352
|
+
try:
|
|
353
|
+
distance = round(moon_distance(today), 0)
|
|
354
|
+
except Exception as ex:
|
|
355
|
+
_LOGGER.error("Error al calcular moon_distance: %s", ex)
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
angular_diameter = round(moon_angular_diameter(today), 2)
|
|
359
|
+
except Exception as ex:
|
|
360
|
+
_LOGGER.error("Error al calcular moon_angular_diameter: %s", ex)
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
moon_phase_name = get_moon_phase_name(today)
|
|
364
|
+
except Exception as ex:
|
|
365
|
+
_LOGGER.error("Error al calcular moon_phase_name: %s", ex)
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
lunation_duration = get_lunation_duration(today)
|
|
369
|
+
except Exception as ex:
|
|
370
|
+
_LOGGER.error("Error al calcular lunation_duration: %s", ex)
|
|
371
|
+
|
|
372
|
+
# Moonrise y moonset aproximados (UTC)
|
|
373
|
+
try:
|
|
374
|
+
rise_utc, set_utc = moon_rise_set(self.latitude, self.longitude, today)
|
|
375
|
+
rise_local = rise_utc.astimezone(TIMEZONE).isoformat() if rise_utc else None
|
|
376
|
+
set_local = set_utc.astimezone(TIMEZONE).isoformat() if set_utc else None
|
|
377
|
+
except Exception as ex:
|
|
378
|
+
_LOGGER.error("Error al calcular moon_rise_set: %s", ex)
|
|
379
|
+
rise_local = None
|
|
380
|
+
set_local = None
|
|
212
381
|
|
|
213
|
-
# Formatear
|
|
214
|
-
|
|
382
|
+
# Formatear datos para guardar
|
|
383
|
+
moon_data_formatted = {
|
|
215
384
|
"actualitzat": {"dataUpdate": current_time.isoformat()},
|
|
385
|
+
"last_lunar_update_date": today.isoformat(),
|
|
216
386
|
"dades": [
|
|
217
387
|
{
|
|
218
|
-
"
|
|
219
|
-
"
|
|
220
|
-
"
|
|
388
|
+
"moon_day": moon_day_today,
|
|
389
|
+
"moon_phase": phase,
|
|
390
|
+
"moon_phase_name": moon_phase_name,
|
|
391
|
+
"illuminated_percentage": illuminated,
|
|
392
|
+
"moon_distance": distance,
|
|
393
|
+
"moon_angular_diameter": angular_diameter,
|
|
394
|
+
"lunation": lunation,
|
|
395
|
+
"lunation_duration": lunation_duration,
|
|
396
|
+
"moonrise": rise_local,
|
|
397
|
+
"moonset": set_local
|
|
221
398
|
}
|
|
222
399
|
]
|
|
223
400
|
}
|
|
224
401
|
|
|
225
402
|
# Guardar el archivo
|
|
226
|
-
async with aiofiles.open(
|
|
227
|
-
await file.write(json.dumps(
|
|
228
|
-
_LOGGER.info("Archivo
|
|
403
|
+
async with aiofiles.open(moon_file, "w", encoding="utf-8") as file:
|
|
404
|
+
await file.write(json.dumps(moon_data_formatted, ensure_ascii=False, indent=4))
|
|
405
|
+
_LOGGER.info("Archivo moon_%s_data.json creado con datos iniciales", town_id)
|
|
229
406
|
|
|
230
407
|
except Exception as ex:
|
|
231
|
-
_LOGGER.error("Error al crear
|
|
408
|
+
_LOGGER.error("Error general al crear moon_%s_data.json: %s", town_id, ex)
|
|
232
409
|
|
|
233
410
|
async def async_step_user(
|
|
234
411
|
self, user_input: dict[str, Any] | None = None
|
|
@@ -366,9 +543,10 @@ class MeteocatConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
|
366
543
|
self.province_name = station_metadata.get("provincia", {}).get("nom", "")
|
|
367
544
|
self.station_status = station_metadata.get("estats", [{}])[0].get("codi", "")
|
|
368
545
|
|
|
369
|
-
# Crear archivos de alertas y
|
|
546
|
+
# Crear archivos de alertas, sol y luna
|
|
370
547
|
await self.create_alerts_file()
|
|
371
548
|
await self.create_sun_file()
|
|
549
|
+
await self.create_moon_file()
|
|
372
550
|
return await self.async_step_set_api_limits()
|
|
373
551
|
except Exception as ex:
|
|
374
552
|
_LOGGER.error("Error al obtener los metadatos de la estación: %s", ex)
|
|
@@ -42,20 +42,29 @@ QUOTA_BASIC = "quota_basic"
|
|
|
42
42
|
QUOTA_XEMA = "quota_xema"
|
|
43
43
|
QUOTA_QUERIES = "quota_queries"
|
|
44
44
|
LIGHTNING_FILE_STATUS = "lightning_file_status"
|
|
45
|
+
SUN = "sun"
|
|
45
46
|
SUNRISE = "sunrise"
|
|
46
47
|
SUNSET = "sunset"
|
|
47
48
|
SUN_FILE_STATUS = "sun_file_status"
|
|
49
|
+
MOON_PHASE = "moon_phase"
|
|
50
|
+
MOON_FILE_STATUS = "moon_file_status"
|
|
51
|
+
MOONRISE = "moonrise"
|
|
52
|
+
MOONSET = "moonset"
|
|
48
53
|
|
|
49
54
|
from homeassistant.const import Platform
|
|
50
55
|
|
|
51
|
-
ATTRIBUTION = "Powered by Meteocatpy"
|
|
56
|
+
ATTRIBUTION = "Powered by Meteocatpy & Solarmoonpy"
|
|
52
57
|
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
|
53
58
|
DEFAULT_NAME = "METEOCAT"
|
|
54
59
|
|
|
55
60
|
# Tiempos para validación de API
|
|
56
61
|
DEFAULT_VALIDITY_DAYS = 1 # Número de días a partir de los cuales se considera que el archivo de información está obsoleto
|
|
57
|
-
DEFAULT_VALIDITY_HOURS =
|
|
62
|
+
DEFAULT_VALIDITY_HOURS = 6 # Hora a partir de la cual la API tiene la información actualizada de predicciones disponible para descarga
|
|
58
63
|
DEFAULT_VALIDITY_MINUTES = 0 # Minutos a partir de los cuales la API tiene la información actualizada de predicciones disponible para descarga
|
|
64
|
+
DEFAULT_UVI_LOW_VALIDITY_HOURS = 5 # Hora a partir de la cual la API tiene la información actualizada de datos UVI disponible para descarga con límite bajo de cuota
|
|
65
|
+
DEFAULT_UVI_LOW_VALIDITY_MINUTES = 0 # Minutos a partir de los cuales la API tiene la información actualizada de datos UVI disponible para descarga con límite bajo de cuota
|
|
66
|
+
DEFAULT_UVI_HIGH_VALIDITY_HOURS = 9 # Hora a partir de la cual la API tiene la información actualizada de datos UVI disponible para descarga con límite alto de cuota
|
|
67
|
+
DEFAULT_UVI_HIGH_VALIDITY_MINUTES = 0 # Minutos a partir de los cuales la API tiene la información actualizada de datos UVI disponible para descarga con límite alto de cuota
|
|
59
68
|
DEFAULT_ALERT_VALIDITY_TIME = 120 # Minutos a partir de los cuales las alertas están obsoletas y se se debe proceder a una nueva llamada a la API
|
|
60
69
|
DEFAULT_QUOTES_VALIDITY_TIME = 240 # Minutos a partir de los cuales los datos de cuotas están obsoletos y se se debe proceder a una nueva llamada a la API
|
|
61
70
|
DEFAULT_LIGHTNING_VALIDITY_TIME = 240 # Minutos a partir de los cuales los datos de rayos están obsoletos y se se debe proceder a una nueva llamada a la API
|
|
@@ -68,6 +77,9 @@ ALERT_VALIDITY_MULTIPLIER_200 = 6 # para 100 < limit_prediccio <= 200
|
|
|
68
77
|
ALERT_VALIDITY_MULTIPLIER_500 = 3 # para 200 < limit_prediccio <= 500
|
|
69
78
|
ALERT_VALIDITY_MULTIPLIER_DEFAULT = 1 # para limit_prediccio > 500
|
|
70
79
|
|
|
80
|
+
# CUOTA ALTA PARA FAVORECER ACTUALIZACIONES DIARIAS DE LAS PREDICCIONES
|
|
81
|
+
PREDICCIO_HIGH_QUOTA_LIMIT = 550
|
|
82
|
+
|
|
71
83
|
# Códigos de sensores de la API
|
|
72
84
|
WIND_SPEED = "wind_speed" # Velocidad del viento
|
|
73
85
|
WIND_DIRECTION = "wind_direction" # Dirección del viento
|