Skip to content

Commit f50b1f9

Browse files
[weather.ha] 0.0.6.3
1 parent bf23c26 commit f50b1f9

34 files changed

+2730
-0
lines changed

weather.ha/LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

weather.ha/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# KODI weather forecast from HomeAssistant
2+
3+
## Instructions
4+
In order to make it work there are a couple of actions which have to be performed on both HomeAssistant and Kodi.
5+
6+
1. In Home Assistant you have to [create a token](https://community.home-assistant.io/t/how-to-get-long-lived-access-token/162159/5) for this plugin.
7+
- get to your Profile page on Home Assistant by clicking on your username in the bottom left panel of the HA page (under Notifications)
8+
- then click on Create Token all the way at the bottom
9+
- give it a meaningful name and then copy it for use in your application.
10+
2. In Kodi you have to do the following:
11+
- Install plugin
12+
- In settings put the long live token and url to your Home Assistant server
13+
- Check the entity IDs
14+
- Done. Enjoy :)

weather.ha/addon.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<addon id="weather.ha" name="Home Assistant Weather" version="0.0.6.3" provider-name="eugeniusz.gienek">
3+
<requires>
4+
<import addon="xbmc.python" version="3.0.1"/>
5+
<import addon="script.module.requests" version="2.27.1"/>
6+
<import addon="script.module.iso8601" version="0.1.12"/>
7+
<import addon="script.module.yaml" version="5.3.0"/>
8+
<import addon="script.module.dateutil" version="2.8.1"/>
9+
</requires>
10+
<extension point="xbmc.python.weather" library="default.py"/>
11+
<extension point="xbmc.addon.metadata">
12+
<summary lang="en_GB">Weather forecast from Home Assistant</summary>
13+
<description lang="en_GB">Weather forecast provided by Your Home Assistant server</description>
14+
<summary lang="pl_PL">Prognoza Pogody Home Assistant</summary>
15+
<description lang="pl_PL">Prognoza pogody od Twojego lokalnego serwera Home Assistant</description>
16+
<platform>all</platform>
17+
<language>en pl</language>
18+
<license>GPL-3.0</license>
19+
<source>https://github.com/Eugeniusz-Gienek/kodi_weather_ha</source>
20+
<assets>
21+
<icon>resources/icon.png</icon>
22+
<fanart>resources/fanart.png</fanart>
23+
<banner>resources/banner.png</banner>
24+
</assets>
25+
</extension>
26+
</addon>

weather.ha/changelog.txt

Whitespace-only changes.

weather.ha/default.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from plugin import KodiHomeAssistantWeatherPlugin
2+
3+
if __name__ == '__main__':
4+
KodiHomeAssistantWeatherPlugin()

weather.ha/lib/__init__.py

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from ._adapter import HomeAssistantAdapter
2+
from ._errors import RequestError
3+
from ._forecast import (
4+
HomeAssistantForecast, HomeAssistantCurrentForecast, HomeAssistantHourlyForecast, HomeAssistantDailyForecast,
5+
HomeAssistantWeatherCondition, HomeAssistantForecastMeta
6+
)
7+
from ._sun import HomeAssistantSunInfo
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import urllib.parse
2+
from typing import Dict, Union
3+
4+
import requests
5+
from requests import RequestException
6+
7+
from ._errors import RequestError
8+
from ._forecast import (
9+
HomeAssistantForecast, HomeAssistantCurrentForecast, HomeAssistantHourlyForecast, HomeAssistantDailyForecast
10+
)
11+
from ._sun import HomeAssistantSunInfo, HomeAssistantSunState
12+
13+
14+
class HomeAssistantAdapter:
15+
@staticmethod
16+
def __make_headers_from_token(token: str) -> Dict[str, str]:
17+
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
18+
19+
@staticmethod
20+
def __request(url: str, token: str, post: bool = False,
21+
data: Union[Dict[str, str], None] = None, check_ssl = True) -> requests.Response:
22+
try:
23+
if post:
24+
r = requests.post(
25+
url=url, headers=HomeAssistantAdapter.__make_headers_from_token(token=token), json=data,
26+
params={"return_response": True}, verify=check_ssl
27+
)
28+
else:
29+
r = requests.get(
30+
url=url, headers=HomeAssistantAdapter.__make_headers_from_token(token=token), params=data, verify=check_ssl
31+
)
32+
except RequestException:
33+
raise RequestError(error_code=-1, url=url, method="POST" if post else "GET", body="")
34+
if not r.ok:
35+
raise RequestError(error_code=r.status_code, url=url, method="POST" if post else "GET", body=r.text)
36+
return r
37+
38+
@staticmethod
39+
def filter_attributes(attributes_received,forecast_type='current'):
40+
output_attributes = {}
41+
allowed_keys = [
42+
'temperature',
43+
'dew_point',
44+
'temperature_unit',
45+
'humidity',
46+
'uv_index',
47+
'pressure',
48+
'pressure_unit',
49+
'wind_bearing',
50+
'wind_speed',
51+
'wind_speed_unit',
52+
'precipitation_unit',
53+
'supported_features',
54+
'visibility_unit',
55+
'attribution',
56+
'friendly_name',
57+
'cloud_coverage'
58+
]
59+
if forecast_type == 'hourly':
60+
allowed_keys = [
61+
'temperature',
62+
'humidity',
63+
'uv_index',
64+
'wind_bearing',
65+
'wind_speed',
66+
'cloud_coverage',
67+
'condition',
68+
'datetime',
69+
'precipitation',
70+
]
71+
elif forecast_type == 'daily':
72+
allowed_keys = [
73+
'temperature',
74+
'humidity',
75+
'uv_index',
76+
'wind_bearing',
77+
'wind_speed',
78+
'condition',
79+
'datetime',
80+
'precipitation',
81+
'templow',
82+
]
83+
for key in attributes_received:
84+
if key in allowed_keys:
85+
output_attributes[key] = attributes_received[key]
86+
for key in allowed_keys:
87+
if not key in output_attributes:
88+
output_attributes[key] = None
89+
return output_attributes
90+
91+
@staticmethod
92+
def get_forecast(server_url: str, entity_id: str, token: str, check_ssl: bool) -> HomeAssistantForecast:
93+
current_url = urllib.parse.urljoin(base=server_url, url=f"/api/states/{entity_id}")
94+
forecast_url = urllib.parse.urljoin(base=server_url, url="/api/services/weather/get_forecasts")
95+
current = HomeAssistantAdapter.__request(url=current_url, token=token, check_ssl=check_ssl)
96+
hourly = HomeAssistantAdapter.__request(
97+
url=forecast_url, token=token, post=True, data={"entity_id": entity_id, "type": "hourly"}, check_ssl=check_ssl
98+
)
99+
daily = HomeAssistantAdapter.__request(
100+
url=forecast_url, token=token, post=True, data={"entity_id": entity_id, "type": "daily"}, check_ssl=check_ssl
101+
)
102+
current_forecast_attributes = HomeAssistantAdapter.filter_attributes(current.json()["attributes"], 'current')
103+
hourly_forecast_attributes = [HomeAssistantAdapter.filter_attributes(hourly_forecast, 'hourly') for hourly_forecast in hourly.json()["service_response"][entity_id]["forecast"]]
104+
daily_forecast_attributes = [HomeAssistantAdapter.filter_attributes(daily_forecast, 'daily') for daily_forecast in daily.json()["service_response"][entity_id]["forecast"]]
105+
return HomeAssistantForecast(
106+
current=HomeAssistantCurrentForecast(**current_forecast_attributes),
107+
hourly=[
108+
HomeAssistantHourlyForecast(**hourly_forecast)
109+
for hourly_forecast in hourly_forecast_attributes
110+
],
111+
daily=[
112+
HomeAssistantDailyForecast(**daily_forecast)
113+
for daily_forecast in daily_forecast_attributes
114+
],
115+
)
116+
117+
@staticmethod
118+
def get_sun_info(server_url: str, entity_id: str, token: str, check_ssl: bool) -> HomeAssistantSunInfo:
119+
sun_url = urllib.parse.urljoin(base=server_url, url=f"/api/states/{entity_id}")
120+
sun = HomeAssistantAdapter.__request(url=sun_url, token=token, check_ssl=check_ssl)
121+
sun_data = sun.json()
122+
return HomeAssistantSunInfo(**sun_data["attributes"], state=HomeAssistantSunState(sun_data["state"]))
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass
5+
class RequestError(Exception):
6+
error_code: int
7+
url: str
8+
method: str
9+
body: str
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from dataclasses import dataclass
2+
from enum import Enum
3+
from typing import List, Union
4+
5+
6+
class HomeAssistantWeatherCondition(str, Enum):
7+
CLEAR_NIGHT = "clear-night"
8+
CLOUDY = "cloudy"
9+
FOG = "fog"
10+
HAIL = "hail"
11+
LIGHTNING = "lightning"
12+
LIGHTNING_RAINY = "lightning-rainy"
13+
PARTLY_CLOUDY = "partlycloudy"
14+
POURING = "pouring"
15+
RAINY = "rainy"
16+
SNOWY = "snowy"
17+
SNOWY_RAINY = "snowy-rainy"
18+
SUNNY = "sunny"
19+
WINDY = "windy"
20+
WINDY_CLOUDY = "windy-cloudy"
21+
EXCEPTIONAL = "exceptional"
22+
23+
24+
@dataclass
25+
class _HomeAssistantForecastCommon:
26+
wind_bearing: float
27+
wind_speed: float
28+
temperature: float
29+
humidity: float
30+
31+
32+
@dataclass
33+
class HomeAssistantForecastMeta:
34+
temperature_unit: str
35+
pressure_unit: str
36+
wind_speed_unit: str
37+
visibility_unit: str
38+
precipitation_unit: str
39+
attribution: str
40+
friendly_name: str
41+
supported_features: int
42+
43+
44+
@dataclass
45+
class _HomeAssistantFutureForecast:
46+
condition: HomeAssistantWeatherCondition
47+
datetime: str
48+
precipitation: float
49+
50+
def __post_init__(self):
51+
self.condition = HomeAssistantWeatherCondition(self.condition)
52+
53+
54+
@dataclass
55+
class HomeAssistantCurrentForecast(_HomeAssistantForecastCommon, HomeAssistantForecastMeta):
56+
dew_point: float
57+
cloud_coverage: float
58+
pressure: float
59+
uv_index: float
60+
61+
62+
@dataclass
63+
class HomeAssistantHourlyForecast(_HomeAssistantForecastCommon, _HomeAssistantFutureForecast):
64+
cloud_coverage: float
65+
uv_index: float
66+
67+
68+
@dataclass
69+
class HomeAssistantDailyForecast(_HomeAssistantForecastCommon, _HomeAssistantFutureForecast):
70+
templow: float
71+
uv_index: Union[float, None] = None
72+
73+
74+
@dataclass
75+
class HomeAssistantForecast:
76+
current: HomeAssistantCurrentForecast
77+
hourly: List[HomeAssistantHourlyForecast]
78+
daily: List[HomeAssistantDailyForecast]

weather.ha/lib/homeassistant/_sun.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from dataclasses import dataclass
2+
from enum import Enum
3+
4+
5+
class HomeAssistantSunState(Enum):
6+
ABOVE_HORIZON = "above_horizon"
7+
BELOW_HORIZON = "below_horizon"
8+
9+
10+
@dataclass
11+
class HomeAssistantSunInfo:
12+
state: HomeAssistantSunState
13+
next_dawn: str
14+
next_dusk: str
15+
next_midnight: str
16+
next_noon: str
17+
next_rising: str
18+
next_setting: str
19+
elevation: float
20+
azimuth: float
21+
rising: bool
22+
friendly_name: str

weather.ha/lib/kodi/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from ._adapter import KodiWeatherPluginAdapter
2+
from ._forecast import (
3+
KodiConditionCode, KodiCurrentForecastData, KodiDailyForecastData, KodiHourlyForecastData, KodiForecastData,
4+
KodiGeneralForecastData, KodiWindDirectionCode
5+
)
6+
from ._settings import KodiPluginSetting
7+
from ._values import KodiLogLevel

0 commit comments

Comments
 (0)