Two demos in "letting the internet touch something at home, carefully":
- See where people are, without ever seeing the room.
local/runs YOLO person detection on a webcam and streams only coordinates outbound toremote/, a DigitalOcean droplet that republishes them as a public API. See below. - Let the internet draw on a physical e-ink photo frame.
frame-pi/runs a live, multiplayer paint canvas on a Raspberry Pi Zero W wired to a Waveshare e-ink HAT;frame-droplet/is a dumb reverse-proxy droplet that tunnels public traffic down to it overautossh— the Pi never accepts an inbound connection. Seeframe-pi/README.mdandframe-droplet/README.md, andexamples/reverse-proxy-tunnel/for the underlying pattern both halves follow.
local/ webcam -> YOLO person detection -> outbound-only WebSocket
remote/ DigitalOcean droplet: ingest coordinates, publish them as a public API
frame-pi/ multiplayer paint app + e-ink renderer, runs on the Pi, dials out via autossh
frame-droplet/ dedicated droplet: TLS + reverse proxy only, no app code, tunnel endpoint for frame-pi
local/opens an outbound WebSocket connection toremote/. It never listens on any inbound port, so nothing on the internet can reach into it.- The only thing that crosses the wire is a small JSON payload of normalized
detections (
x,y,width,height,confidence,timestamp, and for the general object feed, alabel). Video frames are read, run through YOLO, and discarded in the same function — see the comment inlocal/main.py. remote/never touches a camera, a frame, or an image decoder. It just holds the latest JSON snapshot in memory and republishes it over a public REST/SSE API.- The ingest path (
local -> remote) requires a shared-secret bearer token. The public read path (remote -> internet) is intentionally open, since the point is to make the coordinates public.
Remote first (needs a DigitalOcean droplet — see remote/README.md for
the Ansible deploy):
cd remote
cp .env.example .env # set INGEST_TOKEN
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 8000
Local:
cd local
cp .env.example .env # set RTSP_URL, REMOTE_WS_URL, INGEST_TOKEN (must match remote)
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python main.py
Then hit the public API:
curl https://your-droplet/api/people # just person positions
curl https://your-droplet/api/objects # everything YOLO currently sees, labeled
curl -N https://your-droplet/api/stream # live SSE feed of both
This is a demo, not a hardened product. Things worth calling out if you take
it further: the ingest token is a static shared secret (fine for a demo,
rotate/replace with mTLS or per-device tokens for anything real), the public
API has no rate limiting, and remote/ trusts whatever coordinates arrive —
it has no way to verify they came from a real camera versus a script feeding
it fake positions.
frame-pi/ and frame-droplet/ follow the inverse network shape from the
demo above: instead of the internet-facing side hosting the real app, the
real app (a live, multiplayer paint canvas) runs on the Pi, and the
droplet is just a dumb TLS-terminating reverse proxy. The Pi reaches it via
an outbound autossh reverse tunnel — same "never accept an inbound
connection" principle, just tunneling a whole app instead of one WebSocket.
See examples/reverse-proxy-tunnel/README.md for the underlying pattern,
and frame-pi/README.md / frame-droplet/README.md for quickstart, the
hardware constraints (the e-ink panel only does full-frame refreshes, so the
physical frame updates on a timer, not per stroke), and deploy order.
The shared canvas has no content moderation by design — anyone with the URL can draw anything, and it renders periodically on a physical object at home. The only guardrails are technical (a per-connection rate limit, an owner-only reset endpoint), not content filtering.