UPDATE: I’m leaving this series up for historical purposes, but please note that I no longer recommend Ubuntu Core or snaps for use in robotics.
This is the fifth (and final) blog post in this series about creating your first robot with ROS and Ubuntu Core. In the previous post we discussed methods of control, did a little math, and wrote the ROS driver for our robot. But it still required several nodes to be running at once, and sharing it with the world involved uploading your source code somewhere and convincing people to install ROS, build your package, and use it. Today we’re going to simplify both of those issues by discussing ROS launch files, and packaging everything we’ve written as a snap that can be installed by your friends with a few keystrokes, even without knowing anything about ROS.
Alright, let’s get started! Remember that this is also a video series, feel free to watch the video version of this post:
Prerequisites
This post will assume the following:
- You know what snaps are, and have completed the “create your first snap” tutorial.
- You have a store account at dashboard.snapcraft.io
Step 1: Create launch files
In parts 3 and 4, we needed to open several terminals because we needed to run several different ROS nodes. It’s difficult to remember every component of our system, and it gets exponentially harder as we start adding parameters, etc. Running individual nodes is handy for testing things out, but we’re done with that step. It’s time to make our system a little more of a product, something you can just run and use to control your robot. As you learned in the eighth ROS tutorial, ROS has a tool for dealing with this: launch files. These give us a simple language we can use to specify what nodes need to launch at the same time, and with what parameters.
Let’s get started writing launch files for the ROS system that controls our robot. We’ll put these launch files in the edukit_bot
package we’ve been working on throughout this series, in a new launch/
folder. Make that folder now:
$ mkdir ~/edukit_bot_ws/src/edukit_bot/launch
In order for our robot (and launch files) to be reusable, we’re not actually going to create a single launch file. We want a single entry point (i.e. we want to be able to launch a single launch file and have it bring up the whole system), but we also want to keep things as modular as possible. So we’ll write one launch file per subsystem, and then one super launch file in charge of bringing up the entire system. We have two subsystems for our robot: low-level control, and teleoperation. Let’s write our “super” launch file first.
Step 1.1: Write robot launch file
Create a new file ~/edukit_bot_ws/src/edukit_bot/launch/edukit_bot.launch
and open it up in your editor. Make it look like this:
<launch>
<include file="$(find edukit_bot)/launch/includes/teleop.launch.xml" />
<include file="$(find edukit_bot)/launch/includes/control.launch.xml" />
</launch>
That’s it. All this launch file does is “include” the launch files for our two subsystems (which we have yet to write). When this launch file is run, it runs those. Alright, let’s get started on our subsystems' launch files.
Step 1.2: Write teleop launch file
Why are we splitting these up, exactly? Let’s say in the future you want to work toward making this robot autonomous. In that case, you may no longer want the teleoperation nodes to be launched. Do you think you’ll remember which nodes relate to teleoperation and which don’t? I certainly won’t! It’s easier just to keep related pieces together, and remove the “include” line that launches the teleoperation nodes if you want to remove that functionality.
First, you need to create the includes
directory into which we’ll place our subsystem launch files:
$ mkdir ~/edukit_bot_ws/src/edukit_bot/launch/includes
Create a new file ~/edukit_bot_ws/src/edukit_bot/launch/includes/teleop.launch.xml
. Why do we end this one with .xml
and not just .launch
like the other one? Because roslaunch
(the command that runs launch files) will tab-complete files that end in .launch
. Since these subsystem launch files don’t do much by themselves, we don’t want them to be exposed to end users. Ending them with .xml
essentially hides them from roslaunch
, but still lets us use them with includes. Anyway, open that file in your editor. Make it look like this:
<launch>
<node pkg="joy" type="joy_node" name="joystick">
<param name="autorepeat_rate" value="1" />
</node>
<node pkg="teleop_twist_joy" type="teleop_node" name="joystick_to_twist">
<param name="scale_angular" value="4" />
</node>
</launch>
This file launches the two nodes that make up our teleoperation (i.e. what we discussed in part 3). Note that we’re using the autorepeat_rate
parameter of the joy node. This parameter tells joy to rebroadcast joystick values, even if they haven’t changed, at a rate of at least 1 Hz (if the joystick moves, the rate will be much greater than 1 Hz, we just want to make sure we don’t run into our driver’s timeout functionality if the joystick simply hasn’t moved).
We’re also using the scale_angular
parameter of the teleop_node
so the robot actually turns (as we discussed in part 4).
Step 1.3: Update dependencies
We’ve just finished writing a launch file in our package that assumes the presence of other ROS packages (and their nodes). However, we’re not depending on those ROS nodes at all. Let’s add both joy
and teleop_twist_joy
to our list of dependencies in our edukit_bot
’s package.xml
, making it look something like this in the end:
<?xml version="1.0"?>
<package format="2">
<name>edukit_bot</name>
<version>0.1.0</version>
<description>The edukit_bot package</description>
<maintainer email="you@you.com">You</maintainer>
<license>GPLv3</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>rospy</build_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>geometry_msgs</exec_depend>
<exec_depend>python-rpi.gpio</exec_depend>
<exec_depend>joy</exec_depend>
<exec_depend>teleop_twist_joy</exec_depend>
</package>
Step 1.4: Write the low-level control launch file
Create a new file ~/edukit_bot_ws/src/edukit_bot/launch/includes/control.launch.xml
. Make it look like this:
<launch>
<node name="driver" pkg="edukit_bot" type="driver_node" />
</launch>
This one is tremendously simple, as it’s just launching the driver we wrote in part 4. No parameters to tweak, since the default values are suitable (at least for my robot, perhaps yours is different).
We’re done making changes to our package, so let’s rebuild it:
$ cd ~/edukit_bot_ws
$ catkin_make
Step 1.5: Test launch system
Alright, let’s take our new launch files for a spin. This time, you only need a single terminal! Make sure it’s in your classic shell. First, as in the rest of the series, we need to make sure we have permission to access GPIO as a user (remember this resets upon reboot):
$ sudo chmod a+rw /dev/gpiomem
Now activate your workspace:
$ cd ~/edukit_bot_ws
$ source devel/setup.sh
Finally launch the super launch file, which will launch everything else:
$ roslaunch edukit_bot edukit_bot.launch
You’ll see all the nodes that make up our system come up, and you should be able to control your robot. So much easier than running them all individually!
Step 2: Package this workspace as a snap
At this point, we have our entire ROS system launchable by a single command. Wouldn’t it be neat to have this system running as soon as we boot our Raspberry Pi? Also, it would be cool to ask your friends to take your version of this series for a spin, and be able to test theirs out as well! Both of these are easily accomplished by packaging our ROS system as a snap. Note that the workspace (and snapcraft.yaml
) used here is available for reference.
Step 2.1: Add install rules
The first step toward packaging anything is typically to ensure that we’re installing the components of our package correctly. With its use of workspaces, ROS makes this dangerously easy to ignore, but it’s still important. We have two components of our edukit_bot
package: the driver, and the launch files. Open up edukit_bot
’s CMakeLists.txt
, and add the following two install rules to the bottom:
# ... <snip>
# Install driver node
install(PROGRAMS
src/driver_node
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
# Install launch files
install(DIRECTORY launch
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
Now when we install our ROS package, it will install both our driver node as well as the launch files we’ve written.
Step 2.2: Install Snapcraft
In order to build a snap, we need to install a tool called snapcraft
. From your classic shell:
$ sudo apt install snapcraft
Step 2.3: Write the snapcraft.yaml
We tell snapcraft
how to create our snap by writing a snapcraft.yaml
. We can get an initial version with snapcraft init
:
$ cd ~/edukit_bot_ws
$ snapcraft init
Created snap/snapcraft.yaml.
Edit the file to your liking or run `snapcraft` to get started
Do as it asks, and open up snap/snapcraft.yaml
in your editor. Make it look something like this:
name: edukit-bot-kyrofa
version: '1.0'
summary: ROS system for teleoperating edukit bot
description: |
A ROS system for teleoperating the EduKit #3, the robotics kit.
This accompanies the "Your First Robot" series written by Kyle
Fazzari.
Note that the `joystick` interface is needed for access to the
gamepad, and the `physical-memory-control` interface is needed
for access to GPIO. Both of these interfaces must be connected
before this snap will function:
$ sudo snap connect edukit-bot-kyrofa:joystick
$ sudo snap connect edukit-bot-kyrofa:physical-memory-control
grade: stable
confinement: strict
parts:
workspace:
plugin: catkin
rosdistro: kinetic
source: .
catkin-packages: [edukit_bot]
# Required to build RPi.GPIO
build-packages: [python-dev, python-setuptools]
apps:
launch:
command: roslaunch edukit_bot edukit_bot.launch
daemon: simple
plugs:
# Basic networking
- network
- network-bind
# Needed for access to gamepad
- joystick
# Needed for GPIO memory-mapping
- physical-memory-control
Let’s break this down piece by piece.
name: edukit-bot-kyrofa
version: '1.0'
summary: ROS system for teleoperating edukit bot
description: |
A ROS system for teleoperating the EduKit #3, the robotics kit.
This accompanies the "Your First Robot" series written by Kyle
Fazzari.
Note that the `joystick` interface is needed for access to the
gamepad, and the `physical-memory-control` interface is needed
for access to GPIO. Both of these interfaces must be connected
before this snap will function:
$ sudo snap connect edukit-bot-kyrofa:joystick
$ sudo snap connect edukit-bot-kyrofa:physical-memory-control
This is just useful metadata for our snap. The name must be unique (that’s why I put my nickname at the end), but you’re pretty much free to put whatever you want for the rest. The description here hints at confinement, which we’ll cover in a moment.
grade: stable
confinement: strict
grade
can be either stable
or devel
. If it’s devel
, the store will prevent you from releasing into stable channels– think of it as a safety net to prevent accidental releases. If it’s stable
, you can release it anywhere. I personally think this feature is a tad silly, and typically just set it to stable
.
confinement
can be strict
, devmode
, or classic
. strict
enforces confinement, whereas devmode
allows all accesses, even those that would be disallowed under strict confinement (and logs accesses that would otherwise be disallowed for your reference). classic
is even less confined than devmode
, essentially running exactly like something you’d install from a deb. There is more extensive documentation on confinement available.
I personally always use strict
confinement unless I know for sure that the thing I’m snapping won’t run successfully under confinement, in which case I’ll use devmode
. I typically avoid classic
unless I never intend for the app to run confined (e.g. if I was snapping vim, I know it would always need access to everything). In this case, this snap can run quite well under strict
confinement. We’ll discuss this some more momentarily.
parts:
workspace:
plugin: catkin
rosdistro: kinetic
source: .
catkin-packages: [edukit_bot]
# Required to build RPi.GPIO
build-packages: [python-dev, python-setuptools]
This is where we tell snapcraft
how to build our Catkin workspace. Every snap is made up of a number of parts. In our case, there’s only one, called workspace
. We say that it’s built using the catkin
plugin, using kinetic
(as opposed to Lunar or Indigo, as we discussed in part 2). We give it a list of Catkin packages to build, in this case just edukit_bot
. Finally, we give it a list of packages that need to be installed in order to build our ROS package, or more specifically, our RPi.GPIO
dependency.
apps:
launch:
command: roslaunch edukit_bot edukit_bot.launch
daemon: simple
plugs:
# Basic networking
- network
- network-bind
# Needed for access to gamepad
- joystick
# Needed for GPIO memory-mapping
- physical-memory-control
Here we expose our single launch file that brings up the whole system. We create a new app called “launch” that runs the roslaunch
command we used in step 4. We’re saying that this should be running at boot with daemon: simple
. Finally, we provide a list of plugs, which is where confinement comes into play. (Note: if you’re using Ubuntu Core 18, you should use gpio-memory-control
instead of physical-memory-control
).
Since this snap is strictly confined, by default it doesn’t really have access to anything. No network, no GPIO, no controller. So we add plugs for each of those things: ROS needs the network for its nodes to function, we need access to the controller, and we need access to GPIO. Read more about interfaces.
That’s it! Let’s build it.
Step 2.4: Build the snap
This part is pretty easy:
$ cd ~/edukit_bot_ws
$ snapcraft
Note that the Raspberry Pi is not as blazingly fast as we’d all like it to be. This will take some time. You’ll see snapcraft
fetch rosdep
, which is then used to determine the dependencies of the ROS packages in the workspace. This is only edukit_bot
in our case, which we know depends upon rospy
, geometry_msgs
, python-rpi.gpio
, joy
, and teleop_twist_joy
. It then pulls those down and puts them into the snap along with roscore
. Finally, it builds the requested packages in the workspace, and installs them into the snap as well. At the end, you’ll have your snap.
Step 2.5: Test your snap
Now that we have our snap built, let’s test it out! First, install it:
$ cd ~/edukit_bot_ws
$ sudo snap install edukit-bot-kyrofa_1.0_armhf.snap --dangerous
edukit-bot-kyrofa 0.1 installed
Now that the snap is installed, since we made our ROS system a daemon it’s already up and running. Unfortunately, it has already probably died because it can’t access the joystick or GPIO. We need to give it permission to do that by connecting the joystick
and physical-memory-control
interfaces:
$ sudo snap connect edukit-bot-kyrofa:joystick
$ sudo snap connect edukit-bot-kyrofa:physical-memory-control
We only need to do that once. Now that access has been granted, we just need to restart the service so it tries again:
$ sudo snap restart edukit-bot-kyrofa
Now you should be able to drive your robot around as before, but everything is running out of that single snap.
Step 3: Share the snap with others
All you need to do to share your project with others is release the snap in the store!
Step 3.1: Tell Snapcraft who you are
We’re about to use snapcraft
to register and upload a snap using the store account you created when satisfying the prerequisites. For that to work, you need to sign in with snapcraft
. From your classic shell:
$ snapcraft login
Step 3.2: Register the snap name
As I mentioned before, snap names are globally unique, so only one developer can register and publish a snap with a given name. Before you can publish this snap you need to make sure that snap name is registered to you (note that this corresponds to the name
field in the snapcraft.yaml
we created moments ago). Again from your classic shell:
$ snapcraft register <my snap name>
Assuming that name is available, you can proceed to upload it. Otherwise you may need to rename/rebuild your snap to whatever name you managed to register (i.e. change the name field in the YAML and run snapcraft
again, it won’t take long).
Step 3.3: Release the snap
There are four release channels available by default. In order of increasing stability, these channels are edge
, beta
, candidate
, and stable
. We’ve tested this snap and it’s strictly confined, so we can really release it wherever we want. Let’s go ahead and put it into the stable
channel:
$ snapcraft push path/to/my.snap --release=stable
Once the upload and automated reviews finish successfully, anyone in the world can install your snap on the Raspberry Pi controlling their EduKit bot by running:
$ sudo snap install <my snap name>
Remember they also need to connect those interfaces so the snap can use the gamepad and GPIO. Mine (see the code) is also in the stable channel, so you can take it for a spin with:
$ sudo snap install edukit-bot-kyrofa
Conclusion
That’s all, folks! I hope you enjoyed this series, and I hope that this brief introduction to professional robotics will serve you well in the years to come. If this is a field that you’d like to pursue, I can’t recommend internships enough, especially if you’re at the university level. At this point, you can put experience with ROS and Ubuntu Core on your resume, which will help you stand out from the pack!
If you’re looking for ways to continue hacking on this robot using ROS, I suggest doing some research into ways to obtain the wheel speeds. Once you have that, you can start investigating morphing your driver node into something you can use with the diff_drive_controller, which goes back to ros_control, which we discussed in part 4. Who knows, maybe I’ll write another series about that someday.