Controlling the Elgato Key Light

October 16, 2020
Shadow Study

I got an Elgato Key Light as part of my overall working-from-home setup. The light is computer controllable using the Elgato Control Center app. The app is “fine”. It sits in the menu bar on the Mac, and works. You can tweak brightness, on/off state and color temperature. But I wanted to control it from my Hammerspoon-based Zoom controls.

I considered a few different approaches:

  • If the light talked to Alexa or HomeKit, maybe I could use those APIs to control it. (It doesn’t integrate with those systems)
  • If the Control Center app had menu items I could activate, I could use the same approach as I was using to control the Zoom app itself. (The Control Center app has no menus)
  • The Control Center app was clearly controlling the light already - maybe I could replicate whatever the method it was using in my own programs. (Promising, let’s try it!)

I’d love to tell you that I sniffed traffic on my network to reverse engineer the communication protocol between app and light, but in truth I searched for elgato key light controlling api and lots of good resources popped up including some clean, working javascript code and some Powershell code that also included more technical information on the communication approach. 1

The result is just a light turning on and off, but integrated into my centralized Zoom controls hotkey and menu:

Light on and off video
The Tech Details

I’ve been using Node lately for scripts anyway (npm is amazing from a breadth perspective), and so I used this Node.js code as a base. The module was easy to use and worked immediately to turn the light on and off.

lightAPI.on('newLight', (newLight) => {
    lightAPI.updateLightOptions(newLight, options).then(() => {
        console.log("Lights has been updated.");
    }).catch(e => {
        console.error("Error: ", e);

This first version was really slow though, sometimes taking multiple seconds turn the light on or off, which made it unsuitable for responding in real-time.

Once I looked inside the source, the reason was clear. The communication with the Key Light itself was just an HTTP request2, which is typically pretty fast (300-500ms e.g.), but the way your computer “finds the light” is by using Bonjour.

Bonjour is a commonly used protocol by which devices can find each other on a local network by broadcasting a packet to anyone who is listening. But in the above code, when you hit the key to toggle the light, if you “just missed the last broadcast”, you are stuck waiting until you see the next broadcast before you can relay the message to the light.

Luckily, you really don’t need to let the code freshly rediscover the light every single time. The Bonjour protocol delivers you the IP address and port of the light, and that IP and port typically remains static and valid for a while 3.

So cache the ip and port in a file, and just send an HTTP request with the desired options to the light directly. If the light is still listening on that IP/port combo, it will respond. And if that request fails, then you fall back to the “discovery” flow to rediscover the Key Light on the network.

  1. If this hadn’t worked or wasn’t so straightforward, I might have tried finding and interrogating Elgato menubar app using the Mac Accessibility APIs. ↩︎

  2. Surprise, your light is running a web server with no authentication! I actually like this, as I’m tired of dealing with super complicated auth flows and tokens, and the light is only running locally on my network and is relatively harmless. Still, I didn’t expect a bare http server that accepts human-readable JSON in to be running inside my light. ↩︎

  3. The subject of IP and port lifetimes on your local network is beyond this post and my expertise, but the routers I’ve owned are good at assigning and keeping devices on the same IP through power cycles and reconnects so in practice, I shouldn’t need to “rediscover” the light very often. ↩︎