This project actually started much closer to the new year and was mostly finished by the end of February. Unfortunately, I didn't get a chance to post it right away and some things have been going on... all over the world. According to the warnings from Time Machine about not being backed up, it's been 37 days since I started working remotely and stopped connecting to my backup drive (still in the office). In that time much of the world has entered some form of lock-down in response to COVID-19. Also in that time, my cat, Jekyll, was diagnosed with chronic kidney disease and has been going for daily treatments during my lunch break.
I assure you that I am not trying to compare a global health emergency with the condition of my cat. However, both the need for assistance keeping track of days while little seems to change from day-to-day and the need to keep track of appointments in this period of seeming timelessness has made this project very valuable.
As a subscriber to Adafruit's Adabox which, according to their website is "[a quarterly box of] curated Adafruit products focused around a theme, and a unique collectible," I received a box containing a PyPortal. PyPortal is a board packed with sensors like light and temperature as well as wifi and a 3.2" touchscreen display. It can be programmed in CircuitPython, a fork of MicroPython, over USB. All of these things make it super handy as an IoT (internet of things) display or controller. It's a very interesting device, and I will probably pick up another couple in the future, but for a while, it was a device without inspiration. Then, recently, I was working on a project where I was reading calendar data from a google calendar and formating the output for display on a website. It was then that I realized the PyPortal, especially when in its stand, is the perfect size for a digital day calendar.
I have a long history with day calendars. I always get them with intentions of staying organized, current, or at least entertained. However, the tasks of writing in appointments by flipping to the correct date, or remembering to take the days which have passed off of the calendar eventually just cause me to stop using them. In digital format, there's no need to remember to change the day, and because it's connected to a Google calendar, I can add events from any of my devices and it will show up on my display. Unlike a traditional day calendar, I can display the time and show when an event is currently active.
Since I have easy access to some webspace (like where this website is being hosted), I created a script that would read in data from the Google calendar in iCalendar format, build a representation of the data, and then output today's data in JSON (JavaScript Object Notation). JSON is a widely understood format for passing data and most languages have native support for parsing the data, including PHP which is running on my webhost and CircuitPython running on the PyPortal.
ICalendar, or Internet Calendaring and Scheduling Core Object Specification, is a format designed to describe calendar information. Although you would probably be forgiven if you confused it with iCal, formerly the name of Apple's default calendar application. If you're unfamiliar with iCalendar format, an event looks something like this:
BEGIN:VEVENT
DTSTART;TZID=America/New_York:20200130T160000
DTEND;TZID=America/New_York:20200130T170000
RRULE:FREQ=WEEKLY;BYDAY=TH
DTSTAMP:20200423T175135Z
UID:2s13nomcaqtlg4t2jnlc7m285q@google.com
CREATED:20200130T175523Z
DESCRIPTION:
LAST-MODIFIED:20200220T222728Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:John Park's Workshop
TRANSP:OPAQUE
END:VEVENT
You can see that there are fields for things like when the event starts, ends, was created, and when it was modified. There's even information about whether the event is shown as "free" or "busy" though they call it OPAQUE or TRANSPARENT. The tricky field is RRULE, which describes how the event repeats. In this case, John Park broadcasts his show weekly on Thursday. To figure out what days to display, the iCal interpreter needs to calculate what days that event is supposed to occur, make sure that there aren't any exceptions (when you delete an event and say "delete only this event" rather than "delete all events"), and then limit the events to only the timeframe that you want.
Once the calendar is translated into data that's easier to work with, grouped by month, date, and time, it's much easier to select just the day that you want. Although I am considering a way to change the date on my display, for now I am only looking at today's information. If there is no information I return an empty object, but otherwise I return a JSON formatted object. Here's the event from earlier, translated into today's JSON object:{
"DTSTAMP":"20200423T184038Z",
"UID":"2s13nomcaqtlg4t2jnlc7m285qgooglecom11",
"CREATED":"20200130T175523Z",
"DESCRIPTION":"",
"LAST-MODIFIED":"20200220T222728Z",
"LOCATION":"",
"SEQUENCE":"0",
"STATUS":"CONFIRMED",
"SUMMARY":"John Park's Workshop",
"TRANSP":"OPAQUE",
"ALL-DAY":false,
"start":1587672000,
"DTSTART":"20200423T160000",
"end":1587675600,
"DTEND":"20200423T170000",
"day":1587614400,
"end_day":1587700799,
"tzoffset":-14400
}
This may not look much better than the iCalendar format from earlier, but in this format the object is much easier for most programming languages to read. A few new fields are added to make calculating start and end time easier as well as providing a timezone offset in seconds.
Heading over to Photoshop, I laid out a mockup of what I wanted the calendar to look like. Once my design was ready, I used the various examples that Adafruit supplies in their learn guide system to learn enough of DisplayIO to build my framework. The screen is broken down into a title bar to hold the date and time, a large text area to hold just the large number representing the date, and then a number of "rows" to hold the actual appointment information. Each row has a placeholder for the arrow indicating that the event is active and a text block. After the calendar data is downloaded to the PyPortal, the program can step through each appointment and populate the grid. Since the number of events may go down, either when the date changes or when an event is removed from the calendar, a small piece of code has to go through and clear unused cells. Because I need to update the time, as well as whether or not any events are currently active, the screen refreshes every 30 seconds. Because the calendar does not change that often, new data is downloaded every 10 minutes, cutting down on the amount of processing needed. As each event is loaded into its row, a quick check of current time against the start and end time of the event determines if the active event indicator should be shown or not... and that's it!
This project was pretty straightforward in both concept and implementation, really one of the biggest challenges was learning enough of iCal information to build the data for display. However, whether it was learning my way around DisplayIO, translating iCal format, or just getting used to writing code to a CircuitPython device, almost every step of the way had learning opportunities. There may come a time when I know enough of CircuitPython and the surrounding technologies that I won't need to learn something to implement a project like this... but where's the fun in that?