System designTroubleshooting

ROS middleware (RMW) mismatch: no sensor data in your robot web UI

The web UI drives the robot but the panels show no sensor data. Fix the ROS middleware (RMW) mismatch: match RMW_IMPLEMENTATION, ROS_DOMAIN_ID and transport.

LumaSpec Vision11 min read Share on LinkedIn

On this page 11 sections
  1. 01The decision: one middleware, one domain, one transport
  2. 02Why the working demos prove nothing
  3. 03Cause 1: a different RMW_IMPLEMENTATION — check this first
  4. 04Cause 2: a different ROS_DOMAIN_ID, with the port arithmetic
  5. 05Cause 3: the container boundary swallows discovery
  6. 06Cause 4: QoS and topic names the panel never matches
  7. 07Stop the ros2 daemon before trusting any result
  8. 08Symptom to cause, at a glance
  9. 09Pin these settings and it stays fixed
  10. 10When it is not the middleware
  11. 11Questions

You drive the robot from its own web interface, the base obeys every command, yet the camera and LiDAR panels sit empty — while both sensors run perfectly in their own demo terminals. That split, controls alive and sensor data gone, almost never means broken hardware. It means the web UI backend and the sensor drivers are not on the same ROS 2 middleware, domain or network. This guide gives the three rules DDS discovery actually enforces, a test and fix for each cause in order of probability, and the settings to pin so the panels stay populated after the next deploy. Everything happens on the robot; the UI itself needs no rebuild.

The decision: one middleware, one domain, one transport

Every ROS 2 participant on the robot — the web UI backend, each sensor container, the base controller — must agree on three things: the same RMW_IMPLEMENTATION, the same ROS_DOMAIN_ID and the same transport, with udp the common default. Participants on different middleware implementations or different domains coexist silently on one network and never discover each other's topics. The fix is one deployment-wide setting, not per-sensor debugging:

  1. Set RMW_IMPLEMENTATION to the same explicit value in the backend, in every sensor container and in every vendor service or snap.
  2. Set ROS_DOMAIN_ID to one value in the 0–101 range, everywhere.
  3. Put every ROS container on the host network so discovery traffic reaches the rest of the robot.

Then stop the ros2 daemon and re-check with ros2 topic list -t from the backend's environment. Middleware and domain splits account for most cases; container networking and QoS mismatches cover the rest.

Why the working demos prove nothing

A demo that runs in its own terminal proves the sensor and its driver are good — nothing more. Each process inherits the environment of the shell or container that launched it. The web UI backend is a separate ROS 2 participant, usually a rosbridge or WebSocket server in its own container or snap, and discovery happens only between participants that share middleware, domain and transport. Nodes on different settings are like radios on different bands: powered, transmitting, unheard. The browser never touches DDS — it speaks HTTP to the backend, which is exactly why the controls keep working while the panels starve.

Cause 1: a different RMW_IMPLEMENTATION — check this first

ROS 2 sends its topics over DDS through a swappable middleware layer, selected per process by the RMW_IMPLEMENTATION environment variable. The two open-source implementations in common use, Fast DDS and Cyclone DDS, each bring their own discovery behavior and vendor extensions, and mixed implementations are not a supported combination. In practice, a backend on one implementation and drivers on the other is the most frequent reason a robot shows controls but no sensor topics.

Test. Inside the backend's environment — its container or service — run printenv RMW_IMPLEMENTATION. Repeat inside each sensor container and vendor service. An empty result is not reassurance: it means the image's built-in default, and that default has changed between ROS 2 releases, so two environments can both be running defaults and still sit on different middleware. Then run ros2 daemon stop and, from the backend's environment, ros2 topic list -t. Camera and scan topics absent here while present inside a driver container confirms the split, or points to Cause 3.

Fix. Set one explicit value everywhere it can be inherited: RMW_IMPLEMENTATION=rmw_fastrtps_cpp, or RMW_IMPLEMENTATION=rmw_cyclonedds_cpp, in launch scripts, Dockerfile ENV lines and service definitions. Leave it unset nowhere.

Cause 2: a different ROS_DOMAIN_ID, with the port arithmetic

DDS partitions the network by ROS_DOMAIN_ID: default 0, valid range 0–232. Participants on different domains never exchange a byte, even on identical middleware. Each domain's DDS traffic rides on UDP ports derived from the domain number:

base participant port = 7400 + 250 × ROS_DOMAIN_ID

Worked example. Domain 42 gives 7400 + 250 × 42 = 17,900, comfortably clear of trouble. Domain 101 gives 7400 + 25,250 = 32,650, just under the 32,768 floor of the Linux ephemeral port range, 32768–60999 by default. Domain 102 gives 32,900 — inside that range, ports the kernel also hands to unrelated sockets, which is why domain IDs above 101 give flaky discovery on Linux and why the working range is 0–101.

Test. printenv ROS_DOMAIN_ID in the backend's environment and in one driver's. The classic failure: the vendor's base stack and backend run at their default while your own demo launched with ROS_DOMAIN_ID=42 — the demo talks to itself happily and never to the robot.

Fix. Pick one value; 42 is a sound choice. Set it in every environment, at or below 101. On a site with several robots, give each fleet its own domain so one robot's scans never land in another robot's UI.

Cause 3: the container boundary swallows discovery

On the default Docker bridge network, a container gets its own IP stack, and DDS discovery — which leans on UDP multicast and the local interface — does not cross the bridge to the host. A snap or plain service runs on the host network. So a driver in a bridge-mode container and a backend on the host stay invisible to each other even with identical middleware and domain. This is the container-versus-snap split that turns up on robots mixing vendor-packaged sensors with your own builds. Make every ROS participant share one network: run the sensor containers with host networking, network_mode: host in compose or --network host on the command line. If a container must keep a separate network, declare unicast discovery peers instead of multicast — ROS_DISCOVERY_SERVER for Fast DDS, an explicit peers list in a CYCLONEDDS_URI file for Cyclone DDS.

Test. Run ros2 topic list -t inside the sensor container, then on the host after ros2 daemon stop. Topics visible in the container but missing on the host is the signature of a network boundary, not a middleware mismatch.

For the rest of container bring-up — interfaces, device nodes, streaming — the guide on Ethernet 3D camera discovery and streaming in Docker containers walks the network side step by step.

Cause 4: QoS and topic names the panel never matches

Discovery alone does not move data. Reliability is the classic break: camera, depth and scan drivers publish best-effort — the sensor-data QoS profile — and a backend that requests reliable delivery is incompatible, so ROS 2 delivers nothing rather than negotiating down. Run ros2 topic info /scan and look for Publisher count: 1, then ros2 topic hz /scan: a healthy 2D LiDAR settles near 10 Hz, and ros2 topic hz /camera/image_raw near 30 fps on a typical depth camera. A publisher with no measured rate at all points at QoS.

If the rate is healthy but the panel stays blank, check names and transports. image_transport splits one camera into the base topic plus compressed siblings such as /camera/image_raw/compressed, and the panel must subscribe to the exact name and type being published. Where a driver publishes under its own namespace, remap it at the driver — the depth-camera topic remapping guide shows the pattern. A topic that exists but carries no data has its own check in the stream-enable settings guide. For the image pipeline end to end, the compression transport parameters page explains what goes missing when settings do, and this ROS image access walkthrough for a wrist camera covers the node and transport configuration.

Stop the ros2 daemon before trusting any result

The ros2 CLI answers ros2 topic list and friends through a background daemon, and one daemon instance serves one middleware-and-domain combination. A daemon started before you changed RMW_IMPLEMENTATION or ROS_DOMAIN_ID keeps reporting the old world — the CLI tools' issue tracker carries several reports of daemons silently outliving their settings. After any environment change, run ros2 daemon stop; the next ros2 command restarts it with the current environment. Skip this step and you can spend an hour confirming a mismatch that no longer exists.

Symptom to cause, at a glance

What you see Most likely cause First test
Controls work, all sensor panels empty, demos fine RMW or domain split between backend and drivers printenv both values in backend and one driver
ros2 topic list empty from backend, full inside a driver container Container network boundary Topic list inside the container, then on the host
Topic listed, Publisher count: 0 Driver not running, wrong namespace, crashed ros2 node list, driver logs
Publisher count: 1, ros2 topic hz silent QoS incompatibility, reliable UI versus best-effort driver Compare QoS overrides on the panel subscription
Rate healthy, panel still blank Panel on the wrong topic name or transport Panel configuration against ros2 topic list -t
Panel exists but shows a broken player Media pipeline in the backend image, not discovery Missing decoder plugins — see below

A broken player is a codec problem, not a discovery problem: the GStreamer missing-plugins fix restores the picture without touching DDS.

Pin these settings and it stays fixed

  1. RMW_IMPLEMENTATION set explicitly — never unset — in the backend, every driver container, every vendor service and the deploy scripts.
  2. One ROS_DOMAIN_ID at or below 101, recorded in the deployment notes, one per fleet on shared networks.
  3. Host networking for every ROS container on the robot.
  4. The same transport value everywhere, udp being the common default; a vendor snap with its own transport selector must match.
  5. Best-effort QoS on backend subscriptions to sensor topics, matching what the drivers publish.
  6. One post-deploy check: ros2 daemon stop, then ros2 topic list -t from the backend's environment, expecting the camera and scan topics.

Fewer environments mean fewer splits. Consolidating drivers and the backend on one machine — a vision controller with enough camera ports for the whole sensor set — collapses the container-versus-snap split into a single middleware setting, and PoE camera ports feed the sensors over one Ethernet run. If the sensor side needs speccing as well, the 3D sensors finder filters stereo depth cameras and laser profilers, and the area scan cameras category covers the 2D side:

Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
PoE
PoE camera ports
Light outputs
8 built-in channels
$4,479.00 · quotedView and quoteBuild a system with it

For the depth source itself, a structured-light 3D camera that publishes standard depth topics keeps the UI integration plain:

3D sensorsHikrobot MV-DPS200P-02 Projected structured-light 3D cameraMV-DPS200P-02
Type
Stereo / structured light
$8,829.00 · quotedView and quote

When it is not the middleware

Three neighbors of this failure have their own fixes. A vendor camera tool that works while the ROS node sees nothing usually holds the device exclusively — the one-client-at-a-time explanation covers it. A camera unreachable on the network is IP and cabling, not DDS: the subnet-and-link fix for a 3D camera that will not ping applies. And once topics flow, a camera monitoring dashboard keeps the next silent dropout visible.

Worked top to bottom, this is a 15-minute diagnosis: one middleware, one domain, one network, one transport. If the goal is your own dashboard rather than the vendor UI, the guide on embedding a live camera feed in a web dashboard starts from the working topic, and more integration fixes sit on the systems topic page. To size the cameras and controllers behind it all, start from the system configurator.

Questions

Which RMW implementation should a fleet standardize on?

Whichever one most of the existing software already runs — usually the choice baked into the base controller. Fast DDS and Cyclone DDS are the two open-source implementations in common use and both are mature; switching the whole robot to the other costs less than one silent split. The part that actually matters is setting the value explicitly, by name, in every environment instead of letting each image fall back to its own default.

Do the browser and the DDS network ever need to talk directly?

No. The browser speaks plain HTTP and WebSocket to the backend process on the robot, and that backend is the ROS 2 participant doing all discovery and subscription. That is why controls keep working from anywhere on the network while panels starve, and why every fix for this failure lives in the robot's environments — containers, snaps, services — never in the browser or the network between you and the robot.

How do you check the middleware of a process that is already running?

Check its environment, not your shell: printenv RMW_IMPLEMENTATION and printenv ROS_DOMAIN_ID run from inside the same container or service context show what that process inherited. ros2 doctor also reports the middleware and domain of its own environment as a quick cross-check. An empty RMW_IMPLEMENTATION is still an answer — it means the image's default, which can differ from the default of the image running next to it.

Two robots share one workshop network — do they need different domain IDs?

Yes, if they should stay separate. DDS discovery traffic reaches everything on the link, so two robots on the same domain see each other's topics and a panel can end up displaying the wrong robot's scan. Give each robot or fleet its own ROS_DOMAIN_ID in the 0–101 range and keep every environment on that robot consistent with its own value; different domains coexist on one network with no configuration beyond the ID itself.

Can containers stay on the bridge network and still discover the host?

Only by giving up multicast discovery and naming peers explicitly. Fast DDS reaches a discovery server through the ROS_DISCOVERY_SERVER variable; Cyclone DDS takes a peer list through its configuration file. Each container then finds the backend by unicast IP, which crosses the bridge like any other traffic. It works, but it adds a configuration file per container — host networking for sensor containers remains the lower-maintenance choice on a single robot.