Stack ROS

El robot Husarion ROSbot 2R ejecuta Ubuntu 24.04 LTS con ROS 2 Jazzy Jalisco. Esta sección documenta los nodos activos durante una sesión de mapeo, los topics que circulan entre ellos y su relación con el hardware embarcado.


Entorno de ejecución

Elemento Valor
Sistema operativo Ubuntu 24.04 LTS (Noble Numbat)
Distribución ROS ROS 2 Jazzy Jalisco
Workspace del robot /home/iberomsc02/ros2_ws_02/
Directorio de mapas /home/iberomsc02/maps/
Puerto del servidor de mapas 5008

El script start_slam_server.sh inicializa el entorno y lanza el servidor Flask en un solo paso:

# Cargar entorno de ROS2 Jazzy
source /opt/ros/jazzy/setup.bash

# Cargar tu workspace
source /home/iberomsc02/ros2_ws_02/install/setup.bash

# Ejecutar servidor Flask
exec python3 /home/iberomsc02/slam_server.py

Nodos y procesos activos

Durante una sesión de mapeo completa se ejecutan simultáneamente los siguientes procesos:

Proceso Tipo Función
slam_toolbox (Husarion) Nodo ROS 2 Construye y actualiza el mapa de ocupación continuo
rplidar_ros Nodo ROS 2 Publica escaneos LiDAR en /scan
astra_camera Nodo ROS 2 Publica video e imagen de profundidad
wifi_sampler Nodo ROS 2 (Python) Muestrea RSSI por posición, envía las muestras por UDP
slam_server.py Proceso Flask Sirve el mapa bajo demanda en el puerto 5008

Todos los nodos ROS 2 corren dentro del workspace /home/iberomsc02/ros2_ws_02/. El proceso Flask es independiente del grafo ROS pero invoca comandos ROS 2 mediante subprocess.


Topics ROS 2 relevantes

Topic Tipo de mensaje Rol en el sistema
/scan sensor_msgs/LaserScan Publicado por rplidar_ros, consumido por slam_toolbox
/odom nav_msgs/Odometry Usado por wifi_sampler únicamente como disparador periódico
/map nav_msgs/OccupancyGrid Publicado por slam_toolbox, leído por map_saver_cli al guardar
TF: map -> base_link Transformación Provista por slam_toolbox, consultada por wifi_sampler

TF2 para localización precisa

El nodo wifi_sampler necesita saber en todo momento en qué punto del mapa se encuentra el robot para asociar cada medición de RSSI a una coordenada espacial. Existen dos fuentes posibles para esta información: la odometría directa (/odom) y el árbol de transformaciones TF2.

La odometría acumula error de integración con el tiempo: pequeñas imprecisiones en la medición de velocidades de rueda se suman y provocan una deriva progresiva de la posición estimada. El árbol TF2, en cambio, incorpora las correcciones que slam_toolbox aplica al mapa en cada iteración del algoritmo de SLAM. La transformación map -> base_link refleja por tanto la posición corregida del robot en el frame global del mapa, y no la posición acumulada desde el arranque.

Por este motivo, wifi_sampler se suscribe a /odom solo como mecanismo de disparo periódico: cada vez que llega un mensaje de odometría, el nodo consulta el árbol TF2 para obtener la posición real. La consulta intenta primero el frame base_link y, si no está disponible, recurre a base_footprint como alternativa de respaldo:

candidate_frames = ['base_link', 'base_footprint']

for source_frame in candidate_frames:
    try:
        transform = self.tf_buffer.lookup_transform(
            'map',
            source_frame,
            rclpy.time.Time()
        )
        x = transform.transform.translation.x
        y = transform.transform.translation.y
        return x, y
    except (LookupException, ConnectivityException, ExtrapolationException):
        continue

Si ninguno de los dos frames está disponible, la muestra se descarta y se emite una advertencia en el log, acotada a una vez cada cinco segundos para no saturar la consola.


Generación del mapa bajo demanda

Cuando la aplicación Unity solicita el mapa a través del endpoint /generate_map, el servidor Flask ejecuta el siguiente comando ROS 2 mediante subprocess:

ros2 run nav2_map_server map_saver_cli -f /home/iberomsc02/maps/current_map

map_saver_cli se suscribe al topic /map, toma la última imagen de ocupación publicada por slam_toolbox y escribe dos archivos en el directorio de mapas:

Archivo Descripción
current_map.pgm Imagen en escala de grises (Portable Gray Map) con los valores de ocupación: blanco (libre), negro (ocupado), gris (desconocido)
current_map.yaml Metadatos del mapa: resolución en metros por píxel, coordenadas del origen en el frame map, umbral de ocupación y ruta al archivo PGM

Tras la escritura de ambos archivos, el servidor ejecuta el script extract_walls.py para segmentar los obstáculos en segmentos de pared. El resultado se guarda como current_map_walls.json. El endpoint devuelve los tres artefactos codificados en una respuesta JSON única: el PGM en Base64, el YAML como texto plano y el JSON de paredes, con indicación del tamaño en bytes de cada uno. Si la extracción de paredes falla, se devuelve un JSON de paredes vacío para no bloquear la visualización del mapa en Unity.


Archivos de referencia


This site uses Just the Docs, a documentation theme for Jekyll.