diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 3f4bea5..86dfe2d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -2,6 +2,10 @@ - [Introduction](./intro.md) - [ROS 2](./ros2.md) + - [ROS Resources](./ros2_tools_resources.md) + - [ROS Concepts and Design Patterns](./ros2_design_patterns.md) + - [The ROS Command Line Interface](./ros2_cli.md) + - [The ROS API](./ros2_api.md) - [Traffic Editor](./traffic-editor.md) - [Simulation](./simulation.md) - [RMF Core](./rmf-core.md) diff --git a/src/images/Screenshot from 2020-05-21 10-48-43.png b/src/images/Screenshot from 2020-05-21 10-48-43.png new file mode 100644 index 0000000..e124683 Binary files /dev/null and b/src/images/Screenshot from 2020-05-21 10-48-43.png differ diff --git a/src/images/four_turtles.png b/src/images/four_turtles.png new file mode 100644 index 0000000..1cb2b2c Binary files /dev/null and b/src/images/four_turtles.png differ diff --git a/src/images/jfk.jpg b/src/images/jfk.jpg new file mode 100644 index 0000000..8684b31 Binary files /dev/null and b/src/images/jfk.jpg differ diff --git a/src/images/larry.png b/src/images/larry.png new file mode 100644 index 0000000..759c107 Binary files /dev/null and b/src/images/larry.png differ diff --git a/src/images/node_info.png b/src/images/node_info.png new file mode 100644 index 0000000..bc7a8af Binary files /dev/null and b/src/images/node_info.png differ diff --git a/src/images/reset_service.png b/src/images/reset_service.png new file mode 100644 index 0000000..84f0d46 Binary files /dev/null and b/src/images/reset_service.png differ diff --git a/src/images/ros_graph_example.png b/src/images/ros_graph_example.png new file mode 100644 index 0000000..58a93f2 Binary files /dev/null and b/src/images/ros_graph_example.png differ diff --git a/src/images/rqt.png b/src/images/rqt.png new file mode 100644 index 0000000..20e81a7 Binary files /dev/null and b/src/images/rqt.png differ diff --git a/src/images/rqt_graph.png b/src/images/rqt_graph.png new file mode 100644 index 0000000..f163438 Binary files /dev/null and b/src/images/rqt_graph.png differ diff --git a/src/images/rqt_start.png b/src/images/rqt_start.png new file mode 100644 index 0000000..6549105 Binary files /dev/null and b/src/images/rqt_start.png differ diff --git a/src/images/turtle_after.png b/src/images/turtle_after.png new file mode 100644 index 0000000..0960c85 Binary files /dev/null and b/src/images/turtle_after.png differ diff --git a/src/images/turtle_before.png b/src/images/turtle_before.png new file mode 100644 index 0000000..e53e00b Binary files /dev/null and b/src/images/turtle_before.png differ diff --git a/src/images/turtlesim_square.png b/src/images/turtlesim_square.png new file mode 100644 index 0000000..0f05d52 Binary files /dev/null and b/src/images/turtlesim_square.png differ diff --git a/src/images/turtlesim_start.png b/src/images/turtlesim_start.png new file mode 100644 index 0000000..68b4d11 Binary files /dev/null and b/src/images/turtlesim_start.png differ diff --git a/src/ros2.md b/src/ros2.md index 1d40259..7547242 100644 --- a/src/ros2.md +++ b/src/ros2.md @@ -1,3 +1,53 @@ -# ROS 2 +# An Introduction to ROS 2 -Herein we shall discuss Deep Thoughts about ROS 2 +In this chapter we will cover the basics of Robot Operating System (ROS) and +give you all the tools you need to build, debug, and understand robotic +applications. This chapter is laid out from the most general concepts, necessary +for decision makers to make sound decisions, to specific API references needed +by engineers to develop new robotic applications. Somewhere in between high +level concepts, and low level API commands lives the knowledge necessary for +those maintaining and supporting multi-robot deployments in the field. + +A good analogy to learning about ROS is the process of learning about motor +vehicles. At the practical, day-to-day level, most people will learn how to +start a vehicle and safely use it on a motorway. For these individuals, learning +about the high level concepts behind ROS, along with application-specific +commands is probably sufficient. Those who enjoy driving often choose to learn +how to repair and maintain their vehicle. If this is your interest level we +recommend learning the basics of the ROS command line interface. This will allow +you to "check the oil" of your robotic systems and make sure everything is +functioning correctly. Finally, if you are the type that would like to swap out +the engine of your vehicle with something more powerful, or potentially build a +wholly new vehicle from scratch, then the ROS API is the set of tools that will +make this possible. Generally speaking, automotive engineers don't appear into +the world fully formed, and the same is true for roboticists. It is advisable to +work through each phase of understanding as you develop your skills with ROS. + +Following from our analogy above the process of learning how to use robotic +systems built on ROS can be divided roughly into four parts. This chapter works +through these four parts of the process, using ROS 2. Subsequent chapters then +build upon this knowledge and discuss the subtleties of specific +applications. The four parts of this chapter are as follows. + +* Meta-discussion of the tools and resources available to help you in the + learning process. + +* A high level discussion to the design patterns use in ROS. These patterns are + roughly analogous to the subsystems you would find in a vehicle (engine, + brakes, safety, climate control, etc). + +* A treatment of the command line interface (CLI) for ROS. The CLI is a set of + programs for starting, inspecting, controlling, and monitoring a ROS + robot. You can think of this topic as teaching you how check a robot's oil, + and read the instrument panel. + +* An introduction to the ROS application programming interface. This section + will show you how to create your own applications and modify existing software + for your specific application. + +While this books aims to cover the basics it should be made clear that ROS, like +almost all software is a moving target. Technology moves quickly, and while +print media is helpful and delivering high fidelity instruction, that +instruction can become rapidly outdated. For this reason we start this chapter +with a meta-discussion of ROS resources that can be used to help you in your +learning process. diff --git a/src/ros2_api.md b/src/ros2_api.md new file mode 100644 index 0000000..86d67c3 --- /dev/null +++ b/src/ros2_api.md @@ -0,0 +1,5 @@ +# The ROS API + +This section will show you how to create your own applications and modify +existing software for your specific application. + diff --git a/src/ros2_cli.md b/src/ros2_cli.md new file mode 100644 index 0000000..134ef80 --- /dev/null +++ b/src/ros2_cli.md @@ -0,0 +1,1387 @@ +# The ROS Command Line Interface + +The ROS command line interface, or CLI for short, is a set of programs for +starting, inspecting, controlling, and monitoring a ROS robot. The best way to +think of the CLI is a collection of small and simple programs that allow you +perform basic tasks in ROS. Drawing from our car analogy, the CLI can be thought +of as the subsystems of a vehicle, the breaks, the transmission, the window +wipers; all of the smaller parts that are composed together to build the larger +vehicle. What we'll show you in this section is how to turn on the car, put it +gear, turn on the radio, and perhaps check your oil to perform routine +maintenance. The ROS 2 CLI draws heavily from the Unix/Linux philosophy of small +programs that can be composed together. If you are familiar with the command +line interface found in Unix and Linux, or to a lesser extent in MacOS or +Windows you'll feel right at home. + +The ROS command line tools draw heavily from the design patterns mentioned in the +previous section, and directly interface with the APIs we will treat in the next +section. The CLI interface is at its core just a set of simple tools built from +the ROS 2 API, this API is simply an implementation of the high-level patterns +we discussed in the previous section. If your goal is to simply interface with a +particular piece of software written using ROS, the CLI interface is the way you +will go about starting, stopping, and controlling the underlying ROS +software. For more advanced users these tools will allow you to study a ROS +system by exploring the underlying software processes in the system. + +There are only two things you need to memorize from this section. It is actually +quite an amazing feat; everything you need to know about the ROS 2 +CLI can be derived from just two simple commands. From these two +commands you can figure out everything else fairly quickly! Are you ready to +learn the two magic commands? The first command simply tells your computer that +you are using ROS, and what version of ROS you want to use. Let's take a look at +the magic command, you've actually already seen it before: + +``` {.sourceCode .bash} +source /opt/ros/eloquent/setup.bash +``` + +If everything is working correctly this command should simply return. Nothing +happens that you can see, but underneath the hood all sorts of magic has just +occurred. What you've just done is told this particular shell that you are using +ROS 2 Eloquent Elusor, and where all the ROS programs and files live. You should +plan on doing this every time you want to use ROS. The most common mistake new +users have is not running this command. If you're not sure if your ran the +command in a shell, that's okay. The command is idempotent; meaning running it +twice in a row won't break anything. You can run it a million times in a row and +it won't make any difference. + +The other command you need to commit to memory is `ros2`. That's it. That's all +there is to it. Almost everything in the ROS 2 CLI starts with `ros2`. Go ahead, +try it, in the same shell where you just sourced the setup file. If everything +is working correctly you should see the following: + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 +usage: ros2 [-h] Call `ros2 -h` for more detailed usage. ... + +ros2 is an extensible command-line tool for ROS 2. + +optional arguments: + -h, --help show this help message and exit + +Commands: + action Various action related sub-commands + component Various component related sub-commands + daemon Various daemon related sub-commands + doctor Check ROS setup and other potential issues + interface Show information about ROS interfaces + launch Run a launch file + lifecycle Various lifecycle related sub-commands + msg Various msg related sub-commands + multicast Various multicast related sub-commands + node Various node related sub-commands + param Various param related sub-commands + pkg Various package related sub-commands + run Run a package specific executable + security Various security related sub-commands + service Various service related sub-commands + srv Various srv related sub-commands + topic Various topic related sub-commands + wtf Use `wtf` as alias to `doctor` + + Call `ros2 -h` for more detailed usage. + +``` + +The command just told you everything there is to know about the ROS 2 CLI. From +this one command you can figure out what every single ROS 2 CLI program does and +how to use it. If you study the list above you'll notice that there is a long +list of commands. The ROS 2 CLI has a syntax just like most languages. Just like +all English sentences start with a capital letter, all ROS CLI commands start +with `ros2` followed by a command. After the command any number of other things +can come, but most of the commands will tell you and show you what they +want. The rest of this section just walks through each of the commands one by +one. + +It is worth noting before we move on one particular trick. If you are new to the +command line there are two things that will make your life much much +easier. Writing commands using the command line is tricky and error +prone. There are a couple of tools you can use to make the process much +smoother. The first is the `TAB` key. The tab key is magic in the command line +as it attempts to auto complete whatever you type. The tab button can't read +your mind, but for common command combinations you usually only need to type the +first one or two letters. Another tool is the up arrow key. When you use the +command line sometimes you mistype a command, or need to rerun a +command. Pressing the up key will cycle through the previous commands which you +can either rerun, or edit as needed. + +Running Your First ROS Program +============================== + +Let's get started with our first ROS CLI command. The first command we'll visit +is `run`. Let's start by looking at the documentation for the run command. First +we'll type `ros2 run` and see what happens. Give it a try, you won't break +anything. + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 run +usage: ros2 run [-h] [--prefix PREFIX] package_name executable_name ... +ros2 run: error: the following arguments are required: package_name, executable_name, argv +``` + +This output is helpful, but not _that_ helpful. There is one trick to get more +complete information about a ROS 2 command, simply ask the command for help by +adding `--help` to the command. Let's try that again. + + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 run --help +usage: ros2 run [-h] [--prefix PREFIX] package_name executable_name ... + +Run a package specific executable + +positional arguments: + package_name Name of the ROS package + executable_name Name of the executable + argv Pass arbitrary arguments to the executable + +optional arguments: + -h, --help show this help message and exit + --prefix PREFIX Prefix command, which should go before the executable. + Command must be wrapped in quotes if it contains spaces + (e.g. --prefix 'gdb -ex run --args'). +``` + +Much better! Let's take a look at the results. We can see that `ros2 run` is the +command to, "Run a package specific executable." In ROS 2 collections of ROS +software are gathered into logical units called `packages`. Each package +contains all of the source code for the package as a variety of other data that +tells ROS how to build and compile the package and the names of all the +programs, also called `executables`, that can be found in the package. The line +below the description then gives the _positional arguments_ for the +package. Positional arguments are the words and values that come after `ros2` +and the command you run. In this case the syntax for the command sentence we +want to write is as follows: + +`ros2 run ` + +There is one piece of missing information here. What is this `argv` that the +command is asking for? The `argv` element is programmer short hand for variable +arguments, and it simply means, "some number of additional arguments that are +determined by the executable`. It is worth noting that a program can have zero +arguments and you can just leave it blank. This is actually how a lot of +programs work. Just to make this very clear, let's say we had a package +called _math_, and an executable called _add_ that takes in two numbers and +returns the result. In this case _argv_ would be the two numbers to add. The +final command would look like: + +`ros2 run math add 1 2` + +Finally, below the positional arguments we have _optional arguments_. These +arguments are, as the name would suggest, optional. You don't need to included +them, unless you need to. + +Now that we've looked into our help file let's run our first ROS program. For +these tutorials we're going to use a package called "turtlesim", and the program +we want to run is "turtlesim_node." Let's run this program (remember your tab +complete!). Your command should look like the following: + +`ros2 run turtlesim turtlesim_node` + +If everything goes smoothly you should see the following + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 run turtlesim turtlesim_node +[INFO] [turtlesim]: Starting turtlesim with node name /turtlesim +[INFO] [turtlesim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000] +``` + +A window should also pop up with a cute little turtle that looks like the one +below. + + +![image](./images/turtle.png) + + +The real power in ROS, isn't that it can run a program, it is that it can run +lots of programs all that same time, all talking together to make a robot, or +multiple robots, all working together. To illustrate this let's run a second ROS +program that makes our little turtle move around + + +To do this we'll first open a new terminal (using `CTRL-SHIFT-T`). Next we'll +tell that terminal that we want to use ROS Eloquent by using the `source +/opt/ros/eloquent/setup.bash `. Finally, we'll run another program in the +`turtlesim` package to draw a square. See if you can find the program +yourself. If everything works you should have typed the following, and the +following output should be visible. + + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ source /opt/ros/eloquent/setup.bash +kscottz@kscottz-ratnest:~$ ros2 run turtlesim draw_square +[INFO] [draw_square]: New goal [7.544445 5.544445, 0.000000] +[INFO] [draw_square]: Reached goal +[INFO] [draw_square]: New goal [7.448444 5.544445, 1.570796] +[INFO] [draw_square]: Reached goal +``` + +Your screen should look roughly like this: + +![image](./images/turtlesim_square.png) + +It is worth noting that You can stop any ROS program by typing the `Ctrl` and +`C` keys at the same time in the terminal , we call this `CTRL-C`. The astute reader may notice +that `CTRL-C` is usually used at the hotkey combination for copy. For arcane +reasons on most flavors of linux terminal `CTRL-C` ends a program while +`CTRL-SHIFT-C` and `CTRL-SHIFT-V` is used to paste. The reasons for this are +arcane and not worth discussing, just accept this confusing detail that you +must remember. Feel free to try it out. Start and stop the programs and then +restart them. + +ROS Topics +========== + +As it stands we now have two ROS 2 programs running from the `turtlesim` package, +`turtle_node` and `draw_square`. If we reflect on this for a moment we have +`turtle_node` that draws our turtle simulation, and `draw_square` spitting out +commands that make the turtle in `turtle_node` move around. How are these two +programs communicating? ROS programs, also called _nodes_, communicate over +_topics_ on the ROS _message bus_. ROS _topics_ are very similar to telephone +numbers. In the US, like most countries, telephone numbers are broken into +logical sections. In North America, you start with a country code, +followed by a three digit area code, followed by an exchange, and then finally a +house number. ROS topics are very similar but instead of using numbers, +parethesis, and dashes to break up these sections, ROS topics use words and +slashes. Another analogy for ROS topics are the file systems where files are +located in a directory structure broken up by slashes (e.g. C:\foo\bar or +/home/foo/bar). No matter how you do it, these symbols all work to logically +group things together, and topic is just a stream of data arranged in a smart +way. For example, in a vehicle running ROS, the positions of each wheel may be +organized as follows: + +``` +/wheels/front/driver/velocity +/wheels/front/passenger/velocity +/wheels/rear/driver/velocity +/wheels/rear/passenger/velocity +``` + +The key thing to realize about topics, is that they are more like phone numbers +than a file system in that the data they contain is dynamic, meaning it changes +constantly. In our vehicle example the velocity of each wheel might be measured +one thousand times a second or more. Since the data in a ROS topic is constantly +changing an important distinction for a topic is whether the topic is "creating" +or as we like to say in ROS `publishing`, or if it is reading the data, what we call +`subscribing` to the topic. Another way to think of subscribing is _listening_, +this is to say, you listening to values being _published_ on the topic to which +the node has _subscribed_. Many ROS nodes subscribe to one set of topics, +process that input data, and then publish to another set of topics. + +Let's return to our turtlesim example and see if we can use the ROS CLI to +understand the topics, publishers, and subscribers. Let's example the help +information for the `topic` command. To do this we'll run: `ros2 topic --help`. + +This command outputs the following: + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 topic --help +usage: ros2 topic [-h] [--include-hidden-topics] + Call `ros2 topic -h` for more detailed usage. ... + +Various topic related sub-commands + +optional arguments: + -h, --help show this help message and exit + --include-hidden-topics + Consider hidden topics as well + +Commands: + bw Display bandwidth used by topic + delay Display delay of topic from timestamp in header + echo Output messages from a topic + find Output a list of available topics of a given type + hz Print the average publishing rate to screen + info Print information about a topic + list Output a list of available topics + pub Publish a message to a topic + type Print a topic's type + + Call `ros2 topic -h` for more detailed usage. +``` + +Like a Russian nesting doll this ROS command has subcommands! There are quite a +few subcommands; we won't give a treatment of all of them, but let's look at a +few of them.What's great about the ROS CLI is that the subcommands have their +own help command! Why don't we examine the `list` command. Repeating our command +pattern let's try running `ros2 topic list --help`. + + + +``` {.sourceCode .bash} +usage: ros2 topic list [-h] [--spin-time SPIN_TIME] [-t] [-c] + [--include-hidden-topics] + +Output a list of available topics + +optional arguments: + -h, --help show this help message and exit + --spin-time SPIN_TIME + Spin time in seconds to wait for discovery (only + applies when not using an already running daemon) + -t, --show-types Additionally show the topic type + -c, --count-topics Only display the number of topics discovered + --include-hidden-topics + Consider hidden topics as well + +``` + +As indicated at the top of this command help file, `ros2 topic list` does the +following, "Output a list of available topics." There appears to be a variety of +_optional_ arguments that we don't need to include if we don't want to. However, +the `-t, --show-types` line looks interesting. It is worth noting that command +arguments, sometimes called flags, can have two types. A short form indicated +with a single dash ("-"), and a long form indicated by a double dash +("--"). Don't worry, despite looking different both versions of the argument do +the same thing. Let's try running this command, subcommand pair with the +`-show-types` argument. + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 topic list --show-types +/parameter_events [rcl_interfaces/msg/ParameterEvent] +/rosout [rcl_interfaces/msg/Log] +/turtle1/cmd_vel [geometry_msgs/msg/Twist] +/turtle1/color_sensor [turtlesim/msg/Color] +/turtle1/pose [turtlesim/msg/Pose] +``` + +What does this all mean!? On the left hand side we see all of the ROS topics +running on the system. We can see that most of them are gathered in the +`/turtle1/` group. This group defines all the inputs and outputs of the little +turtle on our screen. So what's to the right of the topics? The words in the +brackets ("[]") define the messages used on the topic. Our car wheel example was +simple, we were only publishing velocity, but ROS allows you to publish more +complex data structures that are defined by a _message type_. When we added the +`--show-types` flag we told the command to include this information. We'll dig +into messages in detail a bit later. + +One of the more commonly used topic subcommands for the topic command is +`info`. Unsurprisingly, `info` provides info about a topic. Let's peek at its +help file using `ros2 topic info --help` + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic info --help +usage: ros2 topic info [-h] topic_name + +Print information about a topic + +positional arguments: + topic_name Name of the ROS topic to get info (e.g. '/chatter') + +optional arguments: + -h, --help show this help message and exit +``` + +That seems pretty straight forward. Let's give it a go by running it on +`/turtle1/pose` + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic info /turtle1/pose +Type: turtlesim/msg/Pose +Publisher count: 1 +Subscriber count: 1 +``` +What does this command tell us? First it tells us the _message type_ for the +pose topic; which is `/turtlesim/msg/Pose`. From this we can determine that the +message type comes from the _turtlesim_ package, and its type is `Pose`. ROS +messages have a predefined message type that can be shared by different +programming languages and between different nodes. We can also see that this +topic has a single publisher, that is to say a single node generating data on the +topic. The topic also has a single subscriber, also called a listener, who is +processing the incoming pose data. + +For what it is worth, if we just wanted to know the message type of a topic +there is a subcommand just for that called, `type`. Let's take a look at its +help file and its result. + +``` +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic type --help +usage: ros2 topic type [-h] topic_name + +Print a topic's type + +positional arguments: + topic_name Name of the ROS topic to get type (e.g. '/chatter') + +optional arguments: + -h, --help show this help message and exit +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic type /turtle1/pose +turtlesim/msg/Pose +``` + +While it is not part of topic command it is worthwhile for us to jump ahead +briefly and look at one particular command, subcommand pair, namely the `interface` +command and the show subcommand. This subcommand will print all the +information related to a message type using you can better understand the data +being moved over a topic. In the previous example we saw that the `topic type` +subcommand told up the `/turtle1/pose` topic has a type `turtlesim/msg/Pose`. +But what is a `turtlesim/msg/Pose` you may ask? We can look at the data +structure transferred by this topic by running: `ros2 interface show` +subcommand and giving the message type name as an input. Let's look at the help +for this subcommand and its output: + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 interface show --help +usage: ros2 interface show [-h] type + +Output the interface definition + +positional arguments: + type Show an interface definition (e.g. "std_msgs/msg/String") + +optional arguments: + -h, --help show this help message and exit + +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 interface show turtlesim/msg/Pose +float32 x +float32 y +float32 theta + +float32 linear_velocity +float32 angular_velocity +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ +``` +What does all of this mean? The first thing we see in the output is `float32` +which is just a number type. If you are a computer programmer then this should +look familiar, if you're not a programmer a float is just a number with a +decimal like "1.2345" or "424123123.1231231". The values `x` and `y` are the +position of our turtle, and `theta` is the direction the head is pointing. The +next two values `linear_velocity` and `angular_velocity` are, respectively, how +fast the turtle is moving, and how quickly it is turning. To summarize, this +message tells us where a turtle is on the screen, where it is headed, and how +fast it is moving or rotating. + + +Now that we know what ROS topics are on our simple turtlesim, and their message +types, we can dig in and find out more about how everything works. If we look +back at our topic subcommands, we can see a subcommand called `echo`. Echo is +computer jargon that means "repeat" something. If you echo a topic it means you +want the CLI to repeat what's on a topic. Let's look at the `echo` subcommand's +help text: + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 topic echo --help +usage: ros2 topic echo [-h] + [--qos-profile {system_default,sensor_data,services_default,parameters,parameter_events,action_status_default}] + [--qos-reliability {system_default,reliable,best_effort}] + [--qos-durability {system_default,transient_local,volatile}] + [--csv] [--full-length] + [--truncate-length TRUNCATE_LENGTH] [--no-arr] + [--no-str] + topic_name [message_type] + +Output messages from a topic + +positional arguments: + topic_name Name of the ROS topic to listen to (e.g. '/chatter') + message_type Type of the ROS message (e.g. 'std_msgs/String') + +optional arguments: + -h, --help show this help message and exit + --qos-profile {system_default,sensor_data,services_default,parameters,parameter_events,action_status_default} + Quality of service preset profile to subscribe with + (default: sensor_data) + --qos-reliability {system_default,reliable,best_effort} + Quality of service reliability setting to subscribe + with (overrides reliability value of --qos-profile + option, default: best_effort) + --qos-durability {system_default,transient_local,volatile} + Quality of service durability setting to subscribe + with (overrides durability value of --qos-profile + option, default: volatile) + --csv Output all recursive fields separated by commas (e.g. + for plotting) + --full-length, -f Output all elements for arrays, bytes, and string with + a length > '--truncate-length', by default they are + truncated after '--truncate-length' elements with + '...'' + --truncate-length TRUNCATE_LENGTH, -l TRUNCATE_LENGTH + The length to truncate arrays, bytes, and string to + (default: 128) + --no-arr Don't print array fields of messages + --no-str Don't print string fields of messages +``` + +Wow, that's a lot of features. The top of the help files says that this CLI +program, "output[s] messages from a topic." As we scan the positional arguments we see one +required argument, a topic name, and an optional message type. We know the +message type is optional because it has square brackets ("[]") around it. Let's +give the simple case a whirl before we address some of the optional +elements. Two things to keep in mind: first is that topics are long and easy to mess +up, use the TAB key, second is that this will print a lot of data, fast. You can +use `CTRL-C` to stop command and stop all the output. Let's take a look at the +`/turtle1/pose` topic. + + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic echo /turtle1/pose +x: 5.4078755378723145 +y: 7.081490516662598 +theta: -1.0670461654663086 +linear_velocity: 1.0 +angular_velocity: 0.0 +--- +x: 5.4155988693237305 +y: 7.067478179931641 +theta: -1.0670461654663086 +linear_velocity: 1.0 +angular_velocity: 0.0 +--- +x: 5.423322677612305 +y: 7.053465843200684 +theta: -1.0670461654663086 +linear_velocity: 1.0 +angular_velocity: 0.0 +--- +<< GOING ON FOREVER>> +``` + +What can see all sorts of data. Let's examine what is going on. Between the +dashes (`---`) is a single ROS message on our topic. If you examine the numbers +closely you can see that they are changing; and changing in relation to the +movement of the turtle. Going back to our car example you can see how this would +be useful for understanding the instantaneous velocity of each of our wheels. + +Now that we have the basics down let's dig into a few of the optional +arguments. We see a variety of commands that start with `--qos`, "QOS" here +means "quality of service" and it is a really cool feature that is only in +ROS 2. Without getting too technical QOS is a way of asking for a certain level +of networking robustness. A ROS system can operate over a network, and just like +streaming video or video games, packets can get dropped or not get to their +destination. The QOS settings help you control which packets are the most +important and should get the highest priority. + +Most of the other commands deal with changing the output format of this CLI +program, but there is one in particular that is super handy, and it is also new +in ROS 2. The `--csv` flag stands for "comma separated values" and it a very +simple way of defining a spreadsheet. What this argument does is make the topic +echo command output data in the comma separate value format. Many command lines +allow you send data from the screen to a file using just a little bit of +magic. What's great about this is it allows you to save data for later +review or analysis. To do this file saving in linux we use the `>` character +followed by a file name. Below I show two examples of using the `--csv` + + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic echo /turtle1/pose --csv +7.097168922424316,8.498645782470703,2.442624092102051,0.0,0.4000000059604645 +7.097168922424316,8.498645782470703,2.449024200439453,0.0,0.4000000059604645 +... + +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic echo /turtle1/pose --csv > mydata.csv + + +``` + +The second command above creates a file called mydata.csv. You can look at it +using a CLI utility called `less` (press q to quit), or open it with your +favorite spreadsheet tool. + +Now that we've looked at `ros2 topic echo` let's take a look at a few other +topic subcommands. One thing you may have noticed is that topics can make a lot +of data! More complex robots, like a self driving car, can saturate a high +speed internet connection with how much data it produces. There are two topic +subcommands that can be used to diagnose performance issues. The first +subcommand is `topic hz` which is the abbreviation of Hertz, the unit of +frequency, as in the frequency of a radio station. The `hz` subcommand will +tell you how often a particular topic produces a message. Similarly there is +the `topic bw` subcommand, where `bw` stands for bandwidth, which is a +engineering term related to the _volume_ of data being produced. A high +bandwidth connection can move more data, like high definition video, than a low +bandwidth data, which might move a radio show. Let's take a look at the help +for these two commands. + + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic hz --help +usage: ros2 topic hz [-h] [--window WINDOW] [--filter EXPR] [--wall-time] + topic_name + +Print the average publishing rate to screen + +positional arguments: + topic_name Name of the ROS topic to listen to (e.g. '/chatter') + +optional arguments: + -h, --help show this help message and exit + --window WINDOW, -w WINDOW + window size, in # of messages, for calculating rate + (default: 10000) + --filter EXPR only measure messages matching the specified Python + expression + --wall-time calculates rate using wall time which can be helpful + when clock is not published during simulation +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic bw --help +usage: ros2 topic bw [-h] [--window WINDOW] topic + +Display bandwidth used by topic + +positional arguments: + topic Topic name to monitor for bandwidth utilization + +optional arguments: + -h, --help show this help message and exit + --window WINDOW, -w WINDOW + window size, in # of messages, for calculating rate + (default: 100) +``` + +Both `bw` and `hz` follow the same pattern, they simply take in a topic name +followed by a few optional arguments. The only argument worth noting is the +`window` argument. Both of these commands calculate statistics for a series of +messages, how many messages to use in calculating those statistics in the window +size. The default value for window is 100, so when you call `ros2 topic bw` it +will first collect 100 messages then use that data to calculate the average +message size. Let's give it a shot (use `TAB` to complete and `CTRL-C` to exit) + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic hz /turtle1/pose +average rate: 60.021 + min: 0.001s max: 0.073s std dev: 0.00731s window: 65 +average rate: 61.235 + min: 0.001s max: 0.073s std dev: 0.00523s window: 128 +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic bw /turtle1/pose +Subscribed to [/turtle1/pose] +average: 1.44KB/s + mean: 0.02KB/s min: 0.02KB/s max: 0.02KB/s window: 46 +average: 1.52KB/s + mean: 0.02KB/s min: 0.02KB/s max: 0.02KB/s window: 100 +``` + +As we can see above the `hz` command says that the topic is publishing messages +at 60.021, where the unit is hz, or 60.021 times a second. Notice that the +command give the publishing frequency as an average, followed by the minimum, +maximum, and standard deviation, in seconds. The bandwidth subcommand is very +similar; and we can see that the topic is producing 1.44 kilobytes of data per +second. This command has similar outputs around the minimum, maximum, and mean. + +One tool that is handy when exploring topics is understanding their type. While +we have already looked at the `interface` command to see integral types make up +a topic, the `topic` command has both a tool to query the type of a topic, and a +means to search all topics for a specific type. If all you want to know is a +topic's type you can use the `type` command which will return a type that can +then be further explored with the `interface` command. If instead you would like +to know what topics use a particular message type you can use the `topic find` +command / subcommand pair. Both the `topic type` and `topic interface` command +/ subcommand pairs have a very limited set of optional arguments, so we simply +provide them with our desired topic or message type. Let's take a look at these two commands together: + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic type --help +usage: ros2 topic type [-h] topic_name +Print a topic's type + +positional arguments: + topic_name Name of the ROS topic to get type (e.g. '/chatter') + +optional arguments: + -h, --help show this help message and exit +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic type /turtle1/pose +turtlesim/msg/Pose +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic find --help +usage: ros2 topic find [-h] [-c] [--include-hidden-topics] topic_type + +Output a list of available topics of a given type + +positional arguments: + topic_type Name of the ROS topic type to filter for (e.g. + 'std_msg/msg/String') + +optional arguments: + -h, --help show this help message and exit + -c, --count-topics Only display the number of topics discovered + --include-hidden-topics + Consider hidden topics as wel +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic find turtlesim/msg/Pose +/turtle1/pose +``` +The last subcommand for the topic command is `pub`, pub simply means publish, +and it allows you to publish a command to any ROS topic from the command +line. While you shouldn't need to use this command regularly it can be +particularly handy for testing and debugging when you are building a robot +system. The `pub` command has a number of optional arguments that allow you to +send one or more message, and with different quality of service (QoS) +presets. The format of the command is `ros2 topic pub TOPIC_NAME MESSAGE_TYPE +VALUES`, which means for it to work successfully you must include a +target topic, the topic's message type, and finally the message's values. The +values for the message are specified in the YAML format and we can use the +`interface show` command to understand the format. To illustrate the utility of this +command we'll issue a message to rotate and stop our turtle by publishing +to the `/turtle1/cmd_vel/` topic. Let's first take a look at the `topic pub` +documentation before we construct our command: + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic pub --help +usage: ros2 topic pub [-h] [-r N] [-p N] [-1] [-n NODE_NAME] + [--qos-profile {system_default,sensor_data,services_default,parameters,parameter_events,action_status_default}] + [--qos-reliability {system_default,reliable,best_effort}] + [--qos-durability {system_default,transient_local,volatile}] + topic_name message_type [values] + +Publish a message to a topic + +positional arguments: + topic_name Name of the ROS topic to publish to (e.g. '/chatter') + message_type Type of the ROS message (e.g. 'std_msgs/String') + values Values to fill the message with in YAML format (e.g. + "data: Hello World"), otherwise the message will be + published with default values + +optional arguments: + -h, --help show this help message and exit + -r N, --rate N Publishing rate in Hz (default: 1) + -p N, --print N Only print every N-th published message (default: 1) + -1, --once Publish one message and exit + -n NODE_NAME, --node-name NODE_NAME + Name of the created publishing node + --qos-profile {system_default,sensor_data,services_default,parameters,parameter_events,action_status_default} + Quality of service preset profile to publish with + (default: system_default) + --qos-reliability {system_default,reliable,best_effort} + Quality of service reliability setting to publish with + (overrides reliability value of --qos-profile option, + default: system_default) + --qos-durability {system_default,transient_local,volatile} + Quality of service durability setting to publish with + (overrides durability value of --qos-profile option, + default: system_default) +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ + +``` +Since we want to manually move our turtle we will use the `--once` +flag to issue our command once. It is worth noting that the message type used to +command the velocity of the turtle is complex in that it is made up of other +message types so we'll have to query the base message type. Here's a rough summary of what we will do: + +* Print the `cmd_vel` topic type using `ros2 topic type`, which is `geometry_msgs/msg/Twist` +* Determine the structure of the `Twist` message type using `interface show`. +* Determine the structure of the `Vector3`, which is part of the `Twist` message + type using `inteface show` command a second time. +* Create the YAML syntax for our command. Note the YAML syntax below as it is + rather tricky! The YAML is wrapped in single quotes and a top level set of + curly braces, while subsequent levels follow the + pattern of `name:value`, and `name:{name1:val1,name2:val2}` for nested types + like the `Twist` command. +* Issue the command using `ros2 pub`. + + + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 topic type /turtle1/cmd_vel +geometry_msgs/msg/Twist +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 interface show geometry_msgs/msg/Twist +# This expresses velocity in free space broken into its linear and angular parts. + +Vector3 linear +Vector3 angular +kscottz@kscottz-ratnest:~/Code/ros2multirobotbook$ ros2 interface show geometry_msgs/msg/Vector3 +# This represents a vector in free space. + +float64 x +float64 y +float64 z + +ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist '{linear: {x: 4.0,y: 4.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}' + +``` + +If you did everything correctly you should have moved around the turtle on the +screen. Try changing the command to draw a small picture. + + +ROS 2 Services and Actions +========================== + + +ROS has two main patterns for encapsulating robot behaviors: services and +actions. As we have discussed previously services are the name given to short, +synchronous robot behaviors that can be done quickly, like turning on lights +and switching components on or off. Action is the term to describe longer term, +asynchronous, tasks, that may have intermediate steps. A classic example of an +action is navigation: a robot is provided a goal position and asked to navigate +to that goal. Try as the robot might, since it cannot move infinitely fast, it +takes time to move to a goal and sometimes its path may become blocked. These +two primitives are the backbone of most robotic systems using ROS, and learning +how to use them via the command line will allow you quickly and easily command +a robot to complete a task for you. To aid in clarity of this section we'll +also touch on the `ros2 node` command to determine what node, or software +process is conducting a particular action or service. + +Let's get nodes out of the way quickly. As we have alluded to ROS nodes are +small programs, running in their own process. A ROS system can have ten, +hundreds, or even thousands of nodes running concurrently. Moreover, a ROS +system can have multiple copies of the same node running concurrently on the +same system. In the case of our turtle simulation we can actually create +multiple turtles, each with their own node, all running the exact same +program. ROS Nodes, like ROS topics, have namespaces so that you can address +specific nodes in the case where multiple copies of the same node (program) are +running. Let's dig in a bit by restarting our turtle simulation in a terminal +using `ros2 run turtlesim turtlesim_node`. Now in a new terminal let's first +examine what `ros2 node` has to offer by asking for help. + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 node --help +usage: ros2 node [-h] + Call `ros2 node -h` for more detailed usage. ... + +Various node related sub-commands + +optional arguments: + -h, --help show this help message and exit + +Commands: + info Output information about a node + list Output a list of available nodes + + Call `ros2 node -h` for more detailed usage. +``` +Much like topics we see two subcommands, `info` and `list`. Node list works much the +same as topic list and simply prints a list of all running nodes. Let's see what +is running on our system. + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 node list +/turtlesim +``` + +We have a single node running called "turtlesim". `node info` works in a way +very similar to `topic info` except that it lists information about the nodes we +give it. Let's call it with our single ROS Node `/turtlesim` as its +argument. + + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 node info /turtlesim +/turtlesim + Subscribers: + /parameter_events: rcl_interfaces/msg/ParameterEvent + /turtle1/cmd_vel: geometry_msgs/msg/Twist + Publishers: + /parameter_events: rcl_interfaces/msg/ParameterEvent + /rosout: rcl_interfaces/msg/Log + /turtle1/color_sensor: turtlesim/msg/Color + /turtle1/pose: turtlesim/msg/Pose + Service Servers: + /clear: std_srvs/srv/Empty + /kill: turtlesim/srv/Kill + /reset: std_srvs/srv/Empty + /spawn: turtlesim/srv/Spawn + /turtle1/set_pen: turtlesim/srv/SetPen + /turtle1/teleport_absolute: turtlesim/srv/TeleportAbsolute + /turtle1/teleport_relative: turtlesim/srv/TeleportRelative + /turtlesim/describe_parameters: rcl_interfaces/srv/DescribeParameters + /turtlesim/get_parameter_types: rcl_interfaces/srv/GetParameterTypes + /turtlesim/get_parameters: rcl_interfaces/srv/GetParameters + /turtlesim/list_parameters: rcl_interfaces/srv/ListParameters + /turtlesim/set_parameters: rcl_interfaces/srv/SetParameters + /turtlesim/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically + Service Clients: + + Action Servers: + /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute + Action Clients: +``` + +Wow, that's a lot of information, some of which looks familiar. We can see all +the topics that the node subscribes to, as well as all the nodes it publishes +to. We can also see a number of "action servers" and "service servers". It is +worth noting the client and server relationship here. Since ROS may have +multiple nodes running some nodes may offer service, these are servers, and +other ROS nodes may call those servers, these are the clients. The clients can +be other ROS nodes, or for these examples, a human using the CLI. + +The command line interface for services and actions are very similar, in fact +the both have only four subcommands. Let's run the `action` and `service` +commands and compare them. + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 action --help +usage: ros2 action [-h] + Call `ros2 action -h` for more detailed usage. + ... + +Various action related sub-commands + +optional arguments: +-h, --help show this help message and exit + +Commands: + info Print information about an action + list Output a list of action names + send_goal Send an action goal + show Output the action definition + + Call `ros2 action -h` for more detailed usage. +``` + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 service --help +usage: ros2 service [-h] [--include-hidden-services] + Call `ros2 service -h` for more detailed usage. + ... + +Various service related sub-commands + +optional arguments: + -h, --help show this help message and exit + --include-hidden-services + Consider hidden services as well + +Commands: + call Call a service + find Output a list of available services of a given type + list Output a list of available services + type Output a service's type + + Call `ros2 service -h` for more detailed usage. +``` + +We can see that both commands have a `list` command that gives a list of +available services or actions. If we had multiple nodes running and wanted to +see every service offered calling `ros2 node info` on each node would very +inefficient, particularly if we had tens, or even hundreds of nodes running. +In this case it would be much more efficient to use the list commands for the +action and service commands. We can run these commands below and see that we get +roughly the same list of actions and services listed in our single nodes. + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 service list +/clear +/kill +/reset +/spawn +/turtle1/set_pen +/turtle1/teleport_absolute +/turtle1/teleport_relative +/turtlesim/describe_parameters +/turtlesim/get_parameter_types +/turtlesim/get_parameters +/turtlesim/list_parameters +/turtlesim/set_parameters +/turtlesim/set_parameters_atomically +kscottz@kscottz-ratnest:~$ ros2 action list +/turtle1/rotate_absolute +``` + +Let's begin digging into services. There seem to be quite a few services +listed. Let's take a look at the `/spawn` service, which will create more +turtles. ROS services and actions use messages similar to those used in topics +to communicate and in fact actions and services are built on top of messages. . We can use the `service type` subcommand to determine the message type +used by a particular service. We can find specifics of the message by using the +`interface show` command. Let's see this in practice with the `spawn` service. + + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 service type /spawn +turtlesim/srv/Spawn +kscottz@kscottz-ratnest:~$ ros2 interface show turtlesim/srv/Spawn +float32 x +float32 y +float32 theta +string name # Optional. A unique name will be created and returned if this is empty +--- +string name +``` + +We can see from the output above that the spawn message takes three `float32` +values for its position and orientation as well a "string" for its name. The +`---` indicate the return value of the services. Unlike topics, services have +a return value, which enables them to do things like perform computations and +calculations. + +Let's examine the help for calling a service by running `ros2 service call --help`. + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 service call --help +usage: ros2 service call [-h] [-r N] service_name service_type [values] + +Call a service + +positional arguments: + service_name Name of the ROS service to call to (e.g. '/add_two_ints') + service_type Type of the ROS service (e.g. 'std_srvs/srv/Empty') + values Values to fill the service request with in YAML format (e.g. + "{a: 1, b: 2}"), otherwise the service request will be + published with default values + +optional arguments: + -h, --help show this help message and exit + -r N, --rate N Repeat the call at a specific rate in Hz +``` +The syntax here is very similar to publishing to a topic, but instead of using a +a topic name we use a service name. The service type is just like the topic type +that we used in the past, but instead of using a message type we need a service +type. Finally we give it a value in YAML format. The trick with the YAML is to +encase the string in single quotes. Let's give it a whirl by creating a turtle +named `larry` at a position where all values are zero (tab complete is your +friend). + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 service call /spawn turtlesim/srv/Spawn "{x: 0, y: 0, theta: 0.0, name: 'larry'}" +requester: making request: turtlesim.srv.Spawn_Request(x=0.0, y=0.0, theta=0.0, name='larry') + +response: +turtlesim.srv.Spawn_Response(name='larry') +``` + +If everything is working correctly you should now have a turtle named "larry" in +the lower left hand corner of the screen. + +![image](./images/larry.png) + +Now that you have the basic idea try exploring the other services offered, or +create more turtles at different locations and moving them around. The more you +practice doing this the easier it will get. + +Now that we've looked at services we should cover actions. As we mentioned +previously actions differ from services in a few ways and offer a number of +advantages. Services have the following advantages: + +* Actions have a `goal`. That is to say you send them a goal, and they attempt + to complete it. +* Actions can reject goal requests. This prevents them from becoming too busy. +* Actions are asynchronous and can perform tasks "while you wait." +* Actions will provide you with "updates" while you wait, with information about + their progress. +* Actions are preemptable, which is a fancy way of saying you can cancel them if + you change your mind. + +Just like with services we'll first figure out how to call the sole action in +our ROS system by using the `action list`, `action show`, and `action info` +commands. Recall, that when we called `ros2 action list` we got a single +service. Now that we have "larry" things have changed. Let's take a look. + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 action list +/larry/rotate_absolute +/turtle1/rotate_absolute +``` + + +Now there are two actions available, one for Larry and one for his friend +"turtle1"! Let's rotate turtle1 to face Larry. First we'll call `action info` +using `/turtle1/rotate_absolute` as the input and see what we get. + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 action info /turtle1/rotate_absolute +Action: /turtle1/rotate_absolute +Action clients: 0 +Action servers: 1 + /turtlesim +``` + +Well, that tells us about the client and servers, but it really isn't +helpful. Why don't we look at the `action send_goal` help and see if we can +figure out how to use it. + + +```{.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 action send_goal --help +usage: ros2 action send_goal [-h] [-f] action_name action_type goal + +Send an action goal + +positional arguments: + action_name Name of the ROS action (e.g. '/fibonacci') + action_type Type of the ROS action (e.g. + 'example_interfaces/action/Fibonacci') + goal Goal request values in YAML format (e.g. '{order: 10}') + +optional arguments: + -h, --help show this help message and exit + -f, --feedback Echo feedback messages for the goal +``` +This command needs an action name, an action type, and a goal as YAML. We know +the action name, and we know how to write YAML, so all we need is to determine +the action type. The best way to get the action type is the same way we +published a message. + +We see each of our turtles have one service called `rotate_absolute`. +Let's dig into this action using the info verb. This command has a `-t` +flag to list the types of messages. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 action info /moe/rotate_absolute -t +Action: /moe/rotate_absolute +Action clients: 0 +Action servers: 1 + /turtlesim [turtlesim/action/RotateAbsolute] +``` + +Interesting, what do these terms mean. The first line lists the action +name. The second line gives the current number of clients for the +action. The `Action servers` line gives the total number of action +servers for this action. The last line gives the package and message +type for the action. + +We can see here that we need to know the action name, the type, and the +values. Now the only problem is figuring out the format of the +action type. + +Let's understand the `RotateAbsolute` action message + +The `ros2 interface show` command can be used to find the type of action +message. Let's take a look. + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 interface show turtlesim/action/RotateAbsolute +# The desired heading in radians +float32 theta #< --- This section is the GOAL +--- +# The angular displacement in radians to the starting position +float32 delta #< --- This section is the final result, different from the goal. +--- +# The remaining rotation in radians +float32 remaining # < --- This is the current state. +kscottz@kscottz-ratnest:~$ +``` + +What does this say about rotate absolute? + +* There is a float input, `theta` the desired heading. This first section is the actual goal. +* `delta` -- the angle from the initial heading. This is the value returned when the action completes. +* `remaining` -- the remaining radians to move. This is the value posted by the action while the action is being done. + + +With this information we can create our call to the action server. We'll +use the `-f` flag to make this a bit clearer. Keep an eye on your turtle! It should move, slowly. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 action send_goal -f /turtle1/rotate_absolute turtlesim/action/RotateAbsolute {'theta: 1.70'} +Waiting for an action server to become available... +Sending goal: + theta: 1.7 + +Feedback: + remaining: 0.11599969863891602 + +Goal accepted with ID: 35c40e91590047099ae5bcc3c5151121 + +Feedback: + remaining: 0.09999966621398926 + +Feedback: + remaining: 0.06799960136413574 + +Feedback: + remaining: 0.03599953651428223 + +Result: + delta: -0.09600019454956055 + +Goal finished with status: SUCCEEDED +``` + +If everything worked correctly we should see our turtle has rotated. + + + +ROS parameters +============== + +[The full ROS Param tutorial can be found +here.](https://index.ros.org/doc/ros2/Tutorials/Parameters/Understanding-ROS2-Parameters/) + +In ROS, parameters are values that are shared between nodes in the +system (if you are familiar with the [blackboard design +pattern](https://en.wikipedia.org/wiki/Blackboard_(design_pattern)) in +software engineering). Parameters are values that any node can query or +write to, another good analogy would be global constants in normal +software programs. Parameters are best used to configure your robot. For +example, if you were building an autonomous vehicle and wanted to cap +the maximum velocity of the vehicle at 100 km/h, you could create a +parameter called "MAX\_SPEED" that is visible to all the nodes. + +Let's take a look at the high level param program. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 param --help +Various param related sub-commands + +Commands: + delete Delete parameter + get Get parameter + list Output a list of available parameters + set Set parameter + Call `ros2 param -h` for more detailed usage. +``` + +Params used by TurtleSim + +Let's see what the docs say and then see what happens when we call +`ros2 param list` + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 param --help +usage: ros2 param [-h] +optional arguments: + use_sim_time +/turtlesim: + background_b + background_g + background_r +usage: ros2 param list [-h] [--spin-time SPIN_TIME] [--include-hidden-nodes] + +positional arguments: + node_name Name of the ROS node +< CLIPPED > + +kscottz@ade:~$ ros2 param list +/draw_square: + use_sim_time +/turtlesim: + background_b + background_g + background_r + use_sim_time +``` + +Let's try getting/setting parameters + +The syntax for getting a parameter is as follows: + +`ros2 param get ` + +Let's give it a shot. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 param get /turtlesim background_b +Integer value is: 255 +``` + +Let's try setting a parameter. The syntax for that is as follows: + +`ros2 set ` + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 param set /turtlesim background_b 0 +Set parameter successful +``` + + +ROS bag +============== + +- ROS bags are ROS's tool for recording, and replaying data. +- ROS bags are kinda like log files that let you store data along with + messages. +- ROS systems can generate a lot of data, so you select which topics + you want to bag. +- Bags are a great tool for testing and debugging your application as + well. + +Let's take a look at the base `bag` verb. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 bag -h +usage: ros2 bag [-h] Call `ros2 bag -h` for more detailed usage. ... + +Various rosbag related sub-commands + +Commands: + info ros2 bag info + play ros2 bag play + record ros2 bag record +``` + +Let's try recording our first Bag + +First use `F2` or `F3` to go to the other terminal. Start the +`draw_square` demo again to get the default turtle moving. + +The command for that is: `ros2 run turtlesim draw_square` + +Now let's look at `ros2 bag record -h` + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 bag record -h +usage: ros2 bag record [-h] [-a] [-o OUTPUT] [-s STORAGE] + [-f SERIALIZATION_FORMAT] [--no-discovery] + [-p POLLING_INTERVAL] + [topics [topics ...]] +ros2 bag record +positional arguments: + topics topics to be recorded +optional arguments: + -a, --all recording all topics, required if no topics are listed explicitly. + -o OUTPUT, --output OUTPUT + destination of the bagfile to create, defaults to a + timestamped folder in the current directory + -s STORAGE, --storage STORAGE + storage identifier to be used, defaults to "sqlite3" + -f SERIALIZATION_FORMAT, --serialization-format SERIALIZATION_FORMAT + rmw serialization format in which the messages are + saved, defaults to the rmw currently in use +``` + +Let's Bag! + +- Let's bag the pose data on the `/turtle1/pose topic` +- Save the data to the directory `turtle1.bag` using the `-o` flag. +- The program will bag until you hit `CTRL+C`. Give it a good 30 + seconds. + +Here's my example. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 bag record /turtle1/pose -o turtle1 +[INFO] [rosbag2_storage]: Opened database 'turtle1'. +[INFO] [rosbag2_transport]: Listening for topics... +[INFO] [rosbag2_transport]: Subscribed to topic '/turtle1/pose' +[INFO] [rosbag2_transport]: All requested topics are subscribed. Stopping discovery... +^C[INFO] [rclcpp]: signal_handler(signal_value=2) +``` + +Let's inspect our Bag. + +You can introspect any bag file using the `ros2 bag info` command. This +command will list the messages in the bag, the duration of file, and the +number of messages. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 bag info turtle1 +Files: turtle1.db3 +Bag size: 268.4 KiB +Storage id: sqlite3 +Duration: 68.705s +Start: May 4 2020 16:10:26.556 (1588633826.556) +End May 4 2020 16:11:35.262 (1588633895.262) +Messages: 4249 +Topic information: Topic: /turtle1/pose | Type: turtlesim/msg/Pose | Count: 4249 | Serialization Format: cdr +``` + +Replaying a Bag + +Bags are a great tool for debugging and testing. You can treat a ROS bag +like a recording of a running ROS system. When you play a bag file you +can use most of the ros2 cli tools to inspect the recorded topics. + +To replay the bag, first use `F2/F3` and `CTRL+C` to turn off the main +turtle node and the `draw_square` node. + +Now in a new terminal replay the bag file using the following command: + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 bag play turtle1 +[INFO] [rosbag2_storage]: Opened database 'turtle1'. +``` + +Nothing should happen visibly, but a lot is happening under the hood. +Use `F2` or `F3` to go to a second terminal. Just like a running robot, +you should be able to `list` and `echo` topics. + +``` {.sourceCode .bash} +kscottz@ade:~ros2 topic list +/parameter_events +/rosout +/turtle1/pose + +kscottz@ade:~$ ros2 bag info turtle1 +x: 3.8595714569091797 +y: 3.6481313705444336 +theta: -1.2895503044128418 +linear_velocity: 1.0 +angular_velocity: 0.0 +--- +``` + diff --git a/src/ros2_design_patterns.md b/src/ros2_design_patterns.md new file mode 100644 index 0000000..e090d87 --- /dev/null +++ b/src/ros2_design_patterns.md @@ -0,0 +1,334 @@ +# ROS Concepts and Design Patterns + +As we said, learning about ROS is similar to learning about an +automobile. In fact, a car is a lot like a robot (and sometimes it +really is a robot; cf. the large and active self-driving vehicle +industry). A modern automobile comprises many parts that are +connected to each other. The steering wheel is connected to the front +axle, the brake pedal is connected to the brake calipers, the oxygen +sensor is connected to the fuel injectors, and so on. From this +perspective, a car is a *distributed system*: each part plays a +well-defined role, communicating (whether electrically or mechanically) +as needed with other parts, and the result of that symphony-like +coordination is a working vehicle. + +A key philosophical tenet of ROS is that robotics software should also +be designed and developed as a distributed system. We aim to separate +the functions of a complex system into individual parts that interact +with each other to produce the desired behavior of that system. In ROS +we call those parts *nodes* and we call the interactions between them +*topics* (and sometimes *services*, but we will get to that). + +## The ROS Communication Graph + +Imagine we are building a wheeled robot that chases a red ball. This +robot needs a camera with which to see the ball, a vision system to +process the camera images to figure out where the ball is, a control +system to decide what direction to move, and some motors to move motors +to move the wheels to allow it to move toward the ball. Using ROS we +might construct the system like so: + +![image](./images/ros_graph_example.png) + +This design separates the software into four ROS *nodes*: two device +drivers and two algorithms. Those nodes communicate with each other as +shown, via three ROS *topics*. We call this structure a *ROS +communication graph*: the nodes are the graph vertices and the topics +are the graph edges. You can tell a lot about a ROS system by examining +its communication graph. + +The camera driver node is responsible for handling the details of +interacting with the physical camera, which might happen through a +custom USB protocol, through a vendor-provided library, or in some other +way. Whatever those details, they are encapsulated inside the camera +driver node, which presents a standard *topic* interface to the rest of +the system. As a result, the blob finder node does not need to know +anything about the camera; it simply receives image data in a standard +format that is used for all cameras in ROS. The output of the blob +finder is the detected location of the red ball, also in a standard +format. Then the target follower node can read in the ball location and +produce the steering direction needed to move toward the ball, again in +a standard format. Finally, the motor driver node's responsibility is to +convert the desired steering direction into the specific instructions +necessary to command the robot's wheel motors accordingly. + +## Publish-subscribe messaging: topics and types + +With the example of the ball-chasing robot in mind, we can add some terminology +to describe what is happening as the system operates. First, the ROS +communication graph is based on a well-known pattern called *publish-subscribe +messaging*, or simply *pub-sub*. In a pub-sub system, as the name implies, data +are sent as *messages* from *publishers* to *subscribers*. A publisher may have +zero, one, or multiple subscribers listening to its published messages. Messages +may be published at any time, making the system *asynchronous*. + +In ROS, nodes publish and subscribe via topics, each of which has a name and a +type. A publisher announces that it will be publishing data by *advertising* a +topic. For example, the camera driver node may advertise a topic named `/image` +with type `sensor_msgs/Image`. If the blob finder node subscribes to a topic +with the same name and type, then the two nodes find each other and establish a +connection over which image messages can get from the camera driver to the blob +finder (the nodes find each other and establish those connection in a process +called *discovery*, which will be treated in detail later in this book). Each +message that flows across the `/image` topic will be of type +`sensor_msgs/Image`. + +A single node can be (and often is) both a publisher and a subscriber. In our +example, the blob finder subscribes to image messages and publishes ball +location messages. Similarly the target follower subscribes to ball location +messages and publishes steering direction messages. + +A topic's type is very important. In fact, taken together, the ROS types are +among the most valuable aspects of the entire platform. First, a type tells you +the syntax: which fields, of which types, does the message contain? Second, it +tells you the semantics: what do those fields mean and how they should be +interpreted? For example, a thermometer and a pressure sensor might produce what +appear to be the same data: a floating-point value. But in ROS a well-designed +thermometer driver node would publish one clearly defined type (say, +`sensor_msgs/Temperature`), while a pressure sensor driver node would publish +another (say, `sensor_msgs/FluidPressure`). + +We always advise the use of semantically meaningful message types. +For example, ROS provides simple message types like `std_msgs/Float64`, which +contains a single 64-bit floating-point field called `data`. But you should only +use that sort of generic type for rapid prototyping and experimenting. When you +build a real system, even if something like `std_msgs/Float64` could get the job +done on syntax, you should instead find or define a message that also matches +the semantics of your application. + +## Why publish-subscribe? + +Given that it comes with additional complexity (nodes, topics, types, etc.), it +is reasonable to ask why ROS follows the pub-sub pattern. After more than a +decade of building and deploying ROS-based robot systems, we can identify +several key benefits: + +- **Substitution**: If we decide to upgrade the robot's camera, we need + only modify or replace the camera driver node. The rest of the system +never knew the details of the camera anyway. Similarly, if we find a +better blob finder node, then we can just swap it in for the old one and +nothing else changes. +- **Reuse**: A well-designed blob finder node can be used today on this + robot to chase the red ball, then reused tomorrow on a different robot +to orange cat, and so on. Each new use of a node should require only +configuration (no code) changes. +- **Collaboration**: By cleanly separating concerns between nodes, we + let our blob finder expert do her work independently of the target +follower expert, with neither of them bothering the device driver +expert. It is often the case that a robot application requires the +combined expertise of many people, and it would be difficult to +overstate the importance of ensuring that they can each contribute +confidently and efficiently. +- **Introspection**: Because the nodes are explicitly communicating with + each other via topics, we can listen in. So when the robot fails to +chase the red ball, and we think that the problem is in the blob finder, +we can use developer tools to visualize, log, and play back that nodes +inputs and outputs. The ability to introspect a running system in this +way is instrumental to being able to debug it. +- **Fault tolerance**: Say that the target follower node crashes because + of a bug. If it is running in its own process, then that crash will +not bring down the rest of the system, and we can get things working +again by just restarting the target follower. In general with ROS we +have the choice to run nodes in separate processes, which allows for +such fault tolerance, or run them together in a single process, which +can provide higher performance (and of course we can mix and match the +two approaches). +- **Language independence**: It can happen that our blob finder expert + writes her computer vision code in C++, while our target follower +expert is dedicated to Python. We can accommodate those preferences +easily by just running those nodes in separate processes. In ROS, it is +perfectly reasonable, and in fact quite common, to mix and match the use +of languages in this way. + +## Beyond topics: services, actions, and parameters + +Most ROS data flow over topics, which we introduced in the previous +sections. Topics are best for streaming data, which includes a lot of +the common use cases in robotics. For example, going back to our +ball-chasing robot, most cameras will naturally produces a stream of +images at some rate, say, 30Hz. So it makes sense for the camera driver +to publish the ROS messages containing those images just as soon as +they're received. Then the blob finder will be receiving image messages +at 30Hz, so it might as well publish its ball location messages at the +same rate, and so on, through the target follower to the motor driver. +We might say that such a systems is *clocked from the camera*: the data +rate of the primary sensor, the camera in this case, drives the rate of +computation of the system, with each node reacting in to receipt of +messages published via topics by other nodes. This approach is fairly +common and is appropriate for system like our ball-chasing robot. There +is no reason to do any work until you have a new camera image, and once +you have one you want to process it as quickly as possible and then +command an appropriate steering direction in response. + +(We are making various simplifying assumptions, including that there is +sufficient computational capacity to run all the nodes fast enough to +keep up with the camera's data rate; that we do not have a way to predict +where the ball is going in between camera frames; and that the motors +can be commanded at the same rate the camera produces images.) + +### Services + +So topics get the job done for the basic ball-chasing robot. But now say +that we we want to add the ability to periodically capture an +ultra-high-resolution image. The camera can do it, but it requires +interrupting the usual image stream that we rely on for the application, +so we only want it to happen on demand. This kind of interaction is a +poor fit for the publish-subscribe pattern of a topic. Fortunately, ROS +also offers a request-reply pattern in a second concept: *services*. + +A ROS service is form of remote procedure call (RPC), a common concept +in distributed systems. Calling a ROS service is similar to calling a +normal function in a library via a code API. But because the call may be +dispatched to another process or even another machine on the network, +there is more to it than just copying pointers around. Specifically, a +ROS service is implemented using a pair of ROS messages: a *request* and +a *reply*. The node calling the service populates the request message +and sends it to the node implementing the service, where the request is +processed, resulting in a reply message that is sent back. + +We might implement the new high-res snapshot capability like so: + +- **Define a new service type.** Because services are less widely used than + topics, there are relatively few "standard" service types predefined. +In our case, the new service's request message might include the desired +resolution of the snapshot. The request message could be a standard +`sensor_msgs/Image`. +- **Implement the service.** In the camera driver, we would + advertise the newly defined service so that when a request is +received, the usual image-handling is interrupted temporarily to allow +the device interaction necessary to grab one high-res snapshot, which is +then packed into a reply message and sent back to the node that called +the service. +- **Call the service.** In the target follower node, we might add a + timer so that every 5 minutes, it calls the new service. The target +follower would receive the high-res snapshot in response to each call, +and could then, say, add it to a photo gallery on disk. + +In general, if you have a need for infrequent, on-demand interactions +among nodes, ROS services are a good choice. + +### Actions + +Sometimes, when building robot control systems, there is a need for an +interaction that looks like request-reply, but that can require a lot of +time between the request and the reply. Imagine that we want to wrap up +our ball-chasing control system into a black box that can be invoked as +part of a larger system that makes the robot play football. In this +case, the higher level football controller will periodically want to +say, "please chase the red ball until you have it right in front of +you." Once the ball is in front of the robot, the football controller +wants to stop the ball-chasing controller and invoke the ball-dribbling +controller. + +We *could* achieve this kind of interaction with a ROS service. We could +define a chase-ball service and implement it in the target follower. +Then the football controller could call that service when it wants the +ball chased. But ball-chasing may take quite some time to complete, and +it may fail to complete. Unfortunately, after calling the chase-ball +service, the football controller is stuck waiting for the reply, similar +to the situation in which you call a long-running function in code. The +football controller does not know how well (or poorly) the chase is +going, and it cannot stop the chase. + +For such goal-oriented time-extended tasks, ROS offers a third concept +that is similar to services but more capable: *actions*. A ROS action is +defined by three ROS messages: a goal, a result, and feedback. The goal, +sent once by the node calling the action to initiate the interaction, +indicates what the action is trying to achieve; for ball-chasing it +might be the minimum required distance to the ball. The result, sent +once by the node implementing the action after the action is complete, +indicates what happened; for ball-chasing it might be final distance to +the ball after the chase. The feedback, sent periodically by the node +implementing the action until it is complete, updates the caller on how +things are going; for ball-chasing it might be the current distance to +the ball during the chase. In addition, actions are cancelable, so the +football controller can decide to give up and move onto another tactic +if the case is taking too long or if the feedback messages are showing +that there is little chance of success. + +In general, if you want to support on-demand long-running behaviors, ROS +actions are a good choice. + +### Parameters + +Any nontrivial system requires configuration, and ROS is no exception. +When we start our robot's motor driver node, how do we tell it to +connect to the motors via `/dev/ttyUSB1`? We do not want to hard-code +that information into the node, because on the next robot it might be +`/dev/ttyUSB0` instead. ROS addresses such configuration needs via a +fourth concept: *parameters*. A ROS parameter is what you might expect: +a named, typed, place to store a piece of data. For example, the motor +driver node may define a parameter called `serial_port` with type +string. When it starts up, the node would use the value of that +parameter to know which device to open to get to the motor system. + +ROS parameters can be set in a few ways: + +- **Defaults.** A ROS node that uses a parameter must embed in its code + some default value for that parameter. In the case that nothing else +in the system sets the parameter value explicitly, the node needs some +value to work with. +- **Command-line.** There is standard syntax for setting parameter + values on the command-line when launching a node. Values set in this +manner override defaults in the code. +- **Launch files.** When launching nodes via the `launch` tool instead + of manually via the command-line, you can set parameter values in the +launch file. Values set in this manner override defaults in the code. +- **Service calls.** ROS parameters are dynamically reconfigurable via a + standard ROS service interface, allowing them to be changed on the +fly, if the node hosting the parameters allows it. Values set in this +manner override whatever previous values were set. + +For most nodes, parameter management is relatively simple: define a +handful of parameters, each with a reasonable default; retrieve the +parameters' values at startup, which accounts for changes made via +command-line or launch file; then begin execution and disallow future +changes. This pattern makes sense for the motor driver, which needs to +know which `/dev/ttyUSB` device file to open at startup, and does not +support changing that setting later. But there are cases that require +more sophisticated handling. For example, the blob finder node may +expose as parameters a variety of thresholds or other settings that +configure how it identifies the red ball in images. These kinds of +settings can be changed on the fly, which the target follower might want +to do, based on how well the chase is going. In this case the blob +finder needs to be sure to use the latest values for its parameters, +knowing that they may have been changed by another node. + +In general, when you want to store stable, but possibly changeable, +configuration information in a node, ROS parameters are a good choice. + +## Asynchrony in code: callbacks + +Throughout ROS, you will see a common pattern in the code, which is the +use of *callback functions*, or simply *callbacks*. For example, when +subscribing to a topic, you supply a callback, which is a function that +will be invoked each time your node receives a message on that topic. +Similarly, when you advertise a service, you supply a callback that is +invoked when the service is called. The same goes for actions (for +handling of goals, results, and feedback) and parameters (for handling +of setting new values). + +Programming with callbacks is not familiar to everyone. It differs from the +standard sequential presentation of programming, in which you write a `main()` +function that does A, then B, then C, and so on. By contrast, in ROS (and in +most systems that focus on data-processing and/or control), we follow an +event-based pattern. In this pattern, we do A whenever X happens, B whenever Y +happens, and so on. + +A common structure for a ROS node is the following: + +- **Get parameter values.** Retrieve the node's configuration, + considering defaults and what may have been passed in from outside. +- **Configure.** Do whatever is necessary to configure the node, like + establish connections to hardware devices. +- **Set up ROS interfaces.** Advertise topics, services, and/or actions; + and subscribe to services. Each of these steps supplies a callback +function that is registered by ROS for later invocation. +- **Spin.** Now that everything is configured and ready to go, hand + control over to ROS. As messages flow in and out, ROS will invoke the +callbacks you registered. + +Following this structure, a `main()` function in a ROS node is often very +short: initialize and configure everything, then call a spin function to let +ROS take over. When you are trying to understand what is happening in a ROS +node, look in the callbacks; that is where the real work is happening. diff --git a/src/ros2_fodder.txt b/src/ros2_fodder.txt new file mode 100644 index 0000000..8ef83c0 --- /dev/null +++ b/src/ros2_fodder.txt @@ -0,0 +1,1035 @@ +Pull from slide content below + +Getting Help! + +You should now be ready for the class! + +Some Nomenclature as we Begin + +- **Package** -- A collection of code. +- **Workspace** -- A workspace is a collection of source code / ROS + packages that will run on a robot. It has a uniform directory + structure. A good analogy is python virtual env, or "project" in + most IDEs. +- **Overlay** -- A second workspace with more/different/new packages. + If there are multiple versions of a package/code then the one at the + bottom is used. +- **Underlay** -- The workspace, underneath an overlay, we're aware + this is confusing. +- **Colcon** -- The ROS 2 build tool. Think of it as a layer above + CMake/Make/SetupTools that helps these tools work together smoothly. + +This is a bit confusing. You may ask yourself why we have our own build +tool. The short of it is that the ROS ecosystem consists of tens of +thousands of developers, working on thousands of packages, across a +handful of platforms, using multiple languages. We needed a flexible +system to build code and one didn't exist at the time, and still doesn't +exist. + +Let's Get Started + +As we're diving headfirst into ROS our first job is to checkout a +repository of examples and build it. Roughly the steps to do this are as +follows. + +- Fire up a terminal manager inside the container. I use byobu. You + can use whatever you want. You can also fire up 3 real terminals and + call ade enter on them. +- Source the ROS setup.bash file so we have the right version of ROS + in our path. +- Make a workspace called ros2\_example\_ws. We usually use \_ws to + indicate a workspace. +- Clone an example repository and change to the dashing branch. + - Generally ROS repos have a branch per release. +- Use Colcon to build the source. + +``` {.sourceCode .bash} +source /opt/ros/dashing/setup.bash +mkdir -p ~/ros2_example_ws/src +cd ~/ros2_example_ws +git clone https://github.com/ros2/examples src/examples +cd ~/ros2_example_ws/src/examples/ +git checkout dashing +cd ~/ros2_example_ws +colcon build --symlink-install +``` + +Nodes and Publishers + +- The core of ROS is the ROS pub/sub bus. In ROS parlance this is + called topic. + - A topic has a message type that is published on the bus. These + messages are defined in a yaml file and define the + serialization/deserialization format for ROS messages. + - ROS has a lot of built in message types. There are lots of + pre-defined messages for controlling a robot, distributing + sensor data, and understanding the geometry of your robot. + - ROS publishers produce messages and slowly or as quickly as they + need to. + - A ROS subscriber, subscribes to a topic and then does things + with the information. +- ROS has lots of built-in tools for managing topics. You can list + them, echo (watch) them, rename them (called remap), and store them + to file (called bagging). +- ROS Nodes are basically programs, or processes that run concurrently + on ROS. + - A ROS node can publish to one or more topics. + - That same node can subscribe to other topics. + - Many nodes subscribe to topics, process the data, and publish + the results. + - ROS has tooling to start and stop multiple nodes at the same + time. + +Preparing to Run a ROS Node + +- Open a new terminal, in Byobu you can do this by pressing F2. +- First we need to source the setup.bash file for our workspace. This + will help ROS find the programs we built. + - source ./ros2\_example\_ws/install/setup.bash + - Protip: you can find any file using + find ./ -name <file name> +- **ROS Best Practice** *ALWAYS* build and execute in different + terminals. + - The build terminal should source the global ROS setup.bash file + (i.e. /opt/ros/dashing/setup.bash). + - The execution terminal should source the setup.bash of your + workspace + - This is a common failure mode for new users. If something seems + weird or funky. Create a new terminal and source the correct + bash file. + +Let's Run a Simple C++ Publisher Node. + +- ROS has an advanced, and fairly complex CLI interface. We'll cover + it in depth in our next lesson. +- We are going to ask ros to run the EXECUTABLE publisher\_lambda in + our WORKSPACE named examples\_rclcpp\_minimal\_publisher. +- The syntax for doing this is + ros2 run <WORKSPACE> <EXECUTABLE> +- To run our publishing node, let's run the following command in our + execution terminal: + ros2 run examples\_rclcpp\_minimal\_publisher publisher\_lambda +- If everything works you should see something like this: + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 run examples_rclcpp_minimal_publisher publisher_lambda +[INFO] [minimal_publisher]: Publishing: 'Hello, world! 0' +[INFO] [minimal_publisher]: Publishing: 'Hello, world! 1' +[INFO] [minimal_publisher]: Publishing: 'Hello, world! 2' +[INFO] [minimal_publisher]: Publishing: 'Hello, world! 3' +... +``` + +- To exit the program press CTRL-C + +What just happened? + +- We just executed a ROS node that publishes a simple string message + to a topic called /topic twice a second. +- I'll show you how I know this with some tools. We'll cover these + tools in detail next time. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 topic list +/parameter_events +/rosout +/topic +kscottz@ade:~$ ros2 topic echo /topic +data: Hello, lambda world! 63 +--- +data: Hello, lambda world! 64 +--- +data: Hello, lambda world! 65 +--- +kscottz@ade:~$ ros2 topic hz /topic +average rate: 2.000 +min: 0.500s max: 0.500s std dev: 0.00011s window: 4 +kscottz@ade:~$ +``` + +Digging into the Code + +- Let's take a look at the code. Like a lot of software there is more + than one way to skin a cat. Let's look at the member function + approach. +- Using your favorite editor open the following source file, + ./ros2\_example\_ws/src/examples/rclcpp/minimal\_publisher/member\_function.cpp +- **rclcpp** is an abbreviation of "ROS Client Library C++", its the + ROS C++ API + +``` {.sourceCode .c++} +#include +#include + +#include "rclcpp/rclcpp.hpp" // THIS the header file for ROS 2 C++ API +#include "std_msgs/msg/string.hpp" // This is header for the messages we + // want to user + // These are usually auto generated. + +using namespace std::chrono_literals; + +/* This example creates a subclass of Node and uses std::bind() to register a +* member function as a callback from the timer. */ + // Make a class called Minimal Publisher + class MinimalPublisher : public rclcpp::Node + // Have it inherit from the ROS Node Class +``` + +Let's Build our Node's Constructor + +- The MinimalPublisher constructor inherits from the RCLCPP Base + Class, gives the name a node, and sets our counter. +- The next line creates a publisher object that publishes + std\_msgs::msg. +- The constructor then creates a callback to the function + timer\_callback that gets called every 500ms. + +``` {.sourceCode .c++} +class MinimalPublisher : public rclcpp::Node // Inherit from ROS Node +{ + public: + MinimalPublisher() + : Node("minimal_publisher"), count_(0) // Set the node name + { //Create a publisher that pushes std_msgs::msg to the topic "topic" + publisher_ = this->create_publisher("topic", 10); + timer_ = this->create_wall_timer( // Call timer_callback every 500ms + 500ms, std::bind(&MinimalPublisher::timer_callback, this)); + } +``` + +Now to Handle the Callback + +- In the callback function we do the following: + - Create the ROS std\_msgs::msg::String() to send to our topic. + - Construct the message that will be pushed to the ROS Topic + - Log the results. + - Actually publish the newly constructed message. + +``` {.sourceCode .c++} +private: +void timer_callback() +{ + auto message = std_msgs::msg::String(); // create message + message.data = "Hello, world! " + std::to_string(count_++); // Fill it up + RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); // Log it + publisher_->publish(message); // Publish +} +// Create our private member variables. +rclcpp::TimerBase::SharedPtr timer_; +rclcpp::Publisher::SharedPtr publisher_; +size_t count_; +``` + +Finally, Let's Create the Main for our Node + +- This last little bit creates the main node entry point. +- Initializes rcpcpp with the values from the command line. +- Run's the MinimalPublisher, until a terminate is given +- Finally the node cleans up everything and exits. + +``` {.sourceCode .c++} +int main (int argc, char * argv[]) +{ + rclcpp::init(argc, argv); // Init RCL + rclcpp::spin(std::make_shared());// Run the minimal publish + rclcpp::shutdown(); // Cleanup on shut down. + return 0; +} +``` + +Exercise: Modify and Build this Node + +- Let's try to make a few modification to our node for practice. + - Make it run at 10Hz (100ms) instead of 500. + - Change the topic name from "topic" to "greetings." + - Change the message "Hello Open Road." + - Change the node name from minimal\_publisher, + revenge\_of\_minimal\_publisher +- Once you make these changes + - Save the file. + - Toggle over to your execution window run + - Run colcon build + - In your execution window run + ros2 run examples\_rclcpp\_minimal\_publisher publisher\_member\_function + +Let's Try Subscribing. + +- The pattern here is similar to publishing. +- We basically inherit from the Node class, and define the topic and + message we want. +- Whenever that topic is published we hit a callback. +- If everything is correctly configured the file is at + - /ros2\_example\_ws/src/examples/rclcpp/minimal\_subscriber/member\_function.cpp + +``` {.sourceCode .c++} +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" +using std::placeholders::_1; +// Again we inherit the public interface of a ROS node. +class MinimalSubscriber : public rclcpp::Node +{ + public: + MinimalSubscriber() // Construct our node, calling it minimal_subscriber + : Node("minimal_subscriber") + { // Create a subscription, to messages of the format stdmsg:msg:String + subscription_ = this->create_subscription( + // Subscribe to the topic, "topic" and set a callback for when things are pub'd + "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)); + } + ... +``` + +More Subscriber + +- The subscriber node looks fairly similar to our publisher but + instead of publishing on a regular callback, we get a callback when + a new message hits our topic. + +``` {.sourceCode .c++} +private: + // Whenever we get a new messaged published on our topic + // this callback will be executed. + void topic_callback(const std_msgs::msg::String::SharedPtr msg) const + { + // Log the message that we are subscribed to + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + } + rclcpp::Subscription::SharedPtr subscription_; +}; + +// This is effectively the same boiler plate from last time. +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} +``` + +Let's Modify the Subscriber + +- In the publisher we changed the name of our publisher topic to + greetings. +- Let's change the subscribed topic to greetings. +- Note that there are a lot of ways to change topic names, modifying + source is just one approach. Often we just remap topics instead of + changing source. +- Once you have modified the subscriber run colocon build (it will + build everything) +- Open another terminal, source the bash file, and start the + publisher. + - ros2 run examples\_rclcpp\_minimal\_publisher publisher\_member\_function +- Now run our subscriber. + - ros2 run examples\_rclcpp\_minimal\_subscriber subscriber\_member\_function + +The Result + +If everything went well you should have two screens. The first screen +with the publisher should be spitting out the following + +``` {.sourceCode .bash} +[INFO] [revenge_of_minimal_publisher]: Publishing: 'Hello, Open Road! 1000' +[INFO] [revenge_of_minimal_publisher]: Publishing: 'Hello, Open Road! 1001' +[INFO] [revenge_of_minimal_publisher]: Publishing: 'Hello, Open Road! 1002' +[INFO] [revenge_of_minimal_publisher]: Publishing: 'Hello, Open Road! 1003' +[INFO] [revenge_of_minimal_publisher]: Publishing: 'Hello, Open Road! 1004' +``` + +The subscriber screen should be pushing out: + +``` {.sourceCode .bash} +[INFO] [minimal_subscriber]: I heard: 'Hello, Open Road! 1000' +[INFO] [minimal_subscriber]: I heard: 'Hello, Open Road! 1001' +[INFO] [minimal_subscriber]: I heard: 'Hello, Open Road! 1002' +[INFO] [minimal_subscriber]: I heard: 'Hello, Open Road! 1003' +[INFO] [minimal_subscriber]: I heard: 'Hello, Open Road! 1004' +``` + +**You can terminate both of these programs with CTRL-C** + +*Congratulations, you now know the three most important ROS components, +nodes, publishers, and subscribers.* + +Making Things Happen with Services + +- Publishing and subscribing nodes are the bread and butter of ROS. + This pattern is great for moving around a lot of data, and + processing it quickly. +- However, we often want our robots to respond to data. To construct + simple behaviors in ROS we use services. +- A service is a robotic task that can be performed *synchronously*, + which is just a fancy word for, "while you wait". +- A good analogy for services would be a regular old function call. In + most programs when you call a function, the code making the call + waits for the function to return before proceeding. +- A few toy examples of services for autonomous driving would be: + - Turning Lights Off/On. + - Checking a sensor and returning the results. + - Lock / Unlock a door or window. + - Beeping a horn. +- Services can be called via the command line or through an API call + within another node. +- In ROS services are hosted within a ROS Node, and they can co-exist + with other services as well as publishers and subscribers. + +C++ Service Example + +- As a toy example of a ROS service we are going to make a node that + offers an "AddTwoInts" service. +- What will happen is the service has two inputs, and returns a single + output. +- There is a full tutorial [about the process + here](https://index.ros.org/doc/ros2/Tutorials/Writing-A-Simple-Cpp-Service-And-Client/). + It goes into more detail and it is worth looking at. + +Let's start by looking at a prebuilt srv file for this tutorial. If you +were writing this service from scratch you would need to build this srv +file yourself, but for this example there is one ready for us already. +We'll use less to peek into the srv file. + +Run the following: +less /opt/ros/dashing/share/example\_interfaces/srv/AddTwoInts.srv + +The file should have the following: + +``` {.sourceCode .yaml} +int64 a # <== An input, of type int64, called a +int64 b # <== An input, of type int64, called b +--- +int64 sum # <== An output, of type int64, called sum +``` + +Defining A Service + +Essentially our service is a remote procedure call of a function that +looks like this in pseudocode: int64 sum = AddTwoInts(int64 a, int64b);. + +Let's take a look at the C++ code that defines the service. Use your +favorite text editor to open the following file: +./ros2\_example\_ws/src/examples/rclcpp/minimal\_service/main.cpp. + +``` {.sourceCode .C++} +// This hpp file is autogenerated from the srv file. +#include "example_interfaces/srv/add_two_ints.hpp" +#include "rclcpp/rclcpp.hpp"// ROS header. +// Scope resolution to our services. +using AddTwoInts = example_interfaces::srv::AddTwoInts; +// shared pointer to logger +rclcpp::Node::SharedPtr g_node = nullptr; +// Perform the service call +void handle_service( + const std::shared_ptr request_header,// Header with timestamp etc + const std::shared_ptr request, // This is the input, two int64 a,b + const std::shared_ptr response) // This response is int64 sum +{ + (void)request_header; + RCLCPP_INFO( // Logger message. + g_node->get_logger(), + "request: %" PRId64 " + %" PRId64, request->a, request->b); + response->sum = request->a + request->b; // the actual function. +``` + +> } + +ROS 2 Service Main + +``` {.sourceCode .C++} +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + // get global ROS pointer + g_node = rclcpp::Node::make_shared("minimal_service"); + // Create a service, of type AddTwoInts, named add_two_ints, that points to handle_service + auto server = g_node->create_service("add_two_ints", handle_service); + rclcpp::spin(g_node); // run until shutdown + rclcpp::shutdown(); + g_node = nullptr; + return 0; +} +``` + +The main entry point is pretty simple. It does the following. + +- Initialize the program. +- Get a shared pointer to the ROS node interface. +- Create the service, of type AddTwoInts, named add\_two\_ints, + pointing to the function handle\_service. +- Run the node until shutdown. + +Let's Build and Run our Service + +First we will fire up our service! The syntax for this is +ros2 run <pkg> <program>. + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 run examples_rclcpp_minimal_service service_main +``` + +At this point nothing should happen. We need to *call* the service. To +do that we'll use a command line tool that's a little... long. + +We'll talk about this more in the next lesson, but the syntax is +roughly, +ros2 service call <service\_name> <service\_call\_format> <actual\_data>. + +In this case our service name is /add\_two\_ints and the data type can +be found in example\_interfaces/AddTwoInts, and the input is yaml +encased in quotation marks. Move over to a new terminal and enter the +following: + +``` {.sourceCode .bash} +kscottz@ade:~/ros2_example_ws$ ros2 service call /add_two_ints example_interfaces/AddTwoInts "{a: 1, b: 1}" +waiting for service to become available... +requester: making request: example_interfaces.srv.AddTwoInts_Request(a=1, b=1) + +response: +example_interfaces.srv.AddTwoInts_Response(sum=2) +``` + +Now switch back to your original terminal, you should see something like +this: + +``` {.sourceCode .bash} +kscottz@ade:~$ ros2 run examples_rclcpp_minimal_client client_main +3[INFO] [minimal_service]: Incoming request +a: 1 b: 1 +``` + +Congratulations, you just made your first service call! + +Using a Service in Code + +We just called our service from the command line to test it, but more +often than not we would want to do this in source code. + +Let's look at an example of how to do that. In your editor or using less +take a look at the following file: +/home/kscottz/ros2\_example\_ws/src/examples/rclcpp/minimal\_client/main.cpp + +``` {.sourceCode .C++} +// snipped +#include "example_interfaces/srv/add_two_ints.hpp" // include the service header file. +#include "rclcpp/rclcpp.hpp" +// Scope resolution on underlying call signature. +using AddTwoInts = example_interfaces::srv::AddTwoInts; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); // init ROS C++ interface. + auto node = rclcpp::Node::make_shared("minimal_client"); // shared node memory. + auto client = node->create_client("add_two_ints"); // create client interface. + while (!client->wait_for_service(std::chrono::seconds(1))) {// poll for service to come online + if (!rclcpp::ok()) { // if service doesn't come online, exit gracefully + RCLCPP_ERROR(node->get_logger(), + "client interrupted while waiting for service to appear."); + return 1; + } + RCLCPP_INFO(node->get_logger(), "waiting for service to appear..."); +} +``` + +C++ Service Client Part Deux + +``` {.sourceCode .C++} +// shared memory to request +auto request = std::make_shared(); +request->a = 41; // set the input values +request->b = 1; // set the input values +auto result_future = client->async_send_request(request); // Send the request +if (rclcpp::spin_until_future_complete(node, result_future) != // spin until result + rclcpp::executor::FutureReturnCode::SUCCESS) +{ + RCLCPP_ERROR(node->get_logger(), "service call failed :("); + return 1; +} +auto result = result_future.get(); // Get the result +RCLCPP_INFO(node->get_logger(), "result of %" PRId64 " + %" PRId64 " = %" PRId64, + request->a, request->b, result->sum); // print the result +``` + +> rclcpp::shutdown(); // shutdown return 0; } + +Let's Run Our Client + +- Now we're going to run our service and then call it from the client. +- You'll need two terminals to do this. Remember F2/F3 let you open + and switch to a new terminal in ADE. + +First fire up your service if it isn't already running. + +``` {.sourceCode .bash} +$ ros2 run examples_rclcpp_minimal_service service_main +``` + +Now start the client in a second terminal. + +``` {.sourceCode .bash} +$ ros2 run examples_rclcpp_minimal_client client_main +[INFO] [minimal_client]: Result of add_two_ints: for 41 + 1 = 42 +``` + +The client should fire off a request right away. You can see the result. + +Finally, toggle back to the service. + +``` {.sourceCode .bash} +$ ~/ros2_example_ws$ ros2 run examples_rclcpp_minimal_service service_main +[INFO] [minimal_service]: Incoming request +a: 41 b: 1 +``` + +You can see the debug input has been printed to the terminal. + +ROS C++ Actions + +- Actions are ROS / ROS 2's answers to asynchronous remote procedure + calls. +- Notice how quickly how fast our service call happened. It was more + or less instant. +- Actions are the preferred approach for things that may not happen + instantaneously. +- The canonical example of a ROS Action would be sending the robot a + command to navigate to a way point. +- The process of navigation is going to take a bit of time, what we + want to do is to kick off the process, wait for updates, and then + once things are complete we get a result. +- Just like services there are two parts of an action. The action + server and the action client. *Note that there can be more than one + client.* +- Actions become fairly complex as they can serve multiple clients. + This means the action may need to keep track of multiple concurrent + connections. + - Since action servers can get overwhelmed by requests, they need + to *accept* every request before proceeding to process it. + - The clients can also *cancel* at any time, so that needs to be + handled. + +Parts of an Action + +- Find the action. An action server may be down! +- The Action Request -- the service *can* decline to take an action. +- The Action being accepted. +- The Action being canceled. Sometimes the client changes its mind. +- The action "feedback", sending back info from server to client. +- Send the result to the client -- the result could be the thing + happened succesfully, or not! + +Fibonacci Action + +For our action server we're going to create a toy example, this example +will calculate the Nth number in the [Fibonacci +series](https://en.wikipedia.org/wiki/Fibonacci_number). So, what will +happen when we call this toy action? + +- We will call the action with a single integer indicating the + sequence number of the Fibonacci number we want. +- The action will update us as it calculates the sequence of numbers + and update it us as it calculates a new one. +- When the action gets to our desired number in the sequence, it will + return the results. +- For example, if we called action with the input 7, we would get the + seventh Fibonacci number. Which means, given the series <0, 1, 1, + 2, 3, 5, 8>, would be the number 8. +- The action should update us along the way in the calculation. It + should return the series of numbers every time it calculates a new + number. + +Action Definition Files + +- Actions use a definition file to build all of the ROS boiler plate + like cross language header/definition files for use in multiple + programming languages. +- These action files are written in YAML and use the \*.action suffix. +- The ROS meta build system colcon will use these action files to + auto-magically generate all of the header files. + +Let's take a look at an action file. + +``` {.sourceCode .bash} +/opt/ros/dashing/share/example_interfaces/action/Fibonacci.action +# Goal -- the input, the order we want like 7 +int32 order +--- +# Result -- the *final result*, here the list of values 0,1,1,2,3,5,8.... +int32[] sequence +--- +# Feedback -- the *intermediate result* so <0>,<0,1>,<0,1,1>,<0,1,1,2> ... +int32[] sequence +Fibonacci.action (END) +``` + +Really quick, let's look under the hood! + +As we said previously, the \*.action is used to auto-generate a bunch of +other files. We can see this if we go down one directory to msg. + +What we'll see is that the \*.action file is used to generate a bunch of +ROS topic messages mapping to states in our action. + +Essentially a ROS action is built upon ROS nodes and ROS topics + +``` {.sourceCode .bash} +kscottz@ade:/opt/ros/dashing/share/example_interfaces/action/msg$ cd ~/ +kscottz@ade:~$ cd /opt/ros/dashing/share/example_interfaces/action/msg/ +kscottz@ade:/opt/ros/dashing/share/example_interfaces/action/msg$ ls +FibonacciActionFeedback.msg FibonacciAction.msg FibonacciFeedback.msg FibonacciResult.msg +FibonacciActionGoal.msg FibonacciActionResult.msg FibonacciGoal.msg +kscottz@ade:/opt/ros/dashing/share/example_interfaces/action/msg$ less FibonacciActionGoal.msg +# This file is automatically generated by rosidl-generator +std_msgs/Header header +actionlib_msgs/GoalID goal_id +FibonacciGoal goal +FibonacciActionGoal.msg (END) +kscottz@ade:/opt/ros/dashing/share/example_interfaces/action/msg$ cd ~ +``` + +Let's take a look at Action Server + +- Let's take a look at how our Fibonacci action server. +- Use your favorite text editor to open: + /home/kscottz/ros2\_example\_ws/src/examples/rclcpp/minimal\_action\_server/member\_functions.cpp + +``` {.sourceCode .C++} + +class MinimalActionServer : public rclcpp::Node +{ +public: // Pre-defined interface files. + using Fibonacci = example_interfaces::action::Fibonacci; + using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle; + explicit MinimalActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) + : Node("minimal_action_server", options) + { + using namespace std::placeholders; + // SCARY call to define that this is a Fib. action and what functions + // attach to what events in the action lifecycle. + this->action_server_ = rclcpp_action::create_server( + this->get_node_base_interface(), // The action server is basically + this->get_node_clock_interface(), // a node and we need return pointers + this->get_node_logging_interface(), // to all of standard interaces. + this->get_node_waitables_interface(), + "fibonacci", // and bind our member functions to topic events. + std::bind(&MinimalActionServer::handle_goal, this, _1, _2), + std::bind(&MinimalActionServer::handle_cancel, this, _1), + std::bind(&MinimalActionServer::handle_accepted, this, _1)); + } +``` + +Actions: Accept or Cancel + +Let's deal with accepting a goal, or canceling a goal. + +``` {.sourceCode .C++} +private: + rclcpp_action::Server::SharedPtr action_server_; + + rclcpp_action::GoalResponse handle_goal( + const rclcpp_action::GoalUUID & uuid, // Each request gets a UUID + std::shared_ptr goal) // The goal object + { + RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order); + (void)uuid; + // Let's reject sequences that are over 9000 + if (goal->order > 9000) { + return rclcpp_action::GoalResponse::REJECT; + } // respond with "yes, we'll process this request. + return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; +} + +rclcpp_action::CancelResponse handle_cancel( + const std::shared_ptr goal_handle) +{ + RCLCPP_INFO(this->get_logger(), "Received request to cancel goal"); + (void)goal_handle; + return rclcpp_action::CancelResponse::ACCEPT; +} +``` + +The Meat of the Fib Function + +``` {.sourceCode .C++} +void execute(const std::shared_ptr goal_handle) +{ // This is the meaty part of the function + RCLCPP_INFO(this->get_logger(), "Executing goal"); + rclcpp::Rate loop_rate(1); + const auto goal = goal_handle->get_goal(); // this is our goal value + auto feedback = std::make_shared(); // this is our feedback object + auto & sequence = feedback->sequence; // this is our list of fib values. + sequence.push_back(0); + sequence.push_back(1); + auto result = std::make_shared(); // This is the final result. + + // Do fib as long as ROS is ok! + for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) { + // Check if there is a cancel request + if (goal_handle->is_canceling()) { // Handle a cancel result! + result->sequence = sequence; + goal_handle->canceled(result); + RCLCPP_INFO(this->get_logger(), "Goal Canceled"); + return; + } + // Update sequence + sequence.push_back(sequence[i] + sequence[i - 1]); + // Publish feedback + goal_handle->publish_feedback(feedback); + RCLCPP_INFO(this->get_logger(), "Publish Feedback"); + + loop_rate.sleep(); + } + + // Check if goal is done + if (rclcpp::ok()) { + result->sequence = sequence; + goal_handle->succeed(result); + RCLCPP_INFO(this->get_logger(), "Goal Succeeded"); + } +``` + +> } +> +> void handle\_accepted(const +> std::shared\_ptr<GoalHandleFibonacci> goal\_handle) { // Our +> request was accepted, fire off a new thread. using namespace +> std::placeholders; // this needs to return quickly to avoid blocking +> the executor, so spin up a new thread +> std::thread{std::bind(&MinimalActionServer::execute, this, \_1), +> goal\_handle}.detach(); } + +Let's Put our Class into an Executable + +``` {.sourceCode .C++} +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + + auto action_server = std::make_shared(); + + rclcpp::spin(action_server); + + rclcpp::shutdown(); + return 0; +} +``` + +Let's Run Our Action and Call It. + +- Ordinarily you would call colcon build in your workspace to build + the source code. We're just inspecting this method so this isn't + necessary. +- We'll start the action server and then call it manually using the + ROS 2 CLI. + +``` {.sourceCode .bash} +kscottz@ade:~/ros2_example_ws$ ros2 run examples_rclcpp_minimal_action_server action_server_member_functions +``` + +Now we're going to manually call the server from the ROS 2 CLI. We'll +cover this in more depth in the next lesson. If you're using byobu use +F3 to go to a second terminal or F2 to make a new one. + +``` {.sourceCode .bash} +$ ros2 action send_goal /fibonacci example_interfaces/action/Fibonacci '{order: 10}' +Waiting for an action server to become available... +Sending goal: + order: 10 + +Goal accepted with ID: 0c1b3779c7ea44b69d54c6e1cfac3ff6 + +Result: + sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + +Goal finished with status: SUCCEEDED +``` + +Meanwhile, Back at The Server + +You can use F3 to see what happened to our action and its status +updates. + +``` {.sourceCode .bash} +[INFO] [minimal_action_server]: Received goal request +[INFO] [minimal_action_server]: Executing goal... +[INFO] [minimal_action_server]: Publishing feedback: array('i', [0, 1, 1]) +[INFO] [minimal_action_server]: Publishing feedback: array('i', [0, 1, 1, 2]) +[INFO] [minimal_action_server]: Publishing feedback: array('i', [0, 1, 1, 2, 3]) +[INFO] [minimal_action_server]: Publishing feedback: array('i', [0, 1, 1, 2, 3, 5]) +... SNIP ... +[INFO] [minimal_action_server]: Returning result: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]) +``` + +Action Client + +- Let's take at the client side API implementation. Open the file: + +\~/ros2\_example\_ws/src/examples/rclcpp/ minimal\_action\_client/member\_functions.cpp + +- We'll address the basic implementation but that directory has + additional examples for other use cases and things like canceling an + action mid-process. +- It is worth understanding what we're doing, it is more than sending + just the goal. Roughly this class does the following: + - Check's for a connection to ROS, and the action server. + - Sends the goal. + - Checks that the goal was "accepted" after sending. + - Updates the log/screen as interim feedback gets sent. + - Receives the final results. + +Let's Create A Client Class + +``` {.sourceCode .C++} +#include "example_interfaces/action/fibonacci.hpp" +#include "rclcpp/rclcpp.hpp" +#include "rclcpp_action/rclcpp_action.hpp" +class MinimalActionClient : public rclcpp::Node +{ + public: // looks familiar, pulling in the action interface, and the goal type + using Fibonacci = example_interfaces::action::Fibonacci; + using GoalHandleFibonacci = rclcpp_action::ClientGoalHandle; + + explicit MinimalActionClient(const rclcpp::NodeOptions & node_options = rclcpp::NodeOptions()) + : Node("minimal_action_client", node_options), goal_done_(false) + { // Create a client interface. + this->client_ptr_ = rclcpp_action::create_client( + this->get_node_base_interface(), + this->get_node_graph_interface(), + this->get_node_logging_interface(), + this->get_node_waitables_interface(), + "fibonacci"); + // Create a time and have callback to send goal in 500ms + this->timer_ = this->create_wall_timer( + std::chrono::milliseconds(500), + std::bind(&MinimalActionClient::send_goal, this)); + } +``` + +Sending the Goal + +Our client constructor above set a time to call send\_goal after 500ms. +We'll bind our member functions to the action events and then send the +goals. + +Handling the Responses + +Next up we create our private member variables and define the functions +that get called with the goal response and the periodic feedback. + +``` {.sourceCode .C++} +private: + rclcpp_action::Client::SharedPtr client_ptr_; + rclcpp::TimerBase::SharedPtr timer_; + bool goal_done_; + // handle the response to our request + void goal_response_callback(std::shared_future future) + { + auto goal_handle = future.get(); + if (!goal_handle) { + RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server"); + } else { + RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result"); + } + } + // handle the feedback calls, these should be the format of feedback. + void feedack_callback( + GoalHandleFibonacci::SharedPtr, + const std::shared_ptr feedback) + { + RCLCPP_INFO( + this->get_logger(), + "Next number in sequence received: %" PRId64, + feedback->sequence.back()); + } +``` + +Handling The Result + +``` {.sourceCode .C++} +// handle result callback +void result_callback(const GoalHandleFibonacci::WrappedResult & result) +{ + this->goal_done_ = true; + switch (result.code) { + case rclcpp_action::ResultCode::SUCCEEDED: + break; + case rclcpp_action::ResultCode::ABORTED: + RCLCPP_ERROR(this->get_logger(), "Goal was aborted"); + return; + case rclcpp_action::ResultCode::CANCELED: + RCLCPP_ERROR(this->get_logger(), "Goal was canceled"); + return; + default: + RCLCPP_ERROR(this->get_logger(), "Unknown result code"); + return; + } + + RCLCPP_INFO(this->get_logger(), "Result received"); + for (auto number : result.result->sequence) { + RCLCPP_INFO(this->get_logger(), "%" PRId64, number); + } + } +}; // class MinimalActionClient +``` + +Running our Client Class + +Finally the main function that attaches to our node class. It simply +creates a class instance and runs until completion. + +``` {.sourceCode .C++} +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + auto action_client = std::make_shared(); + + while (!action_client->is_goal_done()) { + rclcpp::spin_some(action_client); + } + + rclcpp::shutdown(); + return 0; +} +``` + +Let's Run our Client + +- We'll start the action server the same way as before + +``` {.sourceCode .bash} +kscottz@ade:~/ros2_example_ws$ ros2 run examples_rclcpp_minimal_action_server action_server_member_functions +``` + +Next we'll run our client. + +``` {.sourceCode .bash} +kscottz@ade:~/ros2_example_ws$ ros2 run examples_rclcpp_minimal_action_client action_client_member_functions +[INFO] [minimal_action_client]: Waiting for action server... +[INFO] [minimal_action_client]: Sending goal request... +[INFO] [minimal_action_client]: Goal accepted :) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1]) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1, 2]) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1, 2, 3]) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1, 2, 3, 5]) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1, 2, 3, 5, 8]) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1, 2, 3, 5, 8, 13]) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21]) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]) +[INFO] [minimal_action_client]: Received feedback: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]) +[INFO] [minimal_action_client]: Goal succeeded! Result: array('i', [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]) +``` + +Wrapping Up... + +- We've just seen the set of API primitives upon which most ROS + systems are made. +- Generally speaking, when you build a robot you work from simple to + complex. You build the nodes and topics first, then the services, + and finally the actions. +- While we addressed all of these topics with the C++ API there is an + equivalent Python API that works similarly. +- Moreover, there are additional API primitives that you can check + out. +- All of these examples are in the workspace that we created. +- I would encourage you to modify these examples to build a better + idea of how they work. + +**Next time we'll cover the ROS 2 CLI** + diff --git a/src/ros2_tools_resources.md b/src/ros2_tools_resources.md new file mode 100644 index 0000000..6d4129e --- /dev/null +++ b/src/ros2_tools_resources.md @@ -0,0 +1,248 @@ +ROS Resources +============= + +The most up to date to date information about ROS can be found on the web and +there are a myriad of resources on-line to help you out in your educational or +practical journey. One thing to keep in mind is that ROS, like most software, +has different versions, and the format and structure of commands and API calls +may differ slightly between versions (although the developers try to keep things +as stable as possible). This book is specifically written for _ROS 2, Eloquent +Elusor_, or ROS Eloquent to be terse. While newer or older versions of ROS will +be generally helpful it is worth paying attention to the version number as there +can be minor changes between versions. ROS has both major versions (i.e. ROS +1, and ROS 2) and minor versions denoted by a pair of matching letter adjectives +and specific nouns related to specific genus and species of turtles +(e.g. Eloquent Elusor, or Foxy Fitzroy). The biggest difference in the CLI and +API come between the major versions, i.e. ROS 1 and ROS 2. There may be minor +changes between minor versions, and usually it is the addition of features, not +their modification or removal. It is worth noting that ROS versions are usually +pegged to specific version of Ubuntu Linux. If your search engine results are +specific enough for your particular problem it is a good practice to append your +ROS version to your search. Moreover, when seeking help or posting questions +online you should always specify the version of ROS version you are using. + + +ROS grew up with the modern web, and as such it has a variety of tools and +forums to help you solve problems and learn about the API and tools. Some of our +web resources actually pre-date more widely used systems, so it helps to know +where they are and how to use them. Probably the most important resource on the +web for ROS users is [answers.ros.org](http://anwers.ros.org). Answers is a Q&A +website similar to StackOverflow. Once you register for Answers you can ask or +answer any question ROS related. Be aware that asking a question well can be +difficult. You should include as much information as possible to help others +answer your question. This means you should include the ROS version, any +debugging or stack trace information you have, and the offending source code. + +Aside from ROS Answers you should check out both the ROS 2 tutorials and API +documentation and the ROS 1 wiki. The ROS 1 wiki can be found at +[](http://wiki.ros.org/) and while it is specifically +dedicated to ROS 1, much of the information is still relevant to ROS 2. If you +are searching for up to date ROS 2 information your go to source for this +information is the ROS 2 tutorials and API documents located at +[](https://index.ros.org/doc/ros2/). Many of +the tutorials you will find in this book pull directly from this body of +work. If you would like to find the latest ROS news and discuss various ROS +features the ROS Discourse forum at +[](https://discourse.ros.org/) is your best bet. ROS +discourse is the community hub where developers discuss their latest projects +and debate the finer points of ROS development. + +For ROS application developers there are number of tools to help you connect +with the broader ROS developer community. Open Robotics supports +[index.ros.org](https://index.ros.org/) which is an extended list of ROS +packages sorted by version. If you are searching for a ROS driver for a +particular piece of hardware then the index is a great place to start. If you +find a package with failing tests, or would like to know the build status of any +ROS package at [build.ros.org](http://build.ros.org/). Similarly, for un-indexed +packages [GitHub maintains a ROS code +tag](https://github.com/topics/ros?o=desc&s=updated). This tag will allow you to +search all of the tagged repositories that are publicly listed. At the time of +writing there were close to 4000 repositories listed on github, so there is a +pretty good chance you'll find what you need. + + +Finally, there are a variety of unofficial resources that you should be aware of +that can be useful, particularly if you want to keep yourself up to date with +the latest ROS projects and features. Both [Open +Robotics](https://twitter.com/openroboticsorg) and +[ROS](https://twitter.com/rosorg) maintain twitter feeds to share the latest +news. We also have a yearly ROS developers conference called +[ROSCon](https://roscon.ros.org/2020/); most talks are freely available +on the web. There are a few other resources that can also be useful including the [ROS +subreddit](https://www.reddit.com/r/ROS/) an "unofficial" [ROS Discord](https://discord.com/invite/HnVcz5a). + +Setting Up Your Computer +=== + +For this chapter we assume that you are working on a modern desktop with a +discrete graphics card. While a graphics card isn't necessary for this chapter +later chapters will be graphics intensive and having one will greatly improve +the end user experience. Moreover, this book assumes you are working with the +Ubuntu Linux 20.04 operating system. While other operating systems are supported +by ROS 2, all of the tutorials and instructions on this book assume you are +running Linux. If instead use a Mac or Windows PC you can either install ROS 2 +Eloquent Elusor using the instructions found in the [ROS 2 installation +instructions](https://index.ros.org/doc/ros2/Installation/Eloquent/). An +alternative path for installation on Mac and PC is to using a virtual +machine. Roughly the process for doing so is as follows: + +1. Install virtual machine software like [Virtual + Box](https://www.virtualbox.org/) or + [VMWare](https://www.vmware.com/products/workstation-pro.html). On your host + machine. +1. Create a virtual machine using the software, and install [Desktop Ubuntu 18.04 Bionic + Beaver from the Canonical website.](https://ubuntu.com/download/desktop) + Configure the installation as you wish. +1. Now start your virtual machine and log in as a user. The directions below + should be applicable. + +For these initial tutorials we will be working with the pre-compiled ROS 2: +Eloquent Elusor desktop version. These directions follow directly from the +installation instructions found on the [Eloquent release +page]https://index.ros.org/doc/ros2/Installation/Eloquent/Linux-Install-Debians/). To +run these commands you'll need a terminal window. To open a terminal in Ubuntu +18.04 click on the nine dots in the bottom left hand of the screen. A dialog +should appear. Enter the word _terminal_ and click on the terminal icon to open +a terminal. Alternatively, you can press the control, alt, and 't' keys +simultaneously to open a terminal (we abbreviate this `CTRL-ALT-T`). + + +Setup Locale +============ + +The first step is to make sure you have a locale which supports `UTF-8`. What this means is that we +will check that the language used by your computer uses a particular format of +text. If you are in a minimal environment, such as a docker container, the locale may be +something minimal like POSIX. We test with the following settings. It +should be fine if you're using a different UTF-8 supported locale. + +``` {.sourceCode .bash} +sudo locale-gen en_US en_US.UTF-8 +sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 +export LANG=en_US.UTF-8 +``` + +Setup Sources +============= + +You will need to add the ROS 2 apt repositories to your system. Out of the box +Ubuntu doesn't know where the ROS 2 binary programs live so we have to give it a +secure location. To do this the computer will prompt you for your root +password. For more technical readers we need to authorize the ROS GPG key with +apt by typing the following command in the terminal: + +``` {.sourceCode .bash} +sudo apt update && sudo apt install curl gnupg2 lsb-release +curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - +``` + + +Install ROS 2 packages +====================== + +The next steps for installing ROS is to do a system update (i.e. check for newer +programs) and then install ROS Eloquent. To do this we run the following +commands. Be aware that these commands will download a lot of data and may take +awhile. It is best to run these commands on your home network. + +``` {.sourceCode .bash} +sudo apt update +``` + +Desktop Install (Recommended): ROS, RViz, demos, tutorials. + +``` {.sourceCode .bash} +sudo apt install ros-eloquent-desktop +``` + +Next we'll install a set of tutorials called `TurtleSim`. To do this we run +another apt command. + +``` {.sourceCode .bash} +sudo apt install ros-eloquent-turtlesim +``` + +ROS 2 command line tools use argcomplete for autocompletion. If you +want autocompletion, installing argcomplete is necessary. We're also going to +install a few other tools to make our lives easier. + +``` {.sourceCode .bash} +sudo apt install python3-argcomplete htop byobu +``` + +Check Your Installation +================= + +ROS uses `environment variables` to help keep track of what version of ROS is +running and where all the programs using ROS are used on the computer. To set +these environment variable we `source`, or load, a bash script file. A bash +script file isn't magic; it is just a series of commands to enter into the +terminal, just like the series of commands we just entered to setup ROS. It is +possible to have different version of ROS running on a single computer. Using +the wrong version of ROS can lead to all sorts of problems and is a common +mistake for new users! If you are having problems try sourcing the correct ROS +bash file. From now on, whenever you open a new terminal, you will +need to tell the computer which version of ROS to use. To set the necessary +environment variables for ROS you need to `source` a bash file every time you +open a new terminal. Yes, this is annoying, but it is a sound approach as it +makes the version of ROS you are using explicit. On Ubuntu 18.04 all versions of +ROS live in `/opt/ros/`. Inside this directory will be a programs and script +files to run ROS. To tell the operating system that we want to use ROS Eloquent +we simply source the ROS Eloquent setup.bash file using the command below. + + +``` {.sourceCode .bash} +source /opt/ros/eloquent/setup.bash +``` + +Once that command runs your terminal should be ready to run a ROS program. Let's +test our installation by running two small ROS programs called `talker` and +`listener`. These two programs will send data back and forth using ROS to +perform the communication. One program was written in C++ and the other in +Python. Running these two different programs is a quick and easy way to check +that your ROS system is configured correctly. To start the talker run the following command. + +``` {.sourceCode .bash} +source /opt/ros/eloquent/setup.bash +ros2 run demo_nodes_cpp talker +``` + +If everything is working correctly you should see something like the following: + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 run demo_nodes_cpp talker +[INFO] [talker]: Publishing: 'Hello World: 1' +[INFO] [talker]: Publishing: 'Hello World: 2' +[INFO] [talker]: Publishing: 'Hello World: 3' +.... +``` + +Now, let's fire up the listener. We're going to use a Python listener in this +example to make sure we installed Python correctly. First we will need a second terminal. We can +open a new terminal tab by entering `CTRL-SHIFT-T` in our terminal. We can also +create a wholly new terminal by pressing `CTRL-ALT-T`. Pick whatever works best +for you. Now in your new terminal source your bash file and run the following +command. + + +``` {.sourceCode .bash} +source /opt/ros/eloquent/setup.bash +ros2 run demo_nodes_py listener +``` + +If everything is working correctly you should see something like the following: + + +``` {.sourceCode .bash} +kscottz@kscottz-ratnest:~$ ros2 run demo_nodes_py listener +[INFO] [listener]: I heard: [Hello World: 264] +[INFO] [listener]: I heard: [Hello World: 265] +[INFO] [listener]: I heard: [Hello World: 266] +``` + +Now that we have tested our ROS installation we can stop these two programs. In +ROS most programs run in infinite loops until the robot is shut down. To stop +these programs we navigate to the terminal running the program and press the +`Ctrl` and `C` keys simultaneously. We call this combo `CTRL-C` and you can use +it to stop just about any program in a terminal. Use it to stop the talk and +listener programs.