Skip to content

particle-iot/blueprint-tachyon-webcam-stream

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tachyon USB Webcam Live Stream

Stream live video from a USB webcam on a Particle Tachyon to a public URL, with device status and remote commands flowing through Particle Cloud events. No port forwarding, no fixed IP, no firmware, and no code changes: the whole application is one Docker Compose project.

  • Difficulty: Intermediate
  • Estimated time: 30 minutes
  • Hardware needed: Tachyon, USB (UVC) webcam
  • Code: this repository

Overview

The application runs as four containers on the Tachyon. Video flows out through a Cloudflare tunnel; control and status flow through the Particle Cloud. Both paths use only outbound connections, so everything works the same on Wi-Fi behind a home router or on 5G behind carrier-grade NAT.

USB webcam
    |
    |  /dev/videoN (V4L2, MJPG preferred)
    v
+-- Tachyon ---------------------------------------------------------------+
|                                                                           |
|  [camera]                [mediamtx]                 [app]                 |
|  ffmpeg capture --RTSP--> HLS / WebRTC <--HLS proxy-- landing page,       |
|      |                    server   ^                  snapshot, /api,     |
|      |                             +----status API--- Particle agent      |
|      +--JPEG--> (snapshots volume) ---------read----->  |          ^      |
|                                                         |          |      |
|  [cloudflared] <------------- HTTP (app:8080) ----------+          |      |
|      |                                                             |      |
+------|-------------------------------------------------------------|-----+
       |  outbound only                                              | HTTPS
       v                                                             v
  https://<random>.trycloudflare.com                        Particle Cloud
  public viewers (HLS in any browser)              events: online, status,
                                                   snapshot, cmd

The demo moment: power the device, watch the Console event log, and a tachyon-cam/online event arrives carrying a clickable public URL.

Tools & Materials

  • Particle Tachyon, set up and online in the Particle Console (desktop or headless OS variant; both work identically since the app is containerized)
  • A USB (UVC) webcam, for example a Logitech C920 class device
  • A USB-C hub if your webcam is USB-A

No other computer is required: every step happens in a shell opened through the Particle Console, and cloud identity comes from the device itself.

Prerequisites

  • A Tachyon that has completed setup and appears online in the Particle Console
  • Particle for Linux 0.20.2 or later and on-device Particle CLI 3.42.1 or later (check with particle version in the device shell)
  • The device identity file written during setup, ~/.particle/particle.config.json (verify with ls ~/.particle/particle.config.json). The app feeds these credentials to particle-api-js, Particle's supported JavaScript API client, so there are no user-created access tokens to manage.

Dependencies

All dependencies are pulled or built on the device by Docker Compose:

  • MediaMTX: the streaming server (RTSP ingest, HLS and WebRTC output)
  • ffmpeg (Alpine package, inside the camera container)
  • cloudflared: the outbound tunnel
  • Node.js 20 + Express (inside the app container)
  • particle-api-js: Particle's supported JavaScript API client, used for event publishing and the command subscription, installed by npm during the image build

Steps

All device-side steps run in a shell opened through the Particle Console, so you never need physical access beyond plugging in the webcam.

1. Plug in the webcam

Connect the USB webcam to the Tachyon (through a hub if needed). If the device is in an enclosure in the field, this is the only physical step.

2. Open a terminal through the Console

In the Particle Console, open your device's details page and select the Terminal tab, then open a new shell. Switch to the user account you configured during setup (the default is particle, with the password you chose):

su - particle

3. Verify the camera enumerated

ls /dev/video*

You should see one or more nodes. Do not assume the webcam is /dev/video0: on Tachyon, /dev/video0 and /dev/video1 can be Qualcomm media framework endpoints, and other nodes may belong to the hardware video codec. The blueprint auto-detects the correct node, so you only need to confirm that nodes exist. If nothing appears, check lsusb and try a different port or a powered hub.

4. Clone this repository

cd ~
git clone https://github.com/particle-iot/blueprint-tachyon-webcam-stream.git
cd blueprint-tachyon-webcam-stream

5. Configure (optional)

There is nothing you have to configure: cloud identity comes from the device's own particle.config.json, and events are attributed to this device automatically. To change defaults such as resolution, bitrate, or the name shown on the page:

cp webcam-stream/.env.example webcam-stream/.env
nano webcam-stream/.env

If DEVICE_NAME is left empty, the device's name from the Particle Console is used.

6. Run it

particle container run

The first run builds the two custom images and pulls the other two, which takes a few minutes. When everything is up you will see, in order:

  • camera: using /dev/videoN (mjpeg) at 1280x720@30fps
  • app logging Particle identity: user=... device=... product=...
  • a trycloudflare.com URL in the cloudflared logs
  • app logging public URL: https://<random>.trycloudflare.com and published tachyon-cam/online ...

Events publish into your product's event stream using the device's own provisioned credentials, and every payload carries the device's name, so the Console event log reads cleanly even with several devices running this blueprint.

7. Watch the stream

Open the Console's Events view. The tachyon-cam/online event carries the public URL in its data; open it on any browser, including your phone on cellular, to see the live stream. On the same LAN as the Tachyon you can also use http://<tachyon-ip>:8080 for the same page, or http://<tachyon-ip>:8889/cam for sub-second WebRTC latency.

8. Send commands from the Console

In the Events view, use Publish event:

  • Name tachyon-cam/cmd, data snapshot: the device replies with a tachyon-cam/snapshot event containing a public link to the latest JPEG.
  • Name tachyon-cam/cmd, data status: the device replies immediately with a tachyon-cam/status event (uptime, stream health, viewer counts).

A tachyon-cam/status heartbeat also arrives automatically every 5 minutes.

9. Stop it

Ctrl+C in the terminal stops the foreground run. To run the application persistently and start it on boot, deploy it through the cloud instead:

particle container push --device <your-device-id>

This builds the images on the device, pushes them to Particle's container registry, and installs the application through the cloud. It starts automatically, survives reboots, and appears in the Console under the device's Containers tab. The device choice is remembered in .particle_env.yaml, so later pushes are just particle container push. Pushed applications carry only the compose file and the images: project files do not travel (which is why the MediaMTX config is baked into its image), and .env does not either, so defaults apply unless you hardcode values in docker-compose.yaml before pushing. This is also the workflow for headless production devices, and requires no changes to the project. Note that particle container run deployments are local only and never appear in the Containers tab.

In docker ps, cloud-managed containers carry a particle_ name prefix, which is the quickest way to tell them apart from local runs.

The local and cloud-deployed forms of this application use the same ports, so only one can run at a time: stop any foreground particle container run (Ctrl+C) before or right after pushing, then reboot or push again so the cloud-managed containers can start.

How It Works

The repo follows Particle's container blueprint convention: the containers: key in blueprint.yaml points the Particle CLI at the webcam-stream/ directory, which holds the Docker Compose project. Running particle container run from the repo root builds and starts everything in that directory.

camera probes /dev/video* until it finds a node that reports MJPG or YUYV capture formats (that is the UVC webcam; Qualcomm framework and codec nodes do not). It captures MJPG when available, encodes to H.264 with libx264 tuned for live latency, and publishes over RTSP to MediaMTX. The same ffmpeg process writes a JPEG to a shared volume every few seconds. The container runs privileged with /dev mounted so detection works no matter where the webcam enumerates; see Extensions for the tighter alternative.

mediamtx turns the RTSP feed into low-latency HLS (what public viewers receive) and WebRTC (for the LAN). Its configuration is baked into a thin custom image rather than bind mounted, because pushed applications carry only the compose file and container images; any config file a service needs must live inside an image. Its control API lets the app report whether the stream is live and how many readers are attached.

app is the single public origin behind the tunnel. It serves the landing page, the latest snapshot, and reverse-proxies HLS requests to MediaMTX. It is also the Particle Cloud agent, built on particle-api-js, Particle's supported JavaScript API client. The device's provisioned particle.config.json is mounted read-only into the container and its credentials are passed to publishEvent() (product-scoped) and getEventStream(). Inbound tachyon-cam/cmd events arrive on the client's EventStream, which parses the SSE protocol and reconnects automatically after network drops, which matters on long-lived cellular connections.

cloudflared opens the outbound tunnel. By default it is a quick tunnel with a random hostname, which the app reads from cloudflared's metrics endpoint and announces via the online event.

Events

Event Direction Data
tachyon-cam/online device to cloud {device, url} on boot, once the tunnel URL is known
tachyon-cam/status device to cloud {device, uptime_s, stream_ready, readers, page_views, url} every STATUS_INTERVAL seconds and on request
tachyon-cam/snapshot device to cloud {device, url} link to the latest JPEG
tachyon-cam/cmd cloud to device snapshot or status

Ports

Port Container Purpose
8080 app Landing page, snapshot, HLS proxy (tunnel origin; also on LAN)
8888 mediamtx HLS direct (LAN)
8889, 8189/udp mediamtx WebRTC (LAN)
8554 mediamtx RTSP ingest (compose network only)
9997 mediamtx Control API (compose network only)
2000 cloudflared Metrics, quick tunnel URL discovery (compose network only)

Usage

Once deployed with particle container push, the app can be started and stopped from any shell on the device, including the Console terminal (which runs as root):

# stop the stream and tunnel (until the next boot; see the note below)
docker ps --format '{{.Names}}' | grep '^particle_webcam-stream' | xargs -r docker stop

# start it again (a new quick tunnel URL is minted and announced via the online event)
docker ps -a --format '{{.Names}}' | grep '^particle_webcam-stream' | xargs -r docker start

# read the current public URL and stream state
curl -s http://localhost:8080/api/info

After a start, the stream takes roughly 10 to 20 seconds to come online while the services reconnect to each other; the landing page retries automatically and flips to LIVE on its own.

A deployed application is restored by the device agent at every boot, regardless of Docker restart policies or a manual docker stop, so the app always comes back after a reboot or power loss. That is the right behavior for a headless product. There is currently no supported way to keep a deployed application stopped across reboots short of removing it (particle container remove). For a demo bench that should stay down until started manually, a small boot-time systemd unit that stops the containers after the agent starts them works as a local workaround.

Day to day, the only interfaces you need are the public URL and the Console. The landing page shows the live stream with device name, uptime, and viewer count, plus a link to the latest snapshot. Tuning knobs live in webcam-stream/.env: resolution, frame rate, bitrate, snapshot cadence, and heartbeat interval. After changing .env, restart with particle container run.

Troubleshooting

Symptom Check
camera: no UVC webcam found lsusb shows the camera? Try another port or a powered hub. Set CAM_DEVICE=/dev/videoN explicitly if your camera reports unusual formats.
No online event Look for the app's startup logs. Particle device config not found means the identity mounts did not resolve: confirm ls ~/.particle/particle.config.json succeeds for the user that ran particle container run, and adjust the volume path in webcam-stream/docker-compose.yaml if you use a different account.
Containers tab shows the app but containers stay created The cloud-deployed containers failed their first start, usually a port conflict with a foreground particle container run of the same project. The agent does not retry a failed start, and a reboot does not recover it (Docker restart policies only revive containers that previously ran). Free the ports, then run particle container push again to reinstall cleanly.
Page shows OFFLINE while the logs show [path cam] stream is available The app could not reach the MediaMTX control API. MediaMTX's default auth allows the api action only from localhost; this blueprint's mediamtx.yml includes an authInternalUsers override for it, so make sure that block is present if you copied an older config.
The access token provided is invalid The token in ~/.particle/particle.config.json is the CLI session token written at setup, and it can expire or be revoked. Run particle login on the device to refresh it in place (deviceId and productId are preserved), then rerun.
cloud events disabled with missing fields The mounted particle.config.json lacks access_token, deviceId, or productId. Re-run device setup, or check that the file was not hand-edited.
URL event never arrives but the tunnel works The app reads the quick tunnel hostname from cloudflared's metrics endpoint. Grab the URL from the cloudflared logs, or set PUBLIC_URL in .env.
Environment values not applied If your tooling version does not interpolate .env, set the values directly in webcam-stream/docker-compose.yaml.
Video is choppy through the tunnel Lower RESOLUTION to 960x540 or BITRATE to 1M. As a fallback, set hlsVariant: mpegts in webcam-stream/mediamtx/mediamtx.yml (higher latency, more tolerant).
WebRTC works on the device but not from other LAN machines Add the Tachyon's LAN IP to webrtcAdditionalHosts in webcam-stream/mediamtx/mediamtx.yml.
Commands ignored Event name must be exactly tachyon-cam/cmd (or your EVENT_PREFIX), data snapshot or status.

Topics Covered

  • Containerized applications on Tachyon with Docker Compose and the Particle CLI
  • V4L2 USB camera capture and live H.264 encoding with ffmpeg
  • Serving live video with MediaMTX (HLS and WebRTC)
  • Public exposure of a NAT'd cellular device with an outbound tunnel
  • Particle Cloud events as a device control plane from Linux, using particle-api-js, Particle's supported JavaScript API client

Extensions

  • Stable production URL: create a named Cloudflare tunnel with your own domain, set TUNNEL_TOKEN and PUBLIC_URL, and switch the cloudflared command as noted in docker-compose.yaml.
  • Tighter device access: replace privileged: true on the camera container with a device_cgroup_rules entry for char major 81 (V4L2) plus the /dev bind, or map the specific /dev/videoN node once known.
  • Hardware encoding: the QCM6490's Venus encoder is reachable through V4L2/GStreamer; swapping libx264 for hardware H.264 frees CPU for inference workloads.
  • Alerting with Logic: trigger a notification if the status heartbeat stops arriving or reports stream_ready: false.
  • Ledger-driven configuration: move resolution and bitrate into a cloud-to-device Ledger so the fleet can be retuned without redeploying.
  • Edge AI overlay: add a detection container (see the Qualcomm AI Hub Models blueprint) and draw results onto the stream before encoding.

Additional Resources

About

Public live video from a USB webcam on Tachyon: MediaMTX + Cloudflare tunnel for the stream, Particle Cloud events for status and control.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors