fleetlessfleetlessdocs
Reference/fleetless.yaml/Cameras

🎥 Cameras

A camera entry says where frames come from and what the bridge is allowed to produce from them. At most 50 cameras live in one file.

yaml
cameras:
  front:
    source:
      kind: ros
      topic: /camera/image_raw
      type: sensor_msgs/msg/Image
    width: 1280
    height: 720
    fps: 15
    bitrate_kbps: 2000
    snapshot_interval_seconds: 5
    description: Forward-facing camera on the mast.
field type bounds absent means
source.kind ros, rtsp, mjpeg or v4l2 — required
width integer 1 to 7680 pixels required
height integer 1 to 4320 pixels required
fps integer 1 to 60 required
bitrate_kbps integer 1 to 50000 required
snapshot_interval_seconds integer 1 to 3600 required
description text 1 to 2000 characters offered as description: null

kind fixes which other fields the source may carry, so an impossible camera is unrepresentable rather than merely invalid. There is no way to write an RTSP camera with a ROS topic.

kind fields
ros topic (ROS graph name), type (sensor_msgs/msg/Image or sensor_msgs/msg/CompressedImage)
rtsp url (rtsp:// or rtsps://, at most 2048 characters), optional transport (tcp or udp, default tcp), optional credentials
mjpeg url (http:// or https://, at most 2048 characters), optional credentials
v4l2 device (a path under /dev/, at most 128 characters)

What the four numbers bound

width, height, fps and bitrate_kbps are the resolution and load the bridge produces before sending, not the camera’s own. Together they are your control over the robot’s bandwidth, which is why they live in the configuration and not in a viewer’s request: a viewer must never be able to make a robot send more.

fps is a ceiling and not a clock, so a camera delivering ten frames a second stays at ten. bitrate_kbps bounds the live encoding only. Raising the resolution or the frame rate against a fixed bitrate buys blur, not detail.

A snapshot can arrive smaller than width and height. Its JPEG has a byte ceiling, and the bridge gives up quality first and then resolution to fit it, reporting the size it actually encoded.

Snapshots always run

snapshot_interval_seconds is how often a still frame is captured, and it runs whether or not anyone is watching. The cloud holds the one frame and serves every reader from it, so a hundred pollers cost the robot one image per interval.

Live streaming is the opposite: the cloud counts viewers, the first one starts the stream and the last one ends it. In low-bandwidth mode a running stream is re-encoded at camera_bitrate_kbps or ended outright, and a new one is refused with the code low_bandwidth.

The four sources

  • ros — the only source the bridge subscribes to rather than opens, so it needs no URL, no device and nobody to authenticate to.
  • rtsp — a network camera the robot itself can reach. udp loses frames on a congested link and loses them silently, which is why an omitted transport means tcp.
  • mjpeg — one JPEG after another over HTTP. There is no transport to choose, and credentials travel as HTTP Basic.
  • v4l2 — a capture device attached to the robot, such as a USB camera. A /dev/v4l/by-id/... symlink survives a reboot that renumbers /dev/video0.

The scheme restrictions and the /dev/ requirement are not a formality. The bridge opens these values with libraries that would as happily serve file: or an ordinary video file, so without the restriction a configuration document would be arbitrary file access on the robot. The bridge re-derives the same constraints itself rather than trusting the wire, and a device may hold no .. segment and may not end in a slash.

Credentials

yaml
    source:
      kind: rtsp
      url: rtsp://cam-1.plant.local/stream1
      credentials:
        username: ops
        password: "..."

Both fields are 1 to 128 characters and both are optional. For MJPEG the bridge sends a real Authorization: Basic header and leaves the URL untouched. RTSP offers no such channel, so there the name and password go inside a connect URL built fresh for that one call and never written back into the stored document.

Credentials may also sit in the URL itself, as rtsp://user:pass@host/stream. Where both are present, the explicit block wins.

There is no secret store behind this. A password written here is in every published version, and those are immutable. It cannot be removed from history and cannot be rotated without republishing, and anyone who may read a robot's configuration history may read every camera password that was ever in it.

One rule bounds that: the publish audit event carries the version number and never the document body.

Plain http:// is permitted for an MJPEG camera, because these usually sit on the robot’s own network. Basic credentials on such a URL then travel in the clear, which is a choice this format lets you make and does not pretend is free.

A camera slug shares one namespace with every datapoint, action, service and publisher in the file. The grammar and the reserved names are on the overview.