← Back to the blog

Recipe: connect an ESP32-CAM and IoT data

Stream MJPEG frames from a two-dollar camera over one WebSocket, send sensor readings on the same connection, ingest MQTT from your fleet, and turn any of it into a workflow trigger.

3 min read
Leer en español

Why IoT ingest

An ESP32-CAM costs a couple of dollars and has no H.264 encoder, so the usual RTMP path needs a transcode hop. StreamHub's WebSocket ingest takes the camera's raw MJPEG frames directly — and lets the same device send sensor data on the same socket. Add MQTT-in for the rest of your fleet, and every reading becomes an event you can automate on.

Architecture in words

The device opens one WebSocket to /ingest/ws, authenticated with a wsk_ key, and sends complete JPEG frames as binary messages. Viewers pull those frames as MJPEG with no relay and no transcode. On the same socket the device can send text data frames, and a separate MQTT-in subscriber pulls messages from your broker; both converge on a single iot.data event that fans out to webhooks and Streamflow.

1. Provision a WebSocket camera

export BASE="https://your-server.example.com/api/v1"
export TOKEN="sk_...."

curl -s -X POST $BASE/apps/iot/ws-ingest -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"room":"cam1","identity":"porton-norte"}'

The response returns an id (wsi_…), a streamKey (wsk_…), and the wsUrl, mjpegUrl and playerUrl.

2. Point the device at the socket

The ESP32 connects and streams JPEG frames:

wss://your-server.example.com/ingest/ws?app=iot&room=cam1&key=wsk_...

Each binary message is one complete JPEG (FF D8 … FF D9). On connect the server sends a ready frame with the limits it will enforce (maxFps 15, maxFrameBytes, and the data-frame caps).

3. Send sensor data on the same connection

The device sends a text frame to publish a reading:

{ "type": "data", "topic": "door", "data": { "open": true, "battery": 87 } }

That becomes an iot.data event { topic, deviceId, data, source: "ws" }. Data-frame limits (from features.ws_ingest): max_data_bytes 16384, max_data_per_sec 5 — an oversize data frame is dropped (only an oversize JPEG closes the socket).

4. Ingest MQTT from the rest of the fleet

Add a subscribe list to the app's mqtt config so broker messages become the same event:

mqtt:
  enabled: true
  url: "mqtt://broker.example.com:1883"
  subscribe:
    - topic: devices/#

Each inbound message dispatches iot.data { topic, data, source: "mqtt" } (JSON when valid, else the raw string), capped at 64 subscriptions and 64 KB per message.

5. Watch the camera and trigger a workflow

View the live MJPEG with no token when public playback is on:

https://your-server.example.com/live/iot/cam1/mjpeg
https://your-server.example.com/live/iot/cam1/frame.jpg

Then, in Streamflow, a trigger.mqtt box firing on topic door (matched from either the WS data frame or MQTT-in) can capture a snapshot and notify you the instant the gate opens.

What you should see

The camera's frames appear at the MJPEG URL within seconds of the device connecting; each sensor reading and each broker message shows up as an iot.data event in your webhook log; and a Streamflow workflow keyed to the topic runs on cue.

Troubleshooting

  • Socket closes immediately — check the key, app and room match the provisioned camera; a mismatch closes with 4401, a disabled feature or exceeded quota with 4403.
  • Frames rejected — a JPEG over max_frame_kb (256 KB default) closes with 4413; lower the camera's resolution/quality.
  • No audio, no recording — WS ingest v1 is video and data only: no audio (the camera has no mic), no MP4/HLS recording, no adaptive bitrate. For full media, use the ffmpeg-relay-to-RTMP path instead.
  • Data-out? — receiving data back on the device over the socket is not implemented yet; this wave is data-in only.

Closing

Cheap cameras and sensors, streamed and automated on your own server — that is the IoT story without a device cloud. If you want firmware, a fleet provisioning flow, or the workflows wired end to end, our team builds those around the open-source ingest as a consulting engagement.