Logo de JdeRobot

BLOG DE PRÁCTICAS EXTERNAS JDEROBOT
ALBERTO LEÓN LUENGO

Foto de Perfil

¡Bienvenido! En esta web se encuentra todo el contenido visto y aprendido en mis Prácticas Externas realizadas con la empresa JdeRobot durante el curso 2025-2026 en el Grado en Ingeniería de Robótica Software.

A continuación, se muestran todas las entradas correspondientes a los blogs de prácticas realizados durante el transcurso de las mismas, cuyo contenido se divide en las 20 semanas que ha durado mi periodo de Prácticas Externas.

SEMANA 01: 22-09-2025 al 26-09-2025

Instalación de Docker en Ubuntu 24.04

En primer lugar, se abre una terminal por defecto en el directorio /home/user, a través de la cual se irán ejecutando los siguientes comandos:

PASO 1: Acceso al escritorio

cd Escritorio/

PASO 2: Clonación del repositorio de RoboticsAcademy

git clone --recurse-submodules https://github.com/JdeRobot/RoboticsAcademy.git

PASO 3: Acceso al repositorio de RoboticsAcademy

cd RoboticsAcademy/

PASO 4: Ejecución del script de preparación

Este script intenta configurar el entorno de desarrollo. Al ejecutar este script por primera vez, aparecen varios errores, ya que faltan dependencias por instalar (yarn y docker).

./scripts/develop_academy.sh
./scripts/develop_academy.sh: linea 146: yarn: orden no encontrada
./scripts/develop_academy.sh: linea 147: yarn: orden no encontrada
./scripts/develop_academy.sh: linea 166: docker: orden no encontrada
Cleaning up...
./scripts/develop_academy.sh: linea 28: docker: orden no encontrada

PASO 5: Ejecución de comandos de configuración

Todos los comandos que se ejecutan a continuación se encargan de actualizar los repositorios de APT, instalar las herramientas necesarias (certificados y curl), crear el directorio para las claves de Docker, descargar la clave GPG de Docker, dar permisos de lectura a dicha clave, añadir el repositorio de Docker a APT y volver a actualizar la lista de paquetes.

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

PASO 6: Instalación de Docker y todos sus componentes

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

PASO 7: Comprobación de la activación de Docker

sudo systemctl status docker

PASO 8: Instalación de Docker Compose

sudo apt install docker-compose

PASO 9: Verificación de la versión de Docker instalada

docker --version

PASO 10: Creación del grupo de usuarios de Docker

sudo groupadd docker

PASO 11: Adición del usuario actual al grupo Docker

sudo usermod -aG docker $USER

PASO 12: Cambio de grupo

newgrp docker

PASO 13: Reejecución del script de preparación

sudo ./scripts/develop_academy.sh

IMPORTANTE: Al ejecutar el comando con sudo, todo se ejecuta correctamente.

Configuración y uso del Docker en Visual Studio Code

PASO 1: Apertura de RoboticsAcademy en Visual Studio Code

cd Escritorio/RoboticsAcademy/
code .

PASO 2: Instalación de las extensiones de Docker

Tras instalar ambas extensiones, es necesario reiniciar el equipo para que Docker y Visual Studio Code se integren correctamente.

Container Tools Extension
Project Manager Extension

PASO 3: Bucle de trabajo a seguir en Visual Studio Code

- Hacer los cambios necesarios dentro del directorio RoboticsInfrastructure/.
- Hacer commit y push de dichos cambios en una nueva rama (Publish Branch).
- Acceder al directorio RoboticsAcademy/scripts/RADI/.

cd scripts/RADI/

- Ejecutar el script build.sh dentro de la nueva rama.

./build.sh -i <branch_name>

- Volver al directorio principal.

cd ../..

- Cambiar la siguiente línea del fichero compose_cfg/dev_humble_cpu.yaml.

# ANTES
robotics-academy:
image: jderobot/robotics-academy:latest

# DESPUÉS
robotics-academy:
image: jderobot/robotics-academy:test

- Ejecutar el script de preparación (sin sudo) para lanzar el Docker de RoboticsAcademy con todos los cambios realizados.

./scripts/develop_academy.sh

- Acceder a la dirección web http://0.0.0.0:7164/ que aparece en la terminal al ejecutar el comando anterior para poder entrar a Unibotics en local y verificar que los cambios se han realizado correctamente.

IMPORTANTE: En el caso de que los cambios se hayan realizado únicamente en el fichero database/universes.sql, sólo sería necesario realizar el último de todos los pasos que componen este bucle de trabajo, escrito a continuación.

./scripts/develop_academy.sh

Modificaciones necesarias para migrar de Gazebo 11 a Gazebo Harmonic

A continuación se enumeran todos y cada uno de los ficheros que se van a modificar para poder llevar a cabo cada una de las tareas principales que se van a abordar durante las Prácticas: La migración de escenarios, robots y otros elementos visuales de Gazebo 11 a Gazebo Harmonic.

Todos estos ficheros se encuentran en el repositorio de GitHub RoboticsInfrastructure, que se encuentra a su vez dentro del repositorio de GitHub RoboticsAcademy (en ambos se trabajará desde la rama humble-devel).

FICHERO RoboticsInfrastructure/Launchers/<exercise_name>/spawn_robot.launch.py

# CAMBIAR EL NOMBRE DEL ROBOT
model_folder = <robot_name>
# CAMBIAR EL NOMBRE DEL FICHERO .YAML
bridge_params = os.path.join(
get_package_share_directory("custom_robots"), "params", "robot_params.yaml"
)
# AÑADIR / ELIMINAR LAS SIGUIENTES LÍNEAS SI EL ROBOT TIENE / NO TIENE CÁMARA
start_gazebo_ros_image_bridge_cmd = Node(
package="ros_gz_image",
executable="image_bridge",
arguments=["/<robot_name>/camera/image_raw"],
output="screen",
)

start_gazebo_ros_depth_bridge_cmd = Node(
package="ros_gz_image",
executable="image_bridge",
arguments=["/<robot_name>/camera/depth"],
output="screen",
)

ld.add_action(start_gazebo_ros_image_bridge_cmd)
ld.add_action(start_gazebo_ros_depth_bridge_cmd)

FICHERO RoboticsInfrastructure/CustomRobots/CMakeLists.txt

# AÑADIR LOS DIRECTORIOS CORRESPONDIENTES A LAS NUEVAS MIGRACIONES
install(
DIRECTORY
<exercise_name>/models
<robot_name>/models
<robot_name>/urdf
<robot_name>/params
DESTINATION share/${PROJECT_NAME})

FICHERO RoboticsInfrastructure/Launchers/<exercise_name>.launch.py

# CAMBIAR RUTAS Y NOMBRES DE LOS FICHEROS CARGADOS
robot_launch_dir = "/opt/jderobot/Launchers/<exercise_name>"
robot_model_dir = os.path.join(package_dir, "models/<robot_name>")
world_file_name = "<exercise_name>.world"

FICHERO RoboticsInfrastructure/Worlds/<exercise_name>.world

# MODIFICAR TODOS LOS APARTADOS CON EL FLAG INCLUDE

# GAZEBO 11
<include>
<uri>model://house_int2</uri>
<pose>0 0 0 0 0 0</pose>


# GAZEBO HARMONIC
<include>
<name>house_int2</name>
<uri>model://house_int2</uri>
<pose>0 0 0 0 0 0</pose>
</include>

FICHERO RoboticsInfrastructure/CustomRobots/<robot_name>/models/<robot_name>/model.sdf

# MODIFICAR TODOS LOS APARTADOS CON EL FLAG SENSOR Y EL FLAG PLUGIN

# GAZEBO 11
<sensor name='laser' type='ray'>
<ray>
<scan>
<horizontal>
<samples>180</samples>
<resolution>1.000000</resolution>
<min_angle>-1.570000</min_angle>
<max_angle>1.570000</max_angle>
</horizontal>
</scan>
<range>
<min>0.080000</min>
<max>10.000000</max>
<resolution>0.010000</resolution>
</range>
</ray>
<update_rate>20.000000</update_rate>
<always_on>1</always_on>
<visualize>1</visualize>
<plugin name="gazebo_ros_head_hokuyo_controller" filename="libgazebo_ros_ray_sensor.so">
<ros>
<remapping>~/out:=/roombaROS/laser/scan</remapping>
</ros>
<output_type>sensor_msgs/LaserScan</output_type>
<frameName>laser</frameName>
<update_rate>20.000000</update_rate>
</plugin>
</sensor>
# GAZEBO HARMONIC
<sensor name="hls_lfcd_lds" type="gpu_lidar">
<always_on>true</always_on>
<visualize>true</visualize>
<pose>-0.064 0 0.121 0 0 0</pose>
<update_rate>5</update_rate>
<topic>turtlebot3/laser/scan</topic>
<gz_frame_id>base_scan</gz_frame_id>
<lidar>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1.000000</resolution>
<min_angle>0.000000</min_angle>
<max_angle>6.280000</max_angle>
</horizontal>
</scan>
<range>
<min>0.20000</min>
<max>3.5</max>
<resolution>0.015000</resolution>
</range>
</lidar>
</sensor>

<plugin filename="ignition-gazebo-odometry-publisher-system" name="ignition::gazebo::systems::OdometryPublisher">
<odom_frame>odom</odom_frame>
<robot_base_frame>base_footprint</robot_base_frame>
<odom_publish_frequency>50</odom_publish_frequency>
<odom_topic>turtlebot3/odom</odom_topic>
<dimensions>3</dimensions>
</plugin>

FICHERO RoboticsInfrastructure/CustomRobots/<robot_name>/params/robot_params.yaml

# AÑADIR LOS TOPICS DE TODOS LOS SENSORES QUE COMPONEN EL ROBOT

# gz topic published by DiffDrive plugin
- ros_topic_name: "<robot_name>/odom"
gz_topic_name: "<robot_name>/odom"
ros_type_name: "nav_msgs/msg/Odometry"
gz_type_name: "gz.msgs.Odometry"
direction: GZ_TO_ROS

# gz topic subscribed to by DiffDrive plugin
- ros_topic_name: "<robot_name>/cmd_vel"
gz_topic_name: "<robot_name>/cmd_vel"
ros_type_name: "geometry_msgs/msg/Twist"
gz_type_name: "gz.msgs.Twist"
direction: ROS_TO_GZ

# gz topic published by Sensors plugin (LIDAR)
- ros_topic_name: "<robot_name>/laser/scan"
gz_topic_name: "<robot_name>/laser/scan"
ros_type_name: "sensor_msgs/msg/LaserScan"
gz_type_name: "gz.msgs.LaserScan"
direction: GZ_TO_ROS

# gz topic published by Sensors plugin (Camera)
- ros_topic_name: "<robot_name>/camera/camera_info"
gz_topic_name: "<robot_name>/camera/camera_info"
ros_type_name: "sensor_msgs/msg/CameraInfo"
gz_type_name: "gz.msgs.CameraInfo"
direction: GZ_TO_ROS

FICHERO RoboticsInfrastructure/database/universes.sql

# MODIFICAR LOS PARÁMETROS DE LOS MUNDOS

# MUNDO NO MIGRADO (ANTES)
25 Vacuums House Markers /opt/jderobot/Launchers/marker_visual_loc.launch.py None ROS2 gazebo {1,-1.5,0.6,0,0,0}

# MUNDO MIGRADO (DESPUÉS)
25 Vacuums House Markers /opt/jderobot/Launchers/marker_visual_loc.launch.py {"gzsim":"/opt/jderobot/Launchers/visualization/marker_visual_loc.config"} ROS2 gz {1,-1.5,0.6,0,0,0}

Lanzamiento del RoboticsBackend en local

PASO 1

Para lanzar el RoboticsBackend y poder trabajar en Unibotics en local, se debe ejecutar el siguiente comando en la terminal:

docker run --rm -it --device /dev/dri -p 6080:6080 -p 1108:1108 -p 7163:7163 jderobot/robotics-backend:latest

PASO 2

Se deja ejecutando el comando anterior en la terminal y se accede a la página web de Unibotics.

PASO 3

Una vez dentro de Unibotics, se selecciona el ejercicio en el que se quiera trabajar.

IMPORTANTE: Para que todo funcione correctamente, se debe seleccionar la opción 'Local ROS2 (RoboticsBackend 4)', que aparece en la esquina superior derecha al hacer clic en la foto de perfil.

Robotics Backend Option

PASO 4

Verificar que, tanto la simulación en Gazebo como la terminal, se han lanzado correctamente.

Robotics Backend Local

Lanzamiento del RoboticsDatabase junto a RoboticsAcademy en local

PASO 1

Para poder lanzar RoboticsDatabase junto a RoboticsAcademy, se debe ejecutar el siguiente comando en la terminal:

docker run --hostname my-postgres --name academy_db -d\
-e POSTGRES_DB=academy_db \
-e POSTGRES_USER=user-dev \
-e POSTGRES_PASSWORD=robotics-academy-dev \
-e POSTGRES_PORT=5432 \
-d -p 5432:5432 \
jderobot/robotics-database:latest

IMPORTANTE: Este comando se ejecuta ÚNICAMENTE la primera vez que se lance el RoboticsDatabase junto a RoboticsAcademy.

PASO 2

Una vez ejecutado el comando anterior, ya no será necesario ejecutarlo más veces, ya que lo que ha hecho este comando es crear un nuevo contenedor para RoboticsDatabase, al cual se llamará con el flag --link cuando se vaya a lanzar RoboticsAcademy. A continuación se muestran 3 formas diferentes de lanzar RoboticsDatabase junto a RoboticsAcademy:

Lanzamiento de RoboticsDatabase + RoboticsAcademy (NVIDIA + GPU)

docker run --rm -it $(nvidia-smi >/dev/null 2>&1 && echo "--gpus all" || echo "") --device /dev/dri -p 6080:6080 -p 6081:6081 -p 1108:1108 -p 7163:7163 -p 7164:7164 --link academy_db jderobot/robotics-academy:latest

IMPORTANTE: Para que este comando funcione, es necesario tener instalados los drivers de NVIDIA en Linux.

Lanzamiento de RoboticsDatabase + RoboticsAcademy (GPU)

docker run --rm -it --device /dev/dri -p 6080:6080 -p 6081:6081 -p 1108:1108 -p 7163:7163 -p 7164:7164 --link academy_db jderobot/robotics-academy:latest

Lanzamiento de RoboticsDatabase + RoboticsAcademy (CPU)

docker run --rm -it -p 6080:6080 -p 6081:6081 -p 1108:1108 -p 7163:7163 -p 7164:7164 --link academy_db jderobot/robotics-academy:latest

PASO 3

Por último, para comprobar que todos los Dockers que se van a utilizar se han descargado y configurado correctamente, se debe ejecutar el siguiente comando en la terminal:

sudo docker images

Cuyo resultado debe ser el siguiente:

REPOSITORY                   TAG       IMAGE ID       CREATED       SIZE
jderobot/robotics-backend latest 0e2a10ccfaf4 5 days ago 27.2GB
jderobot/robotics-academy latest 1c67a50c79c2 13 days ago 28.5GB
jderobot/robotics-database latest 3a1d0407347e 13 days ago 483MB

SEMANA 02: 29-09-2025 al 03-10-2025

Ejercicio de calentamiento: Cambiar el mundo a un ejercicio

Para ir familiarizándonos con el software de RoboticsInfrastructure, se me ha pedido como primer ejercicio cambiar el mundo de Gazebo de uno de los ejercicios que ya se haya migrado a Gazebo Harmonic.

En primer lugar, he accedido al fichero RoboticsInfrastructure/database/universes.sql para localizar aquellos mundos ya migrados a Gazebo Harmonic.

IMPORTANTE: La forma más sencilla de identificar los mundos migrados a Gazebo Harmonic, es mirando si en su columna type aparece escrito gazebo o gz. En caso de que aparezca escrito gazebo, significa que ese mundo todavía se encuentra en Gazebo 11 y no se ha migrado. Pero si aparece escrito gz, significa que ese mundo ya se ha migrado a Gazebo Harmonic.

A continuación se listan todos los mundos que tienen escrito gz en su columna type, y que podrían utilizarse para llevar a cabo este ejercicio:

12	Laser Mapping Warehouse	/opt/jderobot/Launchers/laser_mapping.launch.py	{"gzsim":"/opt/jderobot/Launchers/visualization/laser_mapping.config"}	ROS2	gz	{0.0,0.0,0.0,0.0,0.0,0.0}
25 Vacuums House Markers /opt/jderobot/Launchers/marker_visual_loc.launch.py {"gzsim":"/opt/jderobot/Launchers/visualization/marker_visual_loc.config"} ROS2 gz {1,-1.5,0.6,0,0,0}
31 Rescue People Harmonic /opt/jderobot/Launchers/rescue_people.launch.py {"gzsim":"/opt/jderobot/Launchers/visualization/rescue_people.config"} ROS2 gz {0.0,0.0,0.0,0.0,0.0,0.0}
32 Follow Road Harmonic /opt/jderobot/Launchers/follow_road.launch.py {"gzsim":"/opt/jderobot/Launchers/visualization/follow_road.config"} ROS2 gz {0.0,0.0,0.0,0.0,0.0,0.0}
33 Small Laser Mapping Warehouse /opt/jderobot/Launchers/small_laser_mapping.launch.py {"gzsim":"/opt/jderobot/Launchers/visualization/small_laser_mapping.config"} ROS2 gz {0.0,0.0,0.0,0.0,0.0,0.0}
34 Pick And Place Arm /home/dev_ws/src/IndustrialRobots/ros2_SimRealRobotControl/ros2srrc_launch/moveit2/moveit2.launch.py None ROS2 gazebo {0.0,0.0,0.0,0.0,0.0,0.0}
35 Car Junction /opt/jderobot/Launchers/car_junction.launch.py {"gzsim":"/opt/jderobot/Launchers/visualization/car_junction.config"} ROS2 gz {0.0,0.0,0.0,0.0,0.0,0.0}
36 Drone Gymkhana Harmonic /opt/jderobot/Launchers/drone_gymkhana.launch.py None ROS2 gz {0.0,0.0,0.0,0.0,0.0,0.0}
37 Tower Inspection Harmonic /opt/jderobot/Launchers/power_tower_inspection.launch.py None ROS2 gz {0.0,0.0,0.0,0.0,0.0,0.0}

En mi caso, he seleccionado los mundos correspondientes a los ejercicios de Laser Mapping (número 12) y Marker Based Visual Loc (número 25). El mundo del Laser Mapping es un almacén tipo Amazon, mientras que el mundo del Marker Based Visual Loc es una casa de dos plantas.

El resultado final de este ejercicio visualizará el almacén del ejercicio Laser Mapping en la ventana de Gazebo del ejercicio Marker Based Visual Loc.

A continuación se muestra cómo se ve inicialmente el ejercicio Marker Based Visual Loc al lanzar el Docker de RoboticsAcademy antes de realizar cualquier cambio en el código:

Original World

El único cambio que he realizado ha sido en el fichero RoboticsInfrastructure/Launchers/marker_visual_loc.launch.py en la siguiente línea:

# ANTES (MUNDO ORIGINAL)
world_file_name = "marker_visual_loc.world"

# DESPUÉS (MUNDO NUEVO)
world_file_name = "laser_mapping.world"

Una vez realizado este cambio, he hecho commit y push en una nueva rama que he creado y publicado llamada test-aleon2020. Es importante hacer esto siempre para evitar así cualquier tipo de conflicto con la rama principal.

Como los cambios se han realizado en un fichero que se encuentra dentro de RoboticsInfrastructure, es obligatorio generar un nuevo RADI. Para ello, se deben ejecutar los siguientes comandos en la terminal:

cd scripts/RADI/
./build.sh -i test-aleon2020

IMPORTANTE: Si es la primera vez que se ejecuta este script, el tiempo que tardará en ejecutarse por completo será considerablemente largo, ya que debe configurar todo el entorno. Si es la primera vez que se ejecuta tardará unos 35-45 minutos, de lo contrario, tardará unos 2-3 minutos.

Una vez finalizada la ejecución del script build.sh, se debe regresar al repositorio principal:

cd ../..

Pero antes de ejecutar el script develop_academy.sh, hay que modificar la siguiente línea del fichero RoboticsAcademy/compose_cfg/dev_humble_cpu.yaml:

# ANTES
robotics-academy:
image: jderobot/robotics-academy:latest

# DESPUÉS
robotics-academy:
image: jderobot/robotics-academy:test

Con este cambio ya realizado, ya se puede lanzar el script develop_academy.sh:

./scripts/develop_academy.sh

Y por último, sólo quedaría acceder a la dirección web `http://0.0.0.0:7164/` que aparece en la terminal al ejecutar el comando anterior para poder entrar a Unibotics en local y verificar que los cambios se han realizado correctamente.

A continuación se muestra cómo se ve el ejercicio Marker Based Visual Loc al lanzar el Docker de RoboticsAcademy con todos estos cambios realizados en el código:

New World

Migración del ejercicio Obstacle Avoidance a Gazebo Harmonic

Una vez realizado este primer ejercicio de calentamiento para irme familiarizando con el código de RoboticsInfrastructure, se me ha pedido realizar la migración completa de Gazebo 11 a Gazebo Harmonic del ejercicio Obstacle Avoidance, que introduce de forma práctica la navegación local mediante el uso de campos de fuerza virtuales (VFF, Virtual Force Fields).

Obstacle Avoidance Exercise

ENLACE AL ENUNCIADO DEL EJERCICIO: https://jderobot.github.io/RoboticsAcademy/exercises/AutonomousCars/obstacle_avoidance

A continuación, se encuentran todos los ficheros que se han modificado y/o creado (y de qué forma) para poder llevar a cabo la migración completa de este ejercicio de Gazebo 11 a Gazebo Harmonic:

FICHERO RoboticsInfrastructure/CustomRobots/f1/models/f1_result_laser_no_cam/model.sdf

En este fichero se han modificado las partes correspondientes a las etiquetas <plugin> y <sensor>, donde el plugin pasa de estar declarado dentro del sensor a integrarse en él. Además, es importante añadir al final de la nueva versión los plugins correspondientes al diff-drive-system y al odometry-publisher-system:

# GAZEBO 11

<sensor name='laser' type='ray'>
<ray>
<scan>
<horizontal>
<samples>180</samples>
<resolution>1.000000</resolution>
<min_angle>-1.570000</min_angle>
<max_angle>1.570000</max_angle>
</horizontal>
</scan>
<range>
<min>0.080000</min>
<max>10.000000</max>
<resolution>0.010000</resolution>
</range>
</ray>
<update_rate>20.000000</update_rate>
<plugin name="gazebo_ros_head_hokuyo_controller" filename="libgazebo_ros_ray_sensor.so">
<ros>
<namespace>/f1</namespace>
<remapping>~/out:=laser/scan</remapping>
</ros>
<output_type>sensor_msgs/LaserScan</output_type>
<frame_name>base_scan</frame_name>
</plugin>
<always_on>1</always_on>
<visualize>1</visualize>
</sensor>

# ESTE PLUGIN SE INTEGRA DENTRO DE <SENSOR> AL PASAR A GAZEBO HARMONIC
<plugin name="object_controller" filename="libgazebo_ros_planar_move.so">
<commandTopic>F1ROS/cmd_vel</commandTopic>
<odometryTopic>F1ROS/odom</odometryTopic>
<odometryFrame>odom</odometryFrame>
<odometryRate>20.0</odometryRate>
<robotBaseFrame>xf1</robotBaseFrame>
</plugin>
# GAZEBO HARMONIC

<sensor name="laser" type="gpu_lidar">
<always_on>1</always_on>
<visualize>1</visualize>
<pose>0.500000 0.000000 0.072000 0.000000 0.000000 0.00000</pose>
<update_rate>20.000000</update_rate>
<topic>f1/laser/scan</topic>
<gz_frame_id>base_scan</gz_frame_id>
<lidar>
<scan>
<horizontal>
<samples>180</samples>
<resolution>1.000000</resolution>
<min_angle>-1.570000</min_angle>
<max_angle>1.570000</max_angle>
</horizontal>
</scan>
<range>
<min>0.080000</min>
<max>10.000000</max>
<resolution>0.010000</resolution>
</range>
</lidar>
</sensor>

# AÑADIR ESTE PLUGIN AL FINAL DEL FICHERO
<plugin filename="gz-sim-diff-drive-system" name="gz::sim::systems::DiffDrive">
<!-- wheels -->
<left_joint>wheel_left_joint</left_joint>
<right_joint>wheel_right_joint</right_joint>
<!-- kinematics -->
<wheel_separation>0.287</wheel_separation>
<wheel_radius>0.033</wheel_radius>
<!-- limits -->
<max_linear_acceleration>1.0</max_linear_acceleration>
<topic>F1ROS/cmd_vel</topic>
<frame_id>odom</frame_id>
<!-- <tf_topic>/tf</tf_topic> -->
<odom_publisher_frequency>20.0</odom_publisher_frequency>
<child_frame_id>xf1</child_frame_id>
</plugin>

# AÑADIR ESTE PLUGIN AL FINAL DEL FICHERO
<plugin filename="ignition-gazebo-odometry-publisher-system" name="ignition::gazebo::systems::OdometryPublisher">
<odom_frame>odom</odom_frame>
<robot_base_frame>xf1</robot_base_frame>
<odom_publish_frequency>20.0</odom_publish_frequency>
<odom_topic>F1ROS/odom</odom_topic>
<dimensions>3</dimensions>
</plugin>

FICHERO RoboticsInfrastructure/CustomRobots/f1/params/f1_result_laser_no_cam.yaml

Este fichero se crea de cero en la ruta especificada, aunque se puede coger cualquier fichero del tipo robot_params.yaml como referencia. En este caso, sólo se añaden los topics correspondientes al publicador y al subscriptor del plugin del diff-drive-system y aquellos relativos al publicador del plugin del láser:

# gz topic published by DiffDrive plugin
- ros_topic_name: "F1ROS/odom"
gz_topic_name: "F1ROS/odom"
ros_type_name: "nav_msgs/msg/Odometry"
gz_type_name: "gz.msgs.Odometry"
direction: GZ_TO_ROS

# gz topic subscribed to by DiffDrive plugin
- ros_topic_name: "F1ROS/cmd_vel"
gz_topic_name: "F1ROS/cmd_vel"
ros_type_name: "geometry_msgs/msg/Twist"
gz_type_name: "gz.msgs.Twist"
direction: ROS_TO_GZ

# gz topic published by Sensors plugin (LIDAR)
- ros_topic_name: "F1ROS/laser/scan"
gz_topic_name: "F1ROS/laser/scan"
ros_type_name: "sensor_msgs/msg/LaserScan"
gz_type_name: "gz.msgs.LaserScan"
direction: GZ_TO_ROS

FICHERO RoboticsInfrastructure/CustomRobots/CMakeLists.txt

En este caso, la única modificación realizada en este fichero es la adición de la línea f1/params para que el fichero que se acaba de crear (f1_result_laser_no_cam.yaml) se tenga en cuenta a la hora de lanzar el Docker:

# GAZEBO 11

install(
DIRECTORY
...
# F1
f1/models
f1/launch
f1/worlds
...
DESTINATION share/${PROJECT_NAME})
# GAZEBO HARMONIC

install(
DIRECTORY
...
# F1
f1/models
f1/launch
f1/worlds
f1/params
...
DESTINATION share/${PROJECT_NAME})

FICHERO RoboticsInfrastructure/Launchers/obstacle_avoidance/spawn_robot.launch.py

Una vez creado el directorio obstacle_avoidance/, y a su vez dentro de él el fichero spawn_robot.launch.py, lo único que habría que hacer es coger cualquier fichero con el mismo nombre de otro ejercicio que ya esté migrado a Gazebo Harmonic, copiar su contenido y sólamente habría que comentar todo lo relativo a las variables start_gazebo_ros_image_bridge_cmd y start_gazebo_ros_depth_bridge_cmd, y lo más importante, modificar el nombre del fichero que se le pasa como argumento a bridge_params (en este caso, f1_renault_laser_no_cam.yaml):

# GAZEBO 11
bridge_params = os.path.join(
get_package_share_directory("custom_robots"), "params", "robot_params.yaml"
)
# GAZEBO HARMONIC
bridge_params = os.path.join(
get_package_share_directory("custom_robots"), "params", "f1_renault_laser_no_cam.yaml"
)

FICHERO RoboticsInfrastructure/Launchers/visualization/simple_circuit_obstacles_followingcam.config

El contenido de este fichero es idéntico al del resto de ficheros con el mismo formato de nombre (<universe_name>.config), por lo que lo único que habría que hacer es crear este nuevo fichero y rellenar su contenido con un simple Ctrl+C Ctrl+V de otro fichero que se encuentre en el mismo directorio con el mismo formato de nombre.

<?xml version="1.0"?>

<!-- Quick start dialog -->
<dialog name="quick_start" show_again="false"/>

<!-- Window -->
<window>
<width>1024</width>
<height>768</height>
<style material_theme="Light" material_primary="DeepOrange" material_accent="LightBlue" toolbar_color_light="#f3f3f3" toolbar_text_color_light="#111111" toolbar_color_dark="#414141" toolbar_text_color_dark="#f3f3f3" plugin_toolbar_color_light="#bbdefb" plugin_toolbar_text_color_light="#111111" plugin_toolbar_color_dark="#607d8b" plugin_toolbar_text_color_dark="#eeeeee"/>
<menus>
<drawer visible="false">
</drawer>
<plugins visible="false">
</plugins>
</menus>
<dialog_on_exit>false</dialog_on_exit>
</window>

<!-- GUI plugins -->

<!-- 3D scene -->
<plugin filename="MinimalScene" name="3D View">
<gz-gui>
<title>3D View</title>
<property type="bool" key="showTitleBar">false</property>
<property type="string" key="state">docked</property>
</gz-gui>
<engine>ogre2</engine>
<scene>scene</scene>
<ambient_light>0.4 0.4 0.4</ambient_light>
<background_color>0.8 0.8 0.8</background_color>
<camera_pose>-6 0 6 0 0.5 0</camera_pose>
</plugin>

<plugin filename="CameraTracking" name="Camera tracking">
<gz-gui>
<property key="resizable" type="bool">false</property>
<property key="width" type="double">5</property>
<property key="height" type="double">5</property>
<property key="state" type="string">floating</property>
<property key="showTitleBar" type="bool">false</property>
</gz-gui>
<follow_target>drone0</follow_target>
<follow_offset>-2 0 2</follow_offset>
<follow_pgain>0.4</follow_pgain>
</plugin>

<!-- Plugins that add functionality to the scene -->
<plugin filename="GzSceneManager" name="Scene Manager">
<gz-gui>
<property key="resizable" type="bool">false</property>
<property key="width" type="double">5</property>
<property key="height" type="double">5</property>
<property key="state" type="string">floating</property>
<property key="showTitleBar" type="bool">false</property>
</gz-gui>
</plugin>

<plugin filename="InteractiveViewControl" name="Interactive view control">
<gz-gui>
<property key="resizable" type="bool">false</property>
<property key="width" type="double">5</property>
<property key="height" type="double">5</property>
<property key="state" type="string">floating</property>
<property key="showTitleBar" type="bool">false</property>
</gz-gui>
</plugin>

<plugin filename="WorldStats" name="World stats">
<gz-gui>
<title>World stats</title>
<property type="bool" key="showTitleBar">false</property>
<property type="bool" key="resizable">false</property>
<property type="double" key="height">110</property>
<property type="double" key="width">290</property>
<property type="double" key="z">1</property>
<property type="string" key="state">floating</property>
<anchors target="3D View">
<line own="right" target="right"/>
<line own="bottom" target="bottom"/>
</anchors>
</gz-gui>
<sim_time>true</sim_time>
<real_time>true</real_time>
<real_time_factor>true</real_time_factor>
<iterations>true</iterations>
<topic>/world/world_demo/stats</topic>
</plugin>

FICHERO RoboticsInfrastructure/Launchers/simple_circuit_obstacles_followingcam.launch.py

Para este fichero, lo único que habría que hacer es un Ctrl+C Ctrl+V de cualquier fichero con el mismo formato de nombre que ya haya sido migrado a Gazebo Harmonic y modificar únicamente las líneas correspondientes a las variables robot_launch_dir y world_file_name, correspondientes a las rutas en las que se encuentran el directorio que almacena el fichero spawn_robot.launch.py y el fichero del mundo que utiliza este ejercicio.

robot_launch_dir = "/opt/jderobot/Launchers/obstacle_avoidance"
world_file_name = "simple_circuit_obstacles_followingcam.world"

IMPORTANTE: En caso de que no se haya creado dentro del directorio obstacle_avoidance/ el fichero robot_state_publisher.launch.py, es obligatorio comentar y/o eliminar del código todo lo relacionado con la variable robot_state_publisher_cmd.

FICHERO RoboticsInfrastructure/Worlds/simple_circuit_obstacles_followingcam.world

En este fichero, lo único que habría que realizar es un Ctrl+C Ctrl+V de cualquier fichero con el mismo formato de nombre que ya haya sido migrado a Gazebo Harmonic y sustituir todas aquellos flags <include> de la versión antigua por aquellos que aparezcan en la versión nueva.

<?xml version="1.0" ?>

<sdf version="1.9">
<world name="default">
<plugin
filename="gz-sim-physics-system"
name="gz::sim::systems::Physics">
</plugin>
<plugin
filename="gz-sim-scene-broadcaster-system"
name="gz::sim::systems::SceneBroadcaster">
</plugin>
<plugin
filename="gz-sim-sensors-system"
name="gz::sim::systems::Sensors">
<render_engine>ogre2</render_engine>
</plugin>

<scene>
<shadows>false</shadows>
</scene>

<light type="directional" name="sun">
<!-- true -->
<pose>0 0 20 -1.3 0 0.5</pose>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.01 0.01 0.01 1</specular>
<intensity>2</intensity>
<visualize>false</visualize>
</light>
<light type="point" name="point_light">
<pose>0.73 0.09 8.77 0 0 0</pose>
<diffuse>1 1 1 1</diffuse>
<specular>0.01 0.01 0.01 1</specular>
<attenuation>
<range>10</range>
<linear>0.5</linear>
<constant>0.8</constant>
<quadratic>0.001</quadratic>
</attenuation>
<cast_shadows>false</cast_shadows>
<visualize>false</visualize>
</light>
<light type="point" name="point_light_02">
<pose>0.13 0.46 11.60 0 0 0</pose>
<diffuse>1 1 1 1</diffuse>
<specular>0.01 0.01 0.01 1</specular>
<attenuation>
<range>10</range>
<linear>0.2</linear>
<constant>0.5</constant>
<quadratic>0.001</quadratic>
</attenuation>
<cast_shadows>false</cast_shadows>
<visualize>false</visualize>
</light>
<light type="point" name="point_light_03">
<pose>4.27 -1.27 11.21 0 0 0</pose>
<diffuse>1 1 1 1</diffuse>
<specular>0.01 0.01 0.01 1</specular>
<attenuation>
<range>10</range>
<linear>0.5</linear>
<constant>0.8</constant>
<quadratic>0.001</quadratic>
</attenuation>
<cast_shadows>false</cast_shadows>
<visualize>false</visualize>
</light>
<light type="point" name="point_light_04">
<pose>-0.31 -3.78 8.61 0 0 0</pose>
<diffuse>1 1 1 1</diffuse>
<specular>.01 .01 .01 1</specular>
<attenuation>
<range>10</range>
<linear>0.5</linear>
<constant>0.8</constant>
<quadratic>0.001</quadratic>
</attenuation>
<cast_shadows>false</cast_shadows>
<visualize>false</visualize>
</light>

<include>
<uri>model://simple_circuit</uri>
<pose>-53.46 11.50 0 0 0 0</pose>
</include>
<include>
<uri>model://f1_renault_laser_no_cam</uri>
<pose>0.04 0.68 0 0 0 -1.57</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_1</name>
<pose>-0.1 -33 0 0 0 0</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_2</name>
<pose>-7 -40 0 0 0 -1.67</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_3</name>
<pose>-13.2 -18.8 0 0 0 -3</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_4</name>
<pose>-40 -11.5 0 0 0 -1.57</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_5</name>
<pose>-66.83 -34.15 0 0 0 -1.03</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_6</name>
<pose>-70.8 -34.2 0 0 0 -1.33</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_7</name>
<pose>-103 14.6 0 0 0 2.47</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_8</name>
<pose>-52.76 42.3 0 0 0 2.15</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_9</name>
<pose>-33.4 57.3 0 0 0 0</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_10</name>
<pose>-3.5 59 0 0 0 0.785</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_11</name>
<pose>1 26.2 0 0 0 0</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_11</name>
<pose>1 26.2 0 0 0 0</pose>
</include>
<include>
<uri>model://f1_dummy</uri>
<name>f1_dummy_12</name>
<pose>-1 24.5 0 0 0 0</pose>
</include>

</world>
</sdf>

FICHERO RoboticsInfrastructure/database/universes.sql

Este es uno de los cambios más sencillos de realizar en todo el proceso de migración. Lo único que habría que hacer es identificar el ejercicio que se quiere migrar de Gazebo 11 a Gazebo Harmonic (en este caso, Obstacle Avoidance) y tener en cuenta lo siguiente: Cambiar el valor de la columna type de gazebo a gz, y el campo de la tercera columna, en el que inicialmente aparece None, sustituirlo por la ruta al fichero simple_circuit_obstacles_followingcam.config creado anteriormente:

# GAZEBO 11
19 Obstacle Avoidance Default /opt/jderobot/Launchers/simple_circuit_obstacles_followingcam.launch.py None ROS2 gazebo {0.0,0.0,0.0,0.0,0.0,0.0}
# GAZEBO HARMONIC
19 Obstacle Avoidance Default /opt/jderobot/Launchers/simple_circuit_obstacles_followingcam.launch.py {"gzsim":"/opt/jderobot/Launchers/visualization/simple_circuit_obstacles_followingcam.config"} ROS2 gz {0.0,0.0,0.0,0.0,0.0,0.0}

Una vez realizados todos estos cambios en todos los ficheros mencionados, he hecho commit y push en una nueva rama que he creado y publicado llamada obstacle-avoidance-harmonic. Es importante hacer esto siempre para así evitar cualquier tipo de conflicto con la rama principal.

Como todos los cambios se han realizado en ficheros que se encuentran dentro de RoboticsInfrastructure, es obligatorio generar un nuevo RADI. Para ello, se deben ejecutar los siguientes comandos en la terminal:

cd scripts/RADI/
./build.sh -i obstacle-avoidance-harmonic

Una vez finalizada la ejecución del script build.sh, se debe regresar al repositorio principal:

cd ../..

Pero antes de ejecutar el script develop_academy.sh, hay que modificar la siguiente línea del fichero RoboticsAcademy/compose_cfg/dev_humble_cpu.yaml:

# ANTES
robotics-academy:
image: jderobot/robotics-academy:latest

# DESPUÉS
robotics-academy:
image: jderobot/robotics-academy:test

Con este cambio ya realizado, ya se puede lanzar el script develop_academy.sh:

./scripts/develop_academy.sh

Y por último, sólo quedaría acceder a la dirección web `http://0.0.0.0:7164/` que aparece en la terminal al ejecutar el comando anterior para poder entrar a Unibotics en local y verificar que los cambios se han realizado correctamente.

A continuación se muestra cómo se ve el ejercicio Obstacle Avoidance al lanzar el Docker de RoboticsAcademy con todos estos cambios realizados:

IMAGEN UNIBOTICS GAZEBO OBSTACLE AVOIDANCE

Además, para verificar que tanto el escenario como el coche han sido migrados correctamente, se muestra una pequeña animación del coche moviéndose en línea recta para verificar que todo el proceso se ha realizado correctamente:

VÍDEO / GIF COCHE MOVIÉNDOSE POR EL ESCENARIO

SEMANA 03: 06-10-2025 al 10-10-2025

...

SEMANA 04: 13-10-2025 al 17-10-2025

...

SEMANA 05: 20-10-2025 al 24-10-2025

...

SEMANA 06: 27-10-2025 al 31-10-2025

...

SEMANA 07: 03-11-2025 al 07-11-2025

...

SEMANA 08: 10-11-2025 al 14-11-2025

...

SEMANA 09: 17-11-2025 al 21-11-2025

...

SEMANA 10: 24-11-2025 al 28-11-2025

...

SEMANA 11: 01-12-2025 al 05-12-2025

...

SEMANA 12: 08-12-2025 al 12-12-2025

...

SEMANA 13: 15-12-2025 al 19-12-2025

...

SEMANA 14: 22-12-2025 al 26-12-2025

...

SEMANA 15: 29-12-2025 al 02-01-2026

...

SEMANA 16: 05-01-2026 al 09-01-2026

...

SEMANA 17: 12-01-2026 al 16-01-2026

...

SEMANA 18: 19-01-2026 al 23-01-2026

...

SEMANA 19: 26-01-2026 al 30-01-2026

...

SEMANA 20: 02-02-2026 al 06-02-2026

...