March 1, 2026 Hunter McGuire

Stream an OpenMV camera to the cloud

Six lines of MicroPython. Live video in your dashboard. No RTSP server, no port forwarding, no infrastructure.

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

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):

/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.

main.py (OpenMV Cam)
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:

Custom settings
# 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:

Streaming + messaging
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

Coming soon Streaming docs