Introduction: E-paper Calendar: Raspberry Pi With E-ink Screen and Google Calendar API (Full Tutorial)

About: Hello guys! I'm D_NL.

Forgot an appointment? No morewith your very own stylish E-Paper calendar. Follow this full size tutorial to setup your Raspberry Pi, connect it to your Waveshare E-paper and display the events of your calendar using the Google Calendar API.

Goals of this instructable

  • Get to know about SSH, FTP and VNC so your able to wireless update your code and Pi
  • Program your Pi to work with the Waveshare E-paper screen
  • Guide you trough the setup of the Google Calendar API
  • Learn about Cronjobs on the Pi

Supplies

*If you can't get the wifi up and running, maybe you need a keyboard/mouse/monitor and ethernet/hdmi-cable once to setup wifi.

Step 1: Setup Raspberry Pi With Preconfigured Settings

First step is to configure your Raspberry Pi in such way that you can connect to it from your pc or laptop, without hooking it up to a monitor, keyboard and mouse. (This is possible with a Raspbery that has onboard wifi (3 Or 4)or using usb wifi dongles that work out of the box)

Flash microSD card with Raspberry Pi Os using preconfigured settings

Download the 'Raspberry Pi Imager' from raspberrypi.com/software. Choose Raspberry Pi OS and choose your storage (microSD card). Click on the settings wheel to preconfigure your operating system.

  • Enable SSH (Use password authentication)
  • Choose Username and Password
  • Setup Wifi credentials and wifi country
  • Set you locale settings including timezome

Flash the image to your microSD card, insert it into your Pi and boot it up!

Step 2: Connect to Your Pi Through SSH

Your Pi should now be started and connected to WiFi. Next step is to be able to connect to it from your PC, therefore, we are going to connect using SSH.


Check if Raspberry Pi is online

On your PC, start a command line program (eg Windows Command Line or Windows Powershell) and type

ping raspberripi.local
or
ping raspberrypi

you should now see the MAC address and mostly, also the local IP-address.

No Raspberry Pi found? Probably your Pi is not connected. Check out this guide to get your WiFi running. You probably need to connect the Pi to a monitor / keyboard / mouse to use the user interface of Raspberry Pi OS and get your wifi up and running.


Setup SSH

With SSH you can control everything on your Pi. We already enabled this in your OS in the first step. Now we are going to use it! Standard, the preconfigured username is pi and the address of the Pi raspberrypi.local. To connect trough SSH, open up a commandline on your PC and type:

 SSH pi@raspberrypi

You will be prompted for the password (sometimes you need to give a onetime 'yes' to really connect), insert your pass and hit Enter. Congratulations: you're connected!

Update your Pi

Now your connected through SSH, first update your Pi. Type

sudo apt-get update

this will install any new packages that can be added to the Raspberry Pi’s library of applications. When finished, reboot:

sudo reboot

Now update all installed packages:

sudo apt-get upgrade

and reboot again with sudo reboot

Step 3: Enable VNC

Our goal is to work from our PC. But sometimes you just want to use the user interface of Raspberry Pi OS. This is easy with VNC, which establishes a 'remote monitor'.

Setup VNC

Install VNC viewer on your PC from RealVNC. Next, open your command line en login to your Pi through SSH as explained before. Type the following command:

sudo raspi-config

Go to 3. Interface Options > VNC > Enable. VNC is now enabled!

Open VNC viewer on your PC, go to File > New connection. Fill in the IP-address ('raspberrypi') and give it a name. Connect, fill in your password and tada! You can now see and use the user interface of the Pi without using a monitor / keyboard / mouse!


Troubles to get it running: Check the VNC-guide of Raspberry Pi.

Step 4: Setup FTP With Notepad++

Because we are working from our PC, we also want to edit (python)-files remotely. Therefore, we need a FTP-server and Notepad++.

FTP: To transfer files to your Pi we're gonna use FTP.
Notepad++: A code-text editor that has a FTP plugin, so you can easily program directly on your Pi

FTP

There is no FTP-server pre-installed, so we have to do this manually. We're using vsFTPd, this utility is lightweight, secure, and easy to use. Type the following command:

 sudo apt install vsftpd

After installation, configure vsFTPd by editing the settings file:

sudo nano /etc/vsftpd.conf

Nano is a text editor that will run inside your command line program. Remove the # from the following lines:

write_enable=YES

local_umask=022

Next, find the line:

anonymous_enable=YES

and change it to:

anonymous_enable=NO

Go to the end of the document and add the following lines:

user_sub_token=$USER
local_root=/home/$USER/

Now save hitting CTRL + X confirm with Y(es) + Enter. Restart the server:

sudo service vsftpd restart

Done!

Troubles: Check this tutorialon which my steps are based.

Setup Notepad++

Install Notepad++ on your PC: https://notepad-plus-plus.org/downloads/. Open Notepad++ and go to Plugins > Plugins admin and search and install NppFTP. After installation go to Plugins > NppFTP > Show NppFTP window. To connect to the FTP-server of your Pi:

Right click in the NppFTP window > Create new profile

Hostname: raspberrypi
Username: pi
Password: [your password of the Pi]

Click Close and give your profile a name. Right click on it and connect!


All set for the real work!

Step 5: Setup Raspberry Pi to Work With E-ink Screen and Run Sample Code

We are gonna work with Python to control the Waveshare E-ink screen. Therefore, we need to install some library's on the Pi. We simply follow the instructions from the Waveshare Wiki for our 7.5" V2 black/white E-ink screen. Start our command line, login through SSH and follow these steps.

1) Enable SPI interface

sudo raspi-config

Go to 3 Interface options > SPI > Enable SPI. Select Finish to return to the terminal.

Reboot with sudo reboot

2) Optional: Install Python library's (you can skip these step, since they have to be present in Raspberry Pi OS)

  sudo apt-get install python3-pip     
sudo apt-get install python3-pil
sudo apt-get install python3-numpy
sudo pip3 install RPi.GPIO
sudo pip3 install spidev

3) Download the demo from GitHub

git clone https://github.com/waveshare/e-Paper.git
cd e-Paper/RaspberryPi_JetsonNano/

4) Connect the HAT of the E-ink screen to the Raspberry Pi headers. Make sure the switches on the Hat are set to B (Display config) and to 0 (Interface config).

5) Run the sample and watch your screen come to live!

cd python/examples/
python epd_7in5_V2_test.py

Step 6: Create Your First E-paper 'Hello World'

Now are software and hardware are setup, it's time to start coding. Open Notepad++ and connect to the Pi using the FTP plugin (or use Filezilla).

Create folders

Inside the Pi directory create these folders (case sensitive), see screenshot also

calendar
- - prog
- - lib
- - - - waveshare_epd


Next copy the EPD library from the sample code to the 'waveshare_epd' folder.

From e-Paper/RaspberryPi_JetsonNano/python/lib/waveshare_epd/
copy epd7in5_V2.py and epdconfig.py
to
calendar/lib/waveshare_epd/


From e-Paper/RaspberryPi_JetsonNano/python/pic
copy Font.ttc
to
calendar/prog/

Create our Hello World example

In Notepad++ create a new file, save it as cal.py (Python file) to a PC folder.

Copy the code below. This is based on the example code from Waveshare. I really recommend the waveshare wiki to learn about all the 'epd.xxx' commands and how to print text, lines, figures and display monochrome bmp's.


import sys
import os
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
    sys.path.append(libdir)

from waveshare_epd import epd7in5_V2
import logging
import time
from PIL import Image,ImageDraw,ImageFont
import traceback

logging.basicConfig(level=logging.DEBUG) #Enable log-information for debbuging

try:
    epd = epd7in5_V2.EPD()
    epd.init()
    epd.Clear()

    font48 = ImageFont.truetype("Font.ttc", 48)

    #Draw something on the e-ink screen
    Himage = Image.new('1', (epd.width, epd.height), 255)           #Clear the frame (255 = white)
    draw = ImageDraw.Draw(Himage)                                   #Create new image buffer
    draw.text((40, 40), 'Hello world', font = font48, fill = 0)     #Write text on position 40,40 (x,y) in black
    epd.display(epd.getbuffer(Himage))                              #Display the buffer on the screen
    time.sleep(4)

    epd.init()
    epd.Clear()
    epd.sleep()

except IOError as e:
    logging.info(e)

Save the file (cal.py). Next, right click in NppFTP on your 'prog' folder and choose 'Upload current file here'. Cal.py is now uploaded to your Pi. Run the sample, by opening op the command line on your PC and connect through SSH, then type:

cd calendar/prog          #open folder
python cal.py #run the python code

The screen will now show 'Hello World' for 4 seconds, erase it and go to sleep. See enclosed demo.

Step 7: Get the Google Calendar API Working

We now know how to write information to the e-ink screen. Now it is time to get the information from our Google calendar.

First step is simple: Create a project in 'Google cloud'. Next is to setup the calendar API, this can be a bit more challenging, fortunately, Google has a quickstart to get you going, so follow these steps:

https://developers.google.com/calendar/api/quickstart/python

Some tips about the Calendar API:

  1. Enable the API: this is easy
  2. Credentials: Save the credentials.json file in the same folder as your main python file (the 'prog' folder) using NppFTP inside Notepad++ by rightclicking the folder and click 'Upload other file here' and selecte the credentials.json.
  3. Install the Google python library: easy but don't forget to 'sudo reboot' afterwards
  4. Copy the quickstart.py code inside a new document inside Notepad++ and save it to the 'prog' folder
  5. Optionally: Temporary delete the cal.py file (or move it to another folder). Somehow you can get an error when you run the quickstart.py if the cal.py is in the same folder.
  6. Run quickstart.py once using VNC viewer (not from your PC!), because you need to login your Google account to create the 'token.json' file. Therefore launch the 'Terminal' in Raspberry Pi OS (see screenshot):
  7. In the terminal type 'cd calendar/prog' and then 'python quickstart.py'
  8. Chrome webbrowser will automatically open, to login your google account en give access to your project
  9. A token.json file will automatically created inside the 'prog' folder
  10. When done, your upcoming events will list inside the terminal!
  11. Since the token.json file is created, you can now run the quickstart.py from your commandline on your PC. to get the same results

Step 8: Display Google Calendar Information on the Screen

Let's put it all together! We know how to display information on our e-ink screen AND we know how to get the Google Calender events. To keep it simple: we're gonna incorporate parts of the quickstart.py code into our 'Hello world' code to display a simple list of our upcoming events.


###### Waveshare part #######
import sys
import os
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
  sys.path.append(libdir)

from waveshare_epd import epd7in5_V2
import logging
import time
from PIL import Image,ImageDraw,ImageFont
import traceback

logging.basicConfig(level=logging.DEBUG)

###### Calendar API part #####
import datetime
from datetime import date, timedelta
import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']


try:
  #Calendar API part
  creds = None
  if os.path.exists('token.json'):
    creds = Credentials.from_authorized_user_file('token.json', SCOPES)
  if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
      creds.refresh(Request())
    else:
      flow = InstalledAppFlow.from_client_secrets_file(
        'credentials.json', SCOPES)
      creds = flow.run_local_server(port=0)
    with open('token.json', 'w') as token:
      token.write(creds.to_json())

  service = build('calendar', 'v3', credentials=creds)

  now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
  print('Getting the upcoming 10 events')
  events_result = service.events().list(calendarId='primary', timeMin=now,
                     maxResults=10, singleEvents=True,
                     orderBy='startTime').execute()
  events = events_result.get('items', [])

  if not events:
    print('No upcoming events found.')

  #E-paper part
  epd = epd7in5_V2.EPD()
  epd.init()
  epd.Clear()

  font18 = ImageFont.truetype("Font.ttc", 18)

  Himage = Image.new('1', (epd.width, epd.height), 255)      
  draw = ImageDraw.Draw(Himage)                  
  draw.text((10, 20), 'UPCOMING EVENTS', font = font18, fill = 0)

  #Loop trough calendar events and draw them to buffer
  y = 60
  for event in events:
    start = event['start'].get('dateTime', event['start'].get('date'))
    draw.text((10, y), start + ' ' + event['summary'], font = font18, fill = 0)  
    y = y + 30

  #Display buffer on screen for 25 seconds
  epd.display(epd.getbuffer(Himage))                
  time.sleep(25)

  epd.init()
  epd.Clear()
  epd.sleep()

except IOError as e:
  logging.info(e)


Run the code by SSH-ing into your Pi and execute the python code.

cd calendar/prog          #open folder
python cal.py #run the python code


Super! We got our (rudimentary) calendar! In the next steps we're gonna make some nice lay-out!

Step 9: Optimize Your Code to Get a Nice Lay-out

In this step we're gonna make a nice lay-out. To get a good overview I have three imaginary colums: today, events later this week and events next week. To spread the information about this columns, just be creative with the x,y position of the draw text statement, and a lot of if statements. I enclosed the file to get you going.


Some explanation of the code

Current date (top left)

This creates the current date at the top left of the screen. It simply viewing the current date in different formats, using strftime. See this site for an overview of all the date formats.

  #TODAY
  draw.text((20, 20), date.today().strftime("%d"), font = font56, fill = 0)
  draw.text((90, 24), date.today().strftime("%B"), font = font24, fill = 0)
  draw.text((90, 50), date.today().strftime("%A"), font = font24, fill = 0)

  draw.text((20, 120), 'TODAY', font = font18, fill = 0)


Calendar events

This is a bit harder, mostly because Google returns both date events (whole day events) and datetime events (specific start time). What it does:

  • Get the variables of the first event (startdate, starttime, enddate)
  • Set startdate to current date, if an event started before today
  • Check week of the event (if event is not today), if this is not the week of the previous event, then
  • Current week? Display 'later this week'
  • Week after that? Display 'next week'
  • Week after that? To far away: stop loop
  • Check day of event (if event is not today), is this not the day of the previous event, then
  • Display 'DAY Month - date'
  • If all the above is done, display the actual calendar event
 #Loop trough calendar events and draw them to buffer
  x = 20    #start position on x-axis of events on e-ink screen
  y = 140    #start position on y-axis of events on e-ink screen
  curweek = '' #variable for week of event
  curday = ''  #variable for day of event

  for event in events:
    start = event['start'].get('dateTime', event['start'].get('date'))
    end = event['end'].get('dateTime', event['end'].get('date'))
    summ = event['summary'] 

    if len(start) == 10: #events that are 'whole day'-events
        startdate = datetime.datetime.strptime(start,"%Y-%m-%d")
        enddate = datetime.datetime.strptime(end,"%Y-%m-%d")
        time = ''
    if len(start) == 25: #events that start at specific time
        startdate = datetime.datetime.strptime(start,"%Y-%m-%dT%H:%M:%S%z")
        enddate = datetime.datetime.strptime(end,"%Y-%m-%dT%H:%M:%S%z")
        time = startdate.strftime(" (%H:%M)")
    startdate_date = startdate.date()  
    if startdate_date < date.today(): #multi day events, set start date at current date
      startdate = date.today()
      startdate_date = date.today()
    day = startdate.strftime("%a %m-%d")
    week = startdate.strftime("%V")



    if curweek != week and startdate_date != date.today():      
      if (week == date.today().strftime("%V")):      
        x = 280
        y = 25
        draw.text((x, y), 'LATER THIS WEEK', font = font18, fill = 0)
      elif (int(week) == int(date.today().strftime("%V"))+1):
        x = 530
        y = 25
        draw.text((x, y), 'NEXT WEEK', font = font18, fill = 0)       
      else:
        break
      curweek = week
      y = y + 25

    if curday != day and startdate_date != date.today():
      draw.text((x, y+4), day.upper(), font = font18, fill = 0)
      curday = day
      y = y + 20

    draw.text((x, y),' ' + event['summary'] + time, font = font15, fill = 0)  
    y = y + 20


Refresh time

To see if there has been an update, you can display the date time of the refresh very small in the lower right corner.

  #REFRESH TIME
  now = datetime.datetime.now()
  draw.text((700, 465), 'Update: ' + now.strftime('%H:%M'), font = font14, fill = 0)


End of code

When everything is written to the buffer, display it on screen and leave it there till the end of time... or till the next update. This is not written in code, but is done through a cronjob. See next step!

  epd.display(epd.getbuffer(Himage))  

Attachments

Step 10: Setup Cronjob to Refresh Calendar on Time Interval and to Run at Startup

Now we have our code to get the information to the screen, we want to run this code every hour. We can use the cronserver of the Pi for this. Furthermore, we want to run the code at startup.

Run our calendar code every hour

Connect using SSH and type:

crontab -e

You are now prompted for an editor, choose nano (1) and hit Enter

Go tho the end of the file and add the following line:

0 * * * * cd /home/pi/calendar/prog && python cal.py

This will open the folder and run the code from that folder every time we are at minute 0 (so every hour). Hit CTRL + X and save (Y). If you want another interval, use this tool.

Run at startup

Same as above, but now add another line:

@reboot cd /home/pi/calendar/prog && python cal.py &

The & is necessary to prevent a boot problem if your code contains a loop. The & will always boot your Pi and not wait on your code to finish.

Reboot you Pi and check if everything works!