Geek Culture
Published in

Geek Culture

Tutorial: Creating an Indicator Light That Turns on When You’re in a Meeting

A Python script, a Raspberry Pi, a secret Google Calendar API. Oh my!

© Curtis Kennington / Flickr / CC BY-SA 2.0, altered with calendar added.

Introduction

At some point during the 2020 Covid pandemic, I moved to live with my family in Florida. But since I was still working remotely, I had lots of meetings scheduled throughout the day, sometimes even in the evenings! I love my family, but oftentimes they enter my room without knocking while I’m in a team meeting. Other times, they’ll decide to vacuum or play loud music while I’m in a 1–1 with my manager. My family was not happy when I started locking my door (it didn’t help that I would forget to unlock it after meetings ended). Eventually, I decided that modern problems require modern solutions.

I bought a flood lamp, a smart plug, and a Raspberry Pi, and wrote a script that checks Google Calendar to see if I’m currently in a meeting. If I’m busy according to my calendar, then it turns the light on. If I’m not, then it turns the light off. I placed this multicolored, beautiful, bright lamp outside my door and now my family knows not to disturb me when the light is on. Do they respect this “busy beacon”? That’s a great question for another time…

Who is this tutorial for?

This tutorial is perfect for you if you…

  • Have kids, parents, grandparents, siblings, roommates, a girlfriend, a boyfriend, a spouse, a pet chimpanzee, or anyone else who lives/works with you who needs to be informed that you are not to be disturbed
  • Use Google Calendar to track all of your meetings (or when you are free and busy)
  • Are familiar with python basics and object-oriented programming
  • Are looking for a cool project to do on a Raspberry Pi

Here’s a depiction of what this indicator light is designed to prevent:

What you will need

  1. A Raspberry Pi that can connect to your wireless network. This drop-dead gorgeous man I know wrote a tutorial on how to set one up.
  2. Any kind of lamp or anything that lights up when you plug it into the wall. I bought this really cool flood light, which changes colors and comes with a remote control!
  3. A Kasa smart plug, like this one. It’s very important that the brand is Kasa (TP-link) because unlike most smart plugs, these don’t have to be controlled using Amazon Echo or Google Home. You can just send requests to turn it on or off over the network using Kasa’s open API.

How it will work

To avoid using the official Google Calendar API, I will walk you through how to make your calendar public (only free/busy times), and find a secret URL. When anyone visits this URL, they will receive a JSON data dump of all your calendar’s events for a given time period (such as the current day). That will be covered in Part 1 of this tutorial.

Part 2 will go over how to write the Python script that checks this secret URL, fetches the JSON data, determines if you’re currently busy or free, and sends a request to either turn on or turn off the smart plug.

Then in Part 3, I will cover how to move the script over to your Raspberry Pi, and set up a cron job, so that the script runs every minute.

I’m using Python 3.7.3 but any Python version 3.* should work for this project.

Part 1: Finding the secret URL endpoint

In this section, I’ll walk you through how to access a secret URL endpoint that will provide you with an organized list of events in your calendar in beautiful JSON format. Disclaimer: I work at Google but this secret API is NOT insider information. Anyone can find it if they know how to look through their network activity. The advantage of this unofficial API over the official Google Calendar API is that this one is much simpler to use for our purposes. With the secret URL, you will never have to sign in or deal with OAuth.

Let’s begin. First, make your calendar public. You will need to go to your calendar’s settings page and select “Make available to public.” You should also select “See only free/busy (Hide details).” Here are more in-depth instructions.

Once you’ve made your calendar public, scroll down to the “Integrate Calendar” section and find the “Public URL to this calendar”. Open that URL in an incognito window (to make sure you can access it while not being signed-in to Google). It should look something like this:

Now open Chrome’s developer tools (View > Developer > Developer Tools). Go to the “Network” tab. This tool will show you all network requests that are being made by this webpage. If the webpage decides to load an image, submit a form, or download some kind of file, the details of the network request should be listed here. Refresh the page to see what network requests happen when this page loads. You should see something like this:

Look for the list item that begins with “events?” (highlighted in the image above). You can double click it to open the page. The contents should be a JSON dictionary that contains all relevant information about this calendar, including a list of events. This URL should look like:

https://clients6.google.com/calendar/v3/calendars/email@gmail.com/events?calendarId=email%40gmail.com&singleEvents=true&timeZone=America%2FLos_Angeles&maxAttendees=1&maxResults=250&sanitizeHtml=true&timeMin=2021-02-28T00%3A00%3A00-08%3A00&timeMax=2021-04-04T00%3A00%3A00-08%3A00&key=AIzaSyBNlYH01_9Hc5S1J8vuFmu2xUqBZJNAXxs

Splitting it up, this url consists of the domain+path:
clients6.google.com/calendar/v3/calendars/email@gmail.com/events
followed by lots of parameters, including timeMin and timeMax, which control the time range of events. Changing these datetimes to the beginning and end of the current day will give you all of the events on your calendar for today.

By properly setting the parameters in this URL, the Python script, which we will write in the next section, can easily get a nice list of all events on your calendar for the current day.

Part 2: Writing the Python Script

This part will guide you step-by-step on how to write the python script which will retrieve your busy times from Google Calendar, find the Kasa smart plug on your local network, tell it to turn on/off, and much more, including how to implement some cacheing so that you don’t send network requests every minute. If you want to skip this part and just see the code right away, you can find it on Github here.

Getting the busy times from Google Calendar

Let’s start off by writing a function that gets all of the times (start and end) that you’re busy throughout the day. This function should generate a URL that when requested, will return a JSON dump of all the events between 00:00 today and ending at 00:00 tomorrow. It should then process the JSON data and return a list of tuples (start time, end time).

One tricky part is making sure that the URL is properly encoded, meaning / should be turned into %2F and : needs to be %3A.

Also, if you have an all-day event in your calendar, do you want the light to be on all day? I think that any event that lasts a full day or longer should not count as a busy time.

Checking if you’re busy right now

The script will be run every minute, and needs to know if you’re busy at the present time. Let’s write a function that returns true if you’re busy and false if you’re free. A good software engineer will try to make his/her functions as generic as possible, so that they can be reused if needed. In striving to be a good developer, I made this function check if I’m busy at any given time.

This function takes in a list of busy times as tuples (start time, end time), and iterates through all of those busy times to see if the time_to_check falls in between any of them.

Connecting to the Kasa smart plug

Before proceeding with writing code on how to connect to your Kasa smart plug, make sure it’s ready. Once you’ve set it up using the Kasa app, it should be connected to your wifi network.

Download the python-kasa library by running pip install python-kasa.

Once you’ve done that, you should be able to run the command kasa and see information about your device, including its IP address:

$ kasa
No host name given, trying discovery..
Discovering devices on 255.255.255.255 for 3 seconds
== Busybeacon - HS105(US) ==
Host: 192.168.86.165
Device state: OFF
== Generic information ==
Time: 2021-03-14 14:34:13
Hardware: 4.0
Software: 1.0.2 Build 200804 Rel.091847
MAC (rssi): 60:32:B1:73:14:05 (-27)
Location: {'latitude': 0, 'longitude': 0}
== Device specific information ==
LED state: True
On since: None

You can also turn it on and off through the command line like so:

kasa --host [YOUR DEVICE IP] onkasa --host [YOUR DEVICE IP] off

It’s quite straightforward to do these same operations in python using this kasa library.

  • SmartPlug(<ip address>) can create a smart plug object given a specific IP address. SmartPlug is a subclass of SmartDevice. SmartDevice objects have properties including host(IP address) and name (what you named your device when you set it up).
  • discover() lists devices on the network
  • smart_device.update() fetches the smart device’s info, including its state (whether it is turned off or on)
  • smart_device.turn_on() turns the device on and smart_device.turn_off() turns it off.

Many of the methods in the python-kasa library need to be run in an asynchronous context, which makes sense because you don’t typically want your code to stall while a device does something. So to run most of these methods, you will need to use the asyncio library. Install it with pip install asyncio.

The IP address of your smart plug may change occasionally but it won’t change frequently, so it’s a good idea to cache it. Let’s write a function that tries to connect to a smart device given its IP, and if it can’t connect, then it will scan the network looking for a device by a given name.

This function will do its best to return a SmartDevice object. Once we have a reference to a SmartDevice object, we will need a function that sets its state (turns it on and off):

Bringing it all together

To work correctly, the python script needs to do the following:

  1. Get the busy times from Google Calendar.
  2. Scan the network and find the smart plug.
  3. Check if you’re busy right now.
  4. Set the device state accordingly: turn it on if you’re busy, off if free.

One edge case to consider: what if (possibly by mistake) an event gets put on your calendar late at night? The floodlight I bought is bright enough to wake me up, so I made sure to exclude events outside of daytime/worktime hours. I definitely do not work from 9am to 10pm, but that’s the time range that I’ve decided to allow my busybeacon to possibly be turned on.

Cacheing

You probably don’t want the script to check Google Calendar every minute. Nobody should be scheduling meetings one minute in advance, right? You also don’t want to scan your network to find your smart plug every minute. To avoid this, you should cache the busy times and smart plug’s IP in a local file. I decided that I only wanted to refresh the cache (check Google Calendar) every 5 minutes. Here’s what the new algorithm will look like with cacheing:

  1. Get the busy times and smart plug IP from the cache. (Unless the cache doesn’t exist, or hasn’t been updated today, or its time to refresh the cache)
  2. Check if you’re busy right now.
  3. Set the device state accordingly: turn it on if you’re busy, off if free.
  4. Write the busy times and smart plug IP to the cache.

To encode the busy times and IP address of the smart plug, you can use a handy python library called pickle.

Done! The script is ready. You can see the full code here. After you update the constants at the top, including the calendar URL template, you should be able to run it: python3 busybeacon.py.

Part 3: Running the script every minute on your Raspberry Pi

Before starting this part, make sure your Raspberry Pi is set up correctly, turned on, and connected to your wifi network. You don’t need to connect the Pi to a monitor, but you do need to connect to it via SSH. If you have not set up the Raspberry Pi yet, here’s a tutorial on how to do that.

Alright, the first step is to copy the python script over to the Raspberry Pi. You can do that with the scp command. This should copy it to your home directory on the Pi:

scp path/to/busybeacon.py pi@raspberrypi.lan:busybeacon.py

Make sure it works by sshing into the Raspberry Pi and running the script:

python3 busybeacon.py

You may need to install dependencies like python-kasa using pip/pip3.

Now let’s schedule this script to be run every minute. There’s a great tool called cron, which lets you schedule tasks to be run at specified intervals. These tasks and their schedules are written in the cron table. You can edit the cron table using this command:

crontab -e

This will bring up an editor (typically nano or vim) which will let you edit this file. Add the following line to the file:

* * * * *  python3 busybeacon.py

The five asterisks indicate that this command should be run (1) every minute of (2) every hour of (3) every day of the month of (4) every month on (5) every day of the week.

You can find more on scheduling tasks with cron here.

And that’s it! Now you have your very own busybeacon! Hopefully this will help you be less frequently disturbed and prevent any awkward situations. Feel free to leave a comment or get in touch if you have any questions, comments, or suggestions for improving this tutorial.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Paul Bitutsky

Paul Bitutsky

Software Engineer at Google working on the Translate iOS app. Recent graduate of UC Berkeley, majoring in Computer Science and Business.