🤖 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.
Arquitectura ROS2: los nodos publican en el bus DDS; el nodo VLA Inference recibe sensores y publica acciones para el hardware.
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
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.
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.
Á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_publisherpublica 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}')
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
💡 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.
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_filterspara 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.
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.
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
])
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
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/
💡 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
Siguiente módulo
2.5 — Entrenamiento: Imitation Learning, ACT, Diffusion Policy