meteocat 0.1.19
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 -0
- package/.pre-commit-config.yaml +37 -0
- package/.releaserc +11 -0
- package/.releaserc.toml +14 -0
- package/AUTHORS.md +12 -0
- package/CHANGELOG.md +361 -0
- package/LICENSE +194 -0
- package/README.md +41 -0
- package/custom_components/meteocat/__init__.py +139 -0
- package/custom_components/meteocat/condition.py +28 -0
- package/custom_components/meteocat/config_flow.py +192 -0
- package/custom_components/meteocat/const.py +46 -0
- package/custom_components/meteocat/coordinator.py +177 -0
- package/custom_components/meteocat/entity.py +91 -0
- package/custom_components/meteocat/helpers.py +42 -0
- package/custom_components/meteocat/manifest.json +12 -0
- package/custom_components/meteocat/options_flow.py +71 -0
- package/custom_components/meteocat/sensor.py +190 -0
- package/custom_components/meteocat/strings.json +26 -0
- package/custom_components/meteocat/translations/ca.json +26 -0
- package/custom_components/meteocat/translations/en.json +26 -0
- package/custom_components/meteocat/translations/es.json +26 -0
- package/custom_components/meteocat/version.py +2 -0
- package/filetree.py +48 -0
- package/filetree.txt +43 -0
- package/hacs.json +6 -0
- package/package.json +22 -0
- package/poetry.lock +3205 -0
- package/pyproject.toml +64 -0
- package/releaserc.json +18 -0
- package/requirements.test.txt +3 -0
- package/setup.cfg +64 -0
- package/setup.py +10 -0
- package/tests/__init__.py +0 -0
- package/tests/bandit.yaml +17 -0
- package/tests/conftest.py +19 -0
- package/tests/test_init.py +9 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from homeassistant.components.weather import WeatherEntity
|
|
6
|
+
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
|
7
|
+
|
|
8
|
+
from .const import (
|
|
9
|
+
DOMAIN,
|
|
10
|
+
CONF_API_KEY,
|
|
11
|
+
TOWN_ID,
|
|
12
|
+
TEMPERATURE,
|
|
13
|
+
HUMIDITY,
|
|
14
|
+
WIND_SPEED,
|
|
15
|
+
WIND_DIRECTION,
|
|
16
|
+
)
|
|
17
|
+
from .condition import get_condition_from_statcel
|
|
18
|
+
from .coordinator import MeteocatEntityCoordinator
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_LOGGER = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
24
|
+
"""Configura el componente weather basado en una entrada de configuración."""
|
|
25
|
+
api_key = config_entry.data[CONF_API_KEY]
|
|
26
|
+
town_id = config_entry.data[TOWN_ID]
|
|
27
|
+
|
|
28
|
+
# Crear el coordinador
|
|
29
|
+
coordinator = MeteocatEntityCoordinator(hass, api_key, town_id)
|
|
30
|
+
# Asegurarse de que el coordinador esté actualizado antes de agregar la entidad
|
|
31
|
+
await coordinator.async_refresh()
|
|
32
|
+
|
|
33
|
+
async_add_entities([MeteocatWeatherEntity(coordinator)], True)
|
|
34
|
+
|
|
35
|
+
class MeteocatWeatherEntity(WeatherEntity):
|
|
36
|
+
"""Entidad de clima para la integración Meteocat."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, coordinator: MeteocatEntityCoordinator):
|
|
39
|
+
"""Inicializa la entidad MeteocatWeather."""
|
|
40
|
+
self._coordinator = coordinator
|
|
41
|
+
self._attr_temperature_unit = TEMP_CELSIUS
|
|
42
|
+
self._data = {}
|
|
43
|
+
|
|
44
|
+
async def async_update(self):
|
|
45
|
+
"""Actualiza los datos meteorológicos."""
|
|
46
|
+
try:
|
|
47
|
+
# Usamos el coordinador para obtener los datos actualizados
|
|
48
|
+
if self._coordinator.data:
|
|
49
|
+
hourly_forecast = self._coordinator.data["hourly_forecast"]
|
|
50
|
+
current_forecast = hourly_forecast["variables"]
|
|
51
|
+
codi_estatcel = current_forecast.get("estatCel", {}).get("valor")
|
|
52
|
+
is_night = current_forecast.get("is_night", False)
|
|
53
|
+
self._data = {
|
|
54
|
+
"temperature": current_forecast.get(TEMPERATURE, {}).get("valor"),
|
|
55
|
+
"humidity": current_forecast.get(HUMIDITY, {}).get("valor"),
|
|
56
|
+
"wind_speed": current_forecast.get(WIND_SPEED, {}).get("valor"),
|
|
57
|
+
"wind_bearing": current_forecast.get(WIND_DIRECTION, {}).get("valor"),
|
|
58
|
+
"condition": get_condition_from_statcel(codi_estatcel, is_night)["condition"],
|
|
59
|
+
}
|
|
60
|
+
except Exception as err:
|
|
61
|
+
_LOGGER.error("Error al actualizar la predicción de Meteocat: %s", err)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def name(self):
|
|
65
|
+
"""Retorna el nombre de la entidad."""
|
|
66
|
+
return f"Clima {self._coordinator._town_id}"
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def temperature(self):
|
|
70
|
+
"""Retorna la temperatura actual."""
|
|
71
|
+
return self._data.get("temperature")
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def humidity(self):
|
|
75
|
+
"""Retorna la humedad relativa actual."""
|
|
76
|
+
return self._data.get("humidity")
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def wind_speed(self):
|
|
80
|
+
"""Retorna la velocidad del viento."""
|
|
81
|
+
return self._data.get("wind_speed")
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def wind_bearing(self):
|
|
85
|
+
"""Retorna la dirección del viento."""
|
|
86
|
+
return self._data.get("wind_bearing")
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def condition(self):
|
|
90
|
+
"""Retorna la condición climática."""
|
|
91
|
+
return self._data.get("condition")
|
|
@@ -0,0 +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
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"domain": "meteocat",
|
|
3
|
+
"name": "Meteocat",
|
|
4
|
+
"codeowners": ["@figorr"],
|
|
5
|
+
"config_flow": true,
|
|
6
|
+
"dependencies": ["persistent_notification", "http"],
|
|
7
|
+
"iot_class": "cloud_polling",
|
|
8
|
+
"documentation": "https://gitlab.com/figorr/meteocat",
|
|
9
|
+
"loggers": ["meteocatpy"],
|
|
10
|
+
"requirements": ["meteocatpy==0.0.6", "packaging>=20.3", "wrapt>=1.14.0"],
|
|
11
|
+
"version": "0.1.18"
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
import voluptuous as vol
|
|
8
|
+
|
|
9
|
+
from .const import (
|
|
10
|
+
CONF_API_KEY
|
|
11
|
+
)
|
|
12
|
+
from meteocatpy.town import MeteocatTown
|
|
13
|
+
from meteocatpy.exceptions import (
|
|
14
|
+
BadRequestError,
|
|
15
|
+
ForbiddenError,
|
|
16
|
+
TooManyRequestsError,
|
|
17
|
+
InternalServerError,
|
|
18
|
+
UnknownAPIError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_LOGGER = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
class MeteocatOptionsFlowHandler(OptionsFlow):
|
|
24
|
+
"""Manejo del flujo de opciones para Meteocat."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, config_entry: ConfigEntry):
|
|
27
|
+
"""Inicializa el flujo de opciones."""
|
|
28
|
+
self.config_entry = config_entry
|
|
29
|
+
self.api_key: str | None = None
|
|
30
|
+
|
|
31
|
+
async def async_step_init(self, user_input: dict | None = None):
|
|
32
|
+
"""Paso inicial del flujo de opciones."""
|
|
33
|
+
return await self.async_step_update_api_key()
|
|
34
|
+
|
|
35
|
+
async def async_step_update_api_key(self, user_input: dict | None = None):
|
|
36
|
+
"""Permite al usuario actualizar la API Key."""
|
|
37
|
+
errors = {}
|
|
38
|
+
|
|
39
|
+
if user_input is not None:
|
|
40
|
+
self.api_key = user_input[CONF_API_KEY]
|
|
41
|
+
|
|
42
|
+
# Validar la nueva API Key utilizando MeteocatTown
|
|
43
|
+
town_client = MeteocatTown(self.api_key)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
await town_client.get_municipis() # Verificar que la API Key sea válida
|
|
47
|
+
except (
|
|
48
|
+
BadRequestError,
|
|
49
|
+
ForbiddenError,
|
|
50
|
+
TooManyRequestsError,
|
|
51
|
+
InternalServerError,
|
|
52
|
+
UnknownAPIError,
|
|
53
|
+
) as ex:
|
|
54
|
+
_LOGGER.error("Error al validar la nueva API Key: %s", ex)
|
|
55
|
+
errors["base"] = "cannot_connect"
|
|
56
|
+
except Exception as ex:
|
|
57
|
+
_LOGGER.error("Error inesperado al validar la nueva API Key: %s", ex)
|
|
58
|
+
errors["base"] = "unknown"
|
|
59
|
+
|
|
60
|
+
if not errors:
|
|
61
|
+
# Actualizar la configuración de la entrada con la nueva API Key
|
|
62
|
+
self.hass.config_entries.async_update_entry(
|
|
63
|
+
self.config_entry,
|
|
64
|
+
data={**self.config_entry.data, CONF_API_KEY: self.api_key},
|
|
65
|
+
)
|
|
66
|
+
return self.async_create_entry(title="", data={})
|
|
67
|
+
|
|
68
|
+
schema = vol.Schema({vol.Required(CONF_API_KEY): str})
|
|
69
|
+
return self.async_show_form(
|
|
70
|
+
step_id="update_api_key", data_schema=schema, errors=errors
|
|
71
|
+
)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from homeassistant.helpers.entity import DeviceInfo
|
|
5
|
+
from homeassistant.components.sensor import (
|
|
6
|
+
SensorDeviceClass,
|
|
7
|
+
SensorEntity,
|
|
8
|
+
SensorEntityDescription,
|
|
9
|
+
SensorStateClass,
|
|
10
|
+
)
|
|
11
|
+
from homeassistant.core import callback
|
|
12
|
+
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
13
|
+
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
14
|
+
from homeassistant.const import PERCENTAGE, UnitOfTemperature
|
|
15
|
+
|
|
16
|
+
from .const import (
|
|
17
|
+
DOMAIN,
|
|
18
|
+
WIND_SPEED,
|
|
19
|
+
WIND_DIRECTION,
|
|
20
|
+
TEMPERATURE,
|
|
21
|
+
HUMIDITY,
|
|
22
|
+
PRESSURE,
|
|
23
|
+
PRECIPITATION,
|
|
24
|
+
UV_INDEX,
|
|
25
|
+
MAX_TEMPERATURE,
|
|
26
|
+
MIN_TEMPERATURE,
|
|
27
|
+
WIND_GUST,
|
|
28
|
+
WIND_SPEED_UNIT,
|
|
29
|
+
PRESSURE_UNIT,
|
|
30
|
+
PRECIPITATION_UNIT,
|
|
31
|
+
UV_INDEX_UNIT,
|
|
32
|
+
WIND_DIRECTION_UNIT,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from .coordinator import MeteocatSensorCoordinator
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class MeteocatSensorEntityDescription(SensorEntityDescription):
|
|
40
|
+
"""A class that describes Meteocat sensor entities."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
SENSOR_TYPES: tuple[MeteocatSensorEntityDescription, ...] = (
|
|
44
|
+
MeteocatSensorEntityDescription(
|
|
45
|
+
key=WIND_SPEED,
|
|
46
|
+
name="Wind Speed",
|
|
47
|
+
icon="mdi:weather-windy",
|
|
48
|
+
device_class=SensorDeviceClass.WIND_SPEED,
|
|
49
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
50
|
+
native_unit_of_measurement=WIND_SPEED_UNIT,
|
|
51
|
+
),
|
|
52
|
+
MeteocatSensorEntityDescription(
|
|
53
|
+
key=WIND_DIRECTION,
|
|
54
|
+
name="Wind Direction",
|
|
55
|
+
icon="mdi:compass",
|
|
56
|
+
device_class=None,
|
|
57
|
+
native_unit_of_measurement=WIND_DIRECTION_UNIT,
|
|
58
|
+
),
|
|
59
|
+
MeteocatSensorEntityDescription(
|
|
60
|
+
key=TEMPERATURE,
|
|
61
|
+
name="Temperature",
|
|
62
|
+
icon="mdi:thermometer",
|
|
63
|
+
device_class=SensorDeviceClass.TEMPERATURE,
|
|
64
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
65
|
+
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
66
|
+
),
|
|
67
|
+
MeteocatSensorEntityDescription(
|
|
68
|
+
key=HUMIDITY,
|
|
69
|
+
name="Humidity",
|
|
70
|
+
icon="mdi:water-percent",
|
|
71
|
+
device_class=SensorDeviceClass.HUMIDITY,
|
|
72
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
73
|
+
native_unit_of_measurement=PERCENTAGE,
|
|
74
|
+
),
|
|
75
|
+
MeteocatSensorEntityDescription(
|
|
76
|
+
key=PRESSURE,
|
|
77
|
+
name="Pressure",
|
|
78
|
+
icon="mdi:gauge",
|
|
79
|
+
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
|
80
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
81
|
+
native_unit_of_measurement=PRESSURE_UNIT,
|
|
82
|
+
),
|
|
83
|
+
MeteocatSensorEntityDescription(
|
|
84
|
+
key=PRECIPITATION,
|
|
85
|
+
name="Precipitation",
|
|
86
|
+
icon="mdi:weather-rainy",
|
|
87
|
+
device_class=None,
|
|
88
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
89
|
+
native_unit_of_measurement=PRECIPITATION_UNIT,
|
|
90
|
+
),
|
|
91
|
+
MeteocatSensorEntityDescription(
|
|
92
|
+
key=UV_INDEX,
|
|
93
|
+
name="UV Index",
|
|
94
|
+
icon="mdi:sun",
|
|
95
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
96
|
+
native_unit_of_measurement=UV_INDEX_UNIT,
|
|
97
|
+
),
|
|
98
|
+
MeteocatSensorEntityDescription(
|
|
99
|
+
key=MAX_TEMPERATURE,
|
|
100
|
+
name="Max Temperature",
|
|
101
|
+
icon="mdi:thermometer-plus",
|
|
102
|
+
device_class=SensorDeviceClass.TEMPERATURE,
|
|
103
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
104
|
+
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
105
|
+
),
|
|
106
|
+
MeteocatSensorEntityDescription(
|
|
107
|
+
key=MIN_TEMPERATURE,
|
|
108
|
+
name="Min Temperature",
|
|
109
|
+
icon="mdi:thermometer-minus",
|
|
110
|
+
device_class=SensorDeviceClass.TEMPERATURE,
|
|
111
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
112
|
+
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
113
|
+
),
|
|
114
|
+
MeteocatSensorEntityDescription(
|
|
115
|
+
key=WIND_GUST,
|
|
116
|
+
name="Wind Gust",
|
|
117
|
+
icon="mdi:weather-windy",
|
|
118
|
+
device_class=SensorDeviceClass.WIND_SPEED,
|
|
119
|
+
state_class=SensorStateClass.MEASUREMENT,
|
|
120
|
+
native_unit_of_measurement=WIND_SPEED_UNIT,
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@callback
|
|
126
|
+
async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback) -> None:
|
|
127
|
+
"""Set up Meteocat sensors from a config entry."""
|
|
128
|
+
entry_data = hass.data[DOMAIN][entry.entry_id]
|
|
129
|
+
coordinator = entry_data["sensor_coordinator"]
|
|
130
|
+
|
|
131
|
+
async_add_entities(
|
|
132
|
+
MeteocatSensor(coordinator, description, entry_data)
|
|
133
|
+
for description in SENSOR_TYPES
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity):
|
|
138
|
+
"""Representation of a Meteocat sensor."""
|
|
139
|
+
|
|
140
|
+
def __init__(self, coordinator, description, entry_data):
|
|
141
|
+
"""Initialize the sensor."""
|
|
142
|
+
super().__init__(coordinator)
|
|
143
|
+
self.entity_description = description
|
|
144
|
+
self.api_key = entry_data["api_key"] # Usamos la API key de la configuración
|
|
145
|
+
self._town_name = entry_data["town_name"] # Usamos el nombre del municipio
|
|
146
|
+
self._town_id = entry_data["town_id"] # Usamos el ID del municipio
|
|
147
|
+
self._variable_name = entry_data["variable_name"] # Usamos el nombre de la variable
|
|
148
|
+
self._variable_id = entry_data["variable_id"] # Usamos el ID de la variable
|
|
149
|
+
self._station_name = entry_data["station_name"] # Usamos el nombre de la estación
|
|
150
|
+
self._station_id = entry_data["station_id"] # Usamos el ID de la estación
|
|
151
|
+
|
|
152
|
+
# Unique ID for the entity
|
|
153
|
+
self._attr_unique_id = f"{self._town_id}_{self.entity_description.key}"
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def native_value(self):
|
|
157
|
+
"""Return the state of the sensor."""
|
|
158
|
+
value = getattr(self.coordinator.data, self.entity_description.key, None)
|
|
159
|
+
|
|
160
|
+
if self.entity_description.key == WIND_DIRECTION:
|
|
161
|
+
return self._convert_degrees_to_cardinal(value)
|
|
162
|
+
|
|
163
|
+
return value
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def _convert_degrees_to_cardinal(degree: float | None) -> str | None:
|
|
167
|
+
"""Convert degrees to cardinal direction."""
|
|
168
|
+
if degree is None:
|
|
169
|
+
return None
|
|
170
|
+
directions = [
|
|
171
|
+
"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
|
|
172
|
+
"S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N",
|
|
173
|
+
]
|
|
174
|
+
index = int(((degree + 11.25) / 22.5)) % 16
|
|
175
|
+
return directions[index]
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def device_info(self) -> DeviceInfo:
|
|
179
|
+
"""Return the device info."""
|
|
180
|
+
return DeviceInfo(
|
|
181
|
+
identifiers={(DOMAIN, self._town_id)},
|
|
182
|
+
name=self._town_name,
|
|
183
|
+
manufacturer="Meteocat",
|
|
184
|
+
model="Meteocat API",
|
|
185
|
+
additional_properties={
|
|
186
|
+
"Town ID": self._town_id,
|
|
187
|
+
"Station Name": self._station_name,
|
|
188
|
+
"Station ID": self._station_id,
|
|
189
|
+
},
|
|
190
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config": {
|
|
3
|
+
"step": {
|
|
4
|
+
"user": {
|
|
5
|
+
"description": "Enter your Meteocat API Key to validate it.",
|
|
6
|
+
"title": "API Key"
|
|
7
|
+
},
|
|
8
|
+
"select_municipi": {
|
|
9
|
+
"description": "Select a municipality from the list.",
|
|
10
|
+
"title": "Municipality Selection"
|
|
11
|
+
},
|
|
12
|
+
"select_station": {
|
|
13
|
+
"description": "Select a station.",
|
|
14
|
+
"title": "Station Selection"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"error": {
|
|
18
|
+
"bad_request": "Invalid request. Please check your API Key.",
|
|
19
|
+
"forbidden": "Access denied. Check your API Key permissions.",
|
|
20
|
+
"rate_limit_exceeded": "Rate limit exceeded. Please try again later.",
|
|
21
|
+
"server_error": "Server error. Please try again later.",
|
|
22
|
+
"connection_error": "Connection error. Please check your network.",
|
|
23
|
+
"unknown_error": "An unknown error occurred."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config": {
|
|
3
|
+
"step": {
|
|
4
|
+
"user": {
|
|
5
|
+
"description": "Introduïu la API Key subministrada per Meteocat per poder iniciar el procés de validació.",
|
|
6
|
+
"title": "API Key"
|
|
7
|
+
},
|
|
8
|
+
"select_municipi": {
|
|
9
|
+
"description": "Seleccioneu el municipi de la llista.",
|
|
10
|
+
"title": "Selecció de municipi"
|
|
11
|
+
},
|
|
12
|
+
"select_station": {
|
|
13
|
+
"description": "Seleccioneu l'estació de la llista.",
|
|
14
|
+
"title": "Selecció d'estació"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"error": {
|
|
18
|
+
"bad_request": "Petició no vàlida. Reviseu la vostra API Key.",
|
|
19
|
+
"forbidden": "Accés denegat. Reviseu els permisos de la vostra API Key.",
|
|
20
|
+
"rate_limit_exceeded": "Excés del límit de peticions. Si us plau, torneu-ho a provar més tard.",
|
|
21
|
+
"server_error": "Error del servidor. Torneu-ho a provar més tard.",
|
|
22
|
+
"connection_error": "Error de connexió. Reviseu la vostra connexió de xarxa.",
|
|
23
|
+
"unknown_error": "Error desconegut."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config": {
|
|
3
|
+
"step": {
|
|
4
|
+
"user": {
|
|
5
|
+
"description": "Enter your Meteocat API Key to validate it.",
|
|
6
|
+
"title": "API Key"
|
|
7
|
+
},
|
|
8
|
+
"select_municipi": {
|
|
9
|
+
"description": "Select a municipality from the list.",
|
|
10
|
+
"title": "Municipality Selection"
|
|
11
|
+
},
|
|
12
|
+
"select_station": {
|
|
13
|
+
"description": "Select a station.",
|
|
14
|
+
"title": "Station Selection"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"error": {
|
|
18
|
+
"bad_request": "Invalid request. Please check your API Key.",
|
|
19
|
+
"forbidden": "Access denied. Check your API Key permissions.",
|
|
20
|
+
"rate_limit_exceeded": "Rate limit exceeded. Please try again later.",
|
|
21
|
+
"server_error": "Server error. Please try again later.",
|
|
22
|
+
"connection_error": "Connection error. Please check your network.",
|
|
23
|
+
"unknown_error": "An unknown error occurred."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config": {
|
|
3
|
+
"step": {
|
|
4
|
+
"user": {
|
|
5
|
+
"description": "Ingrese la API Key facilitada por Meteocat para poder iniciar el proceso de validación.",
|
|
6
|
+
"title": "API Key"
|
|
7
|
+
},
|
|
8
|
+
"select_municipi": {
|
|
9
|
+
"description": "Seleccione el municipio de la lista.",
|
|
10
|
+
"title": "Selección de municipio"
|
|
11
|
+
},
|
|
12
|
+
"select_station": {
|
|
13
|
+
"description": "Seleccione la estación de la lista.",
|
|
14
|
+
"title": "Selección de estación"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"error": {
|
|
18
|
+
"bad_request": "Petición no válida. Revise su API Key.",
|
|
19
|
+
"forbidden": "Acceso denegado. Revise los permisos de su API Key.",
|
|
20
|
+
"rate_limit_exceeded": "Exceso del límite de peticiones. Por favor, inténtelo de nuevo más tarde.",
|
|
21
|
+
"server_error": "Error del servidor. Inténtelo de nuevo más tarde.",
|
|
22
|
+
"connection_error": "Error de conexión. Revise su conexión de red.",
|
|
23
|
+
"unknown_error": "Error desconocido."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/filetree.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
def generate_file_tree():
|
|
5
|
+
# Ruta donde se generará el archivo
|
|
6
|
+
output_file = 'filetree.txt'
|
|
7
|
+
|
|
8
|
+
# Ejecutar el comando git ls-files para obtener los archivos no ignorados
|
|
9
|
+
git_command = ['git', 'ls-files']
|
|
10
|
+
try:
|
|
11
|
+
# Captura de la salida del comando git
|
|
12
|
+
git_files = subprocess.check_output(git_command).decode('utf-8').splitlines()
|
|
13
|
+
|
|
14
|
+
# Crear una estructura de árbol
|
|
15
|
+
tree = {}
|
|
16
|
+
|
|
17
|
+
# Construir el árbol de directorios
|
|
18
|
+
for file in git_files:
|
|
19
|
+
parts = file.split('/') # Separar la ruta del archivo por '/'
|
|
20
|
+
current = tree
|
|
21
|
+
for part in parts[:-1]: # Recorremos todos los directorios
|
|
22
|
+
current = current.setdefault(part, {})
|
|
23
|
+
current[parts[-1]] = None # El archivo final se marca como 'None'
|
|
24
|
+
|
|
25
|
+
# Función recursiva para imprimir el árbol con la indentación correcta
|
|
26
|
+
def print_tree(directory, indent=""):
|
|
27
|
+
for name, subdirectory in directory.items():
|
|
28
|
+
if subdirectory is None:
|
|
29
|
+
# Si es un archivo, lo imprimimos
|
|
30
|
+
f.write(f"{indent}├── {name}\n")
|
|
31
|
+
else:
|
|
32
|
+
# Si es un directorio, lo imprimimos y recursivamente llamamos a print_tree
|
|
33
|
+
f.write(f"{indent}└── {name}/\n")
|
|
34
|
+
print_tree(subdirectory, indent + " ")
|
|
35
|
+
|
|
36
|
+
# Crear el archivo y escribir la estructura del árbol con codificación UTF-8
|
|
37
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
38
|
+
print_tree(tree)
|
|
39
|
+
|
|
40
|
+
print(f"Árbol de directorios generado en: {os.path.abspath(output_file)}")
|
|
41
|
+
|
|
42
|
+
except subprocess.CalledProcessError as e:
|
|
43
|
+
print(f"Error al ejecutar el comando: {e}")
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f"Ocurrió un error inesperado: {e}")
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
generate_file_tree()
|
package/filetree.txt
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
└── .github/
|
|
2
|
+
└── workflows/
|
|
3
|
+
├── release.yml
|
|
4
|
+
├── .gitignore
|
|
5
|
+
├── .gitlab-ci.yml
|
|
6
|
+
├── .pre-commit-config.yaml
|
|
7
|
+
├── .releaserc.toml
|
|
8
|
+
├── AUTHORS.md
|
|
9
|
+
├── CHANGELOG.md
|
|
10
|
+
├── LICENSE
|
|
11
|
+
├── README.md
|
|
12
|
+
└── custom_components/
|
|
13
|
+
└── meteocat/
|
|
14
|
+
├── __init__.py
|
|
15
|
+
├── condition.py
|
|
16
|
+
├── config_flow.py
|
|
17
|
+
├── const.py
|
|
18
|
+
├── coordinator.py
|
|
19
|
+
├── entity.py
|
|
20
|
+
├── helpers.py
|
|
21
|
+
├── manifest.json
|
|
22
|
+
├── options_flow.py
|
|
23
|
+
├── sensor.py
|
|
24
|
+
├── strings.json
|
|
25
|
+
└── translations/
|
|
26
|
+
├── ca.json
|
|
27
|
+
├── en.json
|
|
28
|
+
├── es.json
|
|
29
|
+
├── version.py
|
|
30
|
+
├── filetree.py
|
|
31
|
+
├── filetree.txt
|
|
32
|
+
├── hacs.json
|
|
33
|
+
├── package-lock.json
|
|
34
|
+
├── poetry.lock
|
|
35
|
+
├── pyproject.toml
|
|
36
|
+
├── requirements.test.txt
|
|
37
|
+
├── setup.cfg
|
|
38
|
+
├── setup.py
|
|
39
|
+
└── tests/
|
|
40
|
+
├── __init__.py
|
|
41
|
+
├── bandit.yaml
|
|
42
|
+
├── conftest.py
|
|
43
|
+
├── test_init.py
|
package/hacs.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "meteocat",
|
|
3
|
+
"version": "0.1.19",
|
|
4
|
+
"description": "[](https://opensource.org/licenses/Apache-2.0)\r [](https://pypi.org/project/meteocat)\r [](https://gitlab.com/figorr/meteocat/commits/master)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"directories": {
|
|
7
|
+
"test": "tests"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
17
|
+
"@semantic-release/exec": "^6.0.3",
|
|
18
|
+
"@semantic-release/git": "^10.0.1",
|
|
19
|
+
"@semantic-release/github": "^11.0.1",
|
|
20
|
+
"semantic-release": "^24.2.0"
|
|
21
|
+
}
|
|
22
|
+
}
|