Etiquetas: , , ,

Neste tutorial, veremos como criar e executar scripts Python no ROS2. Isto permitir-lhe-á criar os seus próprios nós e começar a desenvolver com o ROS.

Criar um espaço de trabalho

Uma boa prática para desenvolver sob o ROS2 é criar espaços de trabalho nos quais instalar os pacotes desejados, separados da instalação principal.

Instalação do Colcon

sudo apt install python3-colcon-common-extensions

Criar pasta

mkdir -p ~/tuto_ws/src
cd ~/tuto_ws

Em seguida, copie um projeto de exemplo, como o tutorial que contém o turtlesim

git clone https://github.com/ros/ros_tutorials.git -b iron

Em seguida, construa o projeto utilizando o comando

colcon build

Para carregar o seu espaço de trabalho num novo terminal

source /opt/ros/iron/setup.bash
cd ~/ros2_ws
source install/local_setup.bash

N.B.: pode colocar os seguintes comandos num ros_profile para a fonte num único comando “source ros_profile”

Criar um pacote python

O ROS2 oferece uma ferramenta simples para criar a arquitetura de arquivos de um pacote Python ou C++. É necessário especificar pelo menos o nome do pacote (my_package). Também é possível dar o nome do nó.

No diretório ~/ros2_sw/src, introduza o seguinte comando

ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name my_node my_package --license Apache-2.0

o script python está agora no diretório ~/ros2_ws/src/my_package/my_package

Uma vez criado o pacote, pode adicionar tantos nós quantos quiser a esta pasta.

Terá de atualizar os ficheiros setup.py e package.xml com as dependências e os pontos de entrada

Instalação de dependências

Antes de compilar o projeto, é aconselhável resolver as dependências.

rosdep install -i --from-path src --rosdistro iron -y

Compilar o projeto

É possível compilar todo o projeto ou selecionar um pacote específico

colcon build --packages_select my_package --symlink-install

N.B.: symlink-install significa que não tens de recompilar sempre que alteras o script Python.

Lançar o nó

Depois de o pacote ter sido compilado, pode iniciar o nó com o comando

ros2 run my_package my_node

O código básico para criar um nó

def main():
    print('Hi from my_package.')

if __name__ == '__main__':
    main()

Quando iniciado, este código apresenta simplesmente o texto da impressão

Criar um editor e um subscritor para o Turtlesim

Neste exemplo, veremos como criar um pacote com dois nós, um controlando a tartaruga e o outro lendo a posição.

Criação de pacotes

ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name projects control_turtle --license Apache-2.0

Aqui está o ficheiro de arquitetura desejado

.
├── LICENSE
├── package.xml
├── projects
│   ├── control_turtle.py
│   └── read_turtle.py
├── setup.cfg
└── setup.py

Adicionar scripts Python

  • Editor controlo_tartaruga.py

Importamos o tipo de mensagem a publicar Torcer para controlar a velocidade da tartaruga

Ligamo-nos ao tópico especificando o nome (‘/turtle1/cmd_vel’) e o tipo de dados (Twist)

Criamos um temporizador que nos permitirá publicar regularmente sobre o tema (create_timer)

Finalmente, publicamos a linha e a velocidade angular

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  control_turtle.py


import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist

import os

# Node should have the same name as the node file
nodename= os.path.splitext(os.path.basename(__file__))[0]

class TurtleController(Node):

	def __init__(self):
		super().__init__(nodename)
		self.get_logger().info(nodename+" started")
		self.publisher_ = self.create_publisher(
			Twist,
			'/turtle1/cmd_vel',
			10)
		timer_period = 0.5  # seconds
		self.timer = self.create_timer(timer_period, self.timer_callback)
		self.i = 0

	def timer_callback(self):
		#msg = String()
		#msg.data = 'Hello World: %d' % self.i
		msg = Twist()
		msg.linear.x = 1.0
		msg.angular.z = 0.5
		self.publisher_.publish(msg)
		self.get_logger().info('Publishing: "%s"' % msg)
		self.i += 1


def main(args=None):
	rclpy.init(args=args)

	turtle_controller = TurtleController()
	try:
		rclpy.spin(turtle_controller)
		turtle_controller.destroy_node()
		rclpy.shutdown()
	except:
		turtle_controller.get_logger().info(nodename+" stopped")


if __name__ == '__main__':
	main()
  • Assinante read_turtle.py

Importar o tipo de mensagem a publicar Pose

Ligamo-nos ao tópico especificando o nome (‘/turtle1/pose’) e o tipo de dados (Pose)

Escrevemos a função de escuta que é executada quando estão disponíveis novos dados sobre o tema

Criamos um temporizador que nos permitirá apresentar os valores com a frequência pretendida

Finalmente, publicamos a linha e a velocidade angular

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  control_turtle.py


import rclpy #rospy
from rclpy.node import Node
from turtlesim.msg import Pose


import os

# Node should have the same name as the node file
nodename= os.path.splitext(os.path.basename(__file__))[0]

class TurtleReader(Node):

	def __init__(self):
		super().__init__(nodename)
		self.get_logger().info(nodename+" started")
		self.subscription = self.create_subscription(
			Pose,
			'/turtle1/pose',
			self.listener_callback,
			10)
		self.subscription  # prevent unused variable warning
		self.timer = self.create_timer(2, self.timer_callback)
		self.msg = None
		
	def listener_callback(self, msg):
		self.msg = msg
		
	def timer_callback(self):
		self.get_logger().info("I read %s" % self.msg )
		


def main(args=None):
	rclpy.init(args=args)

	turtle_reader = TurtleReader()
	try:
		rclpy.spin(turtle_reader)
		turtle_reader.destroy_node()
		rclpy.shutdown()
	except:
		turtle_reader.get_logger().info(nodename+" stopped")





if __name__ == '__main__':
	main()

Modificar setup.py

Nos pontos de entrada do ficheiro setup.py, é necessário especificar os nomes dos nós e a função a executar (main)

entry_points={
        'console_scripts': [
                'control_turtle = projects.control_turtle:main',
                'read_turtle = projects.read_turtle:main',
        ],
},

Compilação do pacote

colcon build --packages_select projects --symlink-install

Lançamento de nós

Para lançar um nó num terminal, é necessário obter a fonte do projeto

source /opt/ros/iron/setup.bash
cd ~/ros2_ws
source install/local_setup.bash

Em três terminais diferentes, execute os seguintes comandos

ros2 run turtlesim turtlesim_node #terminal1
ros2 run projects read_turtle #terminal2
ros2 run projects control_turtle #terminal3

Resultados

Vê-se que a tartaruga descreve a forma de um círculo e a mudança de posição no centro do assinante.

turtlesim-read-control-turtle Criar um script Python no ROS2

Lançar um nó com argumentos

Os nós podem ser configurados em tempo de execução. Neste exemplo, vamos definir parâmetros de entrada para a velocidade linear e angular. Por isso, vamos modificar o código no editor acima

Na inicialização do nó, declaramos os parâmetros com os valores por defeito

    self.declare_parameter('x', 1.0)
    self.declare_parameter('z', 0.5)

Podemos então obter o valor destes parâmetros e armazená-los em variáveis que utilizamos na função de retorno de chamada

    self.linx = self.get_parameter('linx').get_parameter_value().double_value
    self.angz = self.get_parameter('angz').get_parameter_value().double_value

Agora é possível iniciar um nó com e sem o

ros2 run projects circle_turtle #default param
ros2 run projects circle_turtle --ros-args -p linx:=0.5 -p angz:=1.0
turtlesim-circle-param Criar um script Python no ROS2

Código completo

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  control_turtle.py
import rclpy #rospy
from rclpy.node import Node
from std_msgs.msg import String

from turtlesim.msg import Pose
from geometry_msgs.msg import Twist

import os

nodename= os.path.splitext(os.path.basename(__file__))[0]

class TurtleController(Node):

	def __init__(self):
		super().__init__(nodename)
		self.get_logger().info(nodename+" started")
		self.publisher_ = self.create_publisher(
			Twist,
			'/turtle1/cmd_vel',
			10)
			
		self.declare_parameter('linx', 1.0)
		self.declare_parameter('angz', 0.5)
		self.linx = self.get_parameter('linx').get_parameter_value().double_value
		self.angz = self.get_parameter('angz').get_parameter_value().double_value
			
		timer_period = 0.5  # seconds
		self.timer = self.create_timer(timer_period, self.timer_callback)
		self.i = 0

	def timer_callback(self):
		#msg = String()
		#msg.data = 'Hello World: %d' % self.i
		msg = Twist()
		msg.linear.x = self.linx
		msg.angular.z = self.angz
		self.publisher_.publish(msg)
		self.get_logger().info('Publishing: "%s"' % msg)
		self.i += 1


def main(args=None):
	#os.system('ros2 service call /reset std_srvs/srv/Empty') #reset pos
	rclpy.init(args=args)

	turtle_controller = TurtleController()
	try:
		rclpy.spin(turtle_controller)
		turtle_controller.destroy_node()
		rclpy.shutdown()
	except:
		turtle_controller.get_logger().info(nodename+" stopped")


if __name__ == '__main__':
	main()

Abrir os vários terminais com um script

Para poupar tempo durante o desenvolvimento, pode abrir terminais e lançar nós a partir de um único script bash

#open terminal and run turtlesim
gnome-terminal -- $SHELL -c "source ros_profile && ros2 run turtlesim turtlesim_node;exec $SHELL"
gnome-terminal -- $SHELL -c "source ros_profile && ros2 run projects control_turtle;exec $SHELL"

Resolução de problemas

  • Descontinuação do SetupTools

Verificar a versão do setuptools

python3
import setuptools
setuptools.__version__

‘59.6.0’

Instalar o pip

sudo apt install python3-pip

depois instale a versão 58.2.0 do setuptools

python3 -m pip install setuptools==58.2.0

Fontes