Before you can use the Google Calendar API to request events on your calendar, you must first authenticate the device with Google's authentication server.

We've handled this authorization "flow" by creating a CircuitPython library for Google's implementation of OAuth2.0 and an application to run on your device.

Secrets File Setup

Open the file on your CircuitPython device using Mu or your favorite text editor. If you don't have a file yet, copy the template below. You're going to edit this file to enter your Google API credentials.

  • Change aio_username to your Adafruit IO username
  • Change aio_key to your Adafruit IO active key
  • Change timezone to "Etc/UTC"
  • Change google_client_id to the Google client ID you obtained in the previous step
  • Change google_client_secret to the Google client ID you obtained in the previous step

Your file should look like this: 

# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

secrets = {
    'ssid' : 'YOUR_SSID',
    'password' : 'YOUR_SSID_PASS',
    'aio_username': 'YOUR_AIO_USERNAME',
    'aio_key': 'YOUR_AIO_KEY'
    'timezone' : "Etc/UTC", #
    'google_client_id' : 'YOUR_GOOGLE_CLIENT_ID',
    'google_client_secret' : 'YOUR_GOOGLE_CLIENT_SECRET'

Add CircuitPython Code and Project Assets

In the embedded code element below, click on the Download: Project Zip link, and save the .zip archive file to your computer.

Then, uncompress the .zip file, it will unpack to a folder named PyPortal_Google_Calendar

Copy the contents of the PyPortal_Google_Calendar directory to your PyPortal's CIRCUITPY drive.

# SPDX-FileCopyrightText: 2021 Brent Rubell, written for Adafruit Industries
# SPDX-License-Identifier: Unlicense
import board
import busio
from digitalio import DigitalInOut
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_requests as requests
import displayio
from adafruit_display_text.label import Label
from adafruit_bitmap_font import bitmap_font
import adafruit_miniqr
from adafruit_oauth2 import OAuth2

# Add a to your filesystem that has a dictionary called secrets with "ssid" and
# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other
# source control.
# pylint: disable=no-name-in-module,wrong-import-order
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in, please add them there!")

esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

print("Connecting to AP...")
while not esp.is_connected:
        esp.connect_AP(secrets["ssid"], secrets["password"])
    except RuntimeError as e:
        print("could not connect to AP, retrying: ", e)
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)

# Initialize a requests object with a socket and esp32spi interface
requests.set_socket(socket, esp)

# DisplayIO Setup
# Set up fonts
font_small = bitmap_font.load_font("/fonts/Arial-12.pcf")
font_large = bitmap_font.load_font("/fonts/Arial-14.pcf")
# preload fonts
glyphs = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: "

group_verification = displayio.Group()
label_overview_text = Label(
    font_large, x=0, y=45, text="To authorize this device with Google:"

label_verification_url = Label(font_small, x=0, y=100, line_spacing=1)

label_user_code = Label(font_small, x=0, y=150)

label_qr_code = Label(font_small, x=0, y=190, text="Or scan the QR code:")

### helper methods ###
def bitmap_QR(matrix):
    # monochome (2 color) palette

    # bitmap the size of the screen, monochrome (2 colors)
    bitmap = displayio.Bitmap(
        matrix.width + 2 * BORDER_PIXELS, matrix.height + 2 * BORDER_PIXELS, 2
    # raster the QR code
    for y in range(matrix.height):  # each scanline in the height
        for x in range(matrix.width):
            if matrix[x, y]:
                bitmap[x + BORDER_PIXELS, y + BORDER_PIXELS] = 1
                bitmap[x + BORDER_PIXELS, y + BORDER_PIXELS] = 0
    return bitmap

# Set scope(s) of access required by the API you're using
scopes = [""]

# Initialize an oauth2 object
google_auth = OAuth2(
    requests, secrets["google_client_id"], secrets["google_client_secret"], scopes

# Request device and user codes

# Display user code and verification url
# NOTE: If you are displaying this on a screen, ensure the text label fields are
# long enough to handle the user_code and verification_url.
# Details in link below:
    "1) Navigate to the following URL in a web browser:", google_auth.verification_url
print("2) Enter the following code:", google_auth.user_code)

# modify display labels to show verification URL and user code
label_verification_url.text = (
    "1. On your computer or mobile device,\n    go to: %s"
    % google_auth.verification_url
label_user_code.text = "2. Enter code: %s" % google_auth.user_code

# Create a QR code
qr = adafruit_miniqr.QRCode(qr_type=3, error_correct=adafruit_miniqr.L)

# generate the 1-pixel-per-bit bitmap
qr_bitmap = bitmap_QR(qr.matrix)
# we'll draw with a classic black/white palette
palette = displayio.Palette(2)
palette[0] = 0xFFFFFF
palette[1] = 0x000000
# we'll scale the QR code as big as the display can handle
scale = 15
# then center it!
qr_img = displayio.TileGrid(qr_bitmap, pixel_shader=palette, x=170, y=165)
# show the group

# Poll Google's authorization server
print("Waiting for browser authorization...")
if not google_auth.wait_for_authorization():
    raise RuntimeError("Timed out waiting for browser response!")

print("Successfully Authenticated with Google!")

# print formatted keys for adding to
print("Add the following lines to your file:")
print("\t'google_access_token' " + ":" + " '%s'," % google_auth.access_token)
print("\t'google_refresh_token' " + ":" + " '%s'" % google_auth.refresh_token)
# Remove QR code and code/verification labels

label_overview_text.text = "Successfully Authenticated!"
label_verification_url.text = (
    "Check the REPL for tokens to add\n\tto your file"

# prevent exit
while True:

Once all the files are copied from your computer to the CircuitPython device, you should have the following files on your CIRCUITPY drive.

Authenticator Code Usage

On your CIRCUITPY drive, rename to

Then, open the CircuitPython REPL using Mu or another serial monitor.

Your PyPortal should boot into the Google Authenticator code and display a code and URL.

Navigate to the Google Device page and enter the code you see on your device.

Click Next

Select the Google Account you'd like to use with the calendar viewer.

Since Google has not formally verified the application you created in the previous step, you'll be greeted with a warning.

  • Click Advanced
  • Then, Click the Go to {your application name} link

Finally, a dialog will appear displaying the application's requested permissions.

Click Allow.

You'll be presented with a dialog telling you the device has been authenticated.

After 5 seconds, the CircuitPython REPL should display a google_access_token and google_refresh_token.

Copy and paste these lines into the file.

Your file should look like this:

# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it
secrets = {
    'ssid' : 'YOUR_SSID',
    'password' : 'YOUR_SSID_PASS',
    'aio_username': 'YOUR_AIO_USERNAME',
    'aio_key': 'YOUR_AIO_KEY'
    'timezone' : "Etc/UTC", #
    'google_client_id' : 'YOUR_GOOGLE_CLIENT_ID',
    'google_client_secret' : 'YOUR_GOOGLE_CLIENT_SECRET',
    'google_access_token' : 'YOUR_GOOGLE_ACCESS_TOKEN',
    'google_refresh_token' : 'YOUR_GOOGLE_REFRESH_TOKEN'

Now that your device is authorized to make requests to the Google Calendar API, let's use it to fetch calendar events!

This guide was first published on Jan 13, 2021. It was last updated on Jan 13, 2021.

This page (Code Setup) was last updated on Oct 08, 2021.

Text editor powered by tinymce.