- Python 87.6%
- Shell 12.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
camera-sign build-depends on python3-kasa from the PPA, which a bare
ubuntu container cannot resolve ("you have held broken packages"). The
registry mirrors the PPA, so adding it to the container's sources gives
the build the same archive Launchpad's builders see.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
| .forgejo | ||
| .github | ||
| debian | ||
| src/camera_sign | ||
| systemd | ||
| tests | ||
| .commitlintrc.yaml | ||
| .gitignore | ||
| .markdownlint.yaml | ||
| .pre-commit-config.yaml | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
camera-sign
Background
I work from home and live with a disabled and self-conscious wife. I got tired of her asking me, with an anxious voice, "Is the camera on?" And she got very upset the one time she forgot to ask and I told her, after she had walked across my background, "By the way, the camera was on just now." Hence this app.
Functionality
Light an "ON AIR" sign somewhere in the house whenever the webcam is live, so the people at home know not to walk in.
It works with any program that uses the camera -- Teams in a browser,
Zoom, OBS -- because it watches the camera itself rather than any one
app. When anything opens the camera, a Kasa smart plug switches on and powers
the sign. When the camera has been closed for a few seconds, the plug switches
off.
It can watch an Android phone's cameras as well, so a video call taken on
the phone lights the sign just as one on the laptop does. The phone is
reached over adb, by USB cable or over Wi-Fi; see
Watching a phone too.
Hardware
| Part | Role | Price |
|---|---|---|
TP-Link Kasa EP10 smart plug |
Switches the sign's power | $10 |
Cjgyz 8.5 × 11 tabletop LED light box |
The sign | $26 |
| Inkjet transparency film, US Letter | The "ON AIR" insert | $7 |
The sign has to light as soon as it gets power. Before relying on it, plug
it into the EP10, turn it on at its own switch, then turn the plug off and on
from the Kasa app. If the sign stays dark after the plug comes back on, it
needs a press of its button after every power cut and cannot be used this
way. The one I bought works fine.
The insert
There are two to choose from: print/on-air.svg, a plain
"ON AIR" printed landscape, and print/camera-on.svg,
printed portrait, which reads "CAMERA ON (when lit)" and "ENTER AT OWN RISK"
around a laptop on a video call.
Print either one at 100% scale, in its own orientation, on the rough (coated) side of the film. Let it dry fully before handling it. Inkjet black lets some light through; if the background glows, print a second copy and stack the two sheets, lined up, in the frame.
How it detects the camera
Every process that opens a USB webcam raises the uvcvideo driver's
reference count, in /sys/module/uvcvideo/refcnt, by one. The watcher reads
that one file every second. It needs no special permissions and sees the
camera no matter which user or program opened it.
Browsers sometimes open the camera for a moment just to list devices, so the sign lights only after the camera has stayed open for 2 seconds, and goes dark only after it has stayed closed for 5.
The watcher also switches the sign off when it stops (at logout, for instance) and just before the laptop suspends or hibernates, so the sign is never left lit by accident.
When the plug stops answering
A plug that has dropped off the network fails silently: the camera opens, and the sign stays dark. So the watcher tells the plug what to do once a minute even when nothing has changed, and, while that fails, tries again every second. The moment the plug stops answering, a desktop notification says so, and it stays on screen until dismissed; when the plug answers again, a second notification replaces it. The journal records both as well.
A plug can stop answering this one laptop while the Kasa app and other
machines on the network still reach it. That happened once after a Wi-Fi
outage, lasted about 50 minutes, and cleared without anything being changed;
unplugging the plug did not help. Until the second notification appears,
switch the sign by hand, from the Kasa app or the plug's button.
On a phone
Android keeps a list of the apps holding each camera open, and
adb shell dumpsys media.camera prints it under Active Camera Clients:
[] when nothing has a camera, or one line per app -- the camera app, a
video call, anything. The watcher reads that list once a second, but only
while the webcam is idle, since either one is enough to light the sign.
Each check takes about a fifth of a second, over USB or Wi-Fi.
A phone that does not answer within 3 seconds -- out of the house, asleep
off the network, unplugged -- counts as idle, so the webcam goes on working
alone. A desktop notification says so, as when the plug stops answering,
and a second one replaces it when the phone comes back; the journal records
both, once each, not every second. So carrying the phone out of the house
raises the notification, and coming home clears it. The adb server forgets a Wi-Fi phone when the network drops or
the server restarts, so while one is not answering the watcher runs
adb connect again every 30 seconds, without holding up the webcam check.
Install
On Ubuntu 24.04 (noble), 26.04 (resolute) or 26.10 (stonking), the
Debian package in
ppa:pillarsdotnet/ppa
brings the python3-kasa it needs from the same PPA too:
sudo add-apt-repository ppa:pillarsdotnet/ppa
sudo apt install camera-sign
It installs the command, the systemd user unit (enabled for nobody), the
example settings at /usr/share/doc/camera-sign/examples/env.example, and the
printable inserts in /usr/share/camera-sign/print/. Then set it up with
/usr/share/doc/camera-sign/README.Debian: the same steps as below, but using
the copies the package installed instead of downloading them.
Elsewhere, install it with uv:
-
Set up the plug in the
Kasaapp, then give it a fixed address with a DHCP reservation on the router. Find its address withkasa discover. -
Install the command:
uv tool install git+https://github.com/pillarsdotnet/camera-sign -
Tell it where the plug is, and check it works:
mkdir -p ~/.config/camera-sign curl -fsSL https://raw.githubusercontent.com/pillarsdotnet/camera-sign/main/systemd/env.example \ > ~/.config/camera-sign/env $EDITOR ~/.config/camera-sign/env set -a; . ~/.config/camera-sign/env; set +a camera-sign on && sleep 3 && camera-sign offNewer
Kasafirmware refuses local commands without the TP-Link account the plug was set up with. Ifcamera-sign onfails with an authentication error, setCAMERA_SIGN_USERNAMEandCAMERA_SIGN_PASSWORDin that file. -
Run it as a service:
curl -fsSL https://raw.githubusercontent.com/pillarsdotnet/camera-sign/main/systemd/camera-sign.service \ > ~/.config/systemd/user/camera-sign.service systemctl --user daemon-reload systemctl --user enable --now camera-sign journalctl --user -u camera-sign -f
Watching a phone too
This needs adb (sudo apt install adb) and a phone running Android 11
or later.
-
Turn on USB debugging on the phone: Settings → About phone → tap Build number seven times, then Settings → System → Developer options → USB debugging.
-
Plug the phone in, unlocked. A Pixel's USB mode defaults to No data, and
adbcannot see it until that changes: open Settings → Connected devices → USB and choose File transfer. Accept the "Allow USB debugging?" prompt, ticking Always allow, andadb deviceslists the phone asdevice.If
lsusbshows no Google device at all, the phone is not making a data connection. Try the other way up at the phone end, clear lint out of its port with a wooden toothpick, or try another cable; some cables only carry power. -
Move it to Wi-Fi, so the phone can be unplugged. Give it a fixed address with a DHCP reservation, as for the plug, then:
adb tcpip 5555 adb connect 192.168.1.51:5555The phone stops listening on Wi-Fi when it restarts; plug it in and run
adb tcpip 5555again. -
Add the phone to
~/.config/camera-sign/env, check it, and restart the service:echo 'CAMERA_SIGN_PHONE=192.168.1.51:5555' >> ~/.config/camera-sign/env camera-sign status --phone 192.168.1.51:5555 systemctl --user restart camera-signUse the serial from
adb devicesinstead to stay on the cable. The journal reportswatching the cameras on phone ...at startup; open the phone's camera, and the sign lights two seconds later.
Wireless debugging instead of a cable
The adb tcpip 5555 route above has a catch: the phone forgets it at every
restart, and turning it back on needs the USB cable again. Android 11's
wireless debugging can take its place, with a different catch: the phone
turns wireless debugging off at every restart too, along with its port
number, but turning it back on needs only a tap on the phone -- no cable.
-
On the phone, turn on Settings → System → Developer options → Wireless debugging. A laptop the phone already trusts over USB debugging needs no pairing; a fresh one pairs once with
adb pair, from the Pair device with pairing code button in the same screen. -
Wireless debugging listens on a different port after every restart, so a fixed
address:portcannot name it. The watcher finds the current port over mDNS instead, withavahi-browse(sudo apt install avahi-utils), givenmdns:and the phone's real serial fromadb devices:echo 'CAMERA_SIGN_PHONE=mdns:52221JEBF23974' >> ~/.config/camera-sign/env camera-sign status --phone mdns:52221JEBF23974 systemctl --user restart camera-sign -
After every phone restart, turn wireless debugging back on -- the phone always switches it off. Give it a quick-settings tile so that takes one swipe and a tap: in Settings → System → Developer options, open Quick settings developer tiles and switch on Wireless debugging. The tile joins the quick-settings panel by itself.
After a restart, swipe down from the top of the screen (twice, or once with two fingers, for the full panel) and tap the Wireless debugging tile. The first tap on a network asks "Allow wireless debugging on this network?" -- tick Always allow on this network, and later taps at home will not ask. The watcher then finds the new port by itself, within half a minute; nothing needs touching at the laptop end. Until the toggle goes back on, the phone counts as idle, the desktop notification says so, and the webcam still lights the sign.
To stop the phone lighting the sign for a while -- say, a video call on it from another room -- switch the phone off, and back on afterwards:
camera-sign phone off
camera-sign phone on
camera-sign phone # say which it is
The running service notices within a second, with no restart, and logs the
change. The setting is a file, ~/.local/state/camera-sign/phone-off, so it
lasts through restarts and reboots until switched back on. The webcam is
watched either way.
Usage
camera-sign [watch] keep the sign in step with the camera (the default)
camera-sign on|off switch the sign once
camera-sign status say whether the camera is in use
camera-sign phone [on|off]
watch the phone's cameras or not, or say which
Add --phone SERIAL (or set CAMERA_SIGN_PHONE) to watch a phone's cameras
too, with watch or status. Run camera-sign --help for the timing
options.
Development
uv sync
uv run pytest
uv run ruff check . && uv run ruff format --check .
pre-commit install
Commit messages follow Conventional Commits.
Licence
GNU General Public License, version 3 or (at your option) any later
version; see LICENSE.