Skip to content

Commit a43b458

Browse files
authored
Merge pull request #2109 from makermelissa/main
Updated PyPortal Google Calendar example to make better use of PyPort…
2 parents c691fe9 + 3e3b0d0 commit a43b458

File tree

2 files changed

+67
-148
lines changed

2 files changed

+67
-148
lines changed

PyPortal_Google_Calendar/authenticator/code.py

Lines changed: 17 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
# SPDX-FileCopyrightText: 2021 Brent Rubell, written for Adafruit Industries
22
#
33
# SPDX-License-Identifier: Unlicense
4-
import board
5-
import busio
6-
from digitalio import DigitalInOut
7-
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
8-
from adafruit_esp32spi import adafruit_esp32spi
9-
import adafruit_requests as requests
10-
import displayio
114
from adafruit_display_text.label import Label
125
from adafruit_bitmap_font import bitmap_font
13-
import adafruit_miniqr
146
from adafruit_oauth2 import OAuth2
15-
7+
from adafruit_pyportal import Network, Graphics
168

179
# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and
1810
# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other
@@ -24,25 +16,10 @@
2416
print("WiFi secrets are kept in secrets.py, please add them there!")
2517
raise
2618

27-
esp32_cs = DigitalInOut(board.ESP_CS)
28-
esp32_ready = DigitalInOut(board.ESP_BUSY)
29-
esp32_reset = DigitalInOut(board.ESP_RESET)
30-
31-
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
32-
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
33-
34-
print("Connecting to AP...")
35-
while not esp.is_connected:
36-
try:
37-
esp.connect_AP(secrets["ssid"], secrets["password"])
38-
except RuntimeError as e:
39-
print("could not connect to AP, retrying: ", e)
40-
continue
41-
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
19+
network = Network()
20+
network.connect()
4221

43-
# Initialize a requests object with a socket and esp32spi interface
44-
socket.set_interface(esp)
45-
requests.set_socket(socket, esp)
22+
graphics = Graphics()
4623

4724
# DisplayIO Setup
4825
# Set up fonts
@@ -53,50 +30,31 @@
5330
font_small.load_glyphs(glyphs)
5431
font_large.load_glyphs(glyphs)
5532

56-
group_verification = displayio.Group()
5733
label_overview_text = Label(
5834
font_large, x=0, y=45, text="To authorize this device with Google:"
5935
)
60-
group_verification.append(label_overview_text)
36+
graphics.splash.append(label_overview_text)
6137

6238
label_verification_url = Label(font_small, x=0, y=100, line_spacing=1)
63-
group_verification.append(label_verification_url)
39+
graphics.splash.append(label_verification_url)
6440

6541
label_user_code = Label(font_small, x=0, y=150)
66-
group_verification.append(label_user_code)
42+
graphics.splash.append(label_user_code)
6743

6844
label_qr_code = Label(font_small, x=0, y=190, text="Or scan the QR code:")
69-
group_verification.append(label_qr_code)
70-
71-
72-
### helper methods ###
73-
def bitmap_QR(matrix):
74-
# monochome (2 color) palette
75-
BORDER_PIXELS = 2
76-
77-
# bitmap the size of the screen, monochrome (2 colors)
78-
bitmap = displayio.Bitmap(
79-
matrix.width + 2 * BORDER_PIXELS, matrix.height + 2 * BORDER_PIXELS, 2
80-
)
81-
# raster the QR code
82-
for y in range(matrix.height): # each scanline in the height
83-
for x in range(matrix.width):
84-
if matrix[x, y]:
85-
bitmap[x + BORDER_PIXELS, y + BORDER_PIXELS] = 1
86-
else:
87-
bitmap[x + BORDER_PIXELS, y + BORDER_PIXELS] = 0
88-
return bitmap
89-
45+
graphics.splash.append(label_qr_code)
9046

9147
# Set scope(s) of access required by the API you're using
9248
scopes = ["https://www.googleapis.com/auth/calendar.readonly"]
9349

9450
# Initialize an oauth2 object
9551
google_auth = OAuth2(
96-
requests, secrets["google_client_id"], secrets["google_client_secret"], scopes
52+
network.requests,
53+
secrets["google_client_id"],
54+
secrets["google_client_secret"],
55+
scopes,
9756
)
9857

99-
10058
# Request device and user codes
10159
# https://developers.google.com/identity/protocols/oauth2/limited-input-device#step-1:-request-device-and-user-codes
10260
google_auth.request_codes()
@@ -119,24 +77,8 @@ def bitmap_QR(matrix):
11977
label_user_code.text = "2. Enter code: %s" % google_auth.user_code
12078

12179
# Create a QR code
122-
qr = adafruit_miniqr.QRCode(qr_type=3, error_correct=adafruit_miniqr.L)
123-
qr.add_data(google_auth.verification_url.encode())
124-
qr.make()
125-
126-
# generate the 1-pixel-per-bit bitmap
127-
qr_bitmap = bitmap_QR(qr.matrix)
128-
# we'll draw with a classic black/white palette
129-
palette = displayio.Palette(2)
130-
palette[0] = 0xFFFFFF
131-
palette[1] = 0x000000
132-
# we'll scale the QR code as big as the display can handle
133-
scale = 15
134-
# then center it!
135-
qr_img = displayio.TileGrid(qr_bitmap, pixel_shader=palette, x=170, y=165)
136-
group_verification.append(qr_img)
137-
# show the group
138-
board.DISPLAY.show(group_verification)
139-
80+
graphics.qrcode(google_auth.verification_url.encode(), qr_size=2, x=170, y=165)
81+
graphics.display.show(graphics.splash)
14082

14183
# Poll Google's authorization server
14284
print("Waiting for browser authorization...")
@@ -150,9 +92,9 @@ def bitmap_QR(matrix):
15092
print("\t'google_access_token' " + ":" + " '%s'," % google_auth.access_token)
15193
print("\t'google_refresh_token' " + ":" + " '%s'" % google_auth.refresh_token)
15294
# Remove QR code and code/verification labels
153-
group_verification.pop()
154-
group_verification.pop()
155-
group_verification.pop()
95+
graphics.splash.pop()
96+
graphics.splash.pop()
97+
graphics.splash.pop()
15698

15799
label_overview_text.text = "Successfully Authenticated!"
158100
label_verification_url.text = (

PyPortal_Google_Calendar/code.py

Lines changed: 50 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,8 @@
22
#
33
# SPDX-License-Identifier: Unlicense
44
import time
5-
import board
6-
import busio
7-
from digitalio import DigitalInOut
8-
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
9-
from adafruit_esp32spi import adafruit_esp32spi
10-
import adafruit_requests as requests
115
from adafruit_oauth2 import OAuth2
126
from adafruit_display_shapes.line import Line
13-
from adafruit_bitmap_font import bitmap_font
14-
from adafruit_display_text import label
157
from adafruit_pyportal import PyPortal
168
import rtc
179

@@ -60,35 +52,17 @@
6052
print("WiFi secrets are kept in secrets.py, please add them there!")
6153
raise
6254

63-
# If you are using a board with pre-defined ESP32 Pins:
64-
esp32_cs = DigitalInOut(board.ESP_CS)
65-
esp32_ready = DigitalInOut(board.ESP_BUSY)
66-
esp32_reset = DigitalInOut(board.ESP_RESET)
67-
68-
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
69-
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
70-
71-
print("Connecting to AP...")
72-
while not esp.is_connected:
73-
try:
74-
esp.connect_AP(secrets["ssid"], secrets["password"])
75-
except RuntimeError as e:
76-
print("could not connect to AP, retrying: ", e)
77-
continue
78-
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
79-
8055
# Create the PyPortal object
81-
pyportal = PyPortal(esp=esp, external_spi=spi)
56+
pyportal = PyPortal()
8257
r = rtc.RTC()
8358

84-
# Initialize a requests object with a socket and esp32spi interface
85-
socket.set_interface(esp)
86-
requests.set_socket(socket, esp)
59+
pyportal.network.connect()
60+
8761

8862
# Initialize an OAuth2 object with GCal API scope
8963
scopes = ["https://www.googleapis.com/auth/calendar.readonly"]
9064
google_auth = OAuth2(
91-
requests,
65+
pyportal.network.requests,
9266
secrets["google_client_id"],
9367
secrets["google_client_secret"],
9468
scopes,
@@ -105,15 +79,17 @@ def get_current_time(time_max=False):
10579
cur_time = r.datetime
10680
if time_max: # maximum time to fetch events is midnight (4:59:59UTC)
10781
cur_time_max = time.struct_time(
108-
cur_time[0],
109-
cur_time[1],
110-
cur_time[2] + 1,
111-
4,
112-
59,
113-
59,
114-
cur_time[6],
115-
cur_time[7],
116-
cur_time[8],
82+
(
83+
cur_time[0],
84+
cur_time[1],
85+
cur_time[2] + 1,
86+
4,
87+
59,
88+
59,
89+
cur_time[6],
90+
cur_time[7],
91+
cur_time[8],
92+
)
11793
)
11894
cur_time = cur_time_max
11995
cur_time = "{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}{:s}".format(
@@ -145,7 +121,7 @@ def get_calendar_events(calendar_id, max_events, time_min):
145121
"/events?maxResults={1}&timeMin={2}&timeMax={3}&orderBy=startTime"
146122
"&singleEvents=true".format(calendar_id, max_events, time_min, time_max)
147123
)
148-
resp = requests.get(url, headers=headers)
124+
resp = pyportal.network.requests.get(url, headers=headers)
149125
resp_json = resp.json()
150126
if "error" in resp_json:
151127
raise RuntimeError("Error:", resp_json)
@@ -191,6 +167,22 @@ def format_datetime(datetime, pretty_date=False):
191167
return formatted_time
192168

193169

170+
def create_event_labels():
171+
for event_idx in range(MAX_EVENTS):
172+
event_start_label = pyportal.add_text(
173+
text_font=font_events,
174+
text_position=(7, 70 + (event_idx * 40)),
175+
text_color=0x000000,
176+
)
177+
event_text_label = pyportal.add_text(
178+
text_font=font_events,
179+
text_position=(88, 70 + (event_idx * 40)),
180+
text_color=0x000000,
181+
line_spacing=0.75,
182+
)
183+
event_labels.append((event_start_label, event_text_label))
184+
185+
194186
def display_calendar_events(resp_events):
195187
# Display all calendar events
196188
for event_idx in range(len(resp_events)):
@@ -203,39 +195,31 @@ def display_calendar_events(resp_events):
203195
print("Event Description: ", event_name)
204196
print("Event Time:", format_datetime(event_start))
205197
print("-" * 40)
206-
# Generate labels holding event info
207-
label_event_time = label.Label(
208-
font_events,
209-
x=7,
210-
y=70 + (event_idx * 40),
211-
color=0x000000,
212-
text=format_datetime(event_start),
213-
)
214-
pyportal.splash.append(label_event_time)
215-
216-
label_event_desc = label.Label(
217-
font_events,
218-
x=88,
219-
y=70 + (event_idx * 40),
220-
color=0x000000,
221-
text=event_name,
222-
line_spacing=0.75,
223-
)
224-
pyportal.splash.append(label_event_desc)
198+
pyportal.set_text(format_datetime(event_start), event_labels[event_idx][0])
199+
pyportal.set_text(event_name, event_labels[event_idx][1])
200+
201+
# Clear any unused labels
202+
for event_idx in range(len(resp_events), MAX_EVENTS):
203+
pyportal.set_text("", event_labels[event_idx][0])
204+
pyportal.set_text("", event_labels[event_idx][1])
225205

226206

227207
pyportal.set_background(0xFFFFFF)
228208

209+
# Set up calendar event fonts
210+
font_events = "fonts/Arial-14.pcf"
211+
229212
# Add the header
230213
line_header = Line(0, 50, 320, 50, color=0x000000)
231214
pyportal.splash.append(line_header)
232215

233-
font_h1 = bitmap_font.load_font("fonts/Arial-18.pcf")
234-
label_header = label.Label(font_h1, x=10, y=30, color=0x000000)
235-
pyportal.splash.append(label_header)
236-
237-
# Set up calendar event fonts
238-
font_events = bitmap_font.load_font("fonts/Arial-14.pcf")
216+
label_header = pyportal.add_text(
217+
text_font="fonts/Arial-18.pcf",
218+
text_position=(10, 30),
219+
text_color=0x000000,
220+
)
221+
event_labels = []
222+
create_event_labels()
239223

240224
if not google_auth.refresh_access_token():
241225
raise RuntimeError("Unable to refresh access token - has the token been revoked?")
@@ -260,20 +244,13 @@ def display_calendar_events(resp_events):
260244
now = get_current_time()
261245

262246
# setup header label
263-
label_header.text = format_datetime(now, pretty_date=True)
264-
265-
# remove previous event time labels and event description labels
266-
for _ in range(len(events * 2)):
267-
print("removing event label...")
268-
pyportal.splash.pop()
247+
pyportal.set_text(format_datetime(now, pretty_date=True), label_header)
269248

270249
print("fetching calendar events...")
271250
events = get_calendar_events(CALENDAR_ID, MAX_EVENTS, now)
272251

273252
print("displaying events")
274253
display_calendar_events(events)
275254

276-
board.DISPLAY.show(pyportal.splash)
277-
278255
print("Sleeping for %d minutes" % REFRESH_TIME)
279256
time.sleep(REFRESH_TIME * 60)

0 commit comments

Comments
 (0)