Introduction
This guide will walk you through the steps I took for setting up communication between ROS, code that reads from a sensor array, and the Flexiv robot arm.
Basic Information: Publishers and Subscribers
Publishers and subscribers are the bread and butter of ROS programming. A publisher is any node (a ROS way of saying program or device) that actively outputs information. A subscriber is any node that actively takes in that data type, regardless of if it actually does anything with it. You can always have a publisher with no subscriber, it will simply put out information with nothing to listen to it. You cannot, however, have a listener with no publisher. In order for any type of communication in the ROS environment, one must always create a publisher and subscriber node. Later on, we will be using rostopic node in order to see our active nodes, which will allow us to see our publisher data.
Getting Started: msg (message) types
Before starting on any ROS project it is important to know what kind of message you will be publishing. Publishers and subscribers can only communicate with each other if they have both been declared to be using the same data type, i.e. a subscriber that is expecting a string from a publisher will always return an error message if the publisher is sending out something else (integers, floats, arrays, etc.). The ROS library std_msgs (standard messages) has a number of very basic data types all of which can be found here. These are very basic data types such as strings, integers, and booleans. Oftentimes, however, you will need to create your own data type.
Custom msg types
In order to create your own custom message types, you will need to create a .msg file. From your package (if you do not have a workspace or package yet learn more about creating one here), use the following command in the terminal:
mkdir msg
Now navigate into the new directory and create your new .msg file for whatever new data type you want to create. For my project, I created the file ar64.msg, which is a data type that allows for arrays of 64 bit floats.
The notation of a .msg file is as follows (italicized and bolded for aesthetic purposes) :
data type name
data type name
Where data type is your chosen data type, in my case it was an array of 64 bit floats float64[ ] I created three of these in order to interpret data from the x, y, and z axes. It looked like this:
float64[ ] x
float64[ ] y
float64[ ] z
This allows for me to pass in 3 arrays with every published message. We will talk about how to access each data type (x,y,z) later. It’s also noteworthy that you can use 3 different data types for one type of custom message. If you want to pass in a string, integer, and array of floats that is entirely possible.
Building Your Custom msg
In order to have your custom msg available to you, you must change lines in the package.xml and CMakelists.txt files.
In the package.xml file make sure these lines are written and uncommented:
<build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>
The CMakelists.txt file must next be edited. Edit it as so: Within find_package, add message_generation (do not simply copy paste this just anywhere in the file, simply add message_generation)
Do not just add this to your CMakeLists.txt, modify the existing text to add message_generation before the closing parenthesis
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation )
Within catkin_package add CATKIN_DEPENDS message_runtime:
catkin_package( ... CATKIN_DEPENDS message_runtime ... ...)
Next uncomment the following lines of code:
# add_message_files( # FILES # Message1.msg # Message2.msg # )
Replace the .msg files present with the new one you would like to add, in my case it looks like this:
add_message_files( FILES ar64.msg )
Finally, uncomment the following:
# generate_messages( # DEPENDENCIES # std_msgs # )
Now you should be able to see your msg in ROS! Source your devel/setup file again, then use the command rosmsg show msg in order to see if your message is recognized. In my case it looks like this:
rosmsg show ar64
It should return your file contents and data types. Now you must build your workspace using catkin_make or catkin_build depending on your setup, and your new message type is ready to be used with a subscriber or publisher.
Writing Your First Publisher
Example code for the publisher looks like this:
#!/usr/bin/env python
# license removed for brevity
import rospy
from std_msgs.msg import String
def talker():
pub = rospy.Publisher('chatter', String, queue_size=10)
rospy.init_node('talker', anonymous=True)
rate = rospy.Rate(10) # 10hz
while not rospy.is_shutdown():
hello_str = "hello world rospy.get_time()
rospy.loginfo(hello_str)
pub.publish(hello_str)
rate.sleep()
if __name__ == _main_:
try:
talker()
except rospy.ROSInterruptException:
pass
Let’s break it down below.
#!/usr/bin/env python import rospy
These lines should always be present at the top of any ROS python file.
from std_msgs.msg import String
This is where we import our msg type. This is always necessary to tell the publisher what data type we will be putting out. This code is using a standard data type from the std_msgs library. If you were to use a different data type you would simply insert a different data type after import. If you want to import multiple from std_msgs, you separate each data type by commas after import.
If you want to use your own custom data type (if you created one above) you would do so with the following notation:
from yourpackagename.msg import custom_msg_name
For me it looked like this:
from taxilearraytest.msg import ar64 (taxilearraytest is the name of my package) pub = rospy.Publisher('chatter', String, queue_size=10) rospy.init_node('talker', anonymous=True)
These lines are where you actually create your publisher. Notice the notation for the publisher:
publishervar = rospy.Publisher(topic name, data_type, queue_size=int)
Here is where you use your msg type. This is using any msg type you imported at the top. In this example, it’s String. In my code, it is ar64. In this case the node is named talker which is the name you will see in the node list, that’s how you know that your publisher is active. It will publish to a topic named chatter. A topic is how you see your data, it’s the middle ground between a publisher and a subscriber.
rate = rospy.Rate(10) # 10hz rate.sleep()
These two lines are responsible for the rate at which your publisher puts out information. rate.sleep() should always be at the end of your publishing loop, it tells the publisher to wait until the next time step. In this case, after the publisher has put out the information it will wait 10hz before publishing again.
pub.publish(hello_str)
This is the line responsible for publishing your data to the topic. By calling this, you are putting out your current data into the topic. For this example, it is simply the text “hello world”. If you are using a different msg type, you will have the option to use the different properties you created. Using my ar64 data type example, I will add more lines before the publish function in order to set the different data. It looks like this:
ar64.x = array ar64.y = array ar64.z = array pub.publish(ar64)
This allows me to set my properties, then publish the entire package with all properties at the same time.
if __name__ == _main_: try: talker() except rospy.ROSInterruptException: pass
This is where your main call comes from. This block of code will call your publisher function given that your ROS setup is good, which will continue until given an interrupt command (control + C in the terminal on many devices).
Testing Your Publisher
Once you exit your editor, you need to run chmod +x yourscript.py. Make sure to go back to your home workspace and catkin_build or catkin_make. Now source your setup files and use rosrun to run your publisher. The notation is rosrun packagename script.py. In my case, the command would be rosrun taxilearraytest taxilepublisher.py. Your publisher should now be running.
In order to make sure our publisher is working we will use the rostopic function. By running rostopic list you should be able to see your topic in the list. Furthermore, by using rostopic echo yourtopic, you should be able to have the terminal repeat anything being published.
Writing Your Listener
Example code for a listener looks like this:
#!/usr/bin/env python import rospy from std_msgs.msg import String def callback(data): rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data) def listener(): # In ROS, nodes are uniquely named. If two nodes with the same # name are launched, the previous one is kicked off. The # anonymous=True flag means that rospy will choose a unique # name for our 'listener' node so that multiple listeners can # run simultaneously. rospy.init_node('listener', anonymous=True) rospy.Subscriber("chatter", String, callback) # spin() simply keeps python from exiting until this node is stopped rospy.spin() if __name__ == _main_: listener()
Let’s break down the code below. The top portion of the code is the same, we add our necessary imports and lines for implementation.
rospy.init_node('listener', anonymous=True) rospy.Subscriber("chatter", String, callback)
Once again, these two lines are creating our subscriber node. The difference here is that you do not create a new topic you are publishing to, but instead declaring which existing topic you are subscribing to. As such, the topic input for the subscriber must exactly match your publisher’s. In this example, our publisher node publishes to chatter, our subscriber subscribes to chatter. Likewise, our data type matches the data type of our publisher. In order for your publisher and subscriber to communicate, you declare both to the same topic and declare that both will be communicating the same information. Finally, the final argument in the subscriber function is another function that the data can be passed into. In this case it’s the callback function, which is simply repeating the data onto the terminal.
def callback(data): rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
Notice the data.data variable. For almost any std_msg data type, .data will return whatever is being read in. For custom msg types, you cannot use .data unless it is the name of one of your declared data types. In my case, using ar64.data would return an error. Using ar64.x ar64.y or ar64.z would return my data.
Using Your Listener
Once again use the chmod +x yourscript.py and return to your catkin workspace and use the build/make function. Now use rosrun again using your new listener script. If your publisher is also running, you should see your values being repeated. Your publisher and subscriber should now be working.
Use With My Code
Hojung and Dane provided me with a script that is used to read in data from a sensor array that measures force in the x, y, and z directions. By modifying the script I was able to create a publisher that published the data to the sensor_data topic. Hojung’s original output was in the form of a multidimensional array. Using a multidimensional NumPy array is complicated in ROS, so I created my own msg type ar64 as presented above that took in 3 arrays of 64 bit floats for each axis. I declare the ar64 data type under the variable name msg by using msg = ar64(). I then copied each column of the array into the ar64.x ar64.y and ar64.z fields and published that into the topic.
I then modified Michael’s Flexiv code in order to use the already existing robot functions in order to control the robot. I added my own listener to the file using the same ar64 msg type using the same standard listener function. NOTE: you must have the same ar64 msg file as well as edit the CMakelists.txt and package.xml files in order to use the data type.
I then tested threshold values for the sum of each of the 3 axes. I tested both shear and normal force values and used them to create if-statements in the main Flexiv control function in order to move the robot in planar motion using each of the different values. The top half Z input causes the robot to move up, the bottom causes it to move down. Shear in the negative x direction causes it to move away from the user, the opposite moves it toward the user. Shear in the positive y direction causes the robot to move away from the user, the opposite moves it toward the user.
To see the code for the publisher navigate to:
~/TM_catkinws/src/taxilearraytest/src/taxilepublisher.py
To see the code for the listener navigate to:
~/ml_ws/flexiv/catkin_ws/src/flexiv/src/cartesianimpedancecontrol.py