My partner and I both work from home and spend large parts of our days on video calls. In order to avoid disturbing one another, we wanted some way to automatically signal when we are in meetings, particularly when we are presenting.
My company uses Teams, whereas my partner’s company uses Zoom, so we needed to design a solution that worked for both services.
![busy light animation](/assets/img/raspberry-pi-presence-light/busy-light-animation.gif)
Animation courtesy of Jean Liu
Inspired by a number of Raspberry Pi builds on the Internet (here, here, and here), I’ve made a set of lights that synchronise with our Teams and Zoom presence statuses throughout the day. To keep things clear and simple, I’ve mapped the statuses to three colours:
means that the user is available
means that the user is in a meeting
means that the user is presenting
We’re really happy with the result, which has meant that it’s easier than ever to see when the other person is free for a quick chat or a coffee in between meetings. As this was my first Raspberry Pi project, I learned a lot and thought that it would be useful to share my experience.
What you’ll need
I need to start this section with a major caveat.
After starting to write this post, I noticed that one of the key components, the Unicorn pHAT, was discontinued. It’s been replaced by the manufacturer with a new version, the Unicorn HAT Mini. Since the Unicorn HAT Mini pixels and driver chip are different to the previous Unicorn boards, this changes my project instructions slightly. It might also be necessary to make a few changes to run any Python code that talks to the Unicorn HAT Mini.
For clarity, I have included 2 lists of parts: 1) my original build and 2) a suggested build using parts that are currently available.
Original build
- Raspberry Pi Zero W - A tiny headerless Raspberry Pi with WiFi and Bluetooth
- Unicorn pHAT - An LED board that attaches on top of the Raspberry Pi (discontinued)
- Pogo-a-go-go Solderless GPIO Pogo Pins - A solderless solution for connecting the Pi and pHAT (only 3/10 pins required, install them upside-down!)
- pHAT Diffuser - A diffuser to sit on top of the LEDs (optional)
- Pibow Zero W case - A case for the Raspberry Pi Zero W (optional)
Note: I’ve listed the pHAT Diffuser as optional because you can change the Unicorn’s LED brightness programmatically. The Pibow Zero W case is optional too, as it is largely unused if you decide to use the pHAT Diffuser on top of the Unicorn pHAT.
Suggested build (which I’ve not tested)
Keep in mind that the parts listed below weren’t used in my original build, but I’m including them here to provide a starting point for anyone who wants to replicate the project but can’t get the Unicorn pHAT.
Use this build with caution, as I haven’t tried it myself. However, I’d be interested to hear from anyone who has attempted this project with the suggested hardware. I’ll update this post if there are any issues.
This build should also not require soldering - since the Unicorn HAT Mini comes with a female header pre-attached, I’m assuming you can just buy a Raspberry Pi Zero WH (which includes a pre-soldered male header) to plug into it directly.
- Raspberry Pi Zero WH - A tiny Raspberry Pi with WiFi and Bluetooth
- Unicorn HAT Mini - An LED board that attaches on top of the Raspberry Pi
- pHAT Diffuser - A diffuser to sit on top of the LEDs (optional - see note in original build)
- Pibow Zero W case - A case for the Raspberry Pi Zero W (optional - see note in original build)
The solution
TL;DR
Follow the main respository readme here.
For Teams
- Configure an Azure AD app to access your Teams presence following this guide
- Install the Raspberry Pi Presence Light for Teams & Zoom app on your Raspberry Pi
For Zoom
- Create a Zoom webhook app following this guide
- Configure and deploy the Zoom Presence Indicator API to receive the Zoom presence events
- Install the Raspberry Pi Presence Light for Teams & Zoom app on your Raspberry Pi
Towards the end of last year, I found a blog post by Elio Struyf on building a presence indicator for Microsoft Teams using a Raspberry Pi, and it piqued my interest.
Elio’s solution relies on hosting your own Homebridge server, with a custom Homebridge plugin to poll the Microsoft Graph API for presence changes. Any presence change will then be forwarded to an API running on the Pi to update its colour.
However, I felt that a simpler solution would better suit my needs, given HomeKit integration wasn’t a requirement.
Maxi Krause went on the same journey, and their solution entirely removes the need for a Homebridge instance by pushing the polling logic down into a single app running on the Pi. Maxi’s solution does require that you configure your own Azure AD app, but other than that, it was exactly what I wanted, and seemed to work as well as Elio’s solution.
I just needed to adapt it for my requirements, and create a matching implementation for Zoom.
Thankfully, Zoom utilises webhooks, so any presence events can be fired at a specified URL. However, given the Pi wouldn’t be visible outside my local network I created a public API it could call to use as a proxy.
Using the Sample Zoom Webhook App as a starting point, and taking a few pointers from Michael Leo, I modified it to receive Zoom presence events and place them in local storage.*
Instead of calling an Azure AD app, the Zoom version of the Pi app calls this API to retrieve the presence and then matches the response against a set of Zoom presence statuses.
For a full set of instructions to replicate my solution, please follow the guide included as part of the respository readme. It’s worth noting that when you configure the Zoom Presence Indicator API you can omit any of the values for the Azure IoT Hub and it will still operate.
If you decide to try this project out, good luck, and please let me know how it goes!
* My original intention was to push these events to an Azure IoT Hub, which I got working with this API. However, issues implementing the same solution for the Microsoft Graph API resulted in falling back on long polling for both solutions, as a first pass. If I make progress with the aforementioned issue, I may try and implement it that way again.