ROS Notes

Installing ROS on Raspbian.

To install on Raspbian, use a Raspbian image with ROS pre-loaded.

Installing ROS on Debian

Install ROS. [elaborate on this]

The crossed out ones may have to be built from source as of this writing (see below):

$sudo apt  install ros-melodic-joy ros-melodic-teleop-twist-joy ros-melodic-teleop-twist-keyboard ros-melodic-laser-proc ros-melodic-rgbd-launch ros-melodic-depthimage-to-laserscan ros-melodic-rosserial-arduino ros-melodic-rosserial ros-melodic-rosserial-python ros-melodic-rosserial-server ros-melodic-rosserial-client ros-melodic-rosserial-msgs ros-melodic-amcl ros-melodic-map-server ros-melodic-move-base ros-melodic-urdf ros-melodic-xacro ros-melodic-compressed-image-transport ros-melodic-rqt-image-view ros-melodic-gmapping ros-melodic-navigation ros-melodic-interactive-markers

Additional stuff needed for a custom/generic ROS robot:

$sudo apt install ros-melodic-ros-control ros-melodic-ros-controllers

To download the source code for the above crossed out packages, from within the ./catkin_ws/src directory: (These links were found by referring to the pages for each package on wiki.ros.org)

$ git clone https://github.com/ros-teleop/teleop_twist_joy.git

$ git clone https://github.com/ros-perception/depthimage_to_laserscan.git

$ git clone https://github.com/OpenSLAM-org/openslam_gmapping.git

Finally, build everything we just downloaded from the GitHub by doing this inside /catkin_ws:
$ catkin_make

And then:

$source ~/catkin_ws/devel/setup.bash

We also want to be able to include the ros.h library in arduino sketches in case we want to produce publishers/subscribers on an Arduino-like device. This requires rosserial and rosserial-arduino which were installed above, so now just run $ rosrun rosserial_arduino make_libraries.py <path to arduino “libraries” directory>. So the IDE knows about the rosserial library.

General Notes

ROS gets installed on the robot’s on-board computer (what ROS calls the local install) and the computer used to ssh into it (called the remote install).

Every time we want to use a ROS session we need to overlay the environment on our current BASH session by doing $source /opt/ros/melodic/setup.bash. This also lets us pick which ROS distro we want to work with. If we don’t want to do this every time we can just add this to the .bashrc file. We also need to $source  ~/catkin_ws/devel/setup.bash if we want to refer to any packages we’ve created using catkin. This should be added to .bashrc on both the master and remote machines..

For every ROS session we also need to set the environment variable that specifies the Turtlebot model with $ export TURTLEBOT3_MODEL=waffle_pi, which should also be added .bashrc.

Network Configuration for Multiple Machines

ROS is a distributed computing environment. We expect to be using at least two machines (the robot’s on-board computer, called the local machine, which is set as the “main” one, plus the machine we use to ssh into it, which we confusingly call the remote machine). We can add more machines, typically on-board (local) ones, as needed for computational performance.

-Most obviously, each machine needs to be connected to the same network. In the case of the local machine(s), the network connection must happen automatically on boot. For Raspbian, see the Raspbian notes.

-Naturally, we need to know the hostname for each machine’s OS. Check that one machine can ping the other using $ping [hostname]. Also check that one can communicate over a random port using netcat to set up a basic chat functionality on a port of your choosing (see Debian notes).

-ROS knows the hostname of the machine it’s running on so it doesn’t need to be told this, however every machine needs to know the URI of the “master” machine (the main one on the robot itself). This is done on all machines with $export ROS_MASTER_URI=http://[hostname]:11311. Naturally, this should be added to each machine’s .bashrc. Why port 11311? On the master machine, the default port that roscore is accessed on is 11311.

-The main machine itself also needs to have $ export ROS_HOSTNAME=[hostname] in addition to the $export ROS_MASTER_URI=http://[hostname]:11311 line, which should be added to .bashrc. The .local extension should NOT be added to any of these hostnames. IMPORTANT, do not use “localhost” for the hostname on the master, even though this should work from a networking perspective. Remote machines won’t be able to connect for some reason.

Starting ROS

-Turn on the ROS master computer (probably an SBC).

NOTE: Always be sure you’re on the machine you think you’re on (local or remote). Good to dedicate one workspace to the laptop and one to the Pi/SBC.

-SSH into the master machine (the robot, e.g. ssh pi@rostest, p=ss), run roscore and then use roslaunch to run the bringup script relevant for the master machine. For Turtlebot3 this is $ roslaunch tachi_bringup tachi_robot.launch.

NOTE: Bringup scripts launch all the actual/specific launch scripts for various devices. Instead of running a bringup script you could also run each individual launch script separately, I think? Bringup scripts are located at ~/catkin_ws/src/turtlebot3/turtlebot3_bringup/launch

Also on the master machine, after launching the bringup script, if there are any additional nodes that needs to be run that weren’t included in the bringup, we can run those. For example, serial_node, which lets Arduino-like devices publish and subscribe, can be run with rosrun rosserial_python serial_node.py /dev/ttyUSB0. The USB /dev number might not alsways be ttyUSB0. When it’s running properly, $rosnode list should include a result for /serial_node. NOTE: This is now build into the bringup script for Tachi.

-Next, on the remote machine (the laptop) use roslaunch to run the bringup script relevant to the remote machine. For Tachi this is $ roslaunch tachi_bringup tachi_remote.launch.

From that we should be able to see the output from LIDAR, etc by using rviz (see next section).

TROUBLESHOOTING:

 If an error gets throne in the diagnostic section about something having died, it might be because the wrong /dev port assignment was specified in the bringup script. If the issue is with the LIDAR, go into the LIDAR’s launch script to change the port assignment which is located at ~/catkin_ws/src/hls_lfcd_driver/launch/hlds_laser.launch. There’s also a LIDAR-related launch script in ~/catkin_ws/src/turtlebot3/turtlebot3_bringup/launch. I’m not sure which one is actually used. Maybe both somehow?

If in rviz the LIDAR is giving a transform error like frame [base_scan] not found, the frame that the lidar is set to might need to be modified in ~/catkin_ws/src/turtlebot3/turtlebot3_bringup/launch/turtlebot3_robot.launch. In the argument “set_lidar_frame_id” the default could be changed to “base_footprint”, which rviz seems to expect.

Some Example Commands

NOTE: Each new command that gets run has to go in a new terminal on the remote machine.

-To run the visualization GUI do roslaunch tachi_bringup tachi_model.launch which is actually a “shortcut” to run rosrun rviz rviz -d `rospack find tachi_description`/rviz/model.rviz

-To run a general-purpose GUI frontend to most ROS commands, do rqt. Select “Plugins” to see all options.

-To control movement with the keyboard, do roslaunch tachi_teleop tachi_teleop_key.launch, which will take input from the wasdx keys and publish to /cmd_vel.

-To see data is actually flowing over a topic (like from the LIDAR, which publishes on /scan), do rostopic echo /scan

To kill a specific node or package or whatever it’s called once it’s running, we can do rosnode list to see them all, then rosnode kill [nodename]. For example, this can be used to shut off the LIDAR turret if we won’t be using it.

Navigating ROS

-$rosrun <package> <node>        Run a particular node in a particular package. E.g. $rosrun turtlesim turtlesim_node.

-$roslaunch <package> <filename.launch>        Is used to launch multiple nodes at once. This would be typical for starting a robot, where many nodes need to be started at the same time. The launch file is formatted in XML and is placed in a subdirectory within the <package> directory, but the nodes that the launch file starts don’t necessarily need to be a part of <package>, I think. The subdirectory within the <package> directory should be named “launch” by best practice, but the roslaunch command will find the launch wherever it is.

-$rosnode list        List all currently running nodes.

-$rqt                This is a good one! Run this and select Plugins > Topics > Topic Monitor to select and monitor just about everything (numerically).

-rqt_graph        A node that displays a  flow graph that shows which nodes are publishing and subscribing to which topics. Run using $rosrun rqt_graph rqt_graph.

-rqt_plot        A node that displays a time plot of the data running on a topic. Run using $rosrun rqt_plot rqt_plot. Pick the node of interest after the window opens.

-rqt_console        A node that opens a console window that displays general messages about everything that’s going on. Run with $rosrun rqt_console rqt_console.

-rqt_logger_level        A node that opens a window that changes the kinds of messages displayed over rqt_console. Run with $rosrun rqt_logger_level rqt_logger_level.

-$roswtf         An error analysis program. Run in a separate terminal when ROS is running.

A topic is a pathway for a particular kind of message which is established and stays open, and which nodes can either publish or subscribe to. The rostopic command is used to list, publish, get info on, or whatever else relating to topics:

- $rostopic list         List all the currently running topics. Adding -v to that for verbose mode will also show what type of messages are being sent on that topic, and what nodes are subscribed to that topic.

-$rostopic info <topic>        List everyone who’s publishing and subscribed to the topic.

-$rostopic echo <topic>        Echo the actual data (messages) flowing over the topic.

-$rostopic pub <topic> <type> <args>        Publish a message on a topic that already exists.

        

Examples:

$rostopic pub my_topic std_msgs/String "hello there"

$rostopic pub -r 10 my_topic std_msgs/String "hello there" (publish at 10Hz)

$rostopic pub joint_commands sensor_msgs/JointState '{header: {seq: 0, stamp: {secs: 0, nsecs: 0}, frame_id: ""}, name: ["joint1"], position: [150.0], velocity: [0.0], effort: [0.0]}'

$rostopic pub -r 1 joint_commands sensor_msgs/JointState '{header: {seq: 0, stamp: {secs: 0, nsecs: 0}, frame_id: ""}, name: ["left_wheel", "right_wheel"], position: [150.0, 20.0], velocity: [0.0, 0.0], effort: [0.0, 0.0]}'

A service is a one-off communication between two nodes. One node sends a request and another gives a response. Note that the user at a terminal counts as a node. The rosservice command is used to list, call, get info on, or whatever else relating to services.

-$rosservice list        List all current services.

-$rosservice show        Show the definition of a particular service.

-$rosservice call <service>        Call (that is, run), a particular service. E.g. $rosservice call /clear will clear the turtlesim screen, and $rosservice call /spawn 2 2 .2 “” spawns a new turtle at a specific x, y, theta location.

-etc.

A message (msg) is the information packet that flows over a service or a topic. Messages, like the other things in ROS, have their own command called rosmsg:

-$rosmsg list                List all the message types in use.

-rosmsg show                Show the definition of a particular message.

-etc.

A parameter is a piece of information (an integer, float, boolean, string, whatever) that is stored on the ROS parameter server, which can be looked up at will. Good for storing configuration information (like the color of the turtlesim screen background). The rosparam command is used to deal with all the functions of parameters:

-$rosparam list        List all current parameters.

-$rosparam get <param>         Display the value of a parameter. Instead of a particular parameter we can enter / to show the entire contents of the parameter server.

-$rosparam set <param>        Set the value of a parameter. Must then do $rosservice call /clear.

-$rosparam dump <filename.yaml>        Save everything on the parameter server to a file.

-$rosparam load <filename.yaml>         Load the contents of a YAML file to the parameter server.

ROS packages are installed like normal programs in the Linux file system, but we want to be able to move between these packages easily without having to cd through the normal file system. For this we have rospack.

$rospack is used to get information about packages:

$rospack find <package>        Give the file location of a particular ROS package.

$rospack depends <package>                List all dependencies, including dependencies of dependencies.

 $roscd <package>        Change to the directory of a particular ROS package.

$rosls                 List.

Note: A useful ROS location is log, which is where ROS stores log files. Get to it with $roscd log.

Publishing from an Arduino

Writing the necessary Arduino  code is made doable (and even easy) by rosserial_arduino and is described in http://wiki.ros.org/rosserial_arduino/Tutorials.

To let an Arduino or similar device publish (or subscribe to?) a topic to the whole ROS network, do rosrun rosserial_python serial_node.py /dev/ttyUSB0 on the master machine (with roscore running) using the correct USB port for the device. To have devices always connect to the same USB port so that this can be used consistently, one must set udev rules, somehow.

Creating Packages

Catkin is what we use to create ROS packages. A catkin workspace (where all new packages go) must already exist on the system. To set this up the first time, just  $mkdir -p ~/catkin_ws/src.

To create a package, within ~/catkin_ws/src run:

$catkin_create_pkg <package_name> <depend1> <depend2> <depend3> etc.

I think the boilerplate dependencies are std_msgs, rospy, and roscpp.

To then build the package that’s been created in the catkin workspace run catkin_make within ~/catkin_ws and source setup.bash, but most of the work of developing a new package happens between running catkin_create_package and running catkin_make.

Understanding and Using ros_control

The official documentation on ros_control is terrible and I’ve now committed several complete work days to trying to understand it. Prepare for frustration, disenchantment, and a resolve to ignore the existence of ros_control after all in favor of writing your own code, followed by sincere doubt that ROS in general is really worth the time investment to learn, followed by the ros_control puzzle finally starting to fit together, a little, followed by getting ros_control working with your hardware, maybe. These notes are coming together as I figure it out myself, which is slow going in part because my programming skills are poor. Perhaps this is dumbed down enough to help others understand it faster than me. My explanation:

In ROS, there’s a lot of variation possible in how high-level command messages get relayed to motion hardware. For example, a navigation stack will surely be sending velocity commands over the /cmd_vel topic and will be expecting odometry and tf information on those respective topics. But, if the hardware is a usb device connected to a rosserial node, is the hardware interpreting /cmd_vel directly and moving differential drive wheel motors appropriately? That’s kind of awkward because robot configuration information like wheel size and spacing would have to be done on the hardware itself. And is this same hardware publishing an /odom topic? That’s a pretty resource-intensive calculation for what’s probably an Arduino-like microcontroller. But still, those are viable options. Another option would be to write a custom node to subscribe to /cmd_vel and publish /odom and /tf which can be configured on its own and which would take the processing burden off of the actual hardware. This node could then communicate with the hardware with its own, separate topics, still using the serial node, or possibly more directly with a built-in serial interface, which is probably the more professional way to do it.

But why should one have to write one’s own version of such a node, which has been done countless times already? And what if something a little more advanced is needed, like PID (or just P) control of a motor’s position based on encoder input? The ros_control package attempts to prevent people from reinventing these wheels by providing a set of standard packages (called “controllers”) to do these things. What the user has to do is make sure the ros_control controllers being used are subscribing to the topics of interest, and most importantly (almost all the work), write a so-called “hardware interface”, which is the package that actually directly controls the hardware and which one of the generic ros_controllers connects to. The hardware_interface is the “go between” between the ros_controller and the hardware. It would seem to me that the easiest most dumbed down way to do that would be to use topics there too, so long as the robot isn’t a hard real-time system. So the hardware_interface could have its own separate topics just for a rosserial node and that would work, although it seems annoyingly roundabout.

Note: I find it extremely confusing that the ros_control controllers are called controllers at all, because then what do we call the piece of hardware that actually controls servos or motors? It’s incredibly awkward to talk about hardware motor controllers and ros_control controllers in the same context, which is usually what needs to happen. Between that, the difficulty in setting all this up, and the awful documentation, I wind up with severe doubts about the efficacy of ros_control in general, but there seems to be community buy-in and it does in fact prevent one from reinventing various wheels.

Notes on Topics

There are a few commonly-accepted standard topics for controlling ROS robots, like /cmd_vel, which carries geometry_msgs/Twist messages for driving a robot base, and /odom, which carries nav_msgs/Odometry messages for describing the position of the base. Also /tf is standard for specifying everything about a robots pose. But what about everything else? What about controlling or reading the position of each of the joints in an arm? Or the joints of a head? Apparently these things do not at this time have standard topics, but at least there’s a standard message definition for commanding multiple joints over a single topic (the sensor_msgs/JointState message).

First let’s talk about topics. How should they be named, and should each and every joint have its own topic? You could do it that way, but that’s cumbersome. Standard practice seems to be that each mechanical subsystem of the robot should have a separate topic. For example, an arm with a gripper on the end would get it’s own topic (maybe called /joint_states, which might be a standard topic), carrying JointState messages with command data for an array of joints. A “standard” message that holds an array (e.g. like Int16MultiArray) could also be used.

If on the other hand we have a head with just a single joint, it might get it’s own topic (e.g. head_joint, which I just made up) carrying a “standard” message like Int16 or something. It’s all pretty unstructured.

Defining a Message Type

We can define a new kind of msg (message) by making a msg directory in our package directory and then making a file in it called messagename.msg. A msg file is very simple, and will look something like this:

string name_of_this_string
string name_of_this_other_string
uint8 name_of_this_thing_thats_a_uint8_number
int64 Name_of_this_other_thing_thats_a_int64_number

This defines the structure of the message, to be transmitted either over a topic or a service (I think?).

Next we have to do a few things to be sure the package is configured to actually create this message the next time we build the package using catkin_make:

The first thing is to edit package.xml which is in the package’s directory (e.g. in ~/catkin_ws/src/<package>) to include

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

Note: These are included in package.xml by default but are commented. Also note that at runtime we only need message_runtime and that at build time we only need message_generation.

The next thing is to edit CMakeLists.txt which is also in the package’s directory. We want to add

message_generation

to the find_package call, which is near the top of the file. We also want to uncomment the section that looks like this:

add_message_files(
 FILES
 <name_of_new_message>.msg
)

Changing <name_of_new_message> to be the same as our new msg file, having as many of these as we do new message types.

And we also want to uncomment the section that looks like this:

generate_messages(
 DEPENDENCIES
 std_msgs
)

Finally, we rebuild the package by going into ~/catkin_ws and running $catkin_make, (or catkin_make install if we plan to distribute the package to other people to install without having to deal with the source code.)

Defining a Service Type

The process for doing this is just like defining a new message. First we make a srv directory in our package directory and make a file in it called servicename.srv. An srv file is very simple, and will look something like this:

int64 a
int64 b
---
int64 sum

This defines the structure of the service, where everything before the “---” is a request  and everything after the “---” is a response. Here we can tell just from the structure and the naming that this is probably a service that takes requests messages for integers a and b and returns a message containing their sum. IT’S NOT CLEAR TO ME WHETHER THE MESSAGE FOR THIS NEEDS TO BE DEFINED SEPARATELY, OR JUST DEFINING THIS SERVICE DOES EVERYTHING. I THINK THE LATTER.

Next we need to do everything described above for new messages (uncomment and edit lines in packages.xml and CMakeLists.txt).

The ONE difference from what’s above for message is that in CMakeLists.txt we uncomment

add_service_files(

 FILES

<name_of_new_service>.srv

)

changing <name_of_new_service> to be the same as our new srv file, having as many of these as we do new service types.

Finally, we rebuild the package by going into ~/catkin_ws and running $catkin_make, (or catkin_make install if we plan to distribute the package to other people to install without having to deal with the source code.)

Writing a Publisher

We simply make a scripts directory in our package directory and put a .py file in it. The structure of the python file is simple enough:

#!/usr/bin/env python                        #The python shebang.

import rospy                        #Import the rospy library. This is installed to the python environment automatically when ros is installed.
from std_msgs.msg import String        
#This lets us reuse the std_msgs/String message type (a simple string container) for publishing.

def talker():
   pub = rospy.Publisher('chatter', String, queue_size=10)        
#This declares that this  node is publishing to the chatter topic using the message type String, where String s the thing we imported above from std_msgs.msg.
   rospy.init_node('talker', anonymous=True)                
#This tells ROS the name of our node (talker). Anonymous=True adds random numbers to the end of the node name to ensure it has a unique identifier.
   rate = rospy.Rate(10)                 
        #Sets the broadcast rate in Hertz.
   while not rospy.is_shutdown():                
#Checks for ctrl+C, and executes as long as this command has not been given.
       hello_str = "hello world %s" % rospy.get_time()                
#This is the string we’re going to publish.
        rospy.loginfo(hello_str)                #This does three things: Prints the string to the screen,to this node’s log file, and for debugging via rqt_console or such.
        pub.publish(hello_str)        #This publishes the string over the topic chosen above .
        rate.sleep()                #This is used in conjunction with the rate variable declared above. The sleep will happen just long enough to make the rate frequency be correct.

if __name__ == '__main__':
   try:
       talker()
   except rospy.ROSInterruptException:
       pass

If we run this file directly it will work, but what we really want to do is to build it so that it can be called from within the ROS environment. To do that just run $catkin_make  from within ~/catkin_ws and then $source ~/catkin_ws/devel/setup.bash. If a subscriber is being written too, this step can be done after that.  The publisher can now be run with rosrun <package_name> <publisher_name>.py

Writing a Subscriber

We put another .py file in the scripts directory in our package directory. The structure of the python file is simple enough:

#!/usr/bin/env python                #The python shebang.


import rospy                        
#Import the rospy library. This is installed to the python environment automatically when ros is installed
from std_msgs.msg import String        
#This lets us reuse the std_msgs/String message type (a simple string container) for publishing.

def callback(data):
   rospy.loginfo(rospy.get_caller_id() + 'I heard %s', data.data)

def listener():
   rospy.init_node('listener', anonymous=True)        
#This tells ROS the name of our node (listener). Anonymous=True adds random numbers to the end of the node name to ensure it has a unique identifier.
   rospy.Subscriber('chatter', String, callback)                
#This declares that this node is subscribed to the chatter topic which is of type std_msgs.msgs.String. When new messages are received, the function “callback” is invoked with the message as the first argument.
   rospy.spin()                
#This keeps the node from exiting until it has been shut down.

if __name__ == '__main__':
   listener()

If we run this file directly it will work, but what we really want to do is to build it so that it can be called from within the ROS environment. To do that just run $catkin_make  from within ~/catkin_ws and $source ~/catkin_ws/devel/setup.bash. The subscriber can now be run with rosrun <pacakge_name> <subscriber_name>.py.