This is a template ability that lets your OpenHome agent look at a live RTSP camera feed and answer spoken questions about what it sees. It grabs a single frame from the camera on the DevKit, sends it to OpenAI's vision model (gpt-4o), and speaks back a natural description — and it remembers the conversation so follow-up questions work.
The cloud side (main.py) handles intent and conversation; the device side (devkit_functions.py) runs on the DevKit, captures the frame with ffmpeg, and calls OpenAI. The two sides talk through send_devkit_capability_action().
⚠️ Local abilities cannot be tested in the Live Editor. They run on a connected DevKit device.
- "What's happening?" room monitor — describe the scene on demand
- Doorway / entry watch — "is anyone at the door?"
- Object / people counter — "how many people are standing?"
- Multi-camera switching — ask about a second camera in the same conversation
- Accessibility describer — narrate a live view for low-vision users
- Read-the-sign helper — "what's written on the wall?"
- An OpenHome DevKit (or local machine) running the device-side bridge.
ffmpeginstalled on the device (used to grab one frame over RTSP).requestson the device for the OpenAI call.- One or two RTSP camera URLs.
- An OpenAI API key with access to
gpt-4ovision.
Edit the constants at the top of main.py:
RTSP_URL_1 = "rtsp://<user>:<pass>@<camera-ip>:554/<stream-path>"
RTSP_URL_2 = "rtsp://<user>:<pass>@<camera-ip>:554/<stream-path>"
OPENAI_API_KEY = "sk-REPLACE_WITH_YOUR_OPENAI_KEY"RTSP_URL_1is the default camera;RTSP_URL_2is the "other / second" camera.- Replace the OpenAI key with your own (use a placeholder before submitting the template).
Add it to your agent from the OpenHome dashboard or GitHub, set trigger words, then power on the DevKit, connect it, and say a trigger word.
This template uses generic triggers — customize these for your ability:
- "what do you see" / "check the camera" / "look at the camera"
- Configure your own trigger words in the OpenHome dashboard.
- User asks a question about the camera
- The LLM classifies the message (ask vs. exit, which camera, and a short acknowledgement)
- The ability speaks the acknowledgement ("Looking for plants in the camera")
- The DevKit grabs a frame and OpenAI describes it
- The answer is spoken, and the exchange is added to the conversation history for follow-ups
- The loop continues until the user exits
wait_for_complete_transcription()captures the question_classify()callstext_to_text_response()withINTENT_PROMPTand parses a JSON object:{ "intent": "ask|exit", "camera": "camera_1|camera_2", "ack": "<short spoken line>" }- The chosen RTSP URL and the labeled question (plus JSON history) are dispatched to the device:
result = await self.capability_worker.send_devkit_capability_action(
"describe_room",
[url, OPENAI_API_KEY, labeled, json.dumps(history)],
DEVICE_TIMEOUT,
)_read_result()parses the device response and maps failurereasoncodes to friendly spoken messages- On success, the user message and answer are appended to
history(capped atHISTORY_MAX)
_grab_frame()runsffmpegover RTSP (TCP transport) to capture one JPEG frame- The frame is base64-encoded into a data URL
- Prior conversation history is prepended, then the frame + question is sent to OpenAI (
gpt-4o,temperature=0) - The result is emitted as JSON on stdout:
{"ok": true, "answer": "..."}on success{"ok": false, "reason": "camera|openai|auth|config|empty"}on failure
- The
SYSTEM_PROMPTinstructs the model to describe only what's actually visible and to answer naturally for speech.
Dispatches the describe_room action with the camera URL, API key, labeled prompt, and JSON history; waits up to DEVICE_TIMEOUT seconds.
Grabs a frame and calls OpenAI vision. Returns a structured JSON result that the cloud side maps to a spoken answer or a specific error message.
User: "what's happening?" → AI: "Looking at what the camera sees." → AI: (describes the scene)
User: "how many plants are in the picture?" → AI: "Looking for plants in the camera." → AI: "I can see two potted plants on the windowsill."
User: "what's on the other camera?" → AI: "Checking the other camera now." → AI: (describes camera 2)
User: "okay that's all, thanks" → AI: "Okay, all done." (ability exits)
Add more RTSP_URL_* constants and extend the camera selection logic in _classify() / run().
Edit OPENAI_MODEL, max_tokens, or the image detail in devkit_functions.py.
Edit SYSTEM_PROMPT (how answers are phrased) and INTENT_PROMPT (how intent and acknowledgements are produced).
HISTORY_MAX controls follow-up memory; DEVICE_TIMEOUT, CAPTURE_TIMEOUT, and OPENAI_TIMEOUT control how long each step waits.
- Acknowledge before fetching — the spoken
ackkeeps the experience responsive while the frame is captured. - Never invent detail — the system prompt forces the model to describe only what is visible.
- Handle device-down gracefully —
_read_result()always returns a spoken message, even on failure. - Always call
resume_normal_flow()— thefinallyblock guarantees control returns to the Agent.
Check the RTSP URL, credentials, and that the camera is reachable from the DevKit. Look for _grab_frame: ffmpeg failed in the device logs.
The OpenAI call returned 401/403 — verify OPENAI_API_KEY.
The frame was captured but the model returned an empty answer — try rephrasing or check the camera view.