Skip to content

Instantly share code, notes, and snippets.

@JannieT
Last active January 10, 2022 05:22
Show Gist options
  • Save JannieT/1e589b215ec05118ab78ec5d441c69e7 to your computer and use it in GitHub Desktop.
Save JannieT/1e589b215ec05118ab78ec5d441c69e7 to your computer and use it in GitHub Desktop.
MagTag Calendar
import rtc
import time
import board
from adafruit_magtag.magtag import MagTag
from adafruit_oauth2 import OAuth2
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
class Calendar:
def __init__(self, refresh_time: int = 15) -> None:
self.sleep = refresh_time
self.magtag = MagTag()
self.clock = rtc.RTC()
self.token_obtained_at = 0
self.timezone = "+00:00"
self.secrets = {}
self.google_auth: OAuth2
def setup(self) -> None:
# Load our sensitive credentials and tokens
try:
from secrets import secrets
self.secrets = secrets
except ImportError:
print("Credentials and tokens are kept in secrets.py, please add them there!")
raise
# Connect to the configured wifi network
self.magtag.network.connect()
# Initialize an OAuth2 object with GCal API scope
scopes = ["https://www.googleapis.com/auth/calendar.readonly"]
self.google_auth = OAuth2(
self.magtag.network.requests,
secrets["google_client_id"],
secrets["google_client_secret"],
scopes,
secrets["google_access_token"],
secrets["google_refresh_token"],
)
def draw(self) -> None:
self._set_local_time()
events = self._fetch_events(3)
self._show_events(events)
print("Sleeping for %d minutes" % self.sleep)
self.magtag.exit_and_deep_sleep(self.sleep * 60)
# -------------------------------
# Non-API member functions
# -------------------------------
def _set_local_time(self) -> None:
raw = self.magtag.get_local_time(self.secrets["timezone"])
# 2022-01-01 09:10:03.370 001 6 +0200 SAST
zone = raw.split(" ")[4]
self.timezone = zone[:3] + ":" + zone[3:]
def _refresh_token(self) -> None:
# Check if we have a token, if not we need to get one
if (self.google_auth.access_token_expiration is None
or int(time.monotonic()) - self.token_obtained_at
>= self.google_auth.access_token_expiration):
print("Fetching a new access token ...")
if not self.google_auth.refresh_access_token():
raise RuntimeError(
"Unable to refresh access token - has the token been revoked?"
)
self.token_obtained_at = int(time.monotonic())
def _iso_date(self, date: struct_time) -> str:
return "{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}{:s}".format(
date.tm_year, date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec, self.timezone)
def _url_encode(self, raw: str) -> str:
return raw.replace(":", "%3A").replace("+", "%2B").replace(" ", "%20").replace("/", "%2F")
def _fetch_events(self, event_count: int = 3) -> list:
# see: https://developers.google.com/calendar/api/v3/reference/events/list
self._refresh_token()
now = self._iso_date(self.clock.datetime)
minTime = self._url_encode(now)
url = ("https://www.googleapis.com/calendar/v3/calendars/{0}"
"/events?maxResults={1}&timeMin={2}&orderBy=startTime"
"&singleEvents=true").format(self.secrets['calendar_id'], event_count, minTime)
headers = {
"Authorization": "Bearer " + self.google_auth.access_token,
"Accept": "application/json",
"Content-Length": "0",
}
print("Fetching events since " + now)
response = self.magtag.network.requests.get(url, headers=headers)
parsed = response.json()
if "error" in parsed:
raise RuntimeError("Error:", parsed)
response.close()
if not parsed["items"]:
print("No upcoming events found.")
return []
return parsed["items"]
def _event_summary(self, event: dict) -> str:
if "summary" not in event:
return "No summary"
# wrap event name around second line if necessary
lines = self.magtag.wrap_nicely(event["summary"], 25)
# only wrap 2 lines, truncate third..
return "\n".join(lines[0:2])
def _event_time(self, event: dict) -> str:
if "start" not in event:
return "---"
# all-day events
if "date" in event["start"]:
return self._human_date(event["start"]["date"])
if "dateTime" in event["start"]:
return self._human_time(event["start"]["dateTime"])
return "---"
def _human_date(self, date: str) -> str:
(year, month, day) = date.split("-")
months = {
"01": "JAN",
"02": "FEB",
"03": "MAR",
"04": "APR",
"05": "MAY",
"06": "JUN",
"07": "JUL",
"08": "AUG",
"09": "SEP",
"10": "OCT",
"11": "NOV",
"12": "DEC",
}
return "{} {}".format(int(day), months[month])
def _human_time(self, iso_date: str) -> str:
(date_part, time_part) = iso_date.split("T")
return "{} - {}".format(self._human_date(date_part), time_part[:5])
def _show_events(self, events: list) -> None:
self.magtag.set_background(0xFFFFFF)
summary_font = bitmap_font.load_font("fonts/Arial-12.pcf")
time_font = bitmap_font.load_font("fonts/BenchNine-Bold-20.bdf")
for i in range(len(events)):
event = events[i]
label_time = label.Label(
time_font,
x=7,
y=14 + (i * 40),
color=0x000000,
text=self._event_time(event),
)
self.magtag.splash.append(label_time)
label_summary = label.Label(
summary_font,
x=105,
y=14 + (i * 40),
color=0x000000,
line_spacing=0.8,
text=self._event_summary(event),
)
self.magtag.splash.append(label_summary)
board.DISPLAY.show(self.magtag.splash)
board.DISPLAY.refresh()
from calendar import Calendar
calendar = Calendar(refresh_time=15)
calendar.setup()
while True:
calendar.draw()
secrets = {
'ssid': <redacted>,
'password': <redacted>,
'aio_username': <redacted>,
'aio_key': <redacted>,
'timezone': "Africa/Johannesburg", # http://worldtimeapi.org/timezones
'calendar_id': "[email protected]",
'google_client_id': '<redacted>.apps.googleusercontent.com',
'google_client_secret': <redacted>,
'google_access_token': <redacted>,
'google_refresh_token': <redacted>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment