fbpixel
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo apt install python3-colcon-common-extensions
sudo apt install python3-colcon-common-extensions
sudo apt install python3-colcon-common-extensions

Criar pasta

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mkdir -p ~/tuto_ws/src
cd ~/tuto_ws
mkdir -p ~/tuto_ws/src cd ~/tuto_ws
mkdir -p ~/tuto_ws/src
cd ~/tuto_ws

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git clone https://github.com/ros/ros_tutorials.git -b iron
git clone https://github.com/ros/ros_tutorials.git -b iron
git clone https://github.com/ros/ros_tutorials.git -b iron

Em seguida, construa o projeto utilizando o comando

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
colcon build
colcon build
colcon build

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
source /opt/ros/iron/setup.bash
cd ~/ros2_ws
source install/local_setup.bash
source /opt/ros/iron/setup.bash cd ~/ros2_ws source install/local_setup.bash
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name my_node my_package --license Apache-2.0
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name my_node my_package --license Apache-2.0
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
rosdep install -i --from-path src --rosdistro iron -y
rosdep install -i --from-path src --rosdistro iron -y
rosdep install -i --from-path src --rosdistro iron -y

Compilar o projeto

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
colcon build --packages_select my_package --symlink-install
colcon build --packages_select my_package --symlink-install
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ros2 run my_package my_node
ros2 run my_package my_node
ros2 run my_package my_node

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def main():
print('Hi from my_package.')
if __name__ == '__main__':
main()
def main(): print('Hi from my_package.') if __name__ == '__main__': main()
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name projects control_turtle --license Apache-2.0
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name projects control_turtle --license Apache-2.0
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#!/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()
#!/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()
#!/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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#!/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()
#!/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()
#!/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)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
entry_points={
'console_scripts': [
'control_turtle = projects.control_turtle:main',
'read_turtle = projects.read_turtle:main',
],
},
entry_points={ 'console_scripts': [ 'control_turtle = projects.control_turtle:main', 'read_turtle = projects.read_turtle:main', ], },
entry_points={
        'console_scripts': [
                'control_turtle = projects.control_turtle:main',
                'read_turtle = projects.read_turtle:main',
        ],
},

Compilação do pacote

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
colcon build --packages_select projects --symlink-install
colcon build --packages_select projects --symlink-install
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
source /opt/ros/iron/setup.bash
cd ~/ros2_ws
source install/local_setup.bash
source /opt/ros/iron/setup.bash cd ~/ros2_ws source install/local_setup.bash
source /opt/ros/iron/setup.bash
cd ~/ros2_ws
source install/local_setup.bash

Em três terminais diferentes, execute os seguintes comandos

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ros2 run turtlesim turtlesim_node #terminal1
ros2 run projects read_turtle #terminal2
ros2 run projects control_turtle #terminal3
ros2 run turtlesim turtlesim_node #terminal1 ros2 run projects read_turtle #terminal2 ros2 run projects control_turtle #terminal3
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
self.declare_parameter('x', 1.0)
self.declare_parameter('z', 0.5)
self.declare_parameter('x', 1.0) self.declare_parameter('z', 0.5)
    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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
self.linx = self.get_parameter('linx').get_parameter_value().double_value
self.angz = self.get_parameter('angz').get_parameter_value().double_value
self.linx = self.get_parameter('linx').get_parameter_value().double_value self.angz = self.get_parameter('angz').get_parameter_value().double_value
    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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ros2 run projects circle_turtle #default param
ros2 run projects circle_turtle --ros-args -p linx:=0.5 -p angz:=1.0
ros2 run projects circle_turtle #default param ros2 run projects circle_turtle --ros-args -p linx:=0.5 -p angz:=1.0
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#!/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()
#!/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()
#!/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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#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"
#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"
#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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
python3
import setuptools
setuptools.__version__
python3 import setuptools setuptools.__version__
python3
import setuptools
setuptools.__version__

‘59.6.0’

Instalar o pip

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo apt install python3-pip
sudo apt install python3-pip
sudo apt install python3-pip

depois instale a versão 58.2.0 do setuptools

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
python3 -m pip install setuptools==58.2.0
python3 -m pip install setuptools==58.2.0
python3 -m pip install setuptools==58.2.0

Fontes