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 fourth blog post in this series about ROS production. In the previous post we created a snap of our prototype, and released it into the store. In this post, we’re going to work toward an Ubuntu Core image by creating what’s called a gadget snap. A gadget snap contains information such as the bootloader bits, filesystem layout, etc., and is specific to the piece of hardware running Ubuntu Core. We don’t need anything special in that regard (read that link if you do), but the gadget snap is also currently the only place on Ubuntu Core and snaps in general where we can specify our udev symlink rule in order to obtain confined access to our Turtlebot (i.e. allow our snap to be installable and workable without devmode). Eventually there will be a way to do this without requiring a custom gadget, but this is how it’s done today.

Alright, let’s get started. Remember that this is also a video series– feel free to watch the video version of this post:

Step 1: Start with an existing gadget

Canonical publishes gadget snaps for all reference devices, including generic amd64, i386, as well as the Raspberry Pi 2 and 3, and the DragonBoard 410c. If the computer on which you’re going to install Ubuntu Core is among these, you can start with a fork of the corresponding official gadget snap maintained in the github.com/snapcore organization. Since I’m using a NUC, I’ll start with a fork of pc-amd64-gadget (my finished gadget is available for reference, and here’s one for the DragonBoard). If you’re using a different reference device, fork that gadget and you can still follow the rest of these steps.

Step 2: Select a name for your gadget snap

Open the snapcraft.yaml included in your gadget snap fork. You’ll see the same metadata we discussed in the previous post. Since snap names are globally unique, you need to decide on a different name for your gadget. For example, I selected pc-turtlebot-kyrofa. Once you settle on one, register it:

$ snapcraft register my-gadget-snap-name

If the registration succeeded, change the snapcraft.yaml to reflect the new name. You can also update the summary, description, and version as necessary.

Step 3: Add the required udev rule

If you’ll recall, back in the second post in this series, we installed a udev rule to ensure that the Turtlebot showed up at /dev/kobuki. Take a look at that rule:

$ cat /etc/udev/rules.d/57-kobuki.rules
# On precise, for some reason, USER and GROUP are getting ignored.
# So setting mode = 0666 for now.
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="kobuki*", MODE:="0666", GROUP:="dialout", SYMLINK+="kobuki"
# Bluetooth module (currently not supported and may have problems)
# SUBSYSTEM=="tty", ATTRS{address}=="00:00:00:41:48:22", MODE:="0666", GROUP:="dialout", SYMLINK+="kobuki"

It’s outside the scope of this series to explain udev in depth, but the two values I’ve made bold are how the Kobuki base is uniquely identified. We’re going to write the snapd version of this rule, using the same values. At the end of the gadget’s snapcraft.yaml, add the following slot definition:

# Custom serial-port interface for the Turtlebot 2
slots:
 kobuki:
   interface: serial-port
   path: /dev/serial-port-kobuki
   usb-vendor: 0x0403
   usb-product: 0x6001

The name of the slot being defined is kobuki. The symlink path we’re requesting is /dev/serial-port-kobuki. Why not /dev/kobuki? Because snapd only supports namespaced serial port symlinks to avoid conflicts, without documentation, and with silent errors (it’s lovely, I’m not grumpy about it). You can use whatever you like for this path (and the slot name), but make sure to follow the /dev/serial-port-<whatever> pattern, and adjust the rest of the directions in this series accordingly.

Step 4: Build the gadget snap

This step is easy. Just get into the gadget snap directory and run:

$ snapcraft

In the end, you’ll have a gadget snap.

Step 5: Put the gadget snap in the store

You don’t technically need the gadget snap in the store just to create an image, but there are two reasons you will want it there:

  1. Without putting it in the store you have no way of updating it in the future
  2. Without putting it in the store it’s impossible for the serial-port interface we just added to be automatically connected to the snap that needs it, which means the image won’t make the robot move out of the box (Update: This is no longer true, you can specify auto-connections within the gadget now)

Since you’ve already registered the snap name, just push it up (we want our image based on the stable channel, so let’s release into that):

$ snapcraft push /path/to/my/gadget.snap --release=stable

You will in all likelihood receive an error saying it’s been queued for manual review since it’s a gadget snap. It’s true, right now gadget snaps require manual review (although that will change soon). You’ll need to wait for this review to complete successfully before you can take advantage of it actually being in the store (make a post in the store category of the forum if it takes longer than you expect), but you can continue following this series while you wait. I’ll highlight things you’ll need to do differently if your gadget snap isn’t yet available in the store.

Step 6: Adjust our ROS snap to run under strict confinement

As you’ll remember, our ROS snap currently only runs with devmode, and still assumes the Turtlebot is available at /dev/kobuki. We know now that our gadget snap will make the Turtlebot available via a slot at /dev/serial-port-kobuki, so we need to alter our snap slightly to account for this. Fortunately, when we initially created the prototype, we made the serial port configurable. Let’s pat ourselves on the back for a moment, then edit our snapcraft.yaml a bit:

name: my-turtlebot-snap
version: '0.2'
summary: Turtlebot ROS Demo
description: |
  Demo of Turtlebot randomly wandering around, avoiding obstacles and cliffs.

grade: stable

# Using strict confinement here, but that only works if installed
# on an image that exposes /dev/serial-port-kobuki via the gadget.
confinement: strict

parts:
 prototype-workspace:
   plugin: catkin
   rosdistro: kinetic
   catkin-packages: [prototype]

plugs:
 kobuki:
   interface: serial-port

apps:
 system:
   command: roslaunch prototype prototype.launch device_port:=/dev/serial-port-kobuki --screen
   plugs: [network, network-bind, kobuki]
   daemon: simple

Most of this is unchanged from version 0.1 of our snap that we discussed in the previous post. Let’s cover only the parts that changed.

version: 0.2

We’re modifying the snap here, so I suggest changing the version. This isn’t required (the version field is only for human consumption, it’s not used to determine which snap is newest), but it certainly makes support easier (“what version of the snap are you on?").

# Using strict confinement here, but that only works if installed
# on an image that exposes /dev/serial-port-kobuki via the gadget.
confinement: strict

You wrote such a nice comment, it hardly needs more explanation. The point is, now that we have a gadget that makes this device accessible, we can switch to using strict confinement.

plugs:
 kobuki:
   interface: serial-port

This one defines a new plug in this snap, but then says “the kobuki plug is just a serial-port plug.” This is optional, but it’s handy to have our plug named kobuki instead of the more generic serial-port. If you opt to leave this off, keep it in mind for the following section.

apps:
 system:
   command: roslaunch prototype prototype.launch device_port:=/dev/serial-port-kobuki --screen
   plugs: [network, network-bind, kobuki]
   daemon: simple

Here we take advantage of the flexibility we introduced in the second post of the series, and specify that our launch file should be launched with device_port set to the udev symlink defined by the gadget instead of the default /dev/kobuki. We also specify that this app should utilize the kobuki plug defined directly above it, which grants it confined access to the serial port.

Rebuild your snap, and release the updated version into the store as we covered in part 3, but now you can release to the stable channel. Note that this isn’t required, but the reasons for putting this snap in the store are the same as the reasons for putting the gadget in the store (namely, updatability and interface auto-connection).

In the next (and final) post in this series, we’ll put all these pieces together and create an Ubuntu Core image that is ready for the factory.