Thursday, March 30, 2023

Turning on my office lights when I plug in my webcam

Another post in my series of "absurdly specific things no other human will ever find useful, but I'm still writing a blog post about".  I have a USB webcam in my home office on my PC which I use for work meetings.  I only plug that webcam in while I'm working, because I don't trust it otherwise.  Also, I have some lights above my desk that I like to turn on during meetings for better lighting.  The switch for those lights is in the corner behind my desk, and annoying to reach.  I installed a smart switch for them so I could turn them on via Home Assistant, but never got around to creating an easier way to turn them on other than the switch, or doing it via the HA UI.  Most mornings I plug in the webcam and then reach awkwardly over my monitors to hit the light switch.

I decided to automate this.  Now while I've recently acquired a glut of motion sensors, I find they have far too many false positives and false negatives to actually drive most automations with.  Instead I figured I could just detect if my webcam was plugged in and if so, send some signal to HA to turn on the light.  My first instinct was a cron job that checked the output of lsusb, which would have worked, but would have been pretty inefficient running every minute, and would have had a lag.  Instead, I began googling how to trigger an action when plugging in a USB device and discovered udev rules.

udev rules

udev rules are a way to trigger commands when various hardware events happen.  They're commonly used for doing certain things when a USB thumb drive is plugged in.  Anyway, I'm going to skip over a lot of saga here, but if you want to read more about udev rules and how to set them up, you can find a lot of info on Google.  Here is one page that describes some of the details a bit.

With that in mind, what I ended up with was a file named /etc/udev/rules.d/80-logitech-webcam-connect.rules with the following contents:
ACTION=="add"    KERNEL=="video0" SUBSYSTEM=="video4linux" SUBSYSTEMS=="usb" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="082c",  RUN+="/home/me/bin/webcam_alert/webcam_alert.sh connect"
ACTION=="remove" KERNEL=="video0" SUBSYSTEM=="video4linux" SUBSYSTEMS=="usb" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="082c",  RUN+="/home/me/bin/webcam_alert/webcam_alert.sh disconnect"


A couple udev protips I figured out.  I know I didn't explain what any of these lines do, but each all caps word (besides RUN) is a filter, which you'll be looking for and then running the command when they all match.   Note the KERNEL=="video0" portion.  The script works without that, but I was getting duplicate executions, which didn't cause any issues but bothered me.

Another thing is that the longest part, by far, of all this was the point where I had all this working, except my shell script was seemingly just silently failing.  It would log to a file fine, but I slowly realized anything that require network access was just timing out.  Turns out this is due to firewall rules that prevent udev rules from connecting to remote machines.  The command sudo systemctl status systemd-udevd.service helped in debugging this, and then the command sudo systemctl edit systemd-udevd.service allowed me to add an exception for the machine that I have running MQTT.

Shell Script

That command will call this shell script.  You can see there is a connect or disconnect argument that is passed to the script.  The script also logs which argument was passed to it, which helps with debugging a lot.  That script is just taking that connect or disconnect argument and turning it into an ON or OFF payload to send to an MQTT sensor in Home Assistant.

MQTT Auto Discovery Sensors

I am a big fan of MQTT auto discovery sensors in Home Assistant.  If you don't know, MQTT is just a protocol for sending messages around your network, which is used heavily by smart home senors, particularly once you get into more DIY stuff.  You set up a "broker" and then devices can publish JSON messages to "topics", which are just file paths.  Then other devices can subscribe to those topics and receive anything published to them.  If you set up MQTT and want to play around with messages or just see what messages are being sent, I recommend MQTT Explorer as a really good GUI tool.

You can get away without any MQTT for a while, but a lot of the more DIY sensors use it, and once you have it set up it unlocks a lot of capabilities.  For example, I'm using it for both ESPHome and Zigbee2MQTT, where I use it to send all data from my zigbee sensors back to HA.  I was hesitant to do that for a while because I assumed it would introduce lag, but I can't detect any delay in something like a door sensor opening and triggering a light to turn on.

Anyway, once you have MQTT set up, you can use a feature of it in Home Assistant called Auto Discovery.  This has to be enabled, but once you do HA will look for topics matching a certain pattern and when it finds them it'll automatically create a new sensor.  Like I said, I'm a big fan of this, and I've made a project that uses it to grab local weather data and send it to Home Assistant.

You can check out the above shell script if you're interesting in making your own custom MQTT auto discovery sensors, it's the simplest I could set it up.  You need to send two different topics, one to configure the sensor and then another for the data.  The script is a bit inefficient in that it sends both the configuration and data every time it runs, but I can live with that considering it only runs a couple times a day.  The weather data project I linked to is a much better done example of MQTT auto discovery sensors.

Home Assistant

At this point it's simple.  The webcam state gets picked up by HA as a binary sensor with an On and Off state.  I just set up an automation to turn on the light via the Lutron integration whenever it transitions from off to on, and vice versa.

For some reason it detects unplugging faster than plugging in, but it's about a second from plugging in to light being on, and just about instant for unplugging.  The timing is a bit of a moot point as it takes a few seconds for Hangouts to detect the camera before I can join a meeting anyway.

Summary

And there you go.  When I plug in my webcam, a custom udev rule triggers, which runs a custom shell script that publishes a MQTT message to a topic which Home Assistant has been configured to automatically detect and treat as a native sensor, and that sensor then triggers an automation which tells the Lutron integration to turn on the lights, simple.  I've been using this for a week now, and it's performed flawlessly, despite the fact that I'm still shocked anytime anything I make works even once.