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:

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.