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.

Coming soon Vision docs

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.

1

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.

2

Write the loop once

Adapting this to a different problem is a config change, not a rewrite.

main.py: the whole thing
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)
3

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.

The authoring loop
> 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.

Coming soon Read the vision docs