He gets off the bed at 3am.
Now somebody knows.

A local computer-vision alert system that watches the floor beside a bed — not the bed itself. When the cat steps down, it sends a Telegram message so someone can move him before the carpet suffers. It stays completely silent when the only thing moving is you.

Oneengineer — designed, built and maintained solo
~USD 18total hardware cost, one camera
Zerovideo ever written to disk

How it works

Four stages, each one cheap enough to throw away most of the work before the expensive stage runs.

1

Motion gate

The camera streams over RTSP to Frigate on the local network. A motion mask covers the bed, so movement there never wakes the detector.

2

Object detection

Only surviving motion reaches the model, and only the cat class is tracked. People are discarded outright.

3

Zone test

An alert requires the object to be inside a hand-drawn floor polygon for three consecutive frames. The bed sits entirely outside it.

4

Alert

A small Flask service listens on MQTT, checks the armed state and a cooldown, then sends one Telegram message to a family group.

Design decisions

The parts that were actually interesting to get right.

Geometric, not probabilistic

An alert needs the label cat and presence in the floor zone. The bed is outside that polygon, so no confidence threshold or model update can produce an alert about the sleeping human.

Tuned to prefer false positives

The threshold is deliberately permissive. A false ping costs one message; a miss costs a mattress. Optimising for precision would optimise the wrong metric.

Fails loud in setup, silent in operation

The wizard refuses bad input outright. The listener never crashes on a malformed MQTT payload or a failed send — it logs and carries on. A dead listener is worse than a missed message.

A control page with no JavaScript

The arm/disarm page ships a strict CSP with no script-src. Every animation — the cat waking, pupils dilating, lids lifting — is CSS keyframes on clipped SVG geometry.

Nothing leaves the house

Recording is disabled by design — this is a bedroom. Frigate binds to localhost only, the MQTT broker publishes no host ports at all, and the camera has no route to the internet. The only outbound traffic is a line of text to Telegram. No footage is stored, streamed or uploaded, ever.

Which is why there is no hosted demo. A public instance would mean streaming a bedroom to the internet, and every architectural decision here exists to prevent exactly that. The code and the reasoning are the deliverable.

What it doesn't do

Worth being honest about, because these are the real limits.

Three problems worth remembering

The bugs that took longest were not in my code. These are the ones I would want to talk about.

A string comparison in someone else's library

Frigate silently rejected my config and booted into safe mode with no camera. The cause was upstream: it decides whether zone coordinates are relative or absolute with p > "1.0" — a string comparison. "1.000" sorts after "1.0", so it treated fractions as pixel values and threw. Writing 1.0 instead of 1.000 fixed it. Found by reading the library source, not the logs.

A security hardening that broke the broker

The MQTT container crash-looped on exit 14. Its entrypoint starts as root and drops privileges, which needs SETUID and SETGID — the exact capabilities my cap_drop: [ALL] had removed. Rather than grant them back, I start the container as the unprivileged user directly, so there is no privilege drop to perform. Strictly stronger than the original.

A self-test that was lying

The suite reported all-clear while silently skipping checks. Two causes: ${#VAR:-0} is invalid bash and aborted the enclosing block, and the FPS probe called python3 — which on Windows resolves to a Microsoft Store stub that exits non-zero. A green test run that isn't running the tests is worse than a red one.

Animation with no JavaScript allowed

The control page ships default-src 'none' with no script-src. The wake-up sequence — sleepy lids, pupils dilating — is CSS keyframes on SVG geometry, with the eyelid as a rectangle sliding inside a clipping mask so it can never paint outside the eye. State transitions ride on the page load that the form POST already causes.