Skip to content

Commit 81b4362

Browse files
committed
update magtag weather
1 parent a74bbe9 commit 81b4362

File tree

8 files changed

+355
-0
lines changed

8 files changed

+355
-0
lines changed
File renamed without changes.

MagTag_Weather/forecast/code.py

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

0 commit comments

Comments
 (0)