An autonomous mobile robot's compute load is not small. Trying to finish heavy work like mapping and navigation entirely on the edge computer mounted on the chassis quickly runs into a performance ceiling. newbot keeps drive control, sensor acquisition, and safety supervision on the edge unit (a Raspberry Pi 5), and offloads mapping and navigation to a more capable PC.
Image: Robot Operating System logo, Wikimedia Commons (CC BY-SA 4.0)
Division of Roles
- Edge unit (Pi5): drive control, safety supervision, sensor acquisition, self-localization (FAST-LIO)
- PC (heavy processing): mapping (RTAB-Map), navigation via Nav2
Every safety mechanism (velocity arbitration, obstacle guard, emergency stop) stays on the edge unit, so manual operation and the physical emergency stop keep working exactly as before even if the PC or the network goes down. Autonomous commands simply stop arriving, and the system fails safe.
This split wasn't the shape of things from day one. When distributed operation was first introduced, self-localization (FAST-LIO) also lived on the PC side. But the bandwidth measurements described below revealed that streaming the raw LiDAR point cloud straight to the PC just wasn't realistic, so self-localization was moved back to the edge unit — matching the single-machine configuration — with only a decimated point cloud sent to the PC. Rather than applying "offload the heavy work to the PC" as an unconditional rule, the real starting point of this architecture was deciding, from actual measurement, which parts the network couldn't hold up and leaving those on the edge unit.
Sensor Fusion via RTAB-Map
Mapping on the PC side runs on RTAB-Map rather than the slam_toolbox used in single-machine mode. RTAB-Map doesn't compute its own odometry — it takes the /odom FAST-LIO publishes as external odometry, and uses both the camera feed (visual features) and the LiDAR's processed 3D point cloud (geometric shape) for loop closure (recognizing a previously visited place and correcting for map drift). Since RTAB-Map publishes both the map and the map→odom transform, the navigation-side configuration stays on the exact same interface it uses with slam_toolbox, unchanged.
RTAB-Map's Status, Reconsidered After the Fact
That said, the decision to adopt RTAB-Map has since come under reconsideration during a later pass at the safety architecture. RTAB-Map's value — visual-feature loop closure and separating floor from obstacles — both depend on being able to keep the camera on continuously. But real-hardware testing found that enabling the camera pushes CPU usage to about 75%, dragging the self-localization (FAST-LIO) update rate down from 10 Hz to 0.5 Hz. And even setting the camera aside, the floor/obstacle separation still runs on the same blind LiDAR point cloud, so it doesn't fix the underlying weakness (low obstacles near the robot going undetected). On top of that, maintaining two parallel mapping systems (slam_toolbox and RTAB-Map) leaves room for their configurations to drift apart.
Given all that, RTAB-Map is being considered for removal from the default configuration, treated as experimental until there's enough CPU headroom to run the camera continuously. The distributed architecture itself (pushing heavy processing to the PC) and the choice of which algorithm runs on top of it (RTAB-Map vs. slam_toolbox) are separate questions — the latter gets revisited as measurements come in, independent of the former.
The Reality of Limited Wireless Bandwidth
Since the edge unit and the PC are connected over Wi-Fi, raw sensor data can't just be streamed as-is. Two specific kinds of data caused serious bandwidth blowouts in practice.
| Data | Raw size / rate | Symptom |
|---|---|---|
| Raw LiDAR point cloud | ~500 KB/message × 10 Hz | measured at 1.87 Hz (~81% data loss) |
| Raw camera image | ~921 KB/frame × 30 Hz (~27 MB/s) | image dropped to 0 Hz, point cloud collapsed to 0.1 Hz as collateral damage, SSH became unreachable too |
A key lesson from field testing: the moment the PC subscribes to a large topic, bandwidth saturates, and it's not just that topic that stops working — other traffic gets taken down with it. The fix comes down to one rule: always cut the data down on the edge side before sending it to the PC. The LiDAR point cloud is decimated to a quarter before being sent; camera video is decimated to 3 Hz combined with JPEG compression, cutting the data volume by roughly 150x before sending. After decimation, the point cloud arrives at nearly the full rate (about 9.94 Hz), and both mapping and navigation run stably.
Cutting bandwidth didn't solve everything, though. The decimated, compressed camera stream passes on the bandwidth front by itself (measured at only tens of KB/s), but enabling the camera on the edge unit in the first place makes the encoding work alone consume roughly 75% of the CPU, dragging the self-localization update rate down from 10 Hz to 0.5 Hz. In other words, solving the bandwidth constraint just revealed a different constraint — CPU — sitting behind it. It was a concrete reminder that a bottleneck, once knocked down, doesn't disappear; it just moves to the next layer.
Stabilizing the Communication Layer
ROS2's inter-node communication layer (DDS) depends on multicast by default, which can be a source of instability on Wi-Fi. Explicitly configuring the edge unit's and the PC's actual IP addresses as unicast peers reduces the reliance on multicast and improves communication stability.
Real Gotchas Hit While Moving to a Distributed Setup
Migrating from a single-machine setup to the distributed one surfaced several environment-specific problems that weren't anticipated. All of them shared a common shape: the only visible symptom was "it doesn't work," while the actual cause sat in a completely different layer.
- A node fails to start at all because the receive buffer is too small: the DDS implementation in use (CycloneDDS) requests a 10 MB receive buffer for large point-cloud and image topics, but Linux's default
net.core.rmem_maxis far smaller than that, and exceeding it makes a node's startup process fail outright. A temporarysysctlchange disappears on reboot, so it had to be placed as a permanent setting under/etc/sysctl.d/. - Automatic network-interface misselection: the edge unit has a separate fixed-IP interface dedicated to a direct LiDAR connection, and DDS's automatic interface-quality detection mistakenly judged this one "higher quality" and used it for communication with the PC, breaking connectivity entirely. The fix was to explicitly specify the interface name to use via an environment variable, with a startup-time mechanism that auto-detects whichever interface actually exists and sets that variable accordingly.
- Misled by CLI command caching: a CLI command like
ros2 topic listcan return a cached result from a background daemon, making it useless for verifying a change right after adjusting DDS settings (it can look like things are "working" when it's really just showing a stale result). The fix is to stop the daemon before checking after a settings change, or to verify by actually starting the node process. - Inbound traffic blocked by a firewall: on first setup on the PC side, the OS's standard firewall was blocking the relevant DDS inbound UDP port range, and nothing connected until a rule was added to allow it.
- Clock drift specific to a VM environment: depending on the PC-side virtualization layer's time-sync method, clock drift on the order of hundreds of milliseconds could occur, and sometimes took a while to converge. Switching to a more aggressively correcting time-sync daemon resolved it. Any drift in time sync between sensors can affect the process that aligns point clouds with images, and the interpolation used in coordinate transforms — so this is treated as a basic item that bears on mapping accuracy.
None of these turned out to be bugs in ROS2 or in the algorithms themselves — they all came from a lower layer: the OS, the network, the virtualization environment. The lesson that stuck: when bringing up a distributed setup, suspect that foundation before suspecting the application layer.
The Shadow the Distributed Split Casts Over Safety Design
Splitting up the roles has a side effect on how the safety mechanisms are designed, too. The obstacle guard is a layer on the edge unit that watches the raw point cloud directly and clamps forward speed; the PC side has a separate protective layer of its own, Nav2's collision_monitor plus its costmap. The three layers each run on a different data source (raw point cloud vs. LaserScan) and a different, independently maintained set of chassis geometry — a structural problem where nothing notices if these definitions ever drift apart from each other.
More fundamentally, the scope of protection differs between the edge unit and the PC. The obstacle guard running only on the edge unit follows the design principle that "protection should stay alive even if the PC or the network goes down" — and that's the right call. But flip it around: disable the edge-side guard for any reason, and edge-side protection drops to zero. The PC-side collision_monitor has logged well over 100 reactions to obstacles during real-hardware test runs, but that's PC-side protection only, and it disappears the instant Wi-Fi drops. The benefit of the distributed setup — being able to offload heavy processing to the PC — comes with a design burden attached: always knowing where the fail-safe path lives if the PC itself goes down.
Whether the PC (the heavy-processing machine) is even alive is made visible from the edge side too. The PC side publishes a 1 Hz heartbeat signal, and the edge unit treats the PC connection as dropped if that signal's arrival interval exceeds 3 seconds (the connection-detection approach itself is the same idea used for every other component's connection status, covered in "Building the Status Dashboard").