REF.01SYS/MIDDLEWARE

Inside ROS 2: Anatomy of a Robot Communication System

T/18MINSYS/MIDDLEWAREros2ddsqosmiddleware

An under-the-hood tour of ROS 2’s communication fabric—from DDS and core primitives to QoS, executors, and zero-copy paths for large model payloads.

Introduction

In this post, I'll gradually introduce all of the core system components and advanced features that make up a modern, high-performance robot communication system. In particular, I'll be doing an under-the-hood breakdown of ROS 2 (Robot Operating System 2).

This post is the first in a series. It starts broad and then layers in detail (following an inverse-pyramid approach) so you can form an accurate high-level mental model of the complete system without drowning in boilerplate code.

Structure

  1. The Architecture: The fundamental shift from a centralized master to DDS middleware.
  2. Core Primitives: Nodes, Topics, Services, and Actions.
  3. Quality of Service (QoS): Tuning for real-time, high-frequency control loops.
  4. Executors and Callback Groups: Parallelism, scheduling, and deterministic execution.
  5. Scaling Up & Zero-Copy: Hardware-aware optimization for deploying large foundation models on edge hardware.

Notes & Assumptions

  • Target Audience: Anyone curious about how state-of-the-art robotic systems communicate, as well as engineers building robust, production-grade autonomy stacks.
  • Language Targets: Code examples demonstrate high-level policies using Python (rclpy) and performance-critical, real-time paths using modern C++ (rclcpp) targeting the Humble, Iron, and Jazzy distributions.
  • The first section on DDS covers essential networking fundamentals—the rest of the post dives straight into practical systems code, concurrency mechanics, and memory architecture.

1. The Architecture: Nodes and the DDS Middleware

Before diving into the mechanics, let's establish exactly what ROS 2 is. Despite the name, it is not an operating system like Linux or Windows. It is a middleware framework that sits directly between your host operating system kernel and your application process.

Its fundamental purpose is to solve the distributed systems problem in robotics: how do you get dozens of independent hardware drivers, state estimators, planners, and neural networks to talk to each other seamlessly, concurrently, and in real time?

Nodes: The Fundamental Unit

In ROS 2, a Node is a single, self-contained executable process (or shared library component) that performs a specific, isolated task. For example, one node might be a camera driver publishing RGB images, another might be a neural network inference script evaluating action chunks, and a third might be a high-frequency PID controller for an actuator. The ROS ecosystem is essentially a decentralized computational graph of these independent nodes exchanging data.

ROS 2 computational graph of independent nodes exchanging data
Nodes: The Fundamental Unit

The Communication Fabric and the Bottleneck

The "communication fabric" is the underlying networking and serialization layer that physically moves data between these isolated nodes. The inputs are raw sensor streams (images, joint encoders) and the outputs are processed commands (target joint torques, navigation waypoints).

If you are developing complex foundation models for physical tasks—such as evaluating dense visual-motor policies for architectures like π0\pi_0 or VLA models—this fabric can easily become a major system bottleneck. When a camera node generates a 10 MB image tensor, standard communication protocols require the CPU to serialize that data, copy it through the network stack, and deserialize it on the receiving end. If this happens at 60 Hz, you consume massive memory bandwidth just moving bytes around, starving your GPU, spiking your inter-token latency (ITL), and breaking real-time guarantees.

ROS 1: The Centralized Switchboard

To understand why ROS 2 was built, we have to look at the architectural faults of ROS 1. ROS 1 relied on a custom TCP/UDP protocol governed by a centralized master process called roscore.

  • Metaphor: Think of ROS 1 like an old-school telephone switchboard. If Node A wanted to talk to Node B, it first had to ask the central roscore operator for Node B's IP address and port to establish a socket connection.
  • Strengths: It provided unprecedented standardization. Instead of every robotics lab writing custom TCP socket code, researchers could plug-and-play modules from anywhere in the world.
  • Weaknesses: It was a single point of failure. If the roscore process crashed, discovery collapsed system-wide. It also lacked real-time Quality of Service (QoS) guarantees, making it brittle over lossy wireless networks and unsuitable for safety-critical hardware.
ROS 1 architecture with centralized roscore master as a single point of failure
ROS 1 Architecture (The Switchboard)

The Shift to DDS

ROS 2 solves this by replacing the custom protocol and centralized master with DDS (Data Distribution Service)—an OMG (Object Management Group) industry-standard, decentralized middleware protocol originally developed for military, aerospace, autonomous vehicles, and high-frequency trading.

In ROS 2, DDS creates a virtual "Global Data Space." When a ROS 2 node spins up, it doesn't phone home to a master process. Instead, it uses two decentralized discovery phases:

  1. SPDP: The node broadcasts participant presence messages over UDP multicast (using a deterministic port range calculated as 7400+250×ROS_DOMAIN_ID7400 + 250 \times \text{ROS\_DOMAIN\_ID}). This is how nodes on the same subnet announce their existence to one another without a central server.
  2. SEDP: Once participants discover each other, they exchange unicast SEDP packets containing detailed metadata regarding topic names, interface data types, and Quality of Service (QoS) compatibility.
DDS Global Data Space with SPDP multicast discovery and SEDP endpoint exchange
DDS Global Data Space (Decentralized)

2. Core Primitives: Topics, Services, and Actions

To enable inter-node communication, ROS 2 provides three distinct communication patterns built on strict, language-agnostic interface definitions (.msg, .srv, and .action files).

Topics (Publish / Subscribe)

Topics are unidirectional, continuous data streams built on an asynchronous publish-subscribe model. A Publisher broadcasts data onto a named /joint_states without knowledge of subscribers. Any number of Subscribers can consume that topic in real time.

ROS 2 publish/subscribe pipeline from camera driver through neural network interface to control
Topics (Publish / Subscribe)
IMPL
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState

class TopicExampleNode(Node):
    def __init__(self):
        super().__init__('topic_example_node')
        # Publisher: broadcasts to 'joint_commands' (Queue depth = 10)
        self.publisher_ = self.create_publisher(JointState, 'joint_commands', 10)
        # Subscriber: listens to 'joint_states'
        self.subscription = self.create_subscription(
            JointState, 'joint_states', self.listener_callback, 10
        )

    def listener_callback(self, msg: JointState):
        self.get_logger().info(f"Received joint positions: {msg.position}")

    def publish_command(self, positions: list[float]):
        msg = JointState()
        msg.position = positions
        self.publisher_.publish(msg)

When to use: Continuous data streams such as sensor feeds, camera frames, LIDAR point clouds, or high-frequency motor torque commands.

Services (Request / Response)

Services are synchronous or asynchronous bidirectional Remote Procedure Calls (RPCs). A Service Server advertises a service name, and a Service Client sends a Request payload, blocking or yielding until the server processes the data and returns a matching Response.

ROS 2 service request/response between client and server nodes
Services (Request / Response)
IMPL
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node

class ServiceExampleNode(Node):
    def __init__(self):
        super().__init__('service_example_node')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

    def add_two_ints_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info(f"Incoming request: a={request.a}, b={request.b}")
        return response

When to use: Discrete, transactional operations where execution state must be verified before proceeding (e.g., "Reset localization filter," "Trigger gripper vacuum," or "Compute inverse kinematics").

Actions (Goal / Feedback / Result)

Actions are designed for long-running, non-blocking tasks. Since a standard Service call blocks until completion, using a Service for a multi-second motion trajectory would freeze execution. Actions resolve this by combining three messages under the hood:

  • Goal: Client sends target state to server (e.g., "Navigate to Waypoint X").
  • Feedback: Server streams periodic progress updates (e.g., "Distance remaining: 1.2m").
  • Result: Final status response upon success, failure, or preemptive cancellation.
ROS 2 action goal, feedback, and result flow between client and server
Actions (Goal / Feedback / Result)
IMPL
from rclpy.action import ActionServer
from rclpy.node import Node
from nav2_msgs.action import NavigateToPose

class ActionExampleNode(Node):
    def __init__(self):
        super().__init__('action_example_node')
        self._action_server = ActionServer(
            self, NavigateToPose, 'navigate_to_pose', self.execute_callback
        )

    async def execute_callback(self, goal_handle):
        self.get_logger().info('Executing navigation trajectory...')
        # distance_remaining is a real float32 field on nav2_msgs/action/NavigateToPose.Feedback
        # (confirmed against Humble/Iron/Jazzy nav2_msgs) — the full message also carries
        # current_pose, navigation_time, estimated_time_remaining, and number_of_recoveries,
        # omitted here for brevity.
        feedback_msg = NavigateToPose.Feedback()

        for i in range(5):
            feedback_msg.distance_remaining = float(5 - i)
            goal_handle.publish_feedback(feedback_msg)

        goal_handle.succeed()
        result = NavigateToPose.Result()
        return result

Mismatched Frequencies: Stream Coalescing

In multi-modal stacks, sensor data arrives asynchronously across varying frequencies (e.g., 30 Hz RGB camera feeds vs. 500 Hz joint encoders vs. a 10 Hz VLA policy loop). Subscribing to these streams via naive, isolated callbacks causes temporal misalignment and race conditions.

To synchronize heterogeneous feeds without manual buffering, ROS 2 provides message_filters using an approximate-time sliding window:

IMPL
import message_filters
from rclpy.node import Node
from sensor_msgs.msg import Image, JointState

class VLAInferenceNode(Node):
    def __init__(self):
        super().__init__('vla_inference_node')
        
        image_sub = message_filters.Subscriber(self, Image, '/camera/rgb')
        joint_sub = message_filters.Subscriber(self, JointState, '/joint_states')
        
        # Coalesces streams running at 30Hz and 500Hz within a 10ms slop window
        self.ts = message_filters.ApproximateTimeSynchronizer(
            [image_sub, joint_sub], queue_size=10, slop=0.01
        )
        self.ts.registerCallback(self.synchronized_callback)

    def synchronized_callback(self, image_msg: Image, joint_msg: JointState):
        # Callback executes ONLY when a temporally aligned pair is matched
        self.get_logger().info(
            f"Processing aligned pair at timestamp: {image_msg.header.stamp.sec}.{image_msg.header.stamp.nanosec}"
        )

Data Logging & Replay: rosbag2 Mechanics

Offline dataset collection, trajectory replay, and regression testing rely on the high-throughput rosbag2 subsystem.

  • Disk I/O Pipeline: rosbag2 attaches directly to DDS topics, buffering incoming serialized byte streams into memory queues before writing to disk using pluggable storage backends (SQLite3 or high-performance MCAP).
  • Deterministic Simulation Time: To evaluate policy execution offline without wall-clock timing jitter, rosbag2 publishes stored timestamps to the /clock topic. Downstream nodes set use_sim_time = True, forcing execution timers, TF tree calculations, and callback schedules to advance strictly driven by log ticks.

3. Quality of Service (QoS): Tuning for Real-Time Control

In distributed systems, networking requirements differ by payload type. High-frequency joint positions require minimum latency over guaranteed delivery, whereas static map files require complete data integrity regardless of transmission time.

ROS 2 exposes fine-grained DDS QoS profiles to configure delivery behavior per topic:

Reliability:

  • Reliable: Guarantees packet delivery. Missing packets trigger TCP-like retransmission over UDP.
  • Best Effort: Fire-and-forget delivery. Dropped packets are ignored.

History:

  • Keep Last (Depth = N): Retains only the N most recent samples in memory buffers.
  • Keep All: Retains samples up to hardware/DDS resource limits.

The QoS Compatibility Matrix

For a publisher and subscriber to establish a connection, their QoS policies must be compatible. If a publisher offers a lower level of service than a subscriber requests, the connection silently fails to establish traffic flow.

QoS compatibility matrix for Reliable and Best Effort publisher and subscriber combinations
The QoS Compatibility Matrix

Advanced Fault-Tolerance Policies

  • Durability: Controls historical message availability for late-joining subscribers (Volatile drops past messages; Transient Local stores the last N messages for new nodes, ideal for static TF transforms or URDF descriptions).
  • Deadline: Specifies an expected maximum time interval TT between consecutive messages. If missed, ROS 2 fires an event callback, allowing system safety managers to safely trip hardware emergency brakes.
  • Liveliness: Monitors node health across network boundaries. If a publishing node crashes or hangs, the DDS middleware detects liveliness loss and fires a callback—applications build on top of this signal to trigger failover logic, such as promoting a standby node.

4. Executors and Callback Groups: Scheduling Compute

Now let's examine process-level concurrency. Calling rclcpp::spin(node) or rclpy.spin(node) hands control to the Executor—the internal task scheduler that coordinates timers, subscription callbacks, service requests, and action triggers.

The Single-Threaded Executor Problem

By default, ROS 2 nodes utilize a single-threaded executor. The executor queries the DDS layer via kernel epoll or DDS wait-sets to build an execution set. When work arrives, callbacks are executed sequentially on a single thread.

If an inference callback performs an expensive tensor operation blocking the main thread for 100ms, all other timers and callbacks within that node stall. A 500 Hz actuator control loop running in the same process will miss its timing deadline.

Single-threaded executor bottleneck where inference blocks the control loop
Problem: Single-Threaded Executor Bottleneck

Multi-Threaded Executors & Callback Groups

To distribute work across CPU cores, we deploy the MultiThreadedExecutor. However, multithreading shared node state introduces data race conditions. To control concurrent execution safely, callbacks are assigned to Callback Groups:

  • Mutually Exclusive: Callbacks assigned to this group will never execute concurrently. Even if multiple executor threads are free, callbacks within the same group queue sequentially. This protects non-thread-safe internal state.
  • Reentrant: Callbacks assigned to this group can be executed fully concurrently across available executor threads, including overlapping instances of the exact same callback.
Multi-threaded executor with callback groups isolating inference from the control loop
Solution: Multi-Threaded Executor & Callback Groups
IMPL
from rclpy.callback_groups import MutuallyExclusiveCallbackGroup, ReentrantCallbackGroup
from rclpy.executors import MultiThreadedExecutor
from rclpy.node import Node

class ParallelComputeNode(Node):
    def __init__(self):
        super().__init__('parallel_compute_node')
        
        # Group 1: High-priority isolated control timer
        self.control_group = MutuallyExclusiveCallbackGroup()
        # Group 2: Reentrant group for parallel heavy compute
        self.compute_group = ReentrantCallbackGroup()

        self.control_timer = self.create_timer(
            0.002, self.control_loop_callback, callback_group=self.control_group
        )
        self.inference_sub = self.create_subscription(
            Image, '/camera/rgb', self.inference_callback, 10, callback_group=self.compute_group
        )

    def control_loop_callback(self):
        pass # 500 Hz execution loop

    def inference_callback(self, msg):
        pass # Heavy model inference call

Two Critical Concurrency Pitfalls

  • The Default Group Trap: Unassigned timers or subscribers default to the node's single Default Mutually Exclusive Callback Group. Switching to a MultiThreadedExecutor without assigning custom groups provides zero speedup, as callbacks remain serialized.
  • The Synchronous Service Deadlock: Calling a synchronous service client from inside a timer callback assigned to the same Mutually Exclusive group freezes the node. The timer thread blocks waiting for the response, while the executor cannot allocate a thread to process the response because the group lock is held by the timer. The fix is to place synchronous clients in a callback group separate from anything that might block waiting on them—Reentrant is the usual choice, but any distinct group resolves the deadlock.

5. Scaling Up: Zero-Copy Transport for Foundation Models

Composable Nodes: Eliminating IPC Overhead

When running nodes as distinct OS processes, intra-machine communication incurs OS kernel context switches, CPU page-table swaps, and Inter-Process Communication (IPC) serialization overhead.

ROS 2 resolves this via Node Composition. Nodes are compiled as shared libraries (.so or .dylib artifacts) and loaded dynamically at runtime into a shared OS container process (component_container).

Out-of-process IPC between camera and inference nodes with kernel context switches
Before: Out-of-Process IPC
In-process node composition with direct shared_ptr memory access
After: In-Process Composition

When composed into a single container, publishing a topic bypasses serialization, sockets, and DDS entirely. The publisher passes a C++ std::shared_ptr directly to the subscriber's memory space, dropping IPC latency to near zero.

Zero-Copy Transport via Shared Memory (/dev/shm)

When process isolation is required across distinct binaries, transferring 10 MB tensor payloads at 60 Hz over localhost network sockets saturates CPU memory bandwidth.

To resolve this, ROS 2 supports intra-machine Zero-Copy Transport via Loaned Messages. Leveraging POSIX shared memory (e.g., /dev/shm combined with Eclipse Iceoryx or FastDDS SHM transport), the middleware pre-allocates a shared ring-buffer pool. Instead of copying data across user-kernel boundaries, nodes publish pointers directly to shared memory blocks.

Inter-process zero-copy via loaned messages over POSIX shared memory
Zero-Copy via Shared Memory (/dev/shm)

Production C++ Zero-Copy Implementation

c++
#include <memory>
#include <utility>
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"

class ZeroCopyCameraNode : public rclcpp::Node {
public:
  ZeroCopyCameraNode() : Node("zero_copy_camera") {
    // Publisher negotiates loaned message capabilities with underlying DDS transport
    pub_ = this->create_publisher<sensor_msgs::msg::Image>("camera/image_raw", 10);
  }

  void publish_frame() {
    // 1. Verify DDS shared-memory transport capabilities
    if (!pub_->can_loan_messages()) {
      RCLCPP_WARN_ONCE(
        this->get_logger(),
        "Shared memory zero-copy not configured! Falling back to standard transport.");
      return;
    }

    // 2. Loan a pre-allocated memory chunk directly from /dev/shm
    auto loaned_msg = pub_->borrow_loaned_message();

    // 3. Populate memory directly in place (zero stack-to-heap copy)
    sensor_msgs::msg::Image & msg = loaned_msg.get();
    msg.header.stamp = this->now();
    msg.header.frame_id = "camera_optical_frame";
    msg.width = 1920;
    msg.height = 1080;
    msg.encoding = "rgb8";
    msg.step = 1920 * 3;
    // NOTE: .resize() on a dynamic field like `data` works here because the loan is
    // pre-sized to the shared-memory chunk's capacity, but a strict, fixed-size POD
    // loan does not universally accept a runtime resize on every DDS vendor — behavior
    // here can differ between FastDDS and Cyclone DDS implementations, and between
    // rmw implementations more generally. Verify against your specific RMW vendor
    // before relying on this in a hard-real-time path.
    msg.data.resize(1920 * 1080 * 3); // Written directly in shared buffer

    // 4. Transfer pointer ownership to middleware
    pub_->publish(std::move(loaned_msg));
  }

private:
  rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr pub_;
};

Three Strict Systemic Constraints

  • The Python (rclpy) Wall: True zero-copy loaned messages are supported only in C++ (rclcpp). The rclpy Python bindings have no implemented API for loaned messages—every message is constructed as a Python object through the generated message bindings, which requires a real allocation and copy rather than a raw pointer handoff. High-performance pipelines implement sensor ingest and preprocessing components in C++, bridging to Python solely at the GPU boundary or using TorchScript/ONNX C++ execution primitives.
  • Fixed-Size Data (POD) Constraint: Loaned message allocators require pre-allocating deterministic memory chunks. Thus, ROS 2 .msg interfaces must consist of Plain Old Data (POD) or fixed-capacity arrays. Dynamic arrays or unbounded strings invalidate static chunk pre-allocation, causing borrow_loaned_message() to fail.
  • The GPU VRAM Memory Gap: CPU shared memory keeps tensors in host RAM. However, neural network inference requires execution on the GPU. To eliminate host-to-device cudaMemcpy overhead, the ROS 2 Hardware Acceleration Working Group introduced the general Type Adaptation mechanism, which lets a node negotiate a custom, transport-specific representation for a message type instead of the default serialized form. NVIDIA's Isaac ROS builds on top of this mechanism with NITROS (NVIDIA Isaac Transport for ROS), which specifically enables nodes to pass native cudaMalloc VRAM pointers across graph pipelines rather than routing tensors through host RAM at all.

A Latency Model for Robotics Communication

To formalize transport optimization decisions, it helps to borrow the standard latency-bandwidth decomposition used in parallel and distributed computing—the same family of fixed-overhead-plus-transfer-time models that includes the classic Hockney network model—and apply it to inter-process communication:

Latency model comparing standard ROS 2 transport and zero-copy throughput by message size
A Latency Model for Robotics Communication

In standard serialized ROS 2 transport, end-to-end delivery latency TtotalT_{\mathrm{total}} is governed by:

Ttotal=Toverhead+2DBWmemcpy+DBWnetworkT_{\mathrm{total}} = T_{\mathrm{overhead}} + \frac{2D}{BW_{\mathrm{memcpy}}} + \frac{D}{BW_{\mathrm{network}}}

Where DD is payload size in bytes, and ToverheadT_{\mathrm{overhead}} represents fixed kernel context switching and DDS scheduling latency. BWmemcpyBW_{\mathrm{memcpy}} is the host CPU's memory bus bandwidth—the rate at which the CPU can copy bytes between user-space and kernel-space buffers during serialization and deserialization—and BWnetworkBW_{\mathrm{network}} is the effective bandwidth of the transport actually carrying the packet, which on a single machine is the loopback/NIC bandwidth rather than a physical network link. The factor of 2 on the memcpy term accounts for publisher serialization and subscriber deserialization each paying that cost once.

Under Zero-Copy Transport, payload size DD drops to a fixed pointer address size (8 bytes on a 64-bit system):

Ttotal(zero-copy)ToverheadT_{\mathrm{total}}^{\text{(zero-copy)}} \approx T_{\mathrm{overhead}}

Regime Analysis

  • Latency-Bound Regime (D<100KBD < 100\,\mathrm{KB}): For joint states, IMU metrics, or TF frames, standard transport is optimal. Fixed scheduling overhead ToverheadT_{\mathrm{overhead}} dominates. Enabling zero-copy shared memory can actually introduce net latency due to POSIX allocation management locks.
  • Bandwidth-Bound Regime (D>1MBD > 1\,\mathrm{MB}): For RGB images, 3D point clouds, or large embeddings, payload transfer terms D/BWD / BW dominate. Latency scales linearly with payload size, saturating CPU memory bus bandwidth.

Production System Architectural Cheat Sheet

Workload / Data StreamPayload Size (DD)Frequency TargetRecommended ArchitectureQoS & Concurrency Setup
Joint Encoders / IMU Feeds<10KB< 10\,\mathrm{KB}2001000Hz200\text{–}1000\,\mathrm{Hz}Standard Pub/Sub (rclcpp / rclpy)Best Effort, Keep Last (Depth=1), Mutually Exclusive Group
Raw Camera / Point Cloud110MB1\text{–}10\,\mathrm{MB}3060Hz30\text{–}60\,\mathrm{Hz}Composable Nodes or Zero-Copy C++Shared Memory (/dev/shm + Iceoryx), Reentrant Group
VLA Policy Tensors (GPU)>10MB> 10\,\mathrm{MB}1030Hz10\text{–}30\,\mathrm{Hz}Type Adaptation (NITROS)Direct VRAM Pointer Pass (cudaMalloc), Reentrant Group
IK Solver / Hardware State<1KB< 1\,\mathrm{KB}Discrete / On-DemandAsynchronous ServiceDedicated Reentrant Group (Prevents Deadlock)
Long Navigation Paths<100KB< 100\,\mathrm{KB}Long-RunningAction ServerMulti-Threaded Executor with Cancellation Handlers

Epilogue

This post stayed inside the communication fabric: decentralized DDS discovery, Topics / Services / Actions, QoS for real-time control, executor scheduling and callback groups, and zero-copy paths when payloads get large. That is the substrate every ROS 2-based autonomy stack depends on—but it is not the whole of ROS 2.

Plenty of production-critical capabilities were intentionally left out here, including:

  • Lifecycle Nodes: managed state machines (Unconfigured → Inactive → Active → Finalized) for deterministic bring-up, teardown, and fault recovery.
  • SROS 2: TLS / x.509 authentication, access-control policies, and payload encryption over DDS domains.
  • TF2: the distributed transform tree that keeps sensor, robot, and map frames consistent in time.
  • ros2_control and higher-level stacks (Nav2, MoveIt 2): hardware interfaces, controllers, navigation, and manipulation built on top of this fabric.
  • Parameters, launch, and composition tooling: runtime configuration, process graphs, and how teams actually ship multi-node systems.

Each of those deserves its own treatment. Communication is the first cut because if the fabric is wrong, nothing above it stays real-time.

Next in the series: Inside Robot Data Storage—an introduction to the production-grade methods for storing queryable, large-scale multimodal robot data (MCAP, Zarr, sharding, featurization, and the layout patterns that keep fleet corpora usable at training time).

Acknowledgements

A huge thank you to my former colleagues at the Stanford AI Lab and peers across the robotics community for reviewing early technical outlines, auditing discovery protocol details, and providing feedback on real-time execution bounds!

References

ROS 2 Documentation, docs.ros.org

Data Distribution Service (DDS) Specification, OMG Standard

Eclipse Iceoryx Shared Memory Middleware, iceoryx.io

Macenski et al., "Robot Operating System 2: Design, Architecture, and Uses In The Wild," Science Robotics, 2022. arXiv:2211.07752

DISTNEW NOTES BY EMAIL

Subscribe

Occasional notes when a new piece in this series goes up. No extras.