Site icon AranaCorp

Creating a Python script under ROS2

In this tutorial, we’ll look at how to create and run Python scripts under ROS2. This will enable you to create your own nodes and start developing under ROS.

Create a workspace

A good practice when developing under ROS2 is to create workspaces in which to install the desired packages, separate from the main installation.

Colcon installation

sudo apt install python3-colcon-common-extensions

Create folder

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

Then copy a sample project, such as tutorial containing turtlesim

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

Then build the project using the command

colcon build

To load your workspace into a new terminal

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

N.B.: you can put the following commands in a ros_profile for the source in a single “source ros_profile” command

Creating a python package

ROS2 offers a simple tool for creating the file architecture of a Python or C++ package. You must specify at least the package name (my_package). You can also specify the node name.

In the ~/ros2_sw/src directory, enter the following command

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

the python script is now in the directory ~/ros2_ws/src/my_package/my_package

Once the package has been created, you can add as many nodes as you like to this folder.

You’ll need to update the setup.py and package.xml files with dependencies and entry points

Installing dependencies

Before compiling the project, it is advisable to resolve the dependencies.

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

Compiling the project

It is possible to compile the entire project or to select a particular package.

colcon build --packages_select my_package --symlink-install

N.B.: symlink-install avoids recompiling every time you change the Python script.

Launch node

Once the package has been compiled, you can launch the node with the command

ros2 run my_package my_node

The basic code when creating a node

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

if __name__ == '__main__':
    main()

At launch, this code will simply display the text of the print

Creating a Publisher and Subscriber for Turtlesim

In this example, we’ll see how to create a package with two nodes, one controlling the turtle, the other reading the position.

Package creation

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

Here’s the desired file architecture

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

Add Python scripts

Import the type of message to be published Twist for turtle speed control

We connect to the topic by specifying the name (‘/turtle1/cmd_vel’) and the data type (Twist)

We create a timer that will allow us to publish regularly on the topic (create_timer).

Finally, we publish the line and angular velocity

#!/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()

Import the type of message to be published Pose

We connect to the topic by specifying the name (‘/turtle1/pose’) and the data type (Pose)

We write the listening function that runs when new data is available on the topic

Create a timer to display values at the desired frequency

Finally, we publish the line and angular velocity

#!/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()

Modify setup.py

In the setup.py entry points, you must specify the node names and the function to be launched (main)

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

Compiling the package

colcon build --packages_select projects --symlink-install

Launch nodes

To launch a node in a terminal, you must source the project

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

In three different terminals, run the following commands

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

Results

You can see the turtle describe the shape of a circle and its position evolve in the subscriber’s center.

Launching a node with arguments

Nodes can be configured at runtime. In this example, we’ll define input parameters for linear and angular velocity. To do this, we’ll modify the code in the above Advertiser

In node initialization, we declare parameters with default values

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

We can then retrieve the values of these parameters and store them in variables that we use in the callback function

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

You can now launch a node with and without the

ros2 run projects circle_turtle #default param
ros2 run projects circle_turtle --ros-args -p linx:=0.5 -p angz:=1.0

Complete code

#!/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()

Open the various terminals with a script

To save time during development, you can open terminals and launch nodes from a single bash script

#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"

Troubleshoot

Check setuptools version

python3
import setuptools
setuptools.__version__

‘59.6.0’

Install pip

sudo apt install python3-pip

then install version 58.2.0 of setuptools

python3 -m pip install setuptools==58.2.0

Sources

Exit mobile version