Use Case
Cameras as Sensors
A camera can see anything a person can see. Point one at the thing you care about, and it tells you when that thing changes, in about 200 bytes. The footage never leaves the device.
The sensor you need doesn't exist
Almost every business has physical state nobody is measuring, not because it isn't worth knowing, but because there's no sensor for it, or fitting one means an electrician.
No sensor exists
Is the dumpster full? Did the delivery arrive at the back door? Is bay 2 free? Is the shelf empty? Nobody makes a sensor for these.
So a person checks
Or nobody checks, and you find out on Monday that the walk-in door was open all weekend.
Or you buy surveillance
Which uploads video you don't want, to a cloud you're now paying for, and still doesn't tell you when the thing happened.
Answers, not footage
The camera runs the detector on-device and publishes a small message only when the state changes. A stable scene sends nothing at all.
Point it at something
Pick the region you care about. Use roi_level() to read its brightness while the thing changes, and choose a threshold between the two numbers you see. No guessing, no training data.
Write the loop once
Adapting this to a different problem is a config change, not a rewrite.
import time
from tendrl import Client
from tendrl.vision import Camera, Watch, roi_brightness
client = Client(mode="sync")
client.start()
time.sleep(5)
cam = Camera()
watch = Watch(
client,
detect=roi_brightness(roi=(120, 80, 60, 90),
threshold=0.45,
states=("full", "empty")),
debounce=5, # frames that must agree before believing a change
cooldown_s=30,
tags=["coffee"], # routes to a flow / Strand workflow
)
while True:
watch.update(cam.snapshot()) # publishes ONLY on a real transition
time.sleep_ms(500) The tag is the integration
On a change, the device publishes {"state": "empty", "prev": "full", "conf": 0.81} with your tag. A Strand workflow keyed on the same tag turns it into a Slack message, a log entry, or a page. Built visually, no code.
Most of this needs no AI at all
The classical detectors need no model, no training data, and no heap for weights. Reach for them first: they cover far more real problems than people expect.
roi_brightness
A region gets lighter or darker. Coffee pot empty, tank level, shelf stocked, bin full, gate open.
color_blob
A colour appears. Indicator lamps, status LEDs, machine signal towers.
motion
The scene changed. Driveways, trap cams, after-hours activity.
person
Someone is there. Uses OpenMV's built-in model. Weights live in flash, so there's no custom model to train or fit.
And when you do need a model, three more ship in flash
Google's MediaPipe models come with the firmware on the AE3 and N6: nothing to train, nothing to upload, call the detector and it's there. Older boards ship a different model set, and the Device tab greys out any detector whose model the connected board doesn't carry.
face
A face is in frame, with eyes, nose, mouth and ears as keypoints. The cheapest of the three, and the one to try on a board without an NPU.
palm
A hand is in frame, outlined by wrist, knuckles and thumb. Wants an NPU (OpenMV AE3 or N6) to run in real time.
hands
All 21 joints per hand, labelled left or right, fingers and fingertips included. Two models in series, so it wants an NPU too.
Why it stays quiet
A raw detector flips on every shadow, every passing cloud, every bad exposure. Watch is the difference between a demo and something you leave running for a year.
Debounce
A change must hold for N consecutive frames before it counts. Single-frame flickers are ignored.
Transition-only
It publishes once, when the state changes. While the scene is stable it sends nothing: not a heartbeat, not a frame.
Cooldown
A floor between events. A change that comes due mid-cooldown is deferred, not dropped.
This is not an optimization; it's the product. Video is expensive to move, store, and scan. A state change is 200 bytes. The on-device detector's real job is deciding which rare moments are worth the bytes.
Let Claude look through the lens
The hard part was never the code. It's knowing what your scene actually looks like at 3pm in a dim warehouse.
The local dev MCP server gives an AI
assistant a capture_frame tool that returns a real image from your
board. So it doesn't guess: it looks at the actual scene, writes the detector
against what it sees, runs it on the device, looks again when it misfires, and tunes the
threshold. Point the camera, say what you want to know, and watch a Slack message arrive.
> capture_frame
[image: your actual coffee pot, QVGA, ~14 KB]
"I can see the carafe against a light wall. I'll watch the region below
the handle; it goes light when the pot empties. Let me measure first."
> run_script: print(roi_level(cam.snapshot(), (120, 80, 60, 90)))
0.31 # pot full
0.62 # pot empty
→ threshold 0.45 sits neatly between them What you need
An OpenMV camera
Any OpenMV runs the classical detectors: H7 Plus, RT1062, AE3, N6. The in-flash models below need an AE3 or N6. Vision requires the OpenMV csi/sensor module, so it does not run on an ESP32.
Wi-Fi and an API key
Three lines in config.json. Or provision the board from your browser over WebSerial; the key never touches our servers.
The vision extension
One mpremote mip install from your computer (OpenMV firmware has no on-device mip), or pick the vision tier when provisioning. ~40 KB of flash on top of the SDK.
Point a camera at the thing nobody is watching.
Tendrl