Skip to content

Commit 93439d9

Browse files
authored
Merge pull request #2290 from caternuson/iss2266_magtag_weather
Update for MagTag Weather - Add new free API code version
2 parents 3201302 + 049ad7b commit 93439d9

File tree

8 files changed

+357
-0
lines changed

8 files changed

+357
-0
lines changed
File renamed without changes.

MagTag_Weather/forecast/code.py

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
# pylint: disable=redefined-outer-name, eval-used, wrong-import-order
5+
6+
import time
7+
import terminalio
8+
import displayio
9+
import adafruit_imageload
10+
from adafruit_display_text import label
11+
from adafruit_magtag.magtag import MagTag
12+
from secrets import secrets
13+
14+
# --| USER CONFIG |--------------------------
15+
METRIC = False # set to True for metric units
16+
# -------------------------------------------
17+
18+
# ----------------------------
19+
# Define various assets
20+
# ----------------------------
21+
BACKGROUND_BMP = "/bmps/weather_bg.bmp"
22+
ICONS_LARGE_FILE = "/bmps/weather_icons_70px.bmp"
23+
ICONS_SMALL_FILE = "/bmps/weather_icons_20px.bmp"
24+
ICON_MAP = ("01", "02", "03", "04", "09", "10", "11", "13", "50")
25+
DAYS = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
26+
MONTHS = (
27+
"January",
28+
"February",
29+
"March",
30+
"April",
31+
"May",
32+
"June",
33+
"July",
34+
"August",
35+
"September",
36+
"October",
37+
"November",
38+
"December",
39+
)
40+
magtag = MagTag()
41+
42+
# ----------------------------
43+
# Backgrounnd bitmap
44+
# ----------------------------
45+
magtag.graphics.set_background(BACKGROUND_BMP)
46+
47+
# ----------------------------
48+
# Weather icons sprite sheet
49+
# ----------------------------
50+
icons_large_bmp, icons_large_pal = adafruit_imageload.load(ICONS_LARGE_FILE)
51+
icons_small_bmp, icons_small_pal = adafruit_imageload.load(ICONS_SMALL_FILE)
52+
53+
# /////////////////////////////////////////////////////////////////////////
54+
55+
56+
def get_data_source_url(api="forecast", location=None):
57+
"""Build and return the URL for the OpenWeather API."""
58+
if location is None:
59+
raise ValueError("Must specify location.")
60+
if api.upper() == "GEO":
61+
URL = "https://api.openweathermap.org/geo/1.0/direct?q="
62+
URL += location
63+
elif api.upper() == "GEOREV":
64+
URL = "https://api.openweathermap.org/geo/1.0/reverse?limit=1"
65+
URL += "&lat={}".format(location[0])
66+
URL += "&lon={}".format(location[1])
67+
elif api.upper() == "FORECAST":
68+
URL = "https://api.openweathermap.org/data/2.5/forecast?"
69+
URL += "&lat={}".format(location[0])
70+
URL += "&lon={}".format(location[1])
71+
elif api.upper() == "CURRENT":
72+
URL = "https://api.openweathermap.org/data/2.5/weather?"
73+
URL += "&lat={}".format(location[0])
74+
URL += "&lon={}".format(location[1])
75+
else:
76+
raise ValueError("Unknown API type: " + api)
77+
return URL + "&appid=" + secrets["openweather_token"]
78+
79+
80+
def get_latlon(city_name):
81+
"""Use the Geolocation API to determine lat/lon for given city."""
82+
magtag.url = get_data_source_url(api="geo", location=city_name)
83+
raw_data = eval(magtag.fetch())[0]
84+
return raw_data["lat"], raw_data["lon"]
85+
86+
87+
def get_city(latlon_location):
88+
"""Use the Geolocation API to determine city for given lat/lon."""
89+
magtag.url = get_data_source_url(api="georev", location=latlon_location)
90+
raw_data = eval(magtag.fetch())[0]
91+
return raw_data["name"] + ", " + raw_data["country"]
92+
93+
94+
def get_approx_time(latlon_location):
95+
magtag.url = get_data_source_url(api="current", location=latlon_location)
96+
raw_data = eval(magtag.fetch())
97+
return raw_data["dt"] + raw_data["timezone"]
98+
99+
100+
def get_forecast(location):
101+
"""Use Forecast API to fetch weather data and return a "daily" forecast.
102+
NOTE: The data query is assumed to have been done at ~2AM local time so that
103+
the forecast data results are (also approx):
104+
0 3AM
105+
1 6AM
106+
2 9AM
107+
3 12PM
108+
4 3PM
109+
5 6PM
110+
6 9PM
111+
7 12AM
112+
: :
113+
(etc.)
114+
"""
115+
resp = magtag.network.fetch(get_data_source_url(api="forecast", location=location))
116+
json_data = resp.json()
117+
# build "daily" forecast data (similar to Onecall API)
118+
# it is assumed the first entry in the list is for ~3AM local
119+
# and the list is 40 entries at 3 hours intervals
120+
# index local_time
121+
# 0 3AM
122+
# 1 6AM
123+
# 2 9AM
124+
# 3 12PM
125+
# 4 3PM
126+
# 5 6PM
127+
# 6 9PM
128+
# 7 12AM
129+
# etc. etc.
130+
if json_data["cnt"] != 40:
131+
raise RuntimeError("Unexpected forecast response length.")
132+
timezone_offset = json_data["city"]["timezone"]
133+
daily_data = []
134+
# use the 12PM values from each day, access via direct indexing (slice)
135+
for data in json_data["list"][3::8]:
136+
daily_data.append(
137+
{
138+
"dt": data["dt"] + timezone_offset,
139+
"weather": data["weather"],
140+
"temp": {"day": data["main"]["temp"]},
141+
}
142+
)
143+
# add extra data for day 0 (current day)
144+
daily_data[0]["sunrise"] = json_data["city"]["sunrise"] + timezone_offset
145+
daily_data[0]["sunset"] = json_data["city"]["sunset"] + timezone_offset
146+
daily_data[0]["temp"] = {
147+
"morn": json_data["list"][2]["main"]["temp"],
148+
"day": json_data["list"][4]["main"]["temp"],
149+
"night": json_data["list"][6]["main"]["temp"],
150+
}
151+
daily_data[0]["humidity"] = json_data["list"][3]["main"]["humidity"]
152+
daily_data[0]["wind_speed"] = json_data["list"][3]["wind"]["speed"]
153+
154+
return daily_data
155+
156+
157+
def make_banner(x=0, y=0):
158+
"""Make a single future forecast info banner group."""
159+
day_of_week = label.Label(terminalio.FONT, text="DAY", color=0x000000)
160+
day_of_week.anchor_point = (0, 0.5)
161+
day_of_week.anchored_position = (0, 10)
162+
163+
icon = displayio.TileGrid(
164+
icons_small_bmp,
165+
pixel_shader=icons_small_pal,
166+
x=25,
167+
y=0,
168+
width=1,
169+
height=1,
170+
tile_width=20,
171+
tile_height=20,
172+
)
173+
174+
day_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
175+
day_temp.anchor_point = (0, 0.5)
176+
day_temp.anchored_position = (50, 10)
177+
178+
group = displayio.Group(x=x, y=y)
179+
group.append(day_of_week)
180+
group.append(icon)
181+
group.append(day_temp)
182+
183+
return group
184+
185+
186+
def temperature_text(tempK):
187+
if METRIC:
188+
return "{:3.0f}C".format(tempK - 273.15)
189+
else:
190+
return "{:3.0f}F".format(32.0 + 1.8 * (tempK - 273.15))
191+
192+
193+
def wind_text(speedms):
194+
if METRIC:
195+
return "{:3.0f}m/s".format(speedms)
196+
else:
197+
return "{:3.0f}mph".format(2.23694 * speedms)
198+
199+
200+
def update_banner(banner, data):
201+
"""Update supplied forecast banner with supplied data."""
202+
banner[0].text = DAYS[time.localtime(data["dt"]).tm_wday][:3].upper()
203+
banner[1][0] = ICON_MAP.index(data["weather"][0]["icon"][:2])
204+
banner[2].text = temperature_text(data["temp"]["day"])
205+
206+
207+
def update_today(data):
208+
"""Update today info banner."""
209+
date = time.localtime(data["dt"])
210+
sunrise = time.localtime(data["sunrise"])
211+
sunset = time.localtime(data["sunset"])
212+
213+
today_date.text = "{} {} {}, {}".format(
214+
DAYS[date.tm_wday].upper(),
215+
MONTHS[date.tm_mon - 1].upper(),
216+
date.tm_mday,
217+
date.tm_year,
218+
)
219+
today_icon[0] = ICON_MAP.index(data["weather"][0]["icon"][:2])
220+
today_morn_temp.text = temperature_text(data["temp"]["morn"])
221+
today_day_temp.text = temperature_text(data["temp"]["day"])
222+
today_night_temp.text = temperature_text(data["temp"]["night"])
223+
today_humidity.text = "{:3d}%".format(data["humidity"])
224+
today_wind.text = wind_text(data["wind_speed"])
225+
today_sunrise.text = "{:2d}:{:02d} AM".format(sunrise.tm_hour, sunrise.tm_min)
226+
today_sunset.text = "{:2d}:{:02d} PM".format(sunset.tm_hour - 12, sunset.tm_min)
227+
228+
229+
def go_to_sleep(current_time):
230+
"""Enter deep sleep for time needed."""
231+
# compute current time offset in seconds
232+
hour, minutes, seconds = time.localtime(current_time)[3:6]
233+
seconds_since_midnight = 60 * (hour * 60 + minutes) + seconds
234+
two_fifteen = (2 * 60 + 15) * 60
235+
# wake up 15 minutes after 2am
236+
seconds_to_sleep = (24 * 60 * 60 - seconds_since_midnight) + two_fifteen
237+
print(
238+
"Sleeping for {} hours, {} minutes".format(
239+
seconds_to_sleep // 3600, (seconds_to_sleep // 60) % 60
240+
)
241+
)
242+
magtag.exit_and_deep_sleep(seconds_to_sleep)
243+
244+
245+
# ===========
246+
# Location
247+
# ===========
248+
if isinstance(secrets["openweather_location"], str):
249+
# Get lat/lon using city name
250+
city = secrets["openweather_location"]
251+
print("Getting lat/lon for city:", city)
252+
latlon = get_latlon(city)
253+
elif isinstance(secrets["openweather_location"], tuple):
254+
# Get city name using lat/lon
255+
latlon = secrets["openweather_location"]
256+
print("Getting city name for lat/lon:", latlon)
257+
city = get_city(latlon)
258+
else:
259+
raise ValueError("Unknown location:", secrets["openweather_location"])
260+
261+
print("City =", city)
262+
print("Lat/Lon = ", latlon)
263+
264+
# ===========
265+
# U I
266+
# ===========
267+
today_date = label.Label(terminalio.FONT, text="?" * 30, color=0x000000)
268+
today_date.anchor_point = (0, 0)
269+
today_date.anchored_position = (15, 13)
270+
271+
city_name = label.Label(terminalio.FONT, text=city, color=0x000000)
272+
city_name.anchor_point = (0, 0)
273+
city_name.anchored_position = (15, 24)
274+
275+
today_icon = displayio.TileGrid(
276+
icons_large_bmp,
277+
pixel_shader=icons_small_pal,
278+
x=10,
279+
y=40,
280+
width=1,
281+
height=1,
282+
tile_width=70,
283+
tile_height=70,
284+
)
285+
286+
today_morn_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
287+
today_morn_temp.anchor_point = (0.5, 0)
288+
today_morn_temp.anchored_position = (118, 59)
289+
290+
today_day_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
291+
today_day_temp.anchor_point = (0.5, 0)
292+
today_day_temp.anchored_position = (149, 59)
293+
294+
today_night_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
295+
today_night_temp.anchor_point = (0.5, 0)
296+
today_night_temp.anchored_position = (180, 59)
297+
298+
today_humidity = label.Label(terminalio.FONT, text="100%", color=0x000000)
299+
today_humidity.anchor_point = (0, 0.5)
300+
today_humidity.anchored_position = (105, 95)
301+
302+
today_wind = label.Label(terminalio.FONT, text="99m/s", color=0x000000)
303+
today_wind.anchor_point = (0, 0.5)
304+
today_wind.anchored_position = (155, 95)
305+
306+
today_sunrise = label.Label(terminalio.FONT, text="12:12 PM", color=0x000000)
307+
today_sunrise.anchor_point = (0, 0.5)
308+
today_sunrise.anchored_position = (45, 117)
309+
310+
today_sunset = label.Label(terminalio.FONT, text="12:12 PM", color=0x000000)
311+
today_sunset.anchor_point = (0, 0.5)
312+
today_sunset.anchored_position = (130, 117)
313+
314+
today_banner = displayio.Group()
315+
today_banner.append(today_date)
316+
today_banner.append(city_name)
317+
today_banner.append(today_icon)
318+
today_banner.append(today_morn_temp)
319+
today_banner.append(today_day_temp)
320+
today_banner.append(today_night_temp)
321+
today_banner.append(today_humidity)
322+
today_banner.append(today_wind)
323+
today_banner.append(today_sunrise)
324+
today_banner.append(today_sunset)
325+
326+
future_banners = [
327+
make_banner(x=210, y=18),
328+
make_banner(x=210, y=39),
329+
make_banner(x=210, y=60),
330+
make_banner(x=210, y=81),
331+
]
332+
333+
magtag.splash.append(today_banner)
334+
for future_banner in future_banners:
335+
magtag.splash.append(future_banner)
336+
337+
# ===========
338+
# M A I N
339+
# ===========
340+
print("Fetching forecast...")
341+
forecast_data = get_forecast(latlon)
342+
343+
print("Updating...")
344+
update_today(forecast_data[0])
345+
for day, forecast in enumerate(forecast_data[1:5]):
346+
update_banner(future_banners[day], forecast)
347+
348+
print("Refreshing...")
349+
time.sleep(magtag.display.time_to_refresh + 1)
350+
magtag.display.refresh()
351+
time.sleep(magtag.display.time_to_refresh + 1)
352+
353+
print("Sleeping...")
354+
go_to_sleep(get_approx_time(latlon))
355+
# entire code will run again after deep sleep cycle
356+
# similar to hitting the reset button
5.13 KB
Binary file not shown.
2.01 KB
Binary file not shown.
22.3 KB
Binary file not shown.

MagTag_Weather/code.py renamed to MagTag_Weather/onecall/code.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
22
#
33
# SPDX-License-Identifier: MIT
4+
# pylint: disable=redefined-outer-name, eval-used, wrong-import-order
45

56
import time
67
import terminalio

0 commit comments

Comments
 (0)