Skip to content

Commit 11c3b11

Browse files
committed
Minimal version, getting current temp and condition
1 parent 2a1f0a8 commit 11c3b11

File tree

5 files changed

+248
-9
lines changed

5 files changed

+248
-9
lines changed

custom_components/irm_kmi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"""Integration for IRM KMI weather"""
1+
"""Integration for IRM KMI weather"""

custom_components/irm_kmi/api.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""API Client for IRM KMI weather"""
2+
from __future__ import annotations
3+
4+
import asyncio
5+
import socket
6+
7+
import aiohttp
8+
import async_timeout
9+
import hashlib
10+
from datetime import datetime
11+
12+
13+
class IrmKmiApiError(Exception):
14+
"""Exception to indicate a general API error."""
15+
16+
17+
class IrmKmiApiCommunicationError(
18+
IrmKmiApiError
19+
):
20+
"""Exception to indicate a communication error."""
21+
22+
23+
class IrmKmiApiParametersError(
24+
IrmKmiApiError
25+
):
26+
"""Exception to indicate a parameter error."""
27+
28+
29+
class IrmKmiApiAuthenticationError(
30+
IrmKmiApiError
31+
):
32+
"""Exception to indicate an authentication error."""
33+
34+
35+
def _api_key(method_name: str):
36+
"""Get API key."""
37+
return hashlib.md5(f"r9EnW374jkJ9acc;{method_name};{datetime.now().strftime('%d/%m/%Y')}".encode()).hexdigest()
38+
39+
40+
class IrmKmiApiClient:
41+
"""Sample API Client."""
42+
43+
def __init__(
44+
self,
45+
session: aiohttp.ClientSession,
46+
) -> None:
47+
"""Sample API Client."""
48+
self._session = session
49+
self._base_url = "https://app.meteo.be/services/appv4/"
50+
51+
async def get_forecasts_city(self, city_id: int) -> any:
52+
"""Get forecasts for given city."""
53+
return await self._api_wrapper(
54+
params={"ins": city_id,
55+
"s": "getForecasts"}
56+
)
57+
58+
async def _api_wrapper(
59+
self,
60+
params: dict,
61+
path: str = "",
62+
method: str = "get",
63+
data: dict | None = None,
64+
headers: dict | None = None
65+
) -> any:
66+
"""Get information from the API."""
67+
68+
if 's' not in params:
69+
raise IrmKmiApiParametersError("No query provided as 's' argument for API")
70+
else:
71+
params['k'] = _api_key(params['s'])
72+
73+
try:
74+
async with async_timeout.timeout(10):
75+
response = await self._session.request(
76+
method=method,
77+
url=f"{self._base_url}{path}",
78+
headers=headers,
79+
json=data,
80+
params=params
81+
)
82+
response.raise_for_status()
83+
return await response.json()
84+
85+
except asyncio.TimeoutError as exception:
86+
raise IrmKmiApiCommunicationError(
87+
"Timeout error fetching information",
88+
) from exception
89+
except (aiohttp.ClientError, socket.gaierror) as exception:
90+
raise IrmKmiApiCommunicationError(
91+
"Error fetching information",
92+
) from exception
93+
except Exception as exception: # pylint: disable=broad-except
94+
raise IrmKmiApiError(
95+
"Something really wrong happened!"
96+
) from exception

custom_components/irm_kmi/const.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from homeassistant.components.weather import (
2+
ATTR_CONDITION_CLOUDY,
3+
ATTR_CONDITION_FOG,
4+
ATTR_CONDITION_SNOWY_RAINY,
5+
ATTR_CONDITION_LIGHTNING_RAINY,
6+
ATTR_CONDITION_PARTLYCLOUDY,
7+
ATTR_CONDITION_POURING,
8+
ATTR_CONDITION_RAINY,
9+
ATTR_CONDITION_SNOWY,
10+
ATTR_CONDITION_SUNNY,
11+
ATTR_CONDITION_CLEAR_NIGHT,
12+
ATTR_CONDITION_EXCEPTIONAL
13+
)
14+
15+
DOMAIN = 'irm_kmi'
16+
17+
# map ('ww', 'dayNight') tuple from IRM KMI to HA conditions
18+
IRM_KMI_TO_HA_CONDITION_MAP = {
19+
(0, 'd'): ATTR_CONDITION_SUNNY,
20+
(0, 'n'): ATTR_CONDITION_CLEAR_NIGHT,
21+
(1, 'd'): ATTR_CONDITION_PARTLYCLOUDY,
22+
(1, 'n'): ATTR_CONDITION_PARTLYCLOUDY,
23+
(2, 'd'): ATTR_CONDITION_LIGHTNING_RAINY,
24+
(2, 'n'): ATTR_CONDITION_LIGHTNING_RAINY,
25+
(3, 'd'): ATTR_CONDITION_CLOUDY,
26+
(3, 'n'): ATTR_CONDITION_CLOUDY,
27+
(4, 'd'): ATTR_CONDITION_POURING,
28+
(4, 'n'): ATTR_CONDITION_POURING,
29+
(5, 'd'): ATTR_CONDITION_LIGHTNING_RAINY,
30+
(5, 'n'): ATTR_CONDITION_LIGHTNING_RAINY,
31+
(6, 'd'): ATTR_CONDITION_POURING,
32+
(6, 'n'): ATTR_CONDITION_POURING,
33+
(7, 'd'): ATTR_CONDITION_LIGHTNING_RAINY,
34+
(7, 'n'): ATTR_CONDITION_LIGHTNING_RAINY,
35+
(8, 'd'): ATTR_CONDITION_SNOWY_RAINY,
36+
(8, 'n'): ATTR_CONDITION_SNOWY_RAINY,
37+
(9, 'd'): ATTR_CONDITION_SNOWY_RAINY,
38+
(9, 'n'): ATTR_CONDITION_SNOWY_RAINY,
39+
(10, 'd'): ATTR_CONDITION_LIGHTNING_RAINY,
40+
(10, 'n'): ATTR_CONDITION_LIGHTNING_RAINY,
41+
(11, 'd'): ATTR_CONDITION_SNOWY,
42+
(11, 'n'): ATTR_CONDITION_SNOWY,
43+
(12, 'd'): ATTR_CONDITION_SNOWY,
44+
(12, 'n'): ATTR_CONDITION_SNOWY,
45+
(13, 'd'): ATTR_CONDITION_LIGHTNING_RAINY,
46+
(13, 'n'): ATTR_CONDITION_LIGHTNING_RAINY,
47+
(14, 'd'): ATTR_CONDITION_CLOUDY,
48+
(14, 'n'): ATTR_CONDITION_CLOUDY,
49+
(15, 'd'): ATTR_CONDITION_CLOUDY,
50+
(15, 'n'): ATTR_CONDITION_CLOUDY,
51+
(16, 'd'): ATTR_CONDITION_POURING,
52+
(16, 'n'): ATTR_CONDITION_POURING,
53+
(17, 'd'): ATTR_CONDITION_LIGHTNING_RAINY,
54+
(17, 'n'): ATTR_CONDITION_LIGHTNING_RAINY,
55+
(18, 'd'): ATTR_CONDITION_RAINY,
56+
(18, 'n'): ATTR_CONDITION_RAINY,
57+
(19, 'd'): ATTR_CONDITION_POURING,
58+
(19, 'n'): ATTR_CONDITION_POURING,
59+
(20, 'd'): ATTR_CONDITION_SNOWY_RAINY,
60+
(20, 'n'): ATTR_CONDITION_SNOWY_RAINY,
61+
(21, 'd'): ATTR_CONDITION_EXCEPTIONAL,
62+
(21, 'n'): ATTR_CONDITION_EXCEPTIONAL,
63+
(22, 'd'): ATTR_CONDITION_SNOWY,
64+
(22, 'n'): ATTR_CONDITION_SNOWY,
65+
(23, 'd'): ATTR_CONDITION_SNOWY,
66+
(23, 'n'): ATTR_CONDITION_SNOWY,
67+
(24, 'd'): ATTR_CONDITION_FOG,
68+
(24, 'n'): ATTR_CONDITION_FOG,
69+
(25, 'd'): ATTR_CONDITION_FOG,
70+
(25, 'n'): ATTR_CONDITION_FOG,
71+
(26, 'd'): ATTR_CONDITION_FOG,
72+
(26, 'n'): ATTR_CONDITION_FOG,
73+
(27, 'd'): ATTR_CONDITION_EXCEPTIONAL,
74+
(27, 'n'): ATTR_CONDITION_EXCEPTIONAL
75+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Example integration using DataUpdateCoordinator."""
2+
3+
from datetime import timedelta
4+
import logging
5+
6+
import async_timeout
7+
8+
from homeassistant.helpers.aiohttp_client import async_get_clientsession
9+
from homeassistant.helpers.update_coordinator import (
10+
DataUpdateCoordinator,
11+
UpdateFailed,
12+
)
13+
14+
from .api import IrmKmiApiClient, IrmKmiApiError
15+
16+
_LOGGER = logging.getLogger(__name__)
17+
18+
class IrmKmiCoordinator(DataUpdateCoordinator):
19+
"""Coordinator to update data from IRM KMI"""
20+
21+
def __init__(self, hass, city_id):
22+
"""Initialize my coordinator."""
23+
super().__init__(
24+
hass,
25+
_LOGGER,
26+
# Name of the data. For logging purposes.
27+
name="IRM KMI weather",
28+
# Polling interval. Will only be polled if there are subscribers.
29+
update_interval=timedelta(seconds=30),
30+
)
31+
self._api_client = IrmKmiApiClient(session=async_get_clientsession(hass))
32+
self._city_id = city_id
33+
34+
async def _async_update_data(self):
35+
"""Fetch data from API endpoint.
36+
37+
This is the place to pre-process the data to lookup tables
38+
so entities can quickly look up their data.
39+
"""
40+
try:
41+
# Note: asyncio.TimeoutError and aiohttp.ClientError are already
42+
# handled by the data update coordinator.
43+
async with async_timeout.timeout(10):
44+
# Grab active context variables to limit data required to be fetched from API
45+
# Note: using context is not required if there is no need or ability to limit
46+
# data retrieved from API.
47+
data = await self._api_client.get_forecasts_city(city_id=self._city_id)
48+
return data
49+
except IrmKmiApiError as err:
50+
raise UpdateFailed(f"Error communicating with API: {err}")

custom_components/irm_kmi/weather.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,47 @@
11
import logging
2+
23
from homeassistant.components.weather import WeatherEntity
3-
from homeassistant.components.weather import ATTR_CONDITION_PARTLYCLOUDY
44
from homeassistant.const import UnitOfTemperature
5+
from homeassistant.helpers.update_coordinator import (
6+
CoordinatorEntity,
7+
)
8+
9+
from .const import IRM_KMI_TO_HA_CONDITION_MAP as CDT_MAP
10+
from .coordinator import IrmKmiCoordinator
511

612
_LOGGER = logging.getLogger(__name__)
713

814

9-
def setup_platform(hass, config, add_devices, discovery_info=None):
10-
add_devices([IrmKmiWeather()])
11-
_LOGGER.warning("Irm KMI setup")
15+
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
16+
_LOGGER.debug(f"IRM KMI setup. Config: {config}")
17+
coordinator = IrmKmiCoordinator(hass, city_id=config.get("city_id"))
18+
await coordinator.async_request_refresh()
19+
20+
async_add_entities([IrmKmiWeather(
21+
coordinator,
22+
config.get("name", "IRM KMI Weather")
23+
)])
24+
1225

26+
class IrmKmiWeather(CoordinatorEntity, WeatherEntity):
1327

14-
class IrmKmiWeather(WeatherEntity):
28+
def __init__(self, coordinator: IrmKmiCoordinator, name: str) -> None:
29+
super().__init__(coordinator)
30+
self._name = name
1531

1632
@property
1733
def name(self) -> str:
18-
return "IRM KMI Weather"
34+
return self._name
1935

2036
@property
2137
def condition(self) -> str | None:
22-
return ATTR_CONDITION_PARTLYCLOUDY
38+
irm_condition = (self.coordinator.data.get('obs', {}).get('ww'),
39+
self.coordinator.data.get('obs', {}).get('dayNight'))
40+
return CDT_MAP.get(irm_condition, None)
2341

2442
@property
2543
def native_temperature(self) -> float | None:
26-
return 20.2
44+
return self.coordinator.data.get('obs', {}).get('temp')
2745

2846
@property
2947
def native_temperature_unit(self) -> str:

0 commit comments

Comments
 (0)