ROS 2 — Robot Operating System 2
Què és ROS 2?
ROS 2 (Robot Operating System 2) no és un sistema operatiu en el sentit convencional: és un middleware de codi obert que proporciona infraestructura de comunicació, eines i biblioteques per al desenvolupament de software robòtic. És l'estàndard de facto en recerca i cada cop més en producció.
ROS 2 és la versió reescrita de ROS 1, amb millores fonamentals: - Temps real: suport per a comunicació hard real-time (DDS - Data Distribution Service) - Multi-robot: dissenyat per a flotes de robots des del principi - Seguretat: autenticació i xifratge de comunicacions - Portabilitat: suporta Linux, Windows i macOS
Arquitectura de ROS 2
graph TD
subgraph Nodes ROS 2
N1[Node Càmera\ncamera_node]
N2[Node Deteccio\nyolo_detector]
N3[Node Planificacio\npath_planner]
N4[Node Control\nrobot_controller]
end
N1 -- Topic: /camera/image_raw --> N2
N2 -- Topic: /deteccions/objects --> N3
N3 -- Topic: /cmd_vel --> N4
N3 -- Service: /get_plan --> N4
N4 -- Action: /navigate_to_pose --> N3
Conceptes clau:
Nodes: processsos independents que realitzen una tasca específica. Cada node pot ser en Python o C++.
Topics: canals de comunicació pub/sub asíncrons. Un node publica missatges a un topic i altres nodes es subscriuen. Exemple: el node de la càmera publica imatges al topic /camera/image_raw.
Services: comunicació request-response síncrona. El client envia una petició i espera la resposta. Exemple: servei /get_map que retorna el mapa actual.
Actions: com els serveis però per a tasques llargues, amb feedback periòdic. Exemple: acció /navigate_to_pose que navega a una posició i informa del progrés.
Messages: estructures de dades tipades que viatgen per topics i serveis. ROS 2 defineix centenars de tipus estàndard (imatges, poses, nucs de punts, etc.).
Exemple de node ROS 2 en Python
#!/usr/bin/env python3
"""
Node ROS 2 exemple: detector d'obstacles simplificat
Subscriu a les lectures del làser i publica si hi ha un obstacle proper
"""
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
from std_msgs.msg import Bool, String
class DetectorObstacles(Node):
"""Node que detecta obstacles usant un sensor làser (LiDAR)."""
def __init__(self):
super().__init__('detector_obstacles')
# Paràmetre configurable: distància mínima de seguretat (metres)
self.declare_parameter('distancia_minima', 0.5)
self.distancia_minima = self.get_parameter('distancia_minima').value
# Subscriptor: llegeix les dades del LiDAR
self.subscripcio_laser = self.create_subscription(
LaserScan,
'/scan',
self.callback_laser,
10 # Longitud de la cua de missatges
)
# Publicadors: avís d'obstacle i missatge descriptiu
self.pub_obstacle = self.create_publisher(Bool, '/obstacle_detectat', 10)
self.pub_avis = self.create_publisher(String, '/avis_obstacle', 10)
self.get_logger().info(
f'Node detector_obstacles iniciat. '
f'Distancia minima de seguretat: {self.distancia_minima}m'
)
def callback_laser(self, msg: LaserScan):
"""S'executa cada vegada que arriben noves dades del làser."""
# Filtrem les lectures invàlides (inf, nan, 0)
lectures_valides = [
r for r in msg.ranges
if msg.range_min < r < msg.range_max
]
if not lectures_valides:
return
distancia_minima_llegida = min(lectures_valides)
hi_ha_obstacle = distancia_minima_llegida < self.distancia_minima
# Publiquem si hi ha obstacle
msg_bool = Bool()
msg_bool.data = hi_ha_obstacle
self.pub_obstacle.publish(msg_bool)
if hi_ha_obstacle:
msg_avis = String()
msg_avis.data = (
f'ALERTA: Obstacle a {distancia_minima_llegida:.2f}m '
f'(minim: {self.distancia_minima}m)'
)
self.pub_avis.publish(msg_avis)
self.get_logger().warning(msg_avis.data)
def main(args=None):
rclpy.init(args=args)
node = DetectorObstacles()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Executar el node amb Docker:
# Córrer ROS 2 Humble amb Docker (sense necessitat d'instal·lar ROS localment)
docker run -it --rm \
--name ros2-joan-garcia \
--network host \
osrf/ros:humble-desktop-full \
bash
# Dins del contenidor, crear i executar el node
mkdir -p /workspace/src/detector_obstacles/detector_obstacles
# ... (copiar el fitxer Python)
cd /workspace
colcon build
source install/setup.bash
ros2 run detector_obstacles detector_obstacles
Eines de ROS 2
rviz2: visualitzador 3D per a dades robòtiques (mapes, nucs de punts, trajectòries, marcs de referència)
rqt: conjunt d'eines GUI per a depuració i monitorització (gràfics de topics, visualització d'imatges, etc.)
rosbag2: gravació i reproducció de tots els missatges de ROS 2. Indispensable per a depuració i entrenament d'IA.
ros2 launch: arxius de llançament que inicien múltiples nodes simultàniament amb la seva configuració.
Estructura d'un paquet ROS 2
El codi de ROS 2 s'organitza en paquets (packages) dins d'un workspace. Un workspace típic té aquesta estructura:
robot_ws/
└── src/
└── detector_obstacles/
├── package.xml # Metadades i dependencies del paquet
├── setup.py # Configuracio d'instal-lacio (paquets Python)
├── setup.cfg
├── resource/
│ └── detector_obstacles
├── launch/
│ └── detector.launch.py
└── detector_obstacles/
├── __init__.py
└── detector_node.py # El node vist més amunt
# Crear un paquet Python nou dins del workspace
cd robot_ws/src
ros2 pkg create --build-type ament_python detector_obstacles \
--dependencies rclpy sensor_msgs std_msgs
# Compilar tot el workspace
cd ~/robot_ws
colcon build --symlink-install
source install/setup.bash
Services i Actions en Python
Els topics (vistos més amunt) són ideals per a fluxos continus de dades, però quan cal una resposta puntual a una petició concreta (un servei) o una tasca llarga amb feedback (una acció), ROS 2 ofereix mecanismes dedicats.
# Servei ROS 2: retorna si una posicio es segura per navegar-hi
import rclpy
from rclpy.node import Node
from std_srvs.srv import SetBool
class ServeiValidacioPosicio(Node):
def __init__(self):
super().__init__('validacio_posicio')
self.srv = self.create_service(
SetBool, '/validar_posicio', self.callback_validar
)
def callback_validar(self, request, response):
# request.data: True si es vol activar la validacio estricta
response.success = True
response.message = 'Posicio validada correctament' if request.data else 'Validacio desactivada'
return response
def main(args=None):
rclpy.init(args=args)
rclpy.spin(ServeiValidacioPosicio())
rclpy.shutdown()
# Cridar el servei des de la terminal, sense escriure cap client
ros2 service call /validar_posicio std_srvs/srv/SetBool "{data: true}"
Les actions afegeixen feedback periòdic i la possibilitat de cancel·lar tasques llargues (per exemple, /navigate_to_pose, que informa del percentatge de trajecte completat mentre el robot es desplaça). Es defineixen amb tres missatges: Goal (objectiu), Feedback (progrés) i Result (resultat final).
Launch files i paràmetres
Un launch file permet arrencar diversos nodes alhora amb la seva configuració, en lloc d'obrir una terminal per node:
# launch/detector.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='detector_obstacles',
executable='detector_node',
name='detector_obstacles',
parameters=[{'distancia_minima': 0.4}],
output='screen',
),
Node(
package='detector_obstacles',
executable='camera_node',
name='camera_node',
),
])
Miniactivitat — AC5071/04/03 — Paquet ROS 2 propi
Crea un paquet ROS 2 nou (ros2 pkg create) amb un node que publiqui un missatge propi cada segon (per exemple, la temperatura simulada d'un sensor). Afegeix-hi un servei que permeti activar/desactivar les publicacions, i un launch file que arrenqui el node amb un paràmetre configurable. Comprova amb ros2 topic echo i ros2 service call que tot funciona.