PTENES
Inicio / Técnico / Módulo 2.4
MÓDULO 2.4

🤖 ROS2: middleware para robótica real

El sistema operativo de los robots: pub/sub, TF2, Nav2 e integración con VLA. ROS2 es el estándar de la industria para la robótica de producción; entender su arquitectura es obligatorio para cualquier deploy real.

6
Temas
45
Minutos
Técnico
Nivel
Práctica
Tipo
Middleware DDS — Topics · Services · Actions 📷 Camera Node /image_raw 🔄 TF2 Tree /tf · /tf_static 🧠 VLA Inference /vla/actions · 25 Hz 🦾 MoveIt2 /joint_trajectory 🗺 Nav2 /cmd_vel 🤖 Hardware del robot ros2_control · HAL drivers · actuators

Arquitectura ROS2: los nodos publican en el bus DDS; el nodo VLA Inference recibe sensores y publica acciones para el hardware.

1

ROS2 Humble/Iron: arquitectura DDS

El «Linux de la robótica» reinventado con middleware distribuido y sin master node. DDS garantiza comunicación en tiempo real y descubrimiento automático de nodos en cualquier topología de red.

🏗️ Qué es ROS2

ROS2 es el framework middleware estándar de la industria robótica. A diferencia de ROS1, usa DDS (Data Distribution Service) como capa de transporte, que ofrece QoS configurable, descubrimiento automático de nodos y comunicación en tiempo real sin un master node central.

Cualquier robot que llega a producción industrial usa ROS2. Es el «Linux de la robótica». Integrar un modelo VLA en ROS2 es obligatorio para un deploy real. Las distribuciones actuales (Humble LTS, Iron) son estables y tienen soporte hasta 2027+.

✅ Usa ROS2 cuando

  • ✓ Despliegue en hardware real con múltiples sensores
  • ✓ Integración con Nav2/MoveIt2 para planificación
  • ✓ Equipo con varios developers trabajando en paralelo
  • ✓ Necesidad de rosbag2 para reproducir datos
  • ✓ Comunicación entre procesos en CPU distintas

❌ Evita cuando

  • ✗ Prototipo individual sin integración de sensores
  • ✗ Ciclo de entrenamiento puro de ML (PyTorch es suficiente)
  • ✗ Simulación solo en MuJoCo/PyBullet
  • ✗ Proyectos con un equipo sin conocimientos de ROS
  • ✗ Entornos donde el overhead de DDS es prohibitivo
DDS
Fast-RTPS o Cyclone DDS como implementación
Nodos (Nodes)
Procesos independientes con ejecutores de uno o varios hilos
QoS Profiles
reliability, durability, history configurables por topic
colcon
Build system: colcon build --symlink-install
Lifecycle Nodes
Estados: unconfigured → inactive → active → finalized
ament
ament_cmake para C++, ament_python para Python
2

Topics, Services y Actions

Los tres patrones de comunicación en ROS2. Cada uno resuelve un problema diferente: elegir el incorrecto causa errores sutiles y difíciles de depurar en producción.

📡

Topics — Pub/Sub asíncrono

Transmisión de datos continuos: imagen de la cámara, estados de las articulaciones, poses.

El publisher publica sin saber quién está escuchando. N publishers → N subscribers posibles. Alta frecuencia, sin garantía de entrega por defecto. Ideal para la salida de inferencia de VLA.

ros2 topic echo /image_raw
ros2 topic hz /vla/actions # medir frequência
🔁

Servicios — solicitud/respuesta síncrona

Llamadas puntuales con respuesta garantizada: cambiar de modo, activar sensor.

Bloqueante por naturaleza: el cliente espera la respuesta. Se usa para configuración y control del estado, nunca para streaming. Se necesita un timeout para evitar un deadlock.

ros2 service call /switch_mode std_srvs/srv/SetBool "{data: true}"
⚙️

Actions — Tareas largas con feedback

Mover el brazo hasta una posición, navegar hasta un waypoint, ejecutar una tarea compleja.

Goal + Feedback periódico + Result final. Se puede cancelar en cualquier momento. VLA recibe comandos de alto nivel ("tomar objeto") mediante Action y publica acciones de bajo nivel mediante Topic.

ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \
"{pose: {header: {frame_id: 'map'}, pose: {position: {x: 1.0}}}}"

💡 Consejo de arquitectura VLA

VLA inference publica en /vla/actions (Topic, 25 Hz). Los comandos de tarea llegan como Action Goals. La configuración del modelo llega vía Service. Usar rqt_graph para visualizar el grafo de comunicación e identificar cuellos de botella.

3

TF2: árbol de transformaciones 3D

TF2 mantiene un árbol de transformaciones entre todos los frames de referencia del robot, actualizado en tiempo real. VLA necesita saber exactamente dónde está la cámara con respecto al gripper.

🌐 mundo 🗺 mapa 📍 odom 🤖 base_link 📷 camera_link 🦾 tool0 ✋ gripper_link

Árbol TF2 típico: world → map → odom → base_link se ramifica en la cámara y el brazo manipulador.

📐 Por qué TF2 es crítico para VLA

  • → VLA predice la pose del gripper en frame de la cámara — TF2 convierte a frame del mundo automáticamente
  • → El buffer temporal permite consultas al pasado: sincroniza la cámara (30 Hz) con los estados de las articulaciones (1000 Hz)
  • → robot_state_publisher publica automáticamente el árbol TF a partir del URDF del robot
  • → Sin TF2: calcular transformaciones manualmente en cada frame introduce errores y retrasos

// Búsqueda de transformación en Python (rclpy)

import rclpy
from tf2_ros import Buffer, TransformListener

self.tf_buffer = Buffer()
self.tf_listener = TransformListener(self.tf_buffer, self)

# Converter ponto de camera_frame para base_link
try:
    t = self.tf_buffer.lookup_transform(
        'base_link',        # frame destino
        'camera_link',      # frame origem
        rclpy.time.Time()   # timestamp (Time() = mais recente)
    )
    # t.transform.translation.{x,y,z}
    # t.transform.rotation.{x,y,z,w}
except Exception as e:
    self.get_logger().warn(f'TF lookup failed: {e}')
4

Nav2 y MoveIt2: Navegación y Manipulación

Nav2 y MoveIt2 ejecutan la planificación de rutas seguras que delega el VLA. La combinación VLA (qué hacer) + Nav2/MoveIt2 (cómo llegar allí sin colisionar) es el estándar para mobile manipulators.

🗺 Nav2 — Navegación Autónoma

  • • Behavior Trees para lógica de navegación compleja
  • • costmap2d: mapa de costo local + global
  • • Servidor de planificación: NavFn, Smac (A* en cuadrícula 2D)
  • • Controller Server: DWB, MPPI, RPP
  • • Recovery Behaviors: spin, back-up, clear costmap

🦾 MoveIt2 — Manipulación

  • • SRDF: Semantic Robot Description Format
  • • Escena de planificación: colisiones estáticas y dinámicas
  • • Planificadores OMPL: RRT*, PRM, EST
  • • Servo: control real-time para teleop y VLA
  • • Pick-and-Place: pipeline grasp → transport → place

🔗 VLA + Nav2/MoveIt2: división de responsabilidades

🧠
VLA
Decidir qué hacer: "tomar la taza roja", "ir a la mesa"
↓
Action Goal
Pose 6-DOF o waypoint 2D enviados como Action
🦾
Nav2 / MoveIt2
Resuelve como llegar allí sin colisiones ni singularidades

💡 MoveIt Servo para VLA en bucle cerrado

Para VLAs con control continuo (p. ej., ACT, Diffusion Policy), usa moveit_servo en lugar del pipeline estándar. Servo acepta comandos de velocidad/pose de alta frecuencia (100+ Hz) y realiza una comprobación de colisiones en tiempo real; es ideal para políticas que publican velocidades articulares continuamente.

5

ROS2 + VLA: nodo de inferencia

El nodo de inferencia es el puente entre el mundo ML y el mundo ROS2. Integrar PyTorch/ONNX en ROS2 presenta desafíos únicos: GIL, latencia de serialización, sincronización entre cámara y acción, y gestión de GPU memory.

// Esqueleto del nodo VLA en rclpy

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image, JointState
from geometry_msgs.msg import TwistStamped
import message_filters
import onnxruntime as ort
import numpy as np

class VLAInferenceNode(Node):
    def __init__(self):
        super().__init__('vla_inference')

        # Carregar modelo ONNX (inferência otimizada, sem GIL pesado)
        self.session = ort.InferenceSession(
            'policy.onnx',
            providers=['CUDAExecutionProvider', 'CPUExecutionProvider']
        )

        # Sincronizar câmera + joint states temporalmente
        img_sub = message_filters.Subscriber(self, Image, '/image_raw')
        js_sub  = message_filters.Subscriber(self, JointState, '/joint_states')
        self.sync = message_filters.ApproximateTimeSynchronizer(
            [img_sub, js_sub], queue_size=5, slop=0.05
        )
        self.sync.registerCallback(self.inference_callback)

        # Publisher de ações (25 Hz)
        self.action_pub = self.create_publisher(
            TwistStamped, '/vla/actions', 10
        )

    def inference_callback(self, img_msg, js_msg):
        # Preprocessar imagem (sem alocação dinâmica no hot path)
        img = np.frombuffer(img_msg.data, dtype=np.uint8)
        joints = np.array(js_msg.position, dtype=np.float32)

        # Inferência ONNX
        outputs = self.session.run(None, {
            'image': img[None], 'joints': joints[None]
        })
        action = outputs[0][0]  # (6,) joint velocities

        # Publicar
        msg = TwistStamped()
        msg.header.stamp = self.get_clock().now().to_msg()
        # ... preencher msg com action
        self.action_pub.publish(msg)

def main():
    rclpy.init()
    node = VLAInferenceNode()
    rclpy.spin(node)

if __name__ == '__main__':
    main()

✅ Buenas prácticas

  • ✓ ONNX Runtime para inferencia (sin el GIL de PyTorch)
  • ✓ message_filters para sincronización temporal
  • ✓ Buffers de numpy preasignados (sin malloc en el loop)
  • ✓ Ejecutor multihilo que separa CB de la inferencia
  • ✓ Composable nodes para zero-copy entre procesos

❌ Trampas comunes

  • ✗ PyTorch en callback (GIL bloquea el executor ROS2)
  • ✗ np.zeros() en cada frame (malloc en el hot path)
  • ✗ Ejecutor single-thread con cámara a 30 Hz + inferencia
  • ✗ Ignorar la latencia de serialización DDS para arrays grandes
  • ✗ Sin calentamiento del modelo (la primera inferencia es 10× más lenta)

💡 TensorRT para máximo rendimiento

Para alcanzar más de 50 Hz de inferencia en una GPU integrada (Jetson), convierte el modelo a TensorRT antes del deploy: trtexec --onnx=policy.onnx --saveEngine=policy.trt --fp16. Ganancia típica de 3-5× frente a ONNX Runtime estándar con cuantización INT8/FP16.

6

Launch Files y deploy en producción

El despliegue de robótica en producción es ingeniería de sistemas, no solo ML. Un launch file bien estructurado con lifecycle management y restart policies es lo que diferencia una demo de un producto real.

1

Launch File Python

Orquesta todos los nodos con parámetros, remappings y condicionales.

from launch import LaunchDescription
from launch_ros.actions import Node, ComposableNodeContainer

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='vla_inference',
            executable='vla_node',
            name='vla_inference',
            parameters=[{'model_path': 'policy.trt', 'freq_hz': 25}],
            remappings=[('/image_raw', '/camera/color/image_raw')]
        ),
        # ... mais nós
    ])
2

archivo Unit de systemd

Garantiza el reinicio automático tras fallas y el arranque automático.

[Unit]
Description=VLA Robot System
After=network.target

[Service]
ExecStart=/bin/bash -c "source /opt/ros/humble/setup.bash && \
  ros2 launch vla_bringup robot.launch.py"
Restart=on-failure
RestartSec=5s
User=robot

[Install]
WantedBy=multi-user.target
3

rosbag2 para reproducción y depuración

Graba todos los topics para reproducirlos sin conexión y analizarlos post mortem.

# Gravar: todos os topics críticos
ros2 bag record /image_raw /joint_states /vla/actions /tf

# Reproduzir com clock simulado
ros2 bag play recording_2026/ --clock

# Inspecionar bag
ros2 bag info recording_2026/
Lifecycle Nodes
configure → activate → deactivate → cleanup
Parámetros YAML
params.yaml cargado mediante launch, modificable en tiempo de ejecución
Docker + ROS2
osrf/ros:humble-desktop como base; DDS vía host network
Event Handlers
OnProcessExit: reiniciar el nodo si se bloquea durante el launch
Launch Arguments
ros2 launch pkg robot.launch.py use_sim:=true
rosbag2
Reproducción fiel de datasets para entrenamiento offline

💡 Comprobaciones de estado con Lifecycle Nodes

En producción, usa lifecycle_manager de Nav2 como estándar: verifica si cada nodo crítico alcanzó el estado active antes de liberar el sistema. Si el nodo VLA no se activa en 30 s, el lifecycle manager aborta el startup y activa la política de fallback.

✅ Resumen del módulo

✓
DDS sin nodo maestro — ROS2 usa Data Distribution Service con QoS configurable; cualquier nodo descubre automáticamente a cualquier otro en la red.
✓
Topics / Services / Actions — Cada patrón resuelve un problema: streaming asíncrono, request/reply síncrono y tareas largas con feedback y cancelación.
✓
TF2 es obligatorio para VLA — convierte poses del frame de cámara al frame del mundo en tiempo real, con búfer temporal para sincronizar sensores.
✓
Nodo de inferencia optimizado — ONNX/TensorRT, message_filters, buffers preasignados y un executor multihilo son la diferencia entre 5 Hz y 50 Hz de control.

Siguiente módulo

2.5 — Entrenamiento: Imitation Learning, ACT, Diffusion Policy