Detailed content
🔹 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.
🔹 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.
🔹 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.
🔹 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.
🔹 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.
Plan (MoveIt2)
OMPL/Pilz generates a collision-free trajectory; useful for the approach movement.
Control (ros2_control)
JointTrajectoryController execution; VLA can inject reactive setpoints.
Protect
Hardware speed/torque limits, e-stop, workspace bounds.
MoveIt2
Planning + collision.
ros2_control
Controller layer.
IK/FK
Kinematics.
E-stop
Physical safety.
🔹 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
Next module
2.5 — Hardware: arms, grippers, RGB-D sensors, and Jetson edge compute—the body where ROS2 runs.