May 19, 2021, 5:52 p.m.

HomeKit Automation for Elgato Key Light

I am using a HomeKit scene to turn on my Hair Light as well as my ATEM Mini Pro and Camera with one click, as enabling my video in my new setup is quite an involved process. Unfortunately I also have to manually turn on the Elgato Key LED lights using the Elgato Control Center status menu. This is a pain. So I thought to myself that I can simplify things by adding a HomeKit automation that whenever the Hair Light turns on / off, to have the Elgato lights follow it. Turns out this was more tricky than I thought...

First I tried to use Apple Script to interact with the Elgato Control Center status icon. This did not work - I could not get it to drop down and simulate button clicks. Next step was to try and see if I could reverse engineer the protocol between the app and the Elgato lights, and talk to them directly. First step was to get their IP addresses. It did not show in my Unifi portal and I could not locate it in pfSense, so I decided to run this on my firewall:

tcpdump -i ix0.8 -vv -nnvvXSs 1514 port 5353 | grep -i Elgato

This showed all the MDNS queries with Elgato in its name, so after I opened the Elgato app on my iPhone it generated traffic and I could locate the IPs. Next step was to decode the protocol. I listened for traffic on the interface for the Elgato light host IP and then turned on and off the light using the app. This dump I analysed with Wireshark. It was clear plain HTTP were being used over port 9123:

GET /elgato/lights HTTP/1.1
Host: elgato-key-light-d111.local.:9123
Accept: */*
Accept-Language: en-ca
Connection: keep-alive
Accept-Encoding: gzip, deflate
User-Agent: Elgato%20Control%20Center/10368 CFNetwork/1237 Darwin/20.4.0

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 74
Connection: keep-alive

{"numberOfLights":1,"lights":[{"on":0,"brightness":23,"temperature":184}]}

That fetches the current status of the light. The next call was:

PUT /elgato/lights HTTP/1.1
Host: elgato-key-light-d111.local.:9123
Connection: keep-alive
Accept: */*
User-Agent: Elgato%20Control%20Center/10368 CFNetwork/1237 Darwin/20.4.0
Accept-Language: en-ca
Content-Length: 74
Accept-Encoding: gzip, deflate

{"lights":[{"brightness":23,"temperature":184,"on":1}],"numberOfLights":1}

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 74
Connection: keep-alive

{"numberOfLights":1,"lights":[{"on":1,"brightness":23,"temperature":184}]}

This seemed trivial. A single GET to fetch the current status, then a PUT to update the status. I wrote a simple python app to do this for all my lights, passing a ON or OFF parameter that just sets the JSON value "on" to either 1 or 0.

Next step was to deploy the python app to an accessible location on the network, and create a HomeKit automation. I created two automations - one for ON, one for OFF. I'll discuss the ON automation. The automation was set to trigger when the hair light is turned ON, to run a scripting shortcut of type "Run script over SSH". I entered the IP of the host containing the python app, entered authentication credentials and the full path to python3 and the python app file, with the parameter ON.

So I tried testing this automation - and it worked great. However it did not work when I turned the Hair Light on. A little bit of thinking made me realise that the automation is initiated by the Hub which is my HomePod. The HomePod is on a different network than the host with the python file, so the HomePod could not access SSH on that host. A quick firewall rule later and this automation works fantastically. 1 hour of fiddling to save four clicks per video call. Worth it.