Making a Linux-Based “On-Air” Light for My Home Office w/ Camera & Google Calendar Integration

I work from home. I wanted to be able to let my wife know, “Joelle is busy right now.”

This article will be fairly technical, but won’t give step-by-step instructions for everyone. It’s more meant to serve as an inspiration to other computer professionals — to determine how they might do something similar.

I love working from home, being able to have an environment that is set up exactly for me. Everything in my office, from the desk and chair I use, to the room’s lighting was designed to make me comfortable.

But I wanted something outside my office door to indicate I was busy that my wife could see — some sort of display — that was easy for both of us to use.

The wireless RGB light selected to indicate I am “busy”

After all, my wife isn’t a fan of making unscheduled appearances on video meetings, and there are times I want to work uninterrupted.

Specifying My Requirements

Because I’m a technical professional, I naturally started by writing down my requirements. Here’s what I came up with:

  • Mount outside of my home office door, on either the wall or the door itself
  • Work with Linux
  • Integrate with a Google Calendar (I.E. indicate busy when I have a calendar appointment)
  • Turn on automatically when my computer’s webcam is in-use
  • Allow me to manually override the indicator (I.E. turn the light on or off)
  • The control program should be interactive terminal program

Of course, as I thought about this, I ended up digging into each one a bit more.

The “Busy Light” Itself

So, the first thing to decide on was the light itself. After talking with some IT experts at work, the Luxafor Bluetooth was suggested (this Luxafor Flag would also work as a wired solution). It met my requirements — it used a simple USB interface (so it would work on Linux). I could mount it outside my door. It’s wireless and battery powered, which is a huge plus — not having to drill holes in my house is a good thing! The battery lasts for a long time (weeks) between recharges. It’s multi-colored (it has an RGB LED), which includes adjustable brightness, so it won’t blind people in my relatively dark hallway. The wireless transmitter it comes with also has an RGB LED in it, so you can see what is being sent to the light on the other side of the opaque door. It seemed like a perfect solution, albeit one that might require some custom work on the software side.

I would later add to this setup, but I’ll describe that later.

Integration with Google Calendar

I wanted the light to come on when I had a meeting on my calendar. That meant: I needed a way to read the calendar from Linux that I could wrap a script around to do what I needed it to do. Basically, I wanted the light to come on a couple minutes before the meeting started, and stay on for a couple minutes after the meeting was over.

As I thought about this, I thought it would be a good visual reminder to me that a meeting is coming up. There wasn’t really a good place to mount the Luxafor transmitter dongle where it would catch my attention when it came on, so I decided to go with a Luxafor Flag mounted under my center monitor — right in the middle of my vision — for this. The idea was that I would turn both lights on together for meetings. The light turning on under my monitor would remind me that I have a meeting coming up.

I figured out that I could read the calendar via the gcalcli program, and gcalcli was already available as a package for Ubuntu 20.04.

It was also easy to construct a command line that output the information I was interested in, in tab-seperated-value format (for easy parsing by another meeting). I was interested in upcoming meetings over the next 24 hours or so (the agenda view provides that), with start and end times, and without indlucing any meetings I declined. I also wanted the option to specify which calendar I viewed (normally I’d view my main calendar, which shares my email address, but occasionally I’d want to view other calendars I had access to). The command line to do that was:

A tab-seperated-value output from a gcalcli command

Of course you might ask, “How does gcalcli get access to my calendar? The first time you run it, it’ll pop up a browser window and ask you to login, so you simply run gcalcli agenda which will then open up a browser window for you to log into your Google account. Assuming you approve this access, it’ll store an access token in ~/.gcalcli_oauth that provides access to your calendar. Future executions will reuse this authentication. Obviously you want to protect this token, but at least it is restricted to just accessing your calendar.

Webcam Integration

If I met with a coworker outside of a scheduled meeting, or if a video meeting went long, I wanted the busy light to be lit. That means I needed a way to determine if the camera was in use. On Linux, this is fairly easy — I could look to see if the uvcvideo module was in use. The special file /proc/modules is a readable text file that contains one line per module. The third field in the line (a zero in the below example) is the count of how many programs are using this module. I’d just have to poll this file every second or so and look for a value other than zero.

The camera is not currently in use — a reference counter of “0”
The camera is in use for a Zoom meeting — a reference counter of “1”

So that’s easy!

Controlling the Luxafor

So, how do I turn a Luxafor indicator on or off? I played around for a while with this, trying to find the best way. I’m sure there are better ways than what I came up with, but what I came up with works.

After watching Travis Gibson’s presentation on using Raku to drive USB devices, I was inspired to try to control the Luxafor from a Raku (my preferred language) script, so I used a modified version of his LibUSB module to drive the Luxafor. Beyond figuring out the protocol (the easy part, thanks in part to looking at a Python Luxafor script), there were two problems remaining.

First, on more modern Linux distros, an RGB module binds to the Luxafor module. I didn’t want this since I was driving the device directly, so I needed to tell Linux not to bind to this module. To do that, I created the file /etc/modprobe.d/luxafor.conf with contents of:

options usbhid quirks=0x04d8:0xf372:0x0004

The quirks entry for the usbhid module tells the module that for device 0x04d8:0xf372 (the Luxafor flag), apply quirks 0x0004. These quirks tell usbhid to ignore the device and not bind to it.

But the second problem was permissions. I wanted the device to be able to be accessed from a normal user account, not an account running as root. So I created a file as /etc/udev/rules.d/luxafor.rules to tell udev what permissions to use for the device:

SUBSYSTEM==”usb”, ATTR{idVendor}==”04d8", ATTR{idProduct}==”f372" MODE=”0664", OWNER=”jmaslak”

I then rebooted the box to make these take effect (you can do this other ways, but sometimes it’s easier to reboot a box than read the docs, even if I lose a few sysadmin points for having done so). Now I have a device I can control from Raku script.

Terminal Interface

I would also like a command line interface to see all this. Basically, I thought I could run the script that controls the lights from a tmux frame, so it’s always visible to me. I wanted the interface to tell me “You have a meeting now — it’s the meeting about <whatever>” when I had a meeting. That might save me exiting my terminal environment. While I was at it, having the terminal interface also tell me when the next meeting would start, if I wasn’t currently in a meeting, seemed like a handy idea.

The interface I settled on would basically update once a minute (I like some evidence it’s working!) with a status of the next meeting (or indicate I’m currently in a meeting. Here’s an example of the output:

Example Output showing a video call, a 17:15 meeting that was manually ended, and a 17:20 meeting that just started

The “manual override” should be easy for me to use, and it should leave override mode once a different meeting starts. Basically, it overrides the current meeting(s) taking place, but not future ones.

So I put a script together to do all of this — it would turn on the light based on my Google calendar appointments (turning it on 2 minutes before the meeting, turning it off 2 minutes after the meeting) as well as whenever the camera was being used or when I manually overrode the camera.

At this point, I basically had a script that did what I thought I wanted.

Additional Enhancements

I made a couple of additional enhancements to the script over time. First, I decided it would be great to have the Luxafor indicate the status of my personal (not work) calendar, so I extended the script to allow specification of multiple calendars (they both have to be accessible to the same Google account, which wasn’t a problem). Now meetings in either calendar will change the indicator.

This brought up a second problem — hooking the Luxafor to my work computer meant that it only worked when the work computer was turned on. How do I make this work when my work computer is turned off? I moved the Luxafor dongles over to another machine that was always on, and this solved the problem but created a new one: how would I know my work computer’s webcam was in use? The solution was to write an enhancement to my script, a Ubuntu service that monitors the camera and sends a UDP message to the machine with the script running, indicating whether or not this remote camera was in use. As a bonus, this means I can also send these messages from my personal Linux machine if I ever were to hook a camera up to it.

A Plug for Raku

I chose to do this in the Raku programming language. Raku is a fairly new language, that sadly doesn’t yet have the popularity it deserves. Raku has amazing support for concurrency. For instance, I mentioned that I added support for remote machines to signal camera availability. All of the network server code is 25 lines of code (a third of which are braces or blank lines), with remarkably little “boilerplate”:

The network server also supports sending key presses from the remote client (I typically leave the monitor on the “busy indicator” server running all the time, to see my meeting status and the like, but I don’t like needing multiple keyboards on my desk). Raku code uses some unusual syntax (which is inspired by Perl), but the syntax is relatively orthogonal and incredibly flexible, so don’t let that turn you off from a fantastic language!

So, How do I Do This?

I intended more to share my thoughts on how I approached this problem than to provide a step-by-step guide— I expect my solution is very specific to my way of working, my environment, my peripherals, my home network, etc. I hope that it helps inspire your own solution.

That said, my code is available on Github. There is an install doc that describes installation on Ubuntu here (it’s best served with this blog post as a companion). You can also view documentation on the actual busy-indicator.raku script (command line parameters, CLI commands, etc).

Leave a comment