PTENES
← Back to Track 2 MODULE 2.4

🔗 ROS2: Middleware for Real-World Robotics

ROS2 is the glue between perception, planning, and control on the physical robot. Here, you model a VLA as an inference node, synchronize camera + state with QoS, transform poses with tf2, plan with MoveIt2, and close the loop in real time.

6
Topics
40
Minutes
Inter.
Level
Practice
Type

Detailed content

Node graph: from frame to torque /cameraImage+depth /vla_nodeinference /tf2frames /controllertrajectory 🦾 /armtorque topics (pub/sub) · services · actions · QoS
1

🔹 ROS2 vs. ROS1: DDS, real time, safety

ROS2 rewrote the middleware on top of DDS (Data Distribution Service): decentralized communication (without roscore only), QoS configurable, real-time support, and safety (SROS2). The blocks are nodes that communicate via topics (pub/sub), services (req/resp) and actions (long-horizon goals with feedback).

📊 Communication primitives

  • Topic — continuous flow, many-to-many (e.g., images, joints).
  • Service — single synchronous call (e.g., “turn on gripper”).
  • Action — long-duration objective with feedback/cancellation (e.g., "move to pose").
  • Parameter — runtime configuration per node.

⚡ Practical tip

Use topic for continuous streams (images, joints), service for one-off calls and action for "move to pose X" — where you need progress feedback and the ability cancel. Choosing the wrong primitive (e.g., a topic for a single goal) produces fragile code.

DDS

Decentralized transport.

Node

Process unit.

Action

Long-horizon goal + feedback.

SROS2

Security and auth.

2

🔹 Anatomy of a system: perception, planning, control

A ROS2 system breaks the robot down into nodes with a single responsibility: camera/RGB-D drivers, a perception node, planner, joint controller. This modularity lets you swap the "policy" without touching the arm driver—exactly where the VLA comes in.

📊 Typical nodes in a VLA cell

  • /camera — RealSense driver publishes RGB + depth.
  • /vla_node — policy inference, publishes actions.
  • /robot_state_publisher + /tf2 — frame tree.
  • /controller_manager — ros2_control executes the trajectory.
# inspecionar o sistema vivo
ros2 node list
ros2 topic list
ros2 topic hz /camera/color/image_raw     # checar frequência
ros2 topic echo /joint_states --once
ros2 run rqt_graph rqt_graph              # visualizar o grafo

⚡ Practical tip

Before writing code, map the graph with rqt_graph and check the hz for each topic. Camera at 60 Hz, but /joint_states at 10 Hz, it becomes a silent synchronization bottleneck.

Perception

Vision/RGB-D node.

Planning

VLA or MoveIt2.

Control

ros2_control / driver.

Modularity

Switch policies in isolation.

3

🔹 Integrating a VLA: inference node, synchronization, QoS

The VLA becomes a rclpy node that subscribes to camera + state, runs the forward pass, and publishes the command. Image and state need to come from the same instant: use message_filters (ApproximateTimeSynchronizer) and choose the QoS right — BEST_EFFORT for images (drops stale ones), RELIABLE for commands.

import rclpy
from rclpy.node import Node
from message_filters import Subscriber, ApproximateTimeSynchronizer
from sensor_msgs.msg import Image, JointState
from rclpy.qos import QoSProfile, ReliabilityPolicy

class VLANode(Node):
    def __init__(self):
        super().__init__("vla_node")
        qos = QoSProfile(depth=1, reliability=ReliabilityPolicy.BEST_EFFORT)
        img = Subscriber(self, Image, "/camera/color/image_raw", qos_profile=qos)
        st  = Subscriber(self, JointState, "/joint_states")
        sync = ApproximateTimeSynchronizer([img, st], queue_size=2, slop=0.02)
        sync.registerCallback(self.on_obs)

    def on_obs(self, img, state):
        action = self.policy(preprocess(img), state.position)  # forward VLA
        self.pub.publish(to_cmd(action))                       # ação chunked

rclpy.init(); rclpy.spin(VLANode())

✓ Do

  • ✓Synchronize image+state by timestamp (small slop).
  • ✓QoS BEST_EFFORT/depth=1 for high-rate sensors.
  • ✓Run inference in a separate thread/callback group.

✗ Avoid

  • ✗Match incompatible QoS (RELIABLE vs BEST_EFFORT)—won't connect.
  • ✗Block the executor with a heavy forward pass in the main callback.
  • ✗Use images and state from different time steps.

rclpy

ROS2 Python API.

message_filters

Timestamp sync.

QoS

Reliability/depth.

callback group

Safe concurrency.

4

🔹 tf2 and frames: model pose → arm command

The VLA predicts a pose in a frame—usually the camera frame. The arm expects commands in the base. tf2 maintains the timed transform tree (base_link → camera_link → tool0) and converts poses between frames while respecting the timestamp.

from tf2_ros import Buffer, TransformListener
import tf2_geometry_msgs

buf = Buffer(); TransformListener(buf, node)
# transformar a pose prevista (frame da câmera) -> frame base do braço
pose_base = buf.transform(pose_cam, "base_link", timeout=Duration(seconds=0.1))
# agora a pose está no frame que o controlador entende

📊 Classic frame errors

  • Poor hand-eye calibration — an incorrect camera↔base transform shifts everything.
  • Wrong timestamp — transforming with an old/future TF causes a jump.
  • Axis convention — REP-103: x forward, z up.

tf2

Frame tree.

Hand-eye

Camera↔base calibration.

REP-103

Axis convention.

Timestamp

Timed TF.

5

🔹 MoveIt2 and controllers: trajectory, limits, safety

MoveIt2 plans motion with collision checking, kinematics (IK/FK), and time parameterization respecting joint limits. In high-frequency reactive VLA, you often ignore the planner and send setpoints directly via ros2_control — but MoveIt2 comes in for safe approach and collision detection.

1

Plan (MoveIt2)

OMPL/Pilz generates a collision-free trajectory; useful for the approach movement.

2

Control (ros2_control)

JointTrajectoryController execution; VLA can inject reactive setpoints.

3

Protect

Hardware speed/torque limits, e-stop, workspace bounds.

MoveIt2

Planning + collision.

ros2_control

Controller layer.

IK/FK

Kinematics.

E-stop

Physical safety.

6

🔹 Latency and real time: from frame to torque, jitter, watchdogs

The chain camera → inference → tf2 → controller → torque has a latency budget. Worse than high latency is the jitter (variance): an irregular loop destabilizes control. Use a kernel PREEMPT_RT, real-time priority and watchdogs that stop the arm if the command is late.

# watchdog: se não chegar comando em 100ms, freia
self.create_timer(0.1, self.watchdog)
def watchdog(self):
    if (self.now() - self.last_cmd) > 0.1:
        self.hold_position()   # fail-safe

# medir latência fim-a-fim do pipeline
ros2 topic delay /arm_command

⚡ Practical tip

Action chunking (from M2.1) is your ally: predicting K steps amortizes inference and tolerates ROS network jitter, keeping the torque loop smooth even if the VLA only re-infers every N cycles.

Latency

Frame → torque.

Jitter

Loop variance.

PREEMPT_RT

Real-time kernel.

Watchdog

Timeout fail-safe.

✓ Do

  • ✓Measure end-to-end latency and jitter (p99), not just the average.
  • ✓A watchdog that slows the arm if the command is delayed.
  • ✓Action chunking to tolerate ROS network jitter.

✗ Avoid

  • ✗Control loop without an RT kernel in a critical task.
  • ✗Send torque without a timeout/safety limit.
  • ✗Optimize average latency while ignoring jitter spikes.

✅ Module summary

✓
ROS2 on DDS — nodes, topics, services, actions, and QoS.
✓
VLA as a node — synchronize camera+state and choose the right QoS.
✓
tf2 — convert the model pose to the arm frame.
✓
MoveIt2 + ros2_control — safe planning and trajectory execution.
✓
Real time — jitter control with RT, chunking, and watchdogs.

Next module

2.5 — Hardware: arms, grippers, RGB-D sensors, and Jetson edge compute—the body where ROS2 runs.