[ROS Mini Challenge] #5 – make a robot detect and avoid an obstacle

[ROS Mini Challenge] #5 – make a robot detect and avoid an obstacle

In this post, we will see how to make a robot detect and avoid an obstacle in its front. We will move the robot forward until it detects there’s an obstacle (the wall) closer than 1 meter. Then, we will stop the robot so that it does not collide with the wall.

PS: This ROS project is part of our ROS Mini Challenge series, which gives you an opportunity to win an amazing ROS Developers T-shirt! This challenge is already solved. For updates on future challenges, please stay tuned to our Twitter channel.

Step 1: Grab a copy of the ROS Project that contains the code for the challenge

Click here to get your own copy of the project. If you don’t have an account on the ROS Development Studio, you will need to create one. Once you create an account or log in, we will copy the project to your workspace. That done, open your ROSject using the Open button. This might take a few moments, please be patient.

You should now see a notebook with detailed instructions about the challenge. This post includes a summary of these instructions as well as the solution to the challenge.

PS: Please ignore the Claim your Prize! section because…well…you are late the party 🙂

Step 2: Start the Simulation and get the robot moving

  1. Click on the Simulations menu and then Choose launch file . In the dialog that appears, select rotw5.launch under rosbot_gazebo package. Then click the Launch button. You should see a Gazebo window popup showing the simulation: a ROSbot in front of a wall.
  2. Get the robot moving. Pick a Shell from the Tools menu and run the following commands:
user:~$ source ~/catkin_ws/devel/setup.bash
user:~$ rosrun rotw5_pkg detect_wall.py

Yay, the robot moved. But, wait, it didn’t detect and avoid the obstacle – it crashed into the wall! We don’t want that, so let’s fix it in the next section!

Step 3: Identify the problem, man!

So the robot didn’t stop as we expected. As the challenge hinted, something must be wrong with the code, especially the part that should detect the wall and stop the robot. Let’s see.

Fire up the IDE from the Tools menu and browse to catkin_ws/src/rotw5_pkg/src/detect_wall.py

#! /usr/bin/env python

import rospy
from sensor_msgs.msg import LaserScan
from geometry_msgs.msg import Twist

def callback(msg):

  #If the distance to an obstacle in front of the robot is bigger than 1 meter, the robot will move forward
  if msg.ranges[360] > 1:
      move.linear.x = 0.5
      move.angular.z = 0.0

  #If the distance to an obstacle in front of the robot is smaller than 1 meter, the robot will stop
  if msg.ranges[360] < 1:
      move.linear.x = 0.0
      move.angular.z = 0.0

  pub.publish(move)

rospy.init_node('rotw5_node')
sub = rospy.Subscriber('/scan', LaserScan, callback) #We subscribe to the laser's topic
pub = rospy.Publisher('/cmd_vel', Twist)
move = Twist()

rospy.spin()

Now, this is the part that should stop the robot. It says that if there’s an obstacle less than 1 meter away, we should set both linear and angular velocities to zero (stopping the robot)

  #If the distance to an obstacle in front of the robot is smaller than 1 meter, the robot will stop
  if msg.ranges[360] < 1:
      move.linear.x = 0.0
      move.angular.z = 0.0

This is not being executed obviously because the condition msg.ranges[360] < 1 is never True! Let’s test this assumption:

  • In the IDE, add the following lines to the detect_wall.py, after the comment below. Then save.
  #If the distance to an obstacle in front of the robot is smaller than 1 meter, the robot will stop
  print "Number of readings: ", print len(msg.ranges)
  print "Reading at position 360:", msg.ranges[360]
  • From the Shell: Press Ctrl + C to end the current program. Then stop the robot by publishing zero velocities to the /cmd_vel topic. Press Ctrl + C again and then call the Gazebo service that resets the world.
user:~$ rostopic pub /cmd_vel geometry_msgs/Twist "linear:
  x: 0.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.0"
publishing and latching message. Press ctrl-C to terminate
user:~$ rosservice call /gazebo/reset_world "{}"
  • Run the program again and observe the output:
user:~$ rosrun rotw5_pkg detect_wall.py
Number of readings:  720
Reading at position 360: inf
# ...
Number of readings:  720
Reading at position 360: 11.7952680588

You should notice that several readings at 360 are printed, and none of them is less than 1. Whew!

Step 4: Now let’s solve the problem

We saw from the previous section that our problem was the condition msg.ranges[360] < 1, but how did we arrive at this condition?

  • We knew the laser sensor has 720 scan points stored in the ranges array, as seen in the output of our print statement in Step 3.
  • We assumed that the laser had a range of 180 degrees, covering the front of the robot only, so that position 360 is at the front-middle of the robot. But this is false, and we can confirm this by running the following command:
user:~$ rostopic echo /scan -n1
header:
  seq: 49846
  stamp:
    secs: 639
    nsecs:  47000000
  frame_id: "laser"
angle_min: -3.14159011841
angle_max: 3.14159011841
angle_increment: 0.00873877573758
time_increment: 0.0

The command prints out a single message from the /scan topic. We can deduce the range from the angle_max and angle_min values: angle_max - angle_min = 2pi = 360 deg!  Buff! So position 360/720 of the scan messages could be somewhere at the back of the robot. No wonder it didn’t detect the wall.

Now, we have to figure out which position is the front-middle of the robot. Depending on how the sensor was installed, this could be any position. For this robot, the sensor was installed with position “0″ at the front-center, so the readings of positions “0″ or 720 could represent the front-center.  Let’s use the position “0″  and test again.

  • In the IDE, change the front-middle position from 360 to “0″:
#! /usr/bin/env python

import rospy
from sensor_msgs.msg import LaserScan
from geometry_msgs.msg import Twist

def callback(msg):

#If the distance to an obstacle in front of the robot is bigger than 1 meter, the robot will move forward
  if msg.ranges[0] > 1:
      move.linear.x = 0.5
      move.angular.z = 0.0

#If the distance to an obstacle in front of the robot is smaller than 1 meter, the robot will stop
  print "Number of ranges: ", len(msg.ranges)
  print "Reading at position 0:", msg.ranges[0]
  if msg.ranges[0] < 1:
      move.linear.x = 0.0
      move.angular.z = 0.0

  pub.publish(move)

rospy.init_node('rotw5_node')
sub = rospy.Subscriber('/scan', LaserScan, callback) #We subscribe to the laser's topic
pub = rospy.Publisher('/cmd_vel', Twist)
move = Twist()

rospy.spin()
  • From the Shell: Press Ctrl + C to end the current program. Then stop the robot by publishing zero velocities to the /cmd_vel topic. Press Ctrl + C again and then call the Gazebo service that resets the world.
  • Run the program again and observe the output and see if the robot detects and avoids the obstacle:
user:~$ rosrun rotw5_pkg detect_wall.py
Number of ranges:  720
Reading at position 0: 2.49150013924
Number of ranges:  720
#...
Number of ranges:  720
Reading at position 0: 1.92249274254
#...
Number of ranges:  720
Reading at position 0: 0.991922438145

Yay, finally we are able to make the robot detect and avoid the obstacle!

 

Extra: Video of this post

We made a video showing how this challenge was solved. If you prefer “sights and sounds” to “black and white”, here you go:

Related Resources

Feedback

Did you like this post? Do you have questions about what was explained? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS or ROS2 topics, please let us know in the comments area and we will do a video or post about it.

[ROS Mini Challenge] #3 – make a manipulator robot execute a trajectory

[ROS Mini Challenge] #3 – make a manipulator robot execute a trajectory

In this post, we will see how to configure MoveIt to make a manipulator robot execute a trajectory based on a Position Goal. We send a Position Goal to the manipulator, and the robot computes and executes a trajectory plan in order to reach this goal.

PS: This ROS project is part of our ROS Mini Challenge series, which gives you an opportunity to win an amazing ROS Developers T-shirt! This challenge is already solved. For updates on future challenges, please stay tuned to our Twitter channel.

Step 1: Grab a copy of the ROS Project that contains the simulation

Click here to get your own copy of the project. If you don’t have an account on the ROS Development Studio, you will need to create one. Once you create an account or log in, we will copy the project to your workspace. That done, open your ROSject using the Open button. This might take a few moments, please be patient.

You should now see a notebook with detailed instructions about the challenge. Please ignore the Claim your Prize! section because…well…you are late the party 🙂

Step 2: Start the Simulation and run the MoveIt program to configure the manipulator

  1. Click on the Simulations menu and then Choose launch file . In the dialog that appears, select main.launch from the shadow_gazebo package. Then click the Launch button. You should see a Gazebo window popup showing the simulation: a manipulator robot.
  2. Start the MoveIt package – pick a Shell from the Tools menu and run the following command:
user:~$ roslaunch myrobot_moveit_config myrobot_planning_execution.launch

Once you see the green text that says “You can start planning now”, pick the Graphical Tools app from the Tools menu.

Probably, at first, you will see the window a bit displaced.

 

In order to center it, just click on the Resize opened app button. At the end, you should get something like this:

In the window that appears, go to the Planning tab and then to the Select Goal State dropdown under Query.

 

Select “start” and click Update.

 

Click on the Plan button under Commands in order to calculate a suitable trajectory for reaching the goal position.

Finally, click on the Execute button under Commands in order to execute the position goal (and the plan).

You should see the manipulator on the right moving in order to execute the trajectory, as shown below (in the Graphical Tools and the Gazebo windows).

But this didn’t happen did it? Let’s fix that in the next section!

Step 3: Let’s solve the problem!

What the heck was the problem?! Hmm, I wished I had paid more attention to the Shell…because it left a message for us there, in red letters! Let’s go see it:

[ERROR] [1580760680.575679474]: Unable to identify any set of controllers that can actuate the specified joints: [ elbow_joint shoulder_lift_joint shoulder_pan_joint wrist_1_joint wrist_2_joint wrist_3_joint ]
[ERROR] [1580760680.575917519]: Known controllers and their joints:

[ INFO] [1580760680.671585828]: ABORTED: Solution found but controller failed during execution

We didn’t set up a controller for moving the robot arm. Whew!

We need to examine the package to find the fix, so fire up the IDE from the Tools menu and browse to the catkin_ws/src/myrobot_moveit_config package. In the launch subdirectory, we have a file named smart_grasping_sandbox_moveit_controller_manager.launch.xml:

<launch>
  <rosparam file="$(find myrobot_moveit_config)/config/my_controllers.yaml"/>
  <param name="use_controller_manager" value="false"/>
  <param name="trajectory_execution/execution_duration_monitoring" value="false"/>
  <param name="moveit_controller_manager" value="moveit_simple_controller_manager/MoveItSimpleControllerManager"/>
</launch>

The first line of the configuration above refers to a file my_controllers.yamlfile in the config subdirectory let’s open that file up:

controller_list:
    Hi there!

Huh, we expected this file to list the controllers, but all we get here is “Hi there!”! We don’t need that – so this must be the wrong file! We need to find a file containing the controllers we need, so let’s check out some other files there, for example controllers.yaml:

controller_list:
  - name: arm_controller
    action_ns: follow_joint_trajectory
    type: JointTrajectoryController
    joints:
      - shoulder_pan_joint
      - shoulder_lift_joint
      - elbow_joint
      - wrist_1_joint
      - wrist_2_joint
      - wrist_3_joint
  - name: hand_controller
    action_ns: follow_joint_trajectory
    type: FollowJointTrajectory
    joints:
      - H1_F1J1
      - H1_F1J2
      - H1_F1J3
      - H1_F2J1
      - H1_F2J2
      - H1_F2J3
      - H1_F3J1
      - H1_F3J2
      - H1_F3J3

WOW, this might be what we need (ssh! Top secret: it is!), so let’s copy over the contents of controllers to  my_controllers and save!

Step 4: Test the Fix

Now get back to the Shell. Stop the program we ran in Step 2 using Ctrl + C.

^C[rviz_rosdscomputer_8081_1478977940469499802-4] killing on exit
[move_group-3] killing on exit
[joint_state_publisher-2] killing on exit
[rosout-1] killing on exit
[master] killing on exit
shutting down processing monitor...
... shutting down processing monitor complete
done
user:~$ roslaunch myrobot_moveit_config myrobot_planning_execution.launch

After this, repeat everything in Step 2. Oops, yeah, it didn’t work yet!! Instead, we got another error:

[ERROR] [1580771100.563075907, 98.692000000]: Unable to identify any set of controllers that can actuate the specified joints: [ elbow_joint shoulder_lift_joint shoulder_pan_joint wrist_1_joint wrist_2_joint wrist_3_joint ]
[ERROR] [1580771100.563656482, 98.692000000]: Known controllersand their joints:
controller 'hand_controller' controls joints:
  H1_F1J1
  H1_F1J2
  H1_F1J3
  H1_F2J1
  H1_F2J2
  H1_F2J3
  H1_F3J1
  H1_F3J2
  H1_F3J3

The controllers again! Now let’s examine the error message again.

  • The controllers for the parts that need to move could not be found. But these controllers are listed in the my_controllers.yaml .
  • Some other controllers listed in the same file were found.

Perhaps there’s some misconfiguration – let’s have a second look at this my_controllers.yaml file:

  • We have two controllers: arm_controller and hand_controller.
  • They both have an action_ns called follow_joint_trajectory.
  • But they have different types! They should be the same since they have the same “action namespace”!
  • The type for arm_controller is probably wrong because it’s the one we couldn’t find!

Now, change the type for arm_controller to FollowJointTrajectory and repeat step 2! Now the manipulator should execute the trajectory successfully – you should see the robot arm moving in both the Graphical Tools and the Gazebo windows.

This is one example of how to make a manipulator robot execute a trajectory based on a Position Goal. I hope you found it useful.

Extra: Video of this post

We made a video showing how this challenge was solved. If you prefer “sights and sounds” to “black and white”, here you go:

Related Resources

Feedback

Did you like this post? Do you have questions about what was explained? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS or ROS2 topics, please let us know in the comments area and we will do a video or post about it.

[ROS Mini Challenge] #1 – Make a robot rotate according to user input

[ROS Mini Challenge] #1 – Make a robot rotate according to user input

In this post, we will see how to write a ROS program (in Python) to make a robot rotate according to user input. We are going to fix an error in the code that prevents this program from working as we go on.

PS: This ROS project is part of our ROS Mini Challenge series, which gives you an opportunity to win an amazing ROS Developers T-shirt! This challenge is already solved. For updates on future challenges, please stay tuned to our Twitter channel.

Step 1: Grab a copy of the ROS Project that contains the almost-working Python code

Click here to get your own copy of the project. If you don’t have an account on the ROS Development Studio, you will be asked to create one. Once you create an account or log in, the project will be copied to your workspace. That done, open your ROSject using the Open button. This might take a few moments, please be patient.

You should now see a notebook with detailed instructions about the challenge. Please ignore the section that says “Claim your Prize!” because, well, the challenge is closed already 🙂

Step 2: Start the Simulation and run the program

  1. Click on the Simulations menu and then Choose launch file . In the dialog that appears, select rotw1.launchthen click the Launch button. You should see a Gazebo window popup showing the simulation: a robot standing in front of a very beautiful house!
  2. Keeping the Gazebo window in view, pick a Shell from Tools > Shell and run the following commands (input the same numbers shown):
user:~$ rosrun rotw1_code rotate_robot.py
Enter desired angular speed (degrees): 60
Enter desired angle (degrees): 90
Do you want to rotate clockwise? (y/n): y
[INFO] [1580744469.184313, 1044.133000]: shutdown time! Stop the robot

Did the robot rotate? I would be surprised if it did!

Step 3: Find out what the problem is (was!)

Fire up the IDE and locate the file named rotate_robot.py. You will find in the catkin_ws/src/rotw1_code/src directory.

#!/usr/bin/env python

import rospy
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
import time


class RobotControl():

    def __init__(self):
        rospy.init_node('robot_control_node', anonymous=True)
        self.vel_publisher = rospy.Publisher('/velocity', Twist, queue_size=1)
        self.cmd = Twist()
        self.ctrl_c = False
        self.rate = rospy.Rate(10)
        rospy.on_shutdown(self.shutdownhook)

    def publish_once_in_cmd_vel(self):
        """
        This is because publishing in topics sometimes fails the first time you publish.
        In continuous publishing systems, this is no big deal, but in systems that publish only
        once, it IS very important.
        """
        while not self.ctrl_c:
            connections = self.vel_publisher.get_num_connections()
            if connections > 0:
                self.vel_publisher.publish(self.cmd)
                break
            else:
                self.rate.sleep()

    def shutdownhook(self):
        self.stop_robot()
        self.ctrl_c = True

    def stop_robot(self):
        rospy.loginfo("shutdown time! Stop the robot")
        self.cmd.linear.x = 0.0
        self.cmd.angular.z = 0.0
        self.publish_once_in_cmd_vel()

    def get_inputs_rotate(self):
        self.angular_speed_d = int(
            raw_input('Enter desired angular speed (degrees): '))
        self.angle_d = int(raw_input('Enter desired angle (degrees): '))
        clockwise_yn = raw_input('Do you want to rotate clockwise? (y/n): ')
        if clockwise_yn == "y":
            self.clockwise = True
        if clockwise_yn == "n":
            self.clockwise = False

        return [self.angular_speed_d, self.angle_d]

    def convert_degree_to_rad(self, speed_deg, angle_deg):

        self.angular_speed_r = speed_deg * 3.14 / 180
        self.angle_r = angle_deg * 3.14 / 180
        return [self.angular_speed_r, self.angle_r]

    def rotate(self):

        # Initilize velocities
        self.cmd.linear.x = 0
        self.cmd.linear.y = 0
        self.cmd.linear.z = 0
        self.cmd.angular.x = 0
        self.cmd.angular.y = 0

        # Convert speed and angle to radians
        speed_d, angle_d = self.get_inputs_rotate()
        self.convert_degree_to_rad(speed_d, angle_d)

        # Check the direction of the rotation
        if self.clockwise:
            self.cmd.angular.z = -abs(self.angular_speed_r)
        else:
            self.cmd.angular.z = abs(self.angular_speed_r)

        # t0 is the current time
        t0 = rospy.Time.now().secs

        current_angle = 0

        # loop to publish the velocity estimate, current_distance = velocity * (t1 - t0)
        while (current_angle < self.angle_r):

            # Publish the velocity
            self.vel_publisher.publish(self.cmd)
            # t1 is the current time
            t1 = rospy.Time.now().secs
            # Calculate current angle
            current_angle = self.angular_speed_r * (t1 - t0)
            self.rate.sleep()

        # set velocity to zero to stop the robot
        #self.stop_robot()


if __name__ == '__main__':
    robotcontrol_object = RobotControl()
    try:
        res = robotcontrol_object.rotate()
    except rospy.ROSInterruptException:
        pass

The problem was in the def __init__(self) function – we are publishing to the wrong topic – /velocity – instead of /cmd_vel.

Next, let’s see how we found the problem.

Step 4: Learn how the problem was solved

If you’re familiar with ROS, you’ll know that /cmd_vel is the most common name for the topic responsible for moving the robot. It’s not always that, but at least the /velocity topic instantly became suspect! Therefore, we went ahead to examine it to see if it was a proper topic:

user:~$ rostopic info /velocity
Type: geometry_msgs/Twist

Publishers:
 * /robot_control_node_5744_1580744450546 (http://rosdscomputer:45129/)

Subscribers: None

And our suspicions were confirmed: the topic had no subscribers. This was strange, as usually /gazebo would be one of the subscribers if it was connected to the simulation.

On the other hand, /cmd_vel was connected to /gazebo. So, maybe it’s the right topic after all, but let’s confirm that next.

Step 5: Replace /velocity with /cmd_vel and rotate the robot!

Replace /velocity with /cmd_vel in the def __init__(self) function and save.

Now run the program again:

user:~$ rosrun rotw1_code rotate_robot.py
Enter desired angular speed (degrees): 60
Enter desired angle (degrees): 90
Do you want to rotate clockwise? (y/n): y
[INFO] [1580744469.184313, 1044.133000]: shutdown time! Stop the robot

Now, the robot should rotate according to user input – try different numbers!

And that was it. Hope you found this useful.

Extra: Video of this post

We made a video showing how this challenge was solved. If you prefer “sights and sounds” to “black and white”, here you go:

Related Resources

Feedback

Did you like this post? Do you have questions about what was explained? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS or ROS2 topics, please let us know in the comments area and we will do a video or post about it.

[ROS Beginner Mistakes] – 1. Missing execute permission on the Python file

[ROS Beginner Mistakes] – 1. Missing execute permission on the Python file

In this short post, we will show how to avoid a very common mistake most ROS beginners make: missing execute permission on the Python file.

Step 1: Create a Project (ROSject) on ROSDS

Head to http://rosds.online and create a project with a similar configuration as the one shown below. You can change the details as you like, but please make sure you select “Ubuntu 16.04 + ROS Kinetic + Gazebo 7” under “Configuration”.

Once done with that, open your ROSject. This might take a few moments, please be patient.

Step 2: Create a ROS package with a Python program

Pick a Shell from the Tools menu and create a Python package.

user:~$ cd catkin_ws/
user:~/catkin_ws$ cd src
user:~/catkin_ws/src$ catkin_create_pkg missing_permission
Created file missing_permission/package.xml
Created file missing_permission/CMakeLists.txt
Successfully created files in /home/user/catkin_ws/src/missing_permission. Please adjust the values in package.xml.
user:~/catkin_ws/src$ cd missing_permission/
user:~/catkin_ws/src/missing_permission$ mkdir -p src/
user:~/catkin_ws/src/missing_permission$ cd src
user:~/catkin_ws/src/missing_permission/src$ touch missing_perm.py
user:~/catkin_ws/src/missing_permission/src$

Fire up the IDE from the Tools menu, find the missing_perm.py file in the missing_permission package, open the file and paste the following code into it.

#! /usr/bin/env python
import rospy

rospy.init_node("Obiwan")
rate = rospy.Rate(2)
while not rospy.is_shutdown():
    print "Help me Obi-Wan Kenobi, you're my only hope"
    rate.sleep()

Save().

Step 3: Compile and source the workspace.

cd ~/catkin_ws
catkin_make
source devel/setup.bash

Step 4: Run the package with rosrun and roslaunch.

First, we check that the package has been recognized, with rospack list. Then we run it with rosrun.

user:~/catkin_ws$ rospack list | grep missing
missing_permission /home/user/catkin_ws/src/missing_permission
user:~/catkin_ws$ rosrun missing_permission missing_perm.py
[rosrun] Couldn't find executable named missing_perm.py below /home/user/catkin_ws/src/missing_permission
[rosrun] Found the following, but they're either not files,
[rosrun] or not executable:
[rosrun]   /home/user/catkin_ws/src/missing_permission/src/missing_perm.py
user:~/catkin_ws$

The program did not run! What’s that error? Surely that not what we expected. Maybe rosrun does not like us – let’s try roslaunch!

Create a launch file and use it to launch the python program:

user:~/catkin_ws$ cd src/missing_permission/
user:~/catkin_ws/src/missing_permission$ mkdir -p launch
user:~/catkin_ws/src/missing_permission$ cd launch
user:~/catkin_ws/src/missing_permission/launch$ touch missing_perm.launch
user:~/catkin_ws/src/missing_permission$

Open the launch file in the IDE and paste in the following code:

<launch>
    <node name="missing_permission_ex" pkg="missing_permission" type="missing_perm.py" output="screen" />
</launch>

Launch!

user:~/catkin_ws/src/missing_permission$ cd ~/catkin_ws
user:~/catkin_ws$ source devel/setup.bash
user:~/catkin_ws$ roslaunch missing_permission missing_perm.launch
... logging to /home/user/.ros/log/e3188a2e-e921-11e9-8ac1-025ee6e69cec/roslaunch-rosdscomputer-11105.log
...

NODES
  /
    missing_permission_ex (missing_permission/missing_perm.py)

auto-starting new master
process[master]: started with pid [11147]
ROS_MASTER_URI=http://master:11311

setting /run_id to e3188a2e-e921-11e9-8ac1-025ee6e69cec
process[rosout-1]: started with pid [11170]
started core service [/rosout]
ERROR: cannot launch node of type [missing_permission/missing_perm.py]: can't locate node [missing_perm.py] in package [missing_permission]

Oops! It didn’t run again. Now we have another error screaming:

ERROR: cannot launch node of type [missing_permission/missing_perm.py]: can't locate node [missing_perm.py] in package [missing_permission]

What do we do now?

Step 5: Fix the missing execute permission on the Python file and be happy!

user:~/catkin_ws$ cd src/missing_permission/src/
user:~/catkin_ws/src/missing_permission/src$ chmod +x missing_perm.py
user:~/catkin_ws/src/missing_permission/src$

Now let’s try again with both roslaunch and rosrun:

user:~/catkin_ws/src/missing_permission/src$ roslaunch missing_permission missing_perm.launch
...

NODES
  /
    missing_permission_ex (missing_permission/missing_perm.py)

auto-starting new master
process[master]: started with pid [13206]
ROS_MASTER_URI=http://master:11311

setting /run_id to eec6e306-e922-11e9-b72a-025ee6e69cec
process[rosout-1]: started with pid [13229]
started core service [/rosout]
process[missing_permission_ex-2]: started with pid [13242]
Help me Obi-Wan Kenobi, you're my only hope
Help me Obi-Wan Kenobi, you're my only hope
Help me Obi-Wan Kenobi, you're my only hope
...

Running fine with roslaunch. Press Ctrl + C on the roslaunch program and try rosrun:

user:~/catkin_ws/src/missing_permission/src$ rosrun missing_permission missing_perm.py
Unable to register with master node [http://master:11311]: master may not be running yet. Will keep trying.

If you get the error above, please spin another Shell from the Tools menu, and run the following to start the ROS master:

user:~$ roscore

After this, the roscore command should resume and you’ll get:

user:~/catkin_ws/src/missing_permission/src$ rosrun missing_permission missing_perm.py
Unable to register with master node [http://master:11311]: master may not be running yet. Will keep trying.
Help me Obi-Wan Kenobi, you're my only hope
Help me Obi-Wan Kenobi, you're my only hope
Help me Obi-Wan Kenobi, you're my only hope
...

And that was it. As it turned out, roscore had no ill-feeling towards us!

Related Resources

Feedback

Did you like this post? Do you have questions about what was explained? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS or ROS2 topics, please let us know in the comments area and we will do a video or post about it 🙂

 

How to create & test a Publisher in ROS2 (Python)

How to create & test a Publisher in ROS2 (Python)

Hello ROS developers! In this post lets’ see how to create and test a publisher in ROS2 using Python (rclpy).

I break it down into “5 easy steps”, let’s see one by one:

  1. Create a Python-based package in ROS2.
  2. Create the python code for your publisher.
  3. Edit package.xml.
  4. Replace CMakeLists.txt with setup.py.
  5. Run and test your python code in ROS2.

Let’s go!

1. Create a Python-based package in ROS2

At this point, I’m taking for granted that you already have the following in place:

  • Your ROS2 is installed and running. (Don’t have this? No issues, you can spin a free ROS2 development environment at ROSDS. No installation required, just a few clicks and you will have a fully-functional ROS2 installation within your browser.)
  • You have created a ROS2 workspace. Need help on this? See this tutorial.

Once you have crossed the two bridges above, you just need to run this command from the src folder of your ROS2 workspace:

~/ros2_ws/src$ ros2 pkg create ro2_pub_py

Here the name of the package is ros2_pub_py, and our workspace directory is ros2_ws. Feel free to name yours differently.

The final structure of your project should look something like this (when you have completed all steps):

Easy Step 1 done!

2. Create the python code for your publisher

Yes, you guessed right, we must write the publisher in Python, since this post says “using Python”. ROS2 official repo provides 3 different ways with examples on how to write the Python code.

In this post, we will use the “local function” method because…I like it and get to decide here :)…kidding…I think it’s still somewhat ROS1-like while showcasing the differences in ROS2. Take home bonus: try ‘translating’ the code into the “old school” and “member function” variants.

Enough small talk, let’s do the work! Create Python file located at `~/ros2_ws/src/ro2_pub_py/sos_publisher.py`. Here’s the code:

# Copyright 2016 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import rclpy

from std_msgs.msg import String


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

    node = rclpy.create_node('sos_publisher')
    publisher = node.create_publisher(String, 'sos')

    msg = String()
    
    def timer_callback():
        msg.data = 'ObiWan Kenobi, please help me. You\'re my only hope'
        node.get_logger().info('Publishing sos message: "%s"' % msg.data)
        publisher.publish(msg)

    timer_period = 0.5  # seconds
    timer = node.create_timer(timer_period, timer_callback)

    rclpy.spin(node)

    # Destroy the timer attached to the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    node.destroy_timer(timer)
    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

A few notes about the code above:

  • The statement import rclpy imports the ROS2 Python module. This is equivalent to import rospy in ROS1.
  • The statement from std_msgs import String imports the type of message the publisher uses. Basically no change from ROS1.
  • The statements from rclpy.init(args=args) to publisher = node.create_publisher(String, 'sos')  initialize the ROS2 Python module and create a node sos_publisher publishing to the topic sos. Did you notice the subtle differences in how this is achieved in ROS1?
  • The variable timer provides similar functionality as rospy.Rate() in ROS1, providing the frequency at which a particular code block is repeated.

Next!

3. Edit package.xml

Actually, this step is technically part of creating the Python package, because the changes we are doing here is just indicating that it’s a Python package. But I wanted to call this out separately because I think it’s an important step.

Now, the original package.xml should look something like this after creating the package:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="2">
  <name>ros2_pub_py</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="user@todo.todo">user</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

Change to the following, following the example given in the official repo:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="2">
  <name>ros2_pub_py</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="user@todo.todo">user</maintainer>
  <license>Apache License 2.0</license>

  <exec_depend>rclpy</exec_depend>
  <exec_depend>std_msgs</exec_depend>

  <!-- These test dependencies are optional
  Their purpose is to make sure that the code passes the linters -->
  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <export>
    <build_type>ament_python</build_type>
  </export>
</package>

You can spot the differences, right? Great, next step!

4. Replace CMakeLists.txt with setup.py

Similar to step 3 above, this is also something that’s related to package creation that I decided to call out separately. These steps are necessary because as of now ROS2 package creator does not yet support Python-based packages out of the box (the package.xml and CMakeList.txt are created for a C-based package by default). Hopefully, a future release would make these steps unnecessary.

Not in some many words:

  • Delete CMakeList.txt; we don’t need it here.
  • Add a setup.py properly configured for the package and python program.

Here you go, following the official example:

from setuptools import setup

package_name = 'ros2_pub_py'

setup(
    name=package_name,
    version='0.0.0',
    packages=[],
    py_modules=[
        'sos_publisher',
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    author='user',
    author_email="user@todo.todo",
    maintainer='user',
    maintainer_email="user@todo.todo",
    keywords=['ROS', 'ROS2'],
    classifiers=[
        'Intended Audience :: Developers',
        'License :: OSI Approved :: Apache Software License',
        'Programming Language :: Python',
        'Topic :: Software Development',
    ],
    description='TODO: Package description.',
    license='Apache License, Version 2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'sos_publisher = sos_publisher:main',
        ],
    },
)

Also, add a setup.cfg file to the same location as setup.py:

[develop]
script-dir=$base/lib/ros2_pub_py
[install]
install-scripts=$base/lib/ros2_pub_py

Done here. Last step!

5. Run and test your python code in ROS2

Now time to ros2 run your code, but not so fast! Before we become ros2-run-happy, we need to take care of some basic things so that we don’t have bad surprises.

In your ROS2 workspace directory, run:

~/ros2_ws$ colcon build

Yes, we need to rebuild the package, after all those changes.

Also, we need to run:

~/ros2_ws$ source install/setup.bash && source install/local_setup.bash

And then you’re ready for ros2-run:

ros2 run ros2_pub_py sos_publisher

You should see output similar to:

~/ros2_ws$ ros2 run ros2_pub_py sos_publisher
[INFO] [sos_publisher]: Publishing sos message: "ObiWan Kenobi, please help me. You're my only hope"
[INFO] [sos_publisher]: Publishing sos message: "ObiWan Kenobi, please help me. You're my only hope"
[INFO] [sos_publisher]: Publishing sos message: "ObiWan Kenobi, please help me. You're my only hope"
[INFO] [sos_publisher]: Publishing sos message: "ObiWan Kenobi, please help me. You're my only hope"
...

From another terminal, run the following commands and check the output:

~$ ros2 node list
/sos_publisher
~$ ros2 topic list
/parameter_events
/sos
~$ ros2 topic echo /sos
data: ObiWan Kenobi, please help me. You're my only hope

data: ObiWan Kenobi, please help me. You're my only hope

data: ObiWan Kenobi, please help me. You're my only hope

And we’re all done!

Final take away

I hope you found this post useful. I would suggest that you try following the post and creating your package from scratch, but in case you want to have a sneak-peek at the code used for this post, you can find it here: https://rds.theconstructsim.com/r/bayodesegun/ros2_demo_4698/.

Keep pushing your ROS2 learning!


ROS2 basics course

ROS2 basics course

Pin It on Pinterest