A robot that moves on a developer’s laptop has passed an onboarding exercise, not a release test. I would not start an automation project by adding a planner or splitting nodes across machines, because neither fixes the failures a short demo rarely exposes: stale sensor data, inconsistent clocks, and commands that outlive the connection that sent them. Teams pay for that confusion in debugging time first and unsafe motion later.
A moving bot is a poor release criterion because the demo hides failure modes
Read ROS 2 on a laptop in 60 minutes from install to moving bot, then replay the resulting demo with missing odometry before you call it ready. The quickstart’s moving-bot milestone is useful for confirming that ROS 2, a launch file, and a command path work together. It does not establish what happens when one of those pieces stops working during motion.
A common team mistake is to turn that first successful run into an implicit interface contract. A developer sees a cmd_vel message produce movement and assumes the robot will stop when the publisher exits. That assumption depends on the motor controller’s behavior, the command timeout, and whether another publisher is still active. If nobody has specified those conditions, the cost is a late discovery during integration: someone must trace which process owns motion while the robot is already behaving unexpectedly.
Write down the contract before changing the architecture. For each motion command, identify its owner, the conditions under which it expires, and the component that enforces expiration. For each input, identify how old it may be before the controller rejects it. These are testable statements; “the robot responds smoothly” is not, because two observers can watch the same run and disagree about whether it passed.
ROS 2 Jazzy Jalisco is a practical version to pin for a new bench exercise, but the version alone does not make the exercise reproducible because middleware, dependencies, and launch parameters also affect behavior. Record the ROS distribution, the DDS implementation, and the configuration committed with the test. A teammate should be able to replay the same fault without reconstructing your shell history.
Make one failure test as routine as the happy-path demo: start motion at a deliberately capped speed, stop publishing odometry, and record the time until the controller inhibits motion. Treat 0.5 m/s as a trial cap to set for your own platform, not as a universally safe speed. If the test has no expected outcome, the team has found a missing requirement rather than a flaky robot.
QoS defaults can turn a live sensor into a silent data gap
ROS 2 uses DDS quality-of-service policies, and a publisher being visible on the graph does not prove a subscriber can receive its messages. A reliable subscriber, for example, is incompatible with a publisher offering only best-effort delivery. A junior developer may spend hours inspecting callback code when the real fault is a policy mismatch at discovery.
Start with what the running graph actually offers. The following bench probe publishes a string and prints the endpoint details on a machine with ROS 2 Jazzy installed and sourced from the standard path:
source /opt/ros/jazzy/setup.bash
ros2 topic pub -r 10 /probe std_msgs/msg/String '{data: alive}' >/tmp/ros-probe.log 2>&1 &
pub_pid=$!
trap 'kill "$pub_pid" 2>/dev/null || true' EXIT
sleep 2
ros2 topic info --verbose /probe
ros2 topic echo --once /probe std_msgs/msg/String
kill "$pub_pid"
wait "$pub_pid" 2>/dev/null || true
The 10 Hz rate here is a bench setting to make the probe easy to observe, not a recommendation for a sensor. On an actual sensor_msgs/msg/LaserScan topic, inspect reliability, durability, and history on both endpoints with ros2 topic info –verbose. Then check whether the subscriber is receiving timely scans; a matching QoS policy cannot guarantee fresh data if the publisher itself is delayed.
Best effort versus reliable is a choice with different costs. Best effort often wins for frequent sensor samples when a newer sample can replace a lost one, because retransmitting an old sample may add little value. It costs you occasional missing samples, which the consumer must tolerate and measure. Reliable delivery wins when losing an individual message would break the application’s intended behavior; it costs buffering and potentially more delay under a poor connection. Neither setting is a substitute for rejecting stale input, because a delivered message can still arrive too late to use.
Define freshness at the consumer, not just frequency at the publisher. An initial 100 ms maximum scan age can be a value to tune during bench tests; choose it against the robot’s speed, sensor timing, and stopping behavior. Log message header time, receipt time, and whether the controller accepted the sample. Without those fields, a team can “fix” a missing-data incident by changing QoS while leaving the late-data problem intact.
Clock and frame mistakes often masquerade as planner defects
When a navigation run fails, teams often tune the planner first because the visible symptom is a bad path. That can waste days if the underlying error is a transform at the wrong time or a frame with the wrong meaning. A planner cannot make a sound decision from a pose that was assembled using inconsistent timestamps.
Use the ROS coordinate-frame conventions in REP-105 as a check on meaning: map, odom, and base_link are not interchangeable labels. The odom frame supports locally continuous motion estimates, while map can change as localization corrects its estimate. If a team silently treats a map correction as physical movement, a controller may react to a jump the robot never made. Inspect the transform tree with tf2 tools before changing planner parameters.
Time needs the same discipline. A simulation using Gazebo and use_sim_time:=true may publish /clock, while a hardware node using its system clock may stamp nav_msgs/msg/Odometry differently. The resulting transform lookup failures can appear intermittent because they depend on which messages arrived and when. Check the use_sim_time parameter for every node involved, then compare header stamps with the clock each node uses.
Pick a provisional 50 ms transform-age budget for a bench test and adjust it from observed timing and the platform’s motion constraints. Record both the measured age distribution and the count of rejected lookups; reporting only an average hides the late samples most likely to disrupt a run. rosbag2 helps here because a recorded failure gives another developer the same message timing to inspect, although playback still needs the clock and parameters set deliberately.
The cost of skipping these checks is a misleading fix. Increasing a planner’s tolerance might make one run look better because it accepts older data, while reducing the time available to detect a genuinely stale pose. Change a tolerance only after you can show which timestamp or transform caused the rejection.
A watchdog that only logs failures does not stop a robot
ROS 2 DDS deadline and liveliness policies can reveal that an endpoint missed an expected update or disappeared. They do not, by themselves, guarantee that a motor stops, because detection still has to trigger an action through a functioning path. Treat a warning in a terminal as diagnostic evidence, not as a safety mechanism.
Trace the entire stop path with the team: sensor or command expires, a supervisor detects it, the controller rejects further motion, and an actuator-level timeout or independent stop path enforces the result. If the same computer and network connection are required both to command motion and to stop it, losing that connection can defeat the planned response. A separate hardware emergency stop or motor-controller timeout may be warranted, but claiming compliance with ISO 13849 or IEC 61508 requires a safety process and evidence, not the presence of a ROS node.
A provisional 200 ms command timeout is a test setting, not a universal stopping limit. Tune it against command frequency, network behavior, and the time the platform needs to stop. Then interrupt the publisher, disconnect the network, and kill the supervisor in separate trials. Those are different faults: a robot that stops after one may keep moving after another.
Measure elapsed time from the last accepted command to inhibited motion, and record whether the motor controller or the ROS supervisor caused the stop. Run 30 induced dropouts as an initial experiment size rather than treating a clean run as proof of safety. Even zero observed late stops in that set would establish only what happened under those tested conditions. Put the fault procedure in launch_testing or a hardware test script so the next developer can repeat it after a controller change.
Keep security separate from this stop claim. ROS_DOMAIN_ID can help separate discovery domains, and SROS2 can configure DDS security, but neither enforces a physical stop when a command path fails. Mixing those concerns creates a costly review error: the team sees an access-control measure and assumes it has also addressed motion safety.
Trend-led rewrites charge interest before they remove a fault
Read Robotics Software Development Trends for Smart Automation, then ask which proposed change would eliminate a failure your tests have reproduced. Trend discussions can suggest useful approaches, but adopting an architecture because it is current gives a junior developer more interfaces to debug before the team knows which interface is failing.
Consider ROS 2 composable nodes versus separate processes. Composition can win when nodes exchange large messages on one computer and measured transfer overhead is a bottleneck; its cost is shared process fate and harder isolation when one component crashes. Separate processes can win when fault isolation and independent restarts matter more; their cost is extra interprocess communication and deployment configuration. Choose after a trace identifies the bottleneck or a fault test identifies the needed isolation. Otherwise, the rewrite risks changing failure behavior without improving it.
The same discipline applies before switching between Cyclone DDS and Fast DDS. Middleware differences can matter on a particular network, but changing implementations while also changing QoS and node layout makes the result difficult to attribute. Hold the launch configuration constant, run the same rosbag2 input or repeatable hardware scenario, and compare the metric that prompted the change. Use colcon test and pytest for ordinary regressions, while keeping hardware fault results distinct from simulator results because a simulator cannot establish how a real actuator stops.
On your next task, take one motion-related topic and write down its publisher, subscriber, QoS policies, timestamp source, expiry condition, and stop owner. Run one dropout and save the log before proposing a new node or middleware setting. If you cannot identify who rejects an expired command, that is the first defect to fix.



