Skip to content

Commit 9c59432

Browse files
authored
Merge branch 'master' into master
2 parents f9451c2 + b8916c2 commit 9c59432

File tree

10 files changed

+793
-0
lines changed

10 files changed

+793
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# SPDX-FileCopyrightText: 2021 Brent Rubell, written for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
import ssl
5+
import board
6+
import wifi
7+
import socketpool
8+
import adafruit_requests as requests
9+
from adafruit_oauth2 import OAuth2
10+
from adafruit_display_text.label import Label
11+
from adafruit_bitmap_font import bitmap_font
12+
from adafruit_magtag.magtag import Graphics
13+
from adafruit_display_shapes.rect import Rect
14+
15+
# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and
16+
# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other
17+
# source control.
18+
# pylint: disable=no-name-in-module,wrong-import-order
19+
try:
20+
from secrets import secrets
21+
except ImportError:
22+
print("Credentials and tokens are kept in secrets.py, please add them there!")
23+
raise
24+
25+
print("Connecting to %s" % secrets["ssid"])
26+
wifi.radio.connect(secrets["ssid"], secrets["password"])
27+
print("Connected to %s!" % secrets["ssid"])
28+
29+
pool = socketpool.SocketPool(wifi.radio)
30+
requests = requests.Session(pool, ssl.create_default_context())
31+
32+
# DisplayIO setup
33+
font_small = bitmap_font.load_font("/fonts/Arial-12.pcf")
34+
font_large = bitmap_font.load_font("/fonts/Arial-14.pcf")
35+
36+
graphics = Graphics(auto_refresh=False)
37+
display = graphics.display
38+
39+
background = Rect(0, 0, 296, 128, fill=0xFFFFFF)
40+
graphics.splash.append(background)
41+
42+
label_overview_text = Label(
43+
font_large,
44+
x=0,
45+
y=10,
46+
line_spacing=0.75,
47+
color=0x000000,
48+
text="Authorize this device with Google:",
49+
)
50+
graphics.splash.append(label_overview_text)
51+
52+
label_verification_url = Label(
53+
font_small, x=0, y=40, line_spacing=0.75, color=0x000000, max_glyphs=90
54+
)
55+
graphics.splash.append(label_verification_url)
56+
57+
label_user_code = Label(
58+
font_small, x=0, y=80, color=0x000000, line_spacing=0.75, max_glyphs=50
59+
)
60+
graphics.splash.append(label_user_code)
61+
62+
label_qr_code = Label(
63+
font_small, x=0, y=100, color=0x000000, text="Or scan the QR code:"
64+
)
65+
graphics.splash.append(label_qr_code)
66+
67+
# Set scope(s) of access required by the API you're using
68+
scopes = ["https://www.googleapis.com/auth/calendar.readonly"]
69+
70+
# Initialize an OAuth2 object
71+
google_auth = OAuth2(
72+
requests, secrets["google_client_id"], secrets["google_client_secret"], scopes
73+
)
74+
75+
# Request device and user codes
76+
# https://developers.google.com/identity/protocols/oauth2/limited-input-device#step-1:-request-device-and-user-codes
77+
google_auth.request_codes()
78+
79+
# Display user code and verification url
80+
# NOTE: If you are displaying this on a screen, ensure the text label fields are
81+
# long enough to handle the user_code and verification_url.
82+
# Details in link below:
83+
# https://developers.google.com/identity/protocols/oauth2/limited-input-device#displayingthecode
84+
print(
85+
"1) Navigate to the following URL in a web browser:", google_auth.verification_url
86+
)
87+
print("2) Enter the following code:", google_auth.user_code)
88+
label_verification_url.text = (
89+
"1. On your computer or mobile device,\ngo to %s" % google_auth.verification_url
90+
)
91+
label_user_code.text = "2. Enter code: %s" % google_auth.user_code
92+
93+
graphics.qrcode(google_auth.verification_url.encode(), qr_size=2, x=240, y=70)
94+
board.DISPLAY.show(graphics.splash)
95+
display.refresh()
96+
97+
# Poll Google's authorization server
98+
print("Waiting for browser authorization...")
99+
if not google_auth.wait_for_authorization():
100+
raise RuntimeError("Timed out waiting for browser response!")
101+
102+
print("Successfully Authenticated with Google!")
103+
print("Add the following lines to your secrets.py file:")
104+
print("\t'google_access_token' " + ":" + " '%s'," % google_auth.access_token)
105+
print("\t'google_refresh_token' " + ":" + " '%s'" % google_auth.refresh_token)
106+
107+
graphics.splash.pop()
108+
graphics.splash.pop()
109+
graphics.splash.pop()
110+
111+
label_overview_text.text = "Successfully Authenticated!"
112+
label_verification_url.text = (
113+
"Check the REPL for tokens to add\n\tto your secrets.py file"
114+
)
115+
display.refresh()

MagTag_Google_Calendar/code.py

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# SPDX-FileCopyrightText: 2021 Brent Rubell, written for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
import time
5+
import ssl
6+
import board
7+
import rtc
8+
import wifi
9+
import socketpool
10+
import adafruit_requests as requests
11+
from adafruit_oauth2 import OAuth2
12+
from adafruit_display_shapes.line import Line
13+
from adafruit_bitmap_font import bitmap_font
14+
from adafruit_display_text import label
15+
from adafruit_magtag.magtag import MagTag
16+
17+
# Calendar ID
18+
CALENDAR_ID = "[email protected]"
19+
20+
# Maximum amount of events to display
21+
MAX_EVENTS = 3
22+
23+
# Amount of time to wait between refreshing the calendar, in minutes
24+
REFRESH_TIME = 15
25+
26+
# Dict. of month names for pretty-printing the header
27+
MONTHS = {
28+
1: "Jan",
29+
2: "Feb",
30+
3: "Mar",
31+
4: "Apr",
32+
5: "May",
33+
6: "Jun",
34+
7: "Jul",
35+
8: "Aug",
36+
9: "Sep",
37+
10: "Oct",
38+
11: "Nov",
39+
12: "Dec",
40+
}
41+
42+
# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and
43+
# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other
44+
# source control.
45+
# pylint: disable=no-name-in-module,wrong-import-order
46+
try:
47+
from secrets import secrets
48+
except ImportError:
49+
print("Credentials and tokens are kept in secrets.py, please add them there!")
50+
raise
51+
52+
print("Connecting to %s" % secrets["ssid"])
53+
wifi.radio.connect(secrets["ssid"], secrets["password"])
54+
print("Connected to %s!" % secrets["ssid"])
55+
56+
pool = socketpool.SocketPool(wifi.radio)
57+
requests = requests.Session(pool, ssl.create_default_context())
58+
59+
# Initialize an OAuth2 object with GCal API scope
60+
scopes = ["https://www.googleapis.com/auth/calendar.readonly"]
61+
google_auth = OAuth2(
62+
requests,
63+
secrets["google_client_id"],
64+
secrets["google_client_secret"],
65+
scopes,
66+
secrets["google_access_token"],
67+
secrets["google_refresh_token"],
68+
)
69+
70+
71+
def get_current_time(time_max=False):
72+
"""Gets local time from Adafruit IO and converts to RFC3339 timestamp."""
73+
# Get local time from Adafruit IO
74+
magtag.get_local_time(secrets["timezone"])
75+
# Format as RFC339 timestamp
76+
cur_time = r.datetime
77+
if time_max: # maximum time to fetch events is midnight (4:59:59UTC)
78+
cur_time_max = time.struct_time(
79+
cur_time[0], cur_time[1], cur_time[2] + 1, 4, 59, 59, 0, -1, -1
80+
)
81+
cur_time = cur_time_max
82+
cur_time = "{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}{:s}".format(
83+
cur_time[0],
84+
cur_time[1],
85+
cur_time[2],
86+
cur_time[3],
87+
cur_time[4],
88+
cur_time[5],
89+
"Z",
90+
)
91+
return cur_time
92+
93+
94+
def get_calendar_events(calendar_id, max_events, time_min):
95+
"""Returns events on a specified calendar.
96+
Response is a list of events ordered by their start date/time in ascending order.
97+
"""
98+
time_max = get_current_time(time_max=True)
99+
print("Fetching calendar events from {0} to {1}".format(time_min, time_max))
100+
101+
headers = {
102+
"Authorization": "Bearer " + google_auth.access_token,
103+
"Accept": "application/json",
104+
"Content-Length": "0",
105+
}
106+
url = (
107+
"https://www.googleapis.com/calendar/v3/calendars/{0}"
108+
"/events?maxResults={1}&timeMin={2}&timeMax={3}&orderBy=startTime"
109+
"&singleEvents=true".format(calendar_id, max_events, time_min, time_max)
110+
)
111+
resp = requests.get(url, headers=headers)
112+
resp_json = resp.json()
113+
if "error" in resp_json:
114+
raise RuntimeError("Error:", resp_json)
115+
resp.close()
116+
# parse the 'items' array so we can iterate over it easier
117+
items = []
118+
resp_items = resp_json["items"]
119+
if not resp_items:
120+
print("No events scheduled for today!")
121+
for event in range(0, len(resp_items)):
122+
items.append(resp_items[event])
123+
return items
124+
125+
126+
def format_datetime(datetime, pretty_date=False):
127+
"""Formats ISO-formatted datetime returned by Google Calendar API into
128+
a struct_time.
129+
:param str datetime: Datetime string returned by Google Calendar API
130+
:return: struct_time
131+
132+
"""
133+
times = datetime.split("T")
134+
the_date = times[0]
135+
the_time = times[1]
136+
year, month, mday = [int(x) for x in the_date.split("-")]
137+
the_time = the_time.split("-")[0]
138+
if "Z" in the_time:
139+
the_time = the_time.split("Z")[0]
140+
hours, minutes, _ = [int(x) for x in the_time.split(":")]
141+
am_pm = "am"
142+
if hours >= 12:
143+
am_pm = "pm"
144+
# convert to 12hr time
145+
if not hours == 12:
146+
hours -= 12
147+
# via https://github.com/micropython/micropython/issues/3087
148+
formatted_time = "{:02d}:{:02d}{:s}".format(hours, minutes, am_pm)
149+
if pretty_date: # return a nice date for header label
150+
formatted_date = "{}. {:02d}, {:04d} ".format(MONTHS[month], mday, year)
151+
return formatted_date
152+
# Event occurs today, return the time only
153+
return formatted_time
154+
155+
156+
def display_calendar_events(resp_events):
157+
# Display all calendar events
158+
for event_idx in range(len(resp_events)):
159+
event = resp_events[event_idx]
160+
# wrap event name around second line if necessary
161+
event_name = magtag.wrap_nicely(event["summary"], 25)
162+
event_name = "\n".join(event_name[0:2]) # only wrap 2 lines, truncate third..
163+
event_start = event["start"]["dateTime"]
164+
print("-" * 40)
165+
print("Event Description: ", event_name)
166+
print("Event Time:", format_datetime(event_start))
167+
print("-" * 40)
168+
# Generate labels holding event info
169+
label_event_time = label.Label(
170+
font_event,
171+
x=7,
172+
y=35 + (event_idx * 35),
173+
color=0x000000,
174+
text=format_datetime(event_start),
175+
)
176+
magtag.splash.append(label_event_time)
177+
178+
label_event_desc = label.Label(
179+
font_event,
180+
x=88,
181+
y=35 + (event_idx * 35),
182+
color=0x000000,
183+
text=event_name,
184+
line_spacing=0.65,
185+
)
186+
magtag.splash.append(label_event_desc)
187+
188+
189+
# Create a new MagTag object
190+
magtag = MagTag()
191+
r = rtc.RTC()
192+
193+
# DisplayIO Setup
194+
magtag.set_background(0xFFFFFF)
195+
196+
# Add the header
197+
line_header = Line(0, 25, 320, 25, color=0x000000)
198+
magtag.splash.append(line_header)
199+
200+
font_h1 = bitmap_font.load_font("fonts/Arial-Bold-18.pcf")
201+
font_h1.load_glyphs(
202+
b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-,. "
203+
)
204+
label_header = label.Label(
205+
font_h1, x=(board.DISPLAY.width // 5) + 1, y=10, color=0x000000, max_glyphs=13
206+
)
207+
magtag.splash.append(label_header)
208+
209+
# Set up calendar event fonts
210+
font_event = bitmap_font.load_font("fonts/Arial-12.pcf")
211+
font_event.load_glyphs(
212+
b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-:/ ()"
213+
)
214+
215+
if not google_auth.refresh_access_token():
216+
raise RuntimeError("Unable to refresh access token - has the token been revoked?")
217+
access_token_obtained = int(time.monotonic())
218+
219+
while True:
220+
# check if we need to refresh token
221+
if (
222+
int(time.monotonic()) - access_token_obtained
223+
>= google_auth.access_token_expiration
224+
):
225+
print("Access token expired, refreshing...")
226+
if not google_auth.refresh_access_token():
227+
raise RuntimeError(
228+
"Unable to refresh access token - has the token been revoked?"
229+
)
230+
access_token_obtained = int(time.monotonic())
231+
232+
# fetch calendar events!
233+
print("fetching local time...")
234+
now = get_current_time()
235+
236+
# setup header label
237+
label_header.text = format_datetime(now, pretty_date=True)
238+
239+
print("fetching calendar events...")
240+
events = get_calendar_events(CALENDAR_ID, MAX_EVENTS, now)
241+
242+
print("displaying events")
243+
display_calendar_events(events)
244+
245+
board.DISPLAY.show(magtag.splash)
246+
board.DISPLAY.refresh()
247+
248+
print("Sleeping for %d minutes" % REFRESH_TIME)
249+
magtag.exit_and_deep_sleep(REFRESH_TIME * 60)
218 KB
Binary file not shown.
228 KB
Binary file not shown.
248 KB
Binary file not shown.

0 commit comments

Comments
 (0)