Getting video from an embedded camera to a remote viewer usually involves setting up an RTSP server, configuring port forwarding or a VPN, and building a viewer client. With Tendrl's MicroPython SDK, the entire pipeline is handled for you: your device streams JPEG frames over HTTPS, Tendrl relays them via Server-Sent Events, and you view the feed in the dashboard from anywhere.
What you'll need
- An OpenMV Cam (H7 or M4) with MicroPython
- OpenMV IDE for uploading scripts
- A Wi-Fi network
- A free Tendrl account
Step 1: Create an entity with streaming permission
In the Tendrl dashboard, create a new entity (e.g., openmv-cam-1). Make sure the entity's role includes the entity:Stream permission; the built-in DefaultEntity role includes this by default. Copy the entity API key.
Step 2: Configure the device
Create config.json on the OpenMV's flash filesystem (/flash/config.json):
{
"api_key": "YOUR_ENTITY_API_KEY",
"wifi_ssid": "YOUR_WIFI",
"wifi_pw": "YOUR_PASSWORD"
} Step 3: Upload the streaming script
This is the entire script. The SDK handles Wi-Fi connection, camera configuration (QVGA, JPEG, quality 70), the multipart streaming protocol, and reconnection on disconnect.
import asyncio
from tendrl import Client
async def main():
client = Client(mode="async", debug=True)
client.start()
await asyncio.sleep(5)
# Camera auto-configured: QVGA, JPEG, quality 70, 15 FPS
client.start_streaming()
try:
await asyncio.sleep(3600)
except KeyboardInterrupt:
pass
finally:
await client.async_stop()
asyncio.run(main()) Upload via OpenMV IDE, then reset the board. Within a few seconds you should see debug output confirming the Wi-Fi connection and streaming start.
Step 4: View the stream
Go to your entity's detail page in the Tendrl dashboard. A camera icon appears when the stream is active; click it to open the live viewer. The feed works from anywhere with a browser, no local network access required.
Tuning for your network
The defaults (15 FPS, quality 70, QVGA) work well on most Wi-Fi networks. If you're on a slower connection or want higher quality, adjust:
# Slower network: lower FPS and quality
client.start_streaming(target_fps=10, quality=50, framesize="QVGA")
# Better image quality: larger frames
client.start_streaming(target_fps=15, quality=45, framesize="VGA")
# Constrained bandwidth: tiny frames
client.start_streaming(target_fps=15, quality=50, framesize="QQVGA") Bonus: stream + sensor data simultaneously
Since streaming runs as a background task, you can publish MQTT messages at the same time. The SDK prioritizes messaging over video frames so your telemetry is never delayed:
async def main():
client = Client(mode="async", debug=True)
client.start()
await asyncio.sleep(5)
client.start_streaming()
# Publish sensor data alongside the video
while True:
client.publish(
{"temperature": 23.5, "status": "streaming"},
tags=["sensors"]
)
await asyncio.sleep(10) How it works under the hood
The SDK sends JPEG frames over a long-lived POST /api/stream connection using the multipart/x-mixed-replace protocol. Tendrl authenticates with the entity API key, checks the entity:Stream permission, and relays frames to connected viewers via Server-Sent Events. Each SSE event contains a base64-encoded JPEG frame.
Limits: max 10 MB per frame, 30 FPS ceiling, 24-hour max connection, one stream per entity. Idle timeout is 5 minutes (no frames).
What's next
- Custom capture: provide your own capture function for non-standard cameras or image processing
- Trigger workflows: tag your sensor messages and use Strand to send alerts when readings go out of range, with a link to the live camera feed
- Multiple cameras: create one entity per camera and view all feeds from the dashboard
Tendrl