The lamp on my office shelf glows solid red the instant my camera goes live, and turns off the second the meeting ends. Nobody has to knock and wonder. My wife, a kid, the dog-walker — anyone who glances in knows I’m on camera without me saying a word. And by my own stubborn definition, that little bulb is a robot.
The KAUF A21 bulb doing its one job. When my camera's live, this is what the doorway sees.
If Software Changes the Real World, It’s a Robot
Regular readers know I define robotics broadly. If software senses something and then changes something in the real world, I call it a robot. Trading bots, GitOps pipelines, my pool automation — all robots in my book. Most people want a robot to have arms and wheels. I don’t care about the arms.
This thing senses (is my webcam in use?) and acts (make a physical light turn red). That’s the whole loop. It’s a tiny robot that lives on a shelf, and it does exactly one job perfectly. This is “software for one” — I built it for me, my office, my problem. It’ll never be a product. It doesn’t need to be.
One thing up front: this is Linux-only, on purpose, from the start. The trick that makes it work — cheaply asking the kernel “who has the camera open right now?” — is a Linux thing. No polling a cloud, no vendor SDK, no calling Zoom’s API. Just the OS telling me the truth about my own hardware. If you’re on a Mac or Windows, the idea transfers but this exact plumbing does not.
The Problem Is Human, Not Technical
Working from home, the hard part of being on camera isn’t the camera. It’s everyone else in the house not knowing. You’ve lived this: mid-sentence in a meeting, door opens, someone’s holding up a package with a questioning look. Not their fault — there was no signal.
I wanted a signal that’s impossible to miss and requires zero effort from me. No app to remember to toggle. No “mute yourself and mime that you’re busy.” The camera turning on is the trigger. The light follows automatically. If I have to remember to flip it, I’ll forget, and then it’s worthless.
How It Works
Two pieces of hardware and one small daemon.
The fixture is a cheap vintage industrial steampunk table lamp — an E26 socket and some personality, sold without a bulb. The brains is the bulb: a KAUF A21 RGBWW smart bulb. I picked KAUF on purpose. It runs ESPHome out of the box and speaks a plain local REST API. And yes, it’s Home Assistant compatible — but I don’t run Home Assistant. I drive my Tasmota and KAUF devices directly over HTTP. No proprietary cloud, no “you are the product” account in the middle. I can curl it on my own LAN. That matters to me.
The daemon is gocamdet, a little Go program I wrote that watches USB camera in-use state:
gocamdet --watch --interval 2s --script /home/gherlein/bin/lights-meeting.sh
Every two seconds it checks whether anything is holding the camera open. My Logitech MX Brio and the V-Z632 both flip cleanly as Chrome, Slack, and the rest grab and release them. On a state change, gocamdet doesn’t know or care about lights — it just runs my hook script and tells it on or off. That separation is deliberate: the daemon detects, the script decides what to do about it. Swap the script and the same daemon could ring a bell or post to MQTT.
The hook is a dozen lines of shell that talk to the bulb (hostname lights-meeting) over the ESPHome REST API:
That’s it. Camera goes live → bulb turns red. Camera released → bulb turns off. A physical, honest, on-air light.
The same light across the room in the actual office — mid-meeting, webcam up top, a terminal on the big screen. Glance in and you know.
Running As Me, Not Root
Small but I care about it: the systemd unit runs as User=gherlein, not root. There’s no reason for root here. The camera is held by my apps, running as me, so I can see its in-use state without elevated privileges. The hook lives in my home directory. The bulb is just an HTTP endpoint on my LAN.
This is the Rickover reflex talking — least privilege isn’t paranoia, it’s just how you build things that don’t bite you later. If a job doesn’t need root, it doesn’t get root. A webcam status light absolutely does not need root.
What’s In the Repo vs. What’s On My Desk
In the spirit of being honest rather than polished: what’s published isn’t a turnkey copy of what’s running in my office. The gocamdet repo ships a generic gocamdet.service and a hook.sh.example that just logs to the journal — a safe, do-nothing default so you can see the detection working before you wire it to anything. The real light-driving script, lights-meeting.sh, lives in my ~/bin and is customized to my bulb, my hostname, my colors. That’s the right split: the tool is general, the reaction is personal. Clone the daemon, write your own three lines of hook.
What’s Next
Right now it’s binary: on camera or not. But there’s a state I care about that this misses — mic live, video off. Plenty of meetings I’m talking but not on camera, and someone walking in still interrupts a live mic. So the next iteration: blue when the microphone is in use but the camera isn’t, red when the camera’s live. Same daemon, same idea, one more thing to sense and one more color to act on. Detecting mic-in-use on Linux is a slightly different rabbit hole than the camera — that’s a topic for another day.
And bigger picture: right now gocamdet is a standalone daemon shelling out to a script. The real destination is folding it into Gorai, my open-source Go home-automation and robotics framework. Because — say it with me — a thing that senses the world and acts on it is a robot, and Gorai is where my robots live. Once the camera-and-mic light is a Gorai node instead of a one-off daemon, that state becomes just another signal on the bus, and anything else in the house can react to it. This little light was the toe in the water. If you want the bigger picture, I announced Gorai here.
Gorai — the framework all my little robots are going to move into. Even the ones that are just a lightbulb.
If you build one, or you’ve got a cleaner way to detect mic state on Linux, drop me a note on LinkedIn. I’d love to see it.