ROS2 Programming Basics Using Python

ROS2 Programming Basics Using Python

What we are going to learn:

How to send a message using Topic Publisher and Subscriber using Python

To ensure stability, I’ll guide you through ROS2 Foxy on Ubuntu 20.04 system. I’ll be using Docker images for installation, but the process is similar to a local Ubuntu setup.

Prerequisites:

Ubuntu 20.04 installed on your computer.

We will actually create and run a ROS2 package that sends and receives messages through Publisher and Subscriber.

If you want to learn ROS 2 Python in a practical, hands-on way, check out the course ROS 2 Basics in 5 Days: https://app.theconstruct.ai/courses/132

In this course, you’ll cover the foundational concepts needed to start working with ROS 2, as well as more advanced topics, all while engaging in hands-on practice.

1. Make Package:


The command to create a package is as follows:

ros2 pkg create [package_name] --build-type [build_type] --dependencies [dependent package1][dependent package2]

Lets’ make ros2_ws and create the ros_topic_pkg package

mkdir ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create ros_topic_pkg --build-type ament_python --dependencies rclpy std_msgs

2. Write Publisher and Subscriber script

Firstly, we will make empty script publisher.py, subscriber.py in ros_topic_pkg_folder

cd ~/ros2_ws/src/ros_topic_pkg/ros_topic_pkg
touch publisher.py
touch subscriber.py

After make empty script, Using Vim or VsCode, write down this publisher and subscriber code

publihser.py
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from std_msgs.msg import String

class Publisher(Node):
def __init__(self):
super().__init__('Publisher')
qos_profile = QoSProfile(depth=10)
self.publisher = self.create_publisher(String, 'topic', qos_profile)
self.timer = self.create_timer(1, self.publish_msg)
self.count = 0

def publish_msg(self):
msg = String()
msg.data = 'Number: {0}'.format(self.count)
self.publisher.publish(msg)
self.get_logger().info('Published message: {0}'.format(msg.data))
self.count += 1

def main(args=None):
rclpy.init(args=args)
node = Publisher()
try:
rclpy.spin(node)
except KeyboardInterrupt:
node.get_logger().info('Keyboard Interrupt')
finally:
node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

subscriber.py
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from std_msgs.msg import String​

class Subscriber(Node):

def __init__(self):
super().__init__('Subscriber')
qos_profile = QoSProfile(depth=10)
self.subscriber = self.create_subscription(
String,
'topic',
self.subscribe_topic_message,
qos_profile)

def subscribe_topic_message(self, msg):
self.get_logger().info('Received message: {0}'.format(msg.data))​

def main(args=None):
rclpy.init(args=args)
node = Subscriber()
try:
rclpy.spin(node)
except KeyboardInterrupt:
node.get_logger().info('Keyboard Interrupt')
finally:
node.destroy_node()
rclpy.shutdown()​

if __name__ == '__main__':
main()


3. Write a Python package configuration file

Lastly, we have to write down this setup.py file to build Ros2 package

from setuptools import find_packages, setup

package_name = 'ros_topic_pkg'

setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='user',
maintainer_email='user@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'publisher = ros_topic_pkg.publisher:main',
'subscriber = ros_topic_pkg.subscriber:main'
],
},
)

4 Build package:

If you write down all the code correctly, Now we will build ros2_topic pkg

cd ~/ros2_ws && colcon build --symlink-install

If the build is completely normal, you must load the configuration file and set the node for the executable package to run the build node

You can see the file that writes a lot of commands, at the end of the file, Input i to change insert mode and write
source install/setup.bash

Now let’s run the publisher and subscriber we created

First, run publisher to issue a topic

ros2 run ros_topic_pkg publisher

Then Run a new terminal and run the Subscriber after proceeding with the configuration file load

source install/setup.bash


If you wrote the code correctly, you can see that the subscriber is properly subscribing to the numbers issued by the publisher.

Congratulations. You have now learned how to send a message using Topic Publisher and Subscriber using Python.

To learn more about ROS 2, have a look at the course below:

We hope this tutorial was really helpful to you.

This tutorial is created by Robotics Ambassador Park.

Créer votre première map avec slam_toolbox – French ROS2 Tutorial

Créer votre première map avec slam_toolbox – French ROS2 Tutorial


Cours: ROS2 Basics in 5 Days C++: https://app.theconstructsim.com/courses/133

Le but de ce tutoriel est d’apprendre à utiliser slam_toolbox pour créer des maps, les sauvegarder et les réutiliser avec map_server. slam_toolbox est un package regroupant un ensemble d’outils pour la localisation et la cartographie simultanées (SLAM) en robotique, permettant la création de cartes en temps réel et le suivi de la position. La cartographie est essentielle en robotique pour la navigation, la planification efficace de la trajectoire et l’évitement des obstacles. Elle améliore l’autonomie et la précision des robots dans les environnements dynamiques.

Prérequis: Utiliser une installation ROS2 avec Gazebo, turtlebot3 et slam_toolbox

Installation ROS2

Vous pouvez vous connecter au site TheConstruct pour accéder à des machines virtuelles préconfigurées avec une installation ROS2. Pour lancer et tester le code, vous devez lancer la simulation du turtlebot3 sur gazebo. Pour faire cela, entez les commandes suivantes dans le terminal:

# Declare le model de turtlebot3 à simuler
export TURTLEBOT3_MODEL=waffle_pi
# Lance la simulation du turtlebot dans gazebo
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py

Une fois la simulation du turtlebot3 lancée, celle-ci va publier les tf du turtlebot3 et les données obtenues avec son laserscan sur le topic /scan que nous allons utiliser durant le tutoriel. Après avoir exécuté ces commandes, appuyez sur le bouton de l’interface gazebo et sélectionnez Open Gazebo.

Vous devriez obtenir le résultat suivant:
alternative text
Si vous voulez effectuer le tutoriel sur votre installation local, vous devez avoir ROS2 installé (https://docs.ros.org/en/humble/Installation.html), avoir configuré un workspace (https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.html) et avoir installé le turtlebot3.
# pour installer tous les packages du turtlebot3
# remplacez humble par votre distro ROS2cd ~/ros2_ws
sudo apt-get install ros-humble-turtlebot3*
# n'oubliez pas d'exporter le modèle de Turtlebot3 que vous souhaitez utiliser
export TURTLEBOT3_MODEL=waffle_pi

Installer slam_toolbox

Vous pouvez installer slam_toolbox avec la commande suivante:

# Pour install slam_toolbox
sudo apt-get install ros-humble-slam-toolbox

Partie 1: Configurer son package

Nous allons commencer par créer un package ROS2 pour stocker les fichiers launch et yaml necessaires:

cd ros2_ws/src/
# Créer le package
ros2 pkg create slam_setup_tutorial
cd slam_setup_tutorial
# Créer les dossiers pour stocker les launch et params files
mkdir launch
mkdir params

Nous allons maintenant éditer le Cmakelist.txt:

cmake_minimum_required(VERSION 3.8)
project(slam_setup_tutorial)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)

# On declare les dossiers launch et params dans les installs
install(DIRECTORY
  launch
  params
  DESTINATION share/${PROJECT_NAME}/
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

Vous pouvez build votre workspace pour verifier si la configuration du CmakeList.txt fonctionne:

cd ~/ros2_ws
colcon build
source install/setup.bash

Partie 2: Setup slam_toolbox

Les maps générées par slam_toolbox sont au format “occupancy grid”. Ce format représente l’environnement en une grille régulière de cellules, où chaque cellule correspond à une petite zone de l’espace. Voici une map au format occupancy grid:

image pgm map

  • Résolution : La résolution de la carte indique la taille de chaque cellule en mètres par pixel.
  • Valeurs des cellules : Chaque cellule de la grille contient une valeur indiquant le niveau d’occupation :
    • 0 représente un espace libre (en blanc sur la représentation de la map).
    • 100 représente un obstacle (en noir).
    • -1 indique une cellule inconnue ou non explorée (en gris).
  • TF de référence : La carte est associée à un transform de référence global, souvent nommé “map”, qui permet de situer les cellules dans l’espace réel. Cette représentation permet aux robots de naviguer et de planifier des trajectoires en évitant les obstacles et en se déplaçant à travers les espaces libres détectés par leurs capteurs.

Pour pouvoir utiliser slam_toolbox, les tf du robot doivent être configurées en respectant les conventions du REP105 : Coordinate Frames for Mobile Platforms. Une explication plus pratique de cette configuration est décrite dans la documentation de Nav2 sur la configuration des transforms. Ces notions sont paraticulièrement importantes si vous voulez effectuer du slam sur un robot custom. Dans ce tutoriel nous allons utiliser le turtlebot3 qui possède déjà une implementation compléte de ses TF locales. Vous pouvez visuliser le TF tree du robot en appelant le node view_frames du package tf2_tools:

ros2 run tf2_tools view_frames

Ce node va génerer deux fichiers à l’emplacement où vous avez executé la commande: frames_date_hour.pdf and frames_date_hour.gv. En ouvrant le fichier pdf, vous pourrez voir le tf tree du turtlebot3 executé en simulation:

Robot tf_tree

Vous devriez avoir les frames suivantes:

  • La frame odom est la frame de réference pour la localisation locale du robot.
  • La frame base_link represente la frame principale du robot, c’est relativement à celle ci que les differentes treansforms des capteurs et autres links du robot sont déclarées.
  • Les frames des differents capteurs définis relativement à base_link

Le package slam_toolbox va generer une nouvelle frame map qui va servir de réference globale pour la localisation du robot par rapport à l’espace. Si la localisation du robot dans la frame odom est sujet à une dérive et une accumulation d’erreurs à cause de la nature de ses capteurs (odometry visuelle ou via roues codeuses, accélerometre) qui ne possède que des informations relatives, la localisation du robot dans la frame map va souvent être effectuée avec la fusion des sources de localisations “globales”. Dans le cas des robots d’exterieurs, le GPS fait souvent office de reference globale. Pour les robots d’interieurs les lidars vont souvent fournir cette information.

Nous allons utiliser le package crée dans la partie précedante. On va d’abord créer le fichier yaml qui va contenir les parametres pour slam_toolbox:

cd ~/ros2_ws/src/slam_setup_tutorial/
cd params
touch slam_toolbox_params.yaml

Une fois le fichier créer, ouvrez le dans votre éditeur de code et copiez y la configuration suivante:

slam_toolbox:
  ros__parameters:
    # Le robot est lancé en simulation, synchroniser le temps du robot avec la clock de la simulation
    use_sim_time: true
    # Fréquence de mise à jour du SLAM en Hz
    update_rate: 1.0
    # Période de publication de la carte en secondes
    publish_period: 0.1
    # Résolution de la carte créée, en mètres/pixel
    resolution: 0.05
    # Portée maximale considérée pour les valeurs du laser scan
    max_laser_range: 8.0
    # Topic sur lequel le robot publie les données du laser scan
    scan_topic: /scan
    # Frame de référence pour la localisation locale (souvent "odom" par convention)
    odom_frame: odom
    # Frame de référence attachée au robot (souvent "base_link" par convention)
    base_frame: base_link
    # Mode d'opération de slam_toolbox (mapping ou localisation)
    mode: mapping
    # Activer les logs de debug
    debug_logging: true

    # Intervalle de mise à jour de la carte en secondes
    map_update_interval: 5.0
    # Seuil maximal d'occupation pour considérer une cellule comme occupée
    max_occupied_threshold: 0.65
    # Seuil minimal pour considérer une cellule comme libre
    min_free_threshold: 0.196
    # Intervalle de temps minimal entre deux mises à jour de pose en secondes
    minimum_time_interval: 0.5
    # Période de publication des transformations en secondes
    transform_publish_period: 0.02
    # Distance minimale parcourue pour déclencher une mise à jour en mètres
    linear_update: 0.1
    # Rotation minimale effectuée pour déclencher une mise à jour en radians
    angular_update: 0.1
    # Taille du buffer pour les scans laser
    scan_buffer_size: 10
    # Distance maximale des scans stockés dans le buffer en mètres
    scan_buffer_maximum_scan_distance: 8.0
    # Timeout pour la recherche de transformation en secondes
    lookup_transform_timeout_sec: 0.5
    submap:
      # Nombre de cellules dans chaque sous-carte
      number_of_cells: 3500

Cette configuration est adaptée pour le turtlebot3 dans un environemment indoor, pour des en fonction des contraintes de votre plateforme (capteurs, puissance de calcul…) vous devrez optimiser les parametres. Tous les parametres du node slam_toolbox sont listés sur la documentation que vous pouvez trouver ici. Maintenant que nous avons les parametres, nous devons écrire un launch file pour executer le node:

cd ~/ros2_ws/src/slam_setup_tutorial/
cd launch
touch start_slam_toolbox.launch.py
import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import (DeclareLaunchArgument)
from launch.substitutions import (LaunchConfiguration)
from launch_ros.actions import LifecycleNode

def generate_launch_description():
    use_sim_time = LaunchConfiguration('use_sim_time')
    slam_params_file = LaunchConfiguration('slam_params_file')

    declare_use_sim_time_argument = DeclareLaunchArgument(
        'use_sim_time',
        default_value='true',
        description='Use simulation/Gazebo clock')
    # Déclare comme parametre le fichier yaml que nous avons créé precedemment
    declare_slam_params_file_cmd = DeclareLaunchArgument(
        'slam_params_file',
        default_value=os.path.join(get_package_share_directory("slam_setup_tutorial"),
                                   'params', 'slam_toolbox_param.yaml'),
        description='Full path to the ROS2 parameters file to use for the slam_toolbox node')

    # Déclare le node slam_toolbox
    start_async_slam_toolbox_node = LifecycleNode(
        parameters=[
          slam_params_file,
          {
            'use_sim_time': use_sim_time
          }
        ],
        package='slam_toolbox',
        executable='async_slam_toolbox_node',
        name='slam_toolbox',
        output='screen',
        namespace=''
    )

    ld = LaunchDescription()
    ld.add_action(declare_use_sim_time_argument)
    ld.add_action(declare_slam_params_file_cmd)
    ld.add_action(start_async_slam_toolbox_node)

    return ld

On peut maintenant compiler le package avec les commandes suivantes:

cd ~/ros2_ws
colcon build --symlink-install
source install/setup.bash

Vous pouvez maintenant lancer le node avec la commande suivante:

ros2 launch slam_setup_tutorial start_slam_toolbox.launch.py

Vous pouvez ouvrir rviz2 avec le fichier à la racine du projet avec la commande suivant:

rviz2 -d ~/ros2_ws/slam_toolbox_rviz_config.rviz

En ouvrant rviz2 avec la configuration à la racine du tutoriel vous devriez avoir ce visuel:

Image de map incomplete dans rviz

Le robot peut commencer à mapper son environnement en utilisant sa localisation et les valeurs de ses données, c’est ce cycle de localization couplé au mapping que l’on appelle SLAM (Simultaneous Localization And Mapping). Le node de slam_toolbox publie également la tf map utilisée pour la référence de localisation globale qui est une frame parente de la frame odom:

Image tf tree avec la frame map

Partie 3: Sauvegarder la map et la réutiliser

Maintenant que l’on a démarré slam_toolbox, on peut effectuer le mapping de la zone. Pour faire cela vous devez déplacer le robot pour lui permettre de mapper toute la zone. Vous pouvez téléopérer le robot avec la commande suivante:

ros2 run turtlebot3_teleop teleop_keyboard

Une fois la zone totalement explorée par le robot, vous devriez avoir le résultat suivant dans rviz2:

map dans rviz2 completée

Maintenant que nous avons mapper la zone, la prochaine étape est de sauvegarder cette carte pour pouvoir la réutiliser. Pour sauvegarder la carte, vous pouvez appeler le service /slam_toolbox/save_map qui est une wrapper simplifié de map_saver en effectuant la commande suivante:

ros2 service call /slam_toolbox/save_map slam_toolbox/srv/SaveMap "name: {data: 'my_map'}"

Cette commande subscribe sur le topic map et crée des fichiers my_map.pgm et my_map.yaml qui vont être utilisé pour décrire notre map. Le fichier my_map.pgm est une image qui représente l’environnement:

image pgm map

Le fichier my_map.yaml est le fichier de configuration suivant:

# Fichier de l'image de la map
image: my_map.pgm
mode: trinary
# Resolution de la carte en metre/pixel
resolution: 0.05
# origin de la carte dans le referentiel de la frame map
origin: [-2.95, -2.58, 0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.25

Maintenant que notre map est sauvegardée, nous pouvons la republier sur le topic map en utilisant le node map_server. Ce node va cette fois publier la map comme une static map, les points du laserscan ne vont plus mettre à jour la carte. Pour localiser le robot sur cette carte déjà crée, nous allons utiliser le package AMCL pour localiser le robot par rapport à la static map et à publier la map frame à la place slam_toolbox. Le package amcl (Adaptive Monte Carlo Localization) permet à un robot de se localiser dans un environnement connu. Voici comment il fonctionne :

  • Initialisation : Utilise une carte déjà définie comme celle que nous avons créé avec slam_toolbox.
  • Particules : Génère des hypothèses de pose (position + orientation) sous forme de particules.
  • Mise à jour des capteurs : Compare les mesures des capteurs (lidar) avec la carte pour évaluer la pertinence des particules.
  • Resampling : Renforce les particules pertinentes et élimine les autres pour affiner la position estimée.
  • Estimation de la position : Calcule la position moyenne pondérée des particules.
  • Boucle continue : Répète ce processus pour une localisation précise et continue. amcl utilise des fitres à particules et des données de capteurs pour localiser un robot sur une carte connue.

Vous pouvez utiliser lancer le node map_server et amcl avec le fichier launch suivant:

cd ~/ros2_ws/src/slam_setup_tutorial/
cd launch
touch start_map_server_amcl.launch.py
import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():

    map_file = os.path.join(get_package_share_directory(
        'slam_setup_tutorial'), 'params', 'my_map.yaml')
    
    amcl_file = os.path.join(get_package_share_directory(
        'slam_setup_tutorial'), 'params', 'amcl.yaml')

    return LaunchDescription([
        Node(
            package='nav2_map_server',
            executable='map_server',
            name='map_server',
            output='screen',
            parameters=[{'use_sim_time': True},
                        {'yaml_filename': map_file}]
        ),
        Node(
                package='nav2_amcl',
                executable='amcl',
                name='amcl',
                parameters=[amcl_file],
        ),
        Node(
            package='nav2_lifecycle_manager',
            executable='lifecycle_manager',
            name='lifecycle_manager_localization',
            output='screen',
            parameters=[{'use_sim_time': True},
                        {'autostart': True},
                        {'node_names': ['amcl','map_server']}]
        )
    ])

Il faut également créer le fichier de parametres amcl.yaml dans le fichier params:

amcl:
  ros__parameters:
    # Utiliser le temps de simulation au lieu du temps réel
    use_sim_time: True
    # Coefficients pour les bruits de mouvement
    alpha1: 0.2
    alpha2: 0.2
    alpha3: 0.2
    alpha4: 0.2
    alpha5: 0.2
    # Identifiant de la frame de base du robot
    base_frame_id: "base_footprint"
    # Distance seuil pour ignorer les faisceaux de laser
    beam_skip_distance: 0.5
    # Seuil d'erreur pour ignorer les faisceaux de laser
    beam_skip_error_threshold: 0.9
    # Seuil pour commencer à ignorer les faisceaux de laser
    beam_skip_threshold: 0.3
    # Activer ou désactiver le saut de faisceaux
    do_beamskip: false
    # Identifiant de la frame globale (carte)
    global_frame_id: "map"
    # Paramètre lambda pour le modèle de faisceau court
    lambda_short: 0.1
    # Distance maximale pour considérer les probabilités de faisceau laser
    laser_likelihood_max_dist: 2.0
    # Portée maximale du laser
    laser_max_range: 100.0
    # Portée minimale du laser
    laser_min_range: -1.0
    # Type de modèle de laser utilisé
    laser_model_type: "likelihood_field"
    # Nombre maximal de faisceaux laser utilisés
    max_beams: 60
    # Nombre maximal de particules
    max_particles: 2000
    # Nombre minimal de particules
    min_particles: 500
    # Identifiant de la frame odométrique
    odom_frame_id: "odom"
    # Erreur maximale permise pour le filtre particulaire
    pf_err: 0.05
    # Confiance dans les mesures de position
    pf_z: 0.99
    # Paramètre de récupération rapide pour le filtre particulaire
    recovery_alpha_fast: 0.0
    # Paramètre de récupération lente pour le filtre particulaire
    recovery_alpha_slow: 0.0
    # Intervalle de rééchantillonnage des particules
    resample_interval: 1
    # Type de modèle de mouvement du robot
    robot_model_type: "nav2_amcl::DifferentialMotionModel"
    # Fréquence de sauvegarde de la pose estimée (en secondes)
    save_pose_rate: 0.5
    # Écart type pour le modèle de capteur "hit"
    sigma_hit: 0.2
    # Activer ou désactiver la diffusion des transformations
    tf_broadcast: true
    # Tolérance pour la transformation des coordonnées (en secondes)
    transform_tolerance: 1.0
    # Seuil minimal de rotation pour mettre à jour la pose
    update_min_a: 0.2
    # Seuil minimal de translation pour mettre à jour la pose
    update_min_d: 0.25
    # Poids du modèle de capteur "hit"
    z_hit: 0.5
    # Poids du modèle de capteur "max"
    z_max: 0.05
    # Poids du modèle de capteur "rand"
    z_rand: 0.5
    # Poids du modèle de capteur "short"
    z_short: 0.05
    # Topic fournissant les données du scan laser
    scan_topic: scan
    # Topic sur lequel la carte est publiée
    map_topic: map
    # Utiliser seulement la première carte reçue
    first_map_only: false
    # Permet de définir la pose initiale avec le paramètre initial_pose
    set_initial_pose: true
    # Pose initiale du robot
    initial_pose:
      x: -2.0
      y: -0.5
      z: 0.0
      yaw: 0.0

Vous pouvez trouver la documentation de amcl avec la liste des paramètre ici: https://docs.nav2.org/configuration/packages/configuring-amcl.html. Vous pouvez réutiliser le fichier de configuration rviz2 du projet pour visualiser les résultats:

rviz2 -d ~/ros2_ws/slam_toolbox_rviz_config.rviz

Une fois rviz lancé pour visualiser la carte, vous pouvez build le package et executer le launch file pour voir la map crée précédemment:

cd ~/ros2_ws 
colcon build --symlink-install 
source install/setup.bash 
ros2 launch slam_setup_tutorial start_slam_tool_box.launch.py

Vous devriez maintenant voir la map que nous avons généré dans rviz avec le robot positionné dessus:

alternative text

La TF map est republiée et la pose du robot sur la frame map est publiée sur le topic /amcl_pose. Nous avons donc vu comment générer une static map avec slam_toolbox et comment la réutiliser en utilisant map_server et amcl.

Feedback

Cet article vous a plu ? Avez-vous des questions sur ce qui est expliqué ? Quoi qu’il en soit, n’hésitez pas à laisser un commentaire dans la section des commentaires ci-dessous, afin que nous puissions interagir et apprendre les uns des autres.

Si vous souhaitez en savoir plus sur d’autres sujets liés à ROS, faites le nous savoir dans l’espace commentaires et nous ferons une vidéo ou un article à ce sujet.
Topics: ROS Q&A | ros2

This tutorial is created by Robotics Ambassador Enzo.

Video Tutorial

Hướng dẫn tạo một Service Client – Vietnamese ROS2 Tutorial

Hướng dẫn tạo một Service Client – Vietnamese ROS2 Tutorial

This tutorial is created by Robotics Ambassador – Tung

Robotics Ambassador: theconstruct.ai/robotics-ambassador/

Xin chào các bạn!

Hôm nay mình sẽ hướng dẫn các bạn cách tạo một Khách hàng Dịch vụ (Service Client) trong ROS2 sử dụng nền tảng của The Construct.

Nguồn tham khảo

  1. Nền tảng học ROS trực tuyến The Construct
  2. Khóa học ROS2 cơ bản (Python) ROS2 Basics in 5 Days

Trước khi đi vào nội dung chính, nếu bạn nào chưa biết Service trong ROS2 là gì thì mình khuyên các bạn nên ghé qua video mình hướng dẫn các câu lệnh cơ bản với Dịch vụ trong ROS2 trước ở link này nhé:

1. Tạo một package

Đầu tiên, chúng ta sẽ tạo một package chứa code của Client cần tạo.

Tạo một package mới tên là client_pkg trong thư mục ~/ros2_ws/src bằng các lệnh sau:

cd ~/ros2_ws/src
ros2 pkg create client_pkg --build-type ament_python --dependencies rclpy std_srvs

Chúng ta sẽ cần package std_srvs để giao tiếp với các Dịch vụ /moving/stop như trong video trước mình đã hướng dẫn.

2. Tạo một tệp chứa nội dung code của Client

Tiếp theo, trong thư mục client_pkg của package vừa tạo, ta sẽ tạo một tệp mới tên là service_client.py chứa phần code chính của Service Client mà chúng ta muốn tạo. Các bạn hãy copy nội dung phần code sau vào trong file service_client.py vừa tạo:

# import the empty module from std_servs Service interface
from std_srvs.srv import Empty
# import the ROS2 Python client libraries
import rclpy
from rclpy.node import Node

class ClientAsync(Node):

    def __init__(self):
        # Here you have the class constructor

        # call the class constructor to initialize the node as service_client
        super().__init__('service_client')
        # create the Service Client object
        # defines the name and type of the Service Server you will work with.
        self.client = self.create_client(Empty, 'moving')
        # checks once per second if a Service matching the type and name of the Client is available.
        while not self.client.wait_for_service(timeout_sec=1.0):
            # if it is not available, a message is displayed
            self.get_logger().info('service not available, waiting again...')
        
        # create an Empty request
        self.req = Empty.Request()
        

    def send_request(self):
        
        # send the request
        self.future = self.client.call_async(self.req)

def main(args=None):
    # initialize the ROS communication
    rclpy.init(args=args)
    # declare the node constructor
    client = ClientAsync()
    # run the send_request() method
    client.send_request()

    while rclpy.ok():
        # pause the program execution, waits for a request to kill the node (ctrl+c)
        rclpy.spin_once(client)
        if client.future.done():
            try:
                # checks the future for a response from the Service
                # while the system is running. 
                # If the Service has sent a response, the result will be written
                # to a log message.
                response = client.future.result()
            except Exception as e:
                # Display the message on the console
                client.get_logger().info(
                    'Service call failed %r' % (e,))
            else:
                # Display the message on the console
                client.get_logger().info(
                    'the robot is moving' ) 
            break

    client.destroy_node()
    # shutdown the ROS communication
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Mình sẽ giải thích qua đoạn code này nhé. Phần code trong file này cũng đi theo cấu trúc chung của một node trong ROS2. Ở trong hàm main(), sau khi khởi tạo ROS2, chương trình sẽ khai báo object ClientAsync() mà chúng ta đã định nghĩa ở phần trên. Trong phần constructor của class ClientAsync(), lệnh để tạo Client là:

self.client = self.create_client(Empty, 'moving')

Lệnh này sẽ tạo một Client sử dụng kiểu dữ liệu Empty và kết nối tới một Service tên là /moving.

Tiếp theo, vòng lặp while được sử dụng để đảm bảo rằng Service Server (/moving ) đang hoạt động.

while not self.client.wait_for_service(timeout_sec=1.0):

Quay trở lại với hàm main(). Sau khi khởi tạo xong, yêu cầu sẽ được gửi tới server bằng hàmsend_request(). Hàm này rất quan trọng và có nội dung là:

self.future = self.client.call_async(self.req)

Câu lệnh này sẽ gửi một yêu cầu không đồng bộ (asynchronous request) tới Service Server bằng hàm call_async(). Sau đó, lưu phản hồi từ Server về biến self.future. Biến này cũng rất quan trọng. Sau khi tạo yêu cầu, Server sẽ ngay lập tức trả về future cho biết việc gọi và phản hồi được thực hiện (nhưng nó không chứa nội dung của phản hồi).

Cuối cùng, trong hàm main() là vòng lặp while:

while rclpy.ok():
        # pause the program execution, waits for a request to kill the node (ctrl+c)
        rclpy.spin_once(client)
        if client.future.done():
            try:
                # checks the future for a response from the Service
                # while the system is running. 
                # If the Service has sent a response, the result will be written
                # to a log message.
                response = client.future.result()
            except Exception as e:
                # Display the message on the console
                client.get_logger().info(
                    'Service call failed %r' % (e,))
            else:
                # Display the message on the console
                client.get_logger().info(
                    'the robot is moving' ) 
            break

Khi chương trình đang chạy (rclpy.ok()), nó sẽ liên tục kiểm tra xem phản hồi từ dịch vụ đã hoàn thành hay chưa:

if client.future.done():

Nếu nó đã hoàn thành thì chương trình sẽ nhận được giá trị của phản hồi từ Server:

response = client.future.result()

Trong trường hợp này, vì chúng ta đang làm việc với một phản hồi kiểu Empty nên chúng ta sẽ không làm gì cả.

Trong khi đợi phản hồi hoàn thành của Dịch vụ, chương trình sẽ in ra các thông tin vào nhật ký node bằng lệnh:

client.get_logger().info('the robot is moving' ) 

Và cuối cùng phải kể đến dòng lệnh sau:

rclpy.spin_once(client)

Dòng lệnh này tương tự như lệnh rclpy.spin() nhưng chỉ quay vòng node đó một lần.

3. Tạo một tệp launch

Tiếp theo, chúng ta sẽ tạo một file launch để khởi tạo client này.

Tạo một file launch tên là service_client_launch_file.launch.py bằng các lệnh sau:

cd ~/ros2_ws/src/client_pkg
mkdir launch
cd launch
touch service_client_launch_file.launch.py
chmod +x service_client_launch_file.launch.py

Mở file launch vừa tạo và thêm những dòng sau vào:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='client_pkg',
            executable='service_client',
            output='screen'),
    ])

4. Sửa tệp setup.py

Sau khi tạo xong file launch, chúng ta sẽ thêm một số dòng code vào file setup.py để cài đặt tệp launch mà chúng ta vừa mới tạo và thêm entry points vào executable cho script service_client.py

from setuptools import setup
import os
from glob import glob

package_name = 'client_pkg'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name), glob('launch/*.launch.py'))
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='user',
    maintainer_email='user@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
        'service_client = client_pkg.service_client:main',
        ],
    },
)

5. Biên dịch gói vừa tạo

Sau khi code xong, chúng ta sẽ đến với bước biên dịch chương trình. Ta thực hiện các lệnh sau:

cd ~/ros2_ws
colcon build --packages-select client_pkg
source ~/ros2_ws/install/setup.bash

6. Cuối cùng khởi động node Service Server trên Terminal

Node Client vừa tạo được khởi động bằng lệnh sau:

ros2 launch client_pkg service_client_launch_file.launch.py

Sau khi dòng code này chạy, chúng ta sẽ thấy robot bắt đầu di chuyển ngẫu nhiên. Điều này chứng tỏ Service Server /moving đã nhận được yêu cầu từ Client và thực hiện yêu cầu đó.

Chúc các bạn thực hành thành công.

Video Tutorial


Criação de Serviços Personalizados no ROS 2 e Controle de Movimento do Robô em Python – Portuguese ROS Tutorial

Criação de Serviços Personalizados no ROS 2 e Controle de Movimento do Robô em Python – Portuguese ROS Tutorial

This tutorial is created by Robotics Ambassador – Anderson

Robotics Ambassador: theconstruct.ai/robotics-ambassador/

 

O que nós vamos aprender:

  1. Criação de um serviço personalizado no ROS2.
  2. Configuração do ambiente ROS2 para garantir que todas as dependências estão corretamente instaladas e atualizadas.
  3. Criação de um pacote ROS2 incluindo a estrutura de diretórios e os arquivos necessários para o projeto.
  4. Implementação de um servidor e cliente de serviço para controle de movimento do robô.
  5. Criação e edição de arquivos de lançamento (launch files) no ROS2.
  6. Compilação e execução dos pacotes ROS2.
  7. Teste e verificação do serviço para controlar o movimento do robô em diferentes direções.

 

Lista de recursos usados neste post:

  1. Use este rosject: https://app.theconstruct.ai/l/639df241/
  2. The Construct: https://app.theconstructsim.com/
  3. ROS Courses: ROS Basics in 5 Days (C++) https://app.theconstructsim.com/courses/132

 

Panorama

Neste tutorial, vamos aprender a criar serviços personalizados no ROS2 e desenvolver um pacote de controle de movimento para um robô. Vamos passar pelos passos de criação e configuração de serviços, desenvolvimento de um pacote de movimento, implementação do servidor e cliente de serviço, e a criação de arquivos de lançamento. Este tutorial é ideal para quem deseja aprimorar suas habilidades em ROS2 e programação robótica.

 

Abrindo o rosject

Clique no link abaixo para ter acesso ao rosject deste tutorial. Em seguida, clique no botão “RUN”.

https://app.theconstruct.ai/l/639df241/

<figure1>

Criando o Serviço Personalizado

Criação do Pacote de Interfaces Personalizadas

Primeiro, vamos criar um pacote de interfaces personalizadas que conterá nosso serviço. Este pacote será responsável por definir e gerenciar a interface do serviço que iremos usar para controlar o robô.

Copie este bloco de comandos no primeiro terminal:

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake custom_interfaces --dependencies rclcpp std_msgs

Esses comandos criam um novo pacote chamado custom_interfaces com as dependências rclcpp e std_msgs.

Definição do Serviço

Em seguida, vamos definir o serviço que será utilizado para controlar o robô. Para isso, crie uma pasta chamada srv dentro do pacote custom_interfaces e um arquivo Move.srv. Use os comandos:

cd ~/ros2_ws/src/custom_interfaces
mkdir srv
cd srv
touch Move.srv

Edite o arquivo Move.srv com o seguinte conteúdo:

string direction               # Direção para girar (direita ou esquerda)
float64 velocity               # Velocidade angular (em rad/s) ou linear (em m/s)
int32 time                     # Duração do giro (em segundos)
---
bool success                   # O comando foi bem-sucedido?

Esse arquivo define a estrutura do serviço. Ele recebe a direção, velocidade e tempo como entrada, e retorna um booleano indicando se a operação foi bem-sucedida.

Atualizando os Arquivos CMakeLists.txt e package.xml

Agora, precisamos configurar os arquivos CMakeLists.txt e package.xml para que o ROS2 possa construir e utilizar nosso serviço.

No arquivo CMakeLists.txt, adicione:

find_package(rosidl_default_generators REQUIRED)
.
.
.
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/Move.srv"
)

Essas linhas garantem que o gerador de interfaces do ROS2 processe nosso arquivo Move.srv.

No arquivo package.xml, adicione:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

Essas dependências são necessárias para a construção e execução do nosso serviço.

Compilação e Verificação

Agora, compile o pacote e verifique se o serviço foi criado corretamente.

cd ~/ros2_ws
colcon build --packages-select custom_interfaces
source ~/ros2_ws/install/setup.bash

Para verificar se a mensagem de serviço foi criada:

ros2 interface show custom_interfaces/srv/Move

Criando o Pacote de Movimento

Vamos criar um pacote que implementará a lógica de movimento do robô, incluindo um servidor e um cliente de serviço.

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python movement_pkg --dependencies rclpy custom_interfaces std_msgs geometry_msgs sensor_msgs

Este comando cria um pacote chamado movement_pkg com as dependências necessárias.

Crie os arquivos necessários com os comandos:
cd ~/ros2_ws/src/movement_pkg/movement_pkg
touch movement_server.py movement_client.py

Implementação do Servidor de Serviço

Crie e edite o arquivo movement_server.py com o seguinte conteúdo:


from geometry_msgs.msg import Twist
from custom_interfaces.srv import Move
import rclpy
from rclpy.node import Node
import time

class Service(Node):
    def __init__(self):
        super().__init__('move_server')
        self.srv = self.create_service(Move, 'move', self.custom_service_callback)
        self.publisher_ = self.create_publisher(Twist, 'cmd_vel', 10)

    def custom_service_callback(self, request, response):
        msg = Twist()
        if request.direction == "right":
            msg.angular.z = -request.velocity
        elif request.direction == "left":
            msg.angular.z = request.velocity
        elif request.direction == "forward":
            msg.linear.x = request.velocity
        elif request.direction == "backward":
            msg.linear.x = -request.velocity
        elif request.direction == "stop":
            msg.linear.x = 0.0
            msg.angular.z = 0.0
            self.publisher_.publish(msg)
            response.success = True
            return response
        else:
            response.success = False
            return response

        self.publisher_.publish(msg)
        time.sleep(request.time)
        msg.linear.x = 0.0
        msg.angular.z = 0.0
        self.publisher_.publish(msg)
        response.success = True
        return response

def main(args=None):
    rclpy.init(args=args)
    service = Service()
    rclpy.spin(service)
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Explicação do código:

  • Importamos as bibliotecas necessárias.
  • Definimos a classe Service, que herda de Node.
  • No método __init__, criamos o serviço e o publicador.
  • O método custom_service_callback define a lógica de controle do robô com base nos parâmetros recebidos.
  • No final, iniciamos e rodamos o nó ROS2.

Implementação do Cliente de Serviço

Crie e edite o arquivo movement_client.py com o seguinte conteúdo:


from custom_interfaces.srv import Move
import rclpy
from rclpy.node import Node
import sys

class ClientAsync(Node):
    def __init__(self):
        super().__init__('move_client')
        self.client = self.create_client(Move, 'move')
        while not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = Move.Request()

    def send_request(self):
        self.req.direction = str(sys.argv[1])
        self.req.velocity = float(sys.argv[2])
        self.req.time = int(sys.argv[3])
        self.future = self.client.call_async(self.req)

def main(args=None):
    rclpy.init(args=args)
    client = ClientAsync()
    client.send_request()

    while rclpy.ok():
        rclpy.spin_once(client)
        if client.future.done():
            try:
                response = client.future.result()
            except Exception as e:
                client.get_logger().info('Service call failed %r' % (e,))
            else:
                client.get_logger().info('Response state %r' % (response.success,))
            break

    client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Explicação do código:

  • Importamos as bibliotecas necessárias.
  • Definimos a classe ClientAsync, que herda de Node.
  • No método __init__, criamos o cliente de serviço e esperamos que o serviço esteja disponível.
  • O método send_request envia a solicitação ao serviço com os parâmetros fornecidos.
  • No final, iniciamos e rodamos o nó ROS2.

Criando o Arquivo de Lançamento

cd ~/ros2_ws/src/movement_pkg
mkdir launch
cd launch
touch movement_server_launch_file.launch.py
chmod +x movement_server_launch_file.launch.py

Edite o arquivo movement_server_launch_file.launch.py com o seguinte conteúdo:


from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='movement_pkg',
            executable='movement_server',
            output='screen'),
    ])

Este arquivo de lançamento define um nó que executa o servidor de movimento.

Então sua estrutura de pastas e arquivos deve ficar assim:

Modificação do arquivo setup.py

Atualize o arquivo setup.py com o seguinte conteúdo:


from setuptools import setup
import os
from glob import glob

package_name = 'movement_pkg'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name), glob('launch/*.launch.py'))
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='user',
    maintainer_email='user@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
        'movement_server = movement_pkg.movement_server:main',
        'movement_client = movement_pkg.movement_client:main'
        ],
    },
)

Este arquivo define a configuração do pacote, incluindo as entradas do console que apontam para nossos scripts Python.

Compilação e Execução

Compile o pacote movement_pkg:

cd ~/ros2_ws
colcon build --packages-select movement_pkg
source ~/ros2_ws/install/setup.bash

Lançando o Servidor

No primeiro terminal, execute:

ros2 launch movement_pkg movement_server_launch_file.launch.py

Executando o Cliente

No segundo terminal, execute:

source ~/ros2_ws/install/setup.bash
ros2 run movement_pkg movement_client left 0.2 5

No segundo terminal você verá:

Enquanto no primeiro terminal aparecerá:

Isso indica que o serviço foi realizado corretamente.

Na janela do Gazebo, você pode ver o robô se movimentando pelo ambiente.

<figure7>

Outras opções, além de left, são right, forward, backward. Experimente mudar os parametros do comando anterior variando também a velocidade e a duraçao do movimento do robô (respectivamente segundo e terceiro parametros do comando).

Conclusão

Esperamos que este tutorial tenha sido útil e que você tenha aprendido como criar serviços personalizados no ROS2 e controlar o movimento de um robô. Se você gostou deste conteúdo, considere explorar mais recursos do ROS2 e experimentar novas possibilidades para expandir suas habilidades em robótica.

 Vídeo no YouTube

Este foi o post de hoje. Lembre-se que nós temos uma versão deste post em vídeo no YouTube.

Se você gostou do conteúdo, por favor, considere se inscrever no nosso canal do YouTube. Estamos publicando novos conteúdos quase todos os dias.

Continue avançando no seu aprendizado de ROS.

 

Parameterの使い方 – Japanese ROS2 Tutorial

Parameterの使い方 – Japanese ROS2 Tutorial

This tutorial is created by Robotics Ambassador Takumi

 

Robotics Ambassador: theconstruct.ai/robotics-ambassador/

何を学ぶか

このチュートリアルでは公式チュートリアルの内容を参考にROS Paramを動的に書き換える方法を試してみます。

なんで?

ROS 2になって、Parameterの動的な書き換えが可能になりました。
Parameterを初期値やファイルパスの設定とかに使ってる方割と多いと思うのですが、動的にいじれるとサーバの接続先変えたり、テスト中に設定間違えたパラメータ書き直したりできます。
かなり便利で多用しているので、紹介したいと思いました。

環境

ROS2 Humble
rosject link https://app.theconstruct.ai/l/61071157/

作業

パッケージ作成

以下のコマンドでパッケージを作ります。

ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_param_exp --dependencies rclcpp

ソースコード記述

srcディレクトリに参考にc++のコードを書きましょう。

#include 

#include "rclcpp/rclcpp.hpp"

class SampleNodeWithParameters : public rclcpp::Node {
public:
  SampleNodeWithParameters() : Node("node_with_parameters") {
    this->declare_parameter("an_int_param", 0);

    param_subscriber_ = std::make_shared(this);

    auto cb = [this](const rclcpp::Parameter &p) {
      RCLCPP_INFO(
          this->get_logger(),
          "cb: Received an update to parameter \"%s\" of type %s: \"%ld\"",
          p.get_name().c_str(), p.get_type_name().c_str(), p.as_int());
    };
    cb_handle_ = param_subscriber_->add_parameter_callback("an_int_param", cb);
  }

private:
  std::shared_ptr param_subscriber_;
  std::shared_ptr cb_handle_;
};

int main(int argc, char **argv) {
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared());
  rclcpp::shutdown();

  return 0;
}

コードの動作としては、an_int_paramパラメータの更新を受け取ったら、そのデータをログに吐くといういたって単純なモノです。
特徴的な所があるとしたら、コールバック関数の定義にラムダ式を使っている所でしょうか。

コンパイル

次はコンパイルします。
package.xmlとCMakeList.txtの記述が必要ですが、特に難しいことはやってないので、詳しくはRosjectを参照してください。
(sourceはしている前提とします。)

cd ~/ros2_ws/
colcon buildじ

実行

以下のコマンドで実行していきます。

[端末A]

cd ~/ros2_ws/
user:~/ros2_ws$ . install/setup.bash
user:~/ros2_ws$ ros2 run cpp_param_exp parameter_event_handlerき

起動できたら、別の[端末B]を開いて以下のコマンドを実行します。”Set parameter successful”と出れば成功です。

cd ~/ros2_ws/
user:~$ ros2 param set /node_with_parameters an_int_param 10
Set parameter successful

端末Aを見ると以下のメッセージが表示され、parameterの更新に成功していることが分かります。

[INFO] [1714224724.898754729] [node_with_parameters]: cb: Received an update to parameter "an_int_param" of type integer: "10"

また,rqt_reconfigureを使ってguiでデータ設定をする事も出来ます。試しあれ。
[端末B]

 . install/setup.bash
ros2 run rqt_reconfigure rqt_reconfigure

Video Tutorial

Pin It on Pinterest