Skip to content

Commit 3670b48

Browse files
committed
Merge branch 'hnnweb_smhi'
2 parents 99226cd + e50fef1 commit 3670b48

5 files changed

Lines changed: 159 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 2023-02-04
2+
* Add SMHI (Sweden) as weather provider. Added by [hnnweb](https://github.com/mendhak/waveshare-epaper-display/pull/51)
3+
14
## 2023-01-29
25
* Add code to display on the [7.5 inch B version 2 screen](https://www.waveshare.com/product/displays/e-paper/epaper-1/7.5inch-e-paper-hat-b.htm)
36

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ The screen will display date, time, weather icon with high and low, and calendar
1818
- [Weather.gov (US)](#weathergov-us)
1919
- [Climacell (tomorrow.io)](#climacell-tomorrowio)
2020
- [VisualCrossing](#visualcrossing)
21+
- [SMHI (Sweden)](#smhi-sweden)
2122
- [Pick a severe weather warning provider](#pick-a-severe-weather-warning-provider)
2223
- [Met Office (UK)](#met-office-uk-1)
2324
- [Weather.gov (US)](#weathergov-us-1)
@@ -177,7 +178,11 @@ Register on [VisualCrossing](https://www.visualcrossing.com/). Under Account Det
177178

178179
export VISUALCROSSING_APIKEY=XXXXXXXXXXXXXXXXXXXXXX
179180

181+
### SMHI (Sweden)
180182

183+
SMHI requires you to identify yourself. Just set your own email,
184+
185+
export SMHI_SELF_IDENTIFICATION=you@example.com
181186

182187
## Pick a severe weather warning provider
183188

env.sh.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
# export WEATHER_MET_EIREANN=1
1616
# Or, weather.gov self identification
1717
# export WEATHERGOV_SELF_IDENTIFICATION=you@example.com
18+
# Or, SMHI self identification
19+
# export SMHI_SELF_IDENTIFICATION=you@example.com
1820

1921
# Your latitude and longitude to pass to weather providers
2022
export WEATHER_LATITUDE=51.5077

screen-weather-get.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sys
55
import os
66
import logging
7-
from weather_providers import climacell, openweathermap, metofficedatahub, metno, meteireann, accuweather, visualcrossing, weathergov
7+
from weather_providers import climacell, openweathermap, metofficedatahub, metno, meteireann, accuweather, visualcrossing, weathergov, smhi
88
from alert_providers import metofficerssfeed, weathergovalerts
99
from alert_providers import meteireann as meteireannalertprovider
1010
from utility import update_svg, configure_logging
@@ -24,6 +24,7 @@ def format_weather_description(weather_description):
2424
weather_dict[2] = splits[1] if len(splits) > 1 else ''
2525
return weather_dict
2626

27+
2728
def get_weather(location_lat, location_long, units):
2829

2930
# gather relevant environment configs
@@ -37,6 +38,7 @@ def get_weather(location_lat, location_long, units):
3738
visualcrossing_apikey = os.getenv("VISUALCROSSING_APIKEY")
3839
use_met_eireann = os.getenv("WEATHER_MET_EIREANN")
3940
weathergov_self_id = os.getenv("WEATHERGOV_SELF_IDENTIFICATION")
41+
smhi_self_id = os.getenv("SMHI_SELF_IDENTIFICATION")
4042

4143
if (
4244
not climacell_apikey
@@ -47,6 +49,7 @@ def get_weather(location_lat, location_long, units):
4749
and not visualcrossing_apikey
4850
and not use_met_eireann
4951
and not weathergov_self_id
52+
and not smhi_self_id
5053
):
5154
logging.error("No weather provider has been configured (Climacell, OpenWeatherMap, Weather.gov, MetOffice, AccuWeather, Met.no, Met Eireann, VisualCrossing...)")
5255
sys.exit(1)
@@ -93,13 +96,19 @@ def get_weather(location_lat, location_long, units):
9396
logging.info("Getting weather from Climacell")
9497
weather_provider = climacell.Climacell(climacell_apikey, location_lat, location_long, units)
9598

99+
elif smhi_self_id:
100+
logging.info("Getting weather from SMHI")
101+
weather_provider = smhi.SMHI(smhi_self_id, location_lat, location_long, units)
102+
96103
weather = weather_provider.get_weather()
97104
logging.info("weather - {}".format(weather))
98105
return weather
99106

107+
100108
def format_alert_description(alert_message):
101109
return html.escape(alert_message)
102110

111+
103112
def get_alert_message(location_lat, location_long):
104113
alert_message = ""
105114
alert_metoffice_feed_url = os.getenv("ALERT_METOFFICE_FEED_URL")
@@ -149,7 +158,7 @@ def main():
149158

150159
alert_message = get_alert_message(location_lat, location_long)
151160
alert_message = format_alert_description(alert_message)
152-
161+
153162
output_dict = {
154163
'LOW_ONE': "{}{}".format(str(round(weather['temperatureMin'])), degrees),
155164
'HIGH_ONE': "{}{}".format(str(round(weather['temperatureMax'])), degrees),
@@ -167,7 +176,7 @@ def main():
167176
logging.debug("main() - {}".format(output_dict))
168177

169178
logging.info("Updating SVG")
170-
179+
171180
template_svg_filename = f'screen-template.{template_name}.svg'
172181
output_svg_filename = 'screen-output-weather.svg'
173182
update_svg(template_svg_filename, output_svg_filename, output_dict)

weather_providers/smhi.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import logging
2+
from weather_providers.base_provider import BaseWeatherProvider
3+
4+
5+
class SMHI(BaseWeatherProvider):
6+
def __init__(self, smhi_self_id, location_lat, location_long, units):
7+
self.smhi_self_id = smhi_self_id
8+
self.location_lat = location_lat
9+
self.location_long = location_long
10+
self.units = units
11+
12+
# Map SMHI icons to local icons
13+
# Reference: https://opendata.smhi.se/apidocs/metfcst/parameters.html#parameter-wsymb
14+
def get_icon_from_smhi_weathercode(self, weathercode, is_daytime):
15+
16+
icon_dict = {
17+
1: "clear_sky_day" if is_daytime else "clearnight", # Clear sky
18+
2: "clear_sky_day" if is_daytime else "clearnight", # Nearly clear sky
19+
3: "few_clouds" if is_daytime else "partlycloudynight", # Variable cloudiness
20+
4: "scattered_clouds" if is_daytime else "partlycloudynight", # Halfclear sky
21+
5: "mostly_cloudy" if is_daytime else "mostly_cloudy_night", # Cloudy sky
22+
6: "overcast", # Overcast
23+
7: "climacell_fog", # Fog
24+
8: 'climacell_rain_light' if is_daytime else 'rain_night_light', # Light rain showers
25+
9: "climacell_rain" if is_daytime else "rain_night", # Moderate rain showers
26+
10: "climacell_rain_heavy" if is_daytime else "rain_night_heavy", # Heavy rain showers
27+
11: "thundershower_rain", # Thunderstorm
28+
12: "sleet", # Light sleet showers
29+
13: "sleet", # Moderate sleet showers
30+
14: "sleet", # Heavy sleet showers
31+
15: "climacell_snow_light", # Light snow showers
32+
16: "snow", # Moderate snow showers
33+
17: "snow", # Heavy snow showers
34+
18: 'climacell_rain_light' if is_daytime else 'rain_night_light', # Light rain
35+
19: "climacell_rain" if is_daytime else "rain_night", # Moderate rain
36+
20: "climacell_rain_heavy" if is_daytime else "rain_night_heavy", # Heavy rain
37+
21: "thundershower_rain", # Thunder
38+
22: "sleet", # Light sleet
39+
23: "sleet", # Moderate sleet
40+
24: "sleet", # Heavy sleet
41+
25: "climacell_snow_light", # Light snowfall
42+
26: "snow", # Moderate snowfall
43+
27: "snow", # Heavy snowfall
44+
}
45+
46+
icon = icon_dict[weathercode]
47+
logging.debug(
48+
"get_icon_by_weathercode({}) - {}"
49+
.format(weathercode, icon))
50+
51+
return icon
52+
53+
def get_description_from_smhi_weathercode(self, weathercode):
54+
description_dict = {
55+
1: "Clear sky",
56+
2: "Nearly clear sky",
57+
3: "Variable cloudiness",
58+
4: "Halfclear sky",
59+
5: "Cloudy sky",
60+
6: "Overcast",
61+
7: "Fog",
62+
8: "Light rain showers",
63+
9: "Moderate rain showers",
64+
10: "Heavy rain showers",
65+
11: "Thunderstorm",
66+
12: "Light sleet showers",
67+
13: "Moderate sleet showers",
68+
14: "Heavy sleet showers",
69+
15: "Light snow showers",
70+
16: "Moderate snow showers",
71+
17: "Heavy snow showers",
72+
18: "Light rain",
73+
19: "Moderate rain",
74+
20: "Heavy rain",
75+
21: "Thunder",
76+
22: "Light sleet",
77+
23: "Moderate sleet",
78+
24: "Heavy sleet",
79+
25: "Light snowfall",
80+
26: "Moderate snowfall",
81+
27: "Heavy snowfall",
82+
}
83+
description = description_dict[weathercode]
84+
85+
logging.debug(
86+
"get_description_by_weathercode({}) - {}"
87+
.format(weathercode, description))
88+
89+
return description.title()
90+
91+
# Get weather from SMHI API
92+
# https://opendata.smhi.se/apidocs/metfcst/get-forecast.html#get-point-forecast
93+
# The API response is a complete forecast approximately 10 days ahead of the latest current forecast.
94+
# All times in the answer given in UTC.
95+
# Precipitation parameters have a distribution in time (a time interval) until the valid time for current data.
96+
# The interval starts at the time step before. At the beginning of the forecast, the interval is one hour.
97+
# Later in the forecast, the time interval increases (eg 3, 6 and 12 h). Unit remains mm / h.
98+
# So, current hour is index 0, next hour is index 1
99+
# The API accepts 6 decimals in the lon/lat. With more decimals, it returns 404.
100+
101+
def get_weather(self):
102+
103+
url = ("https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/{}/lat/{}/data.json"
104+
.format(self.location_long, self.location_lat))
105+
106+
headers = {"User-Agent": self.smhi_self_id}
107+
108+
response_data = self.get_response_json(url, headers=headers)
109+
logging.debug(response_data)
110+
weather_data = response_data["timeSeries"]
111+
logging.debug("get_weather() - {}".format(weather_data))
112+
113+
# Get the weather code of the first item.
114+
for data in weather_data[0]["parameters"]:
115+
if data["name"] == "Wsymb2":
116+
weather_code = data["values"][0]
117+
118+
daytime = self.is_daytime(self.location_lat, self.location_long)
119+
120+
# { "temperatureMin": "2.0", "temperatureMax": "15.1", "icon": "mostly_cloudy", "description": "Cloudy with light breezes" }
121+
# No Min or Max here. We just get the estimated temperature for the hour.
122+
# Since we get the forecast for several hours and days, get the min/max for the next 12 hours?
123+
weather = {}
124+
temp_list = []
125+
126+
for item in range(0, 12):
127+
for param in weather_data[item]['parameters']:
128+
if param['name'] == 't':
129+
temp = param['values'][0]
130+
temp_list.append(temp)
131+
132+
weather["temperatureMin"] = min(temp_list)
133+
weather["temperatureMax"] = max(temp_list)
134+
weather["icon"] = self.get_icon_from_smhi_weathercode(weather_code, daytime)
135+
weather["description"] = self.get_description_from_smhi_weathercode(weather_code)
136+
logging.debug(weather)
137+
return weather

0 commit comments

Comments
 (0)