USB - Hot plug

A URCap can communicate with USB devices, but only through a backend contribution. The backend contribution can access USB devices within its container, with the access method varying based on the device type.

The pluggable nature of USB is supported using events that is provided to the backend container at runtime, this mechanism is called Hot Plug. The Hot Plug mechanism also takes care of the device ownership that makes sure only one URCap can access a USB device at a time.

Hot Plug Concept

Events

The Hot Plug system consist of two event that can be received by URCaps, one event informing of a USB device being connected to the robot and another event informing that a USB device have been disconnected.

The Hot Plug system sends the events to the backend contribution container by calling one of two executable hooks, that must be placed in the root folder of the container, identified by the hook file names:

  • on_device_add

  • on_device_remove

It is the URCap developer that provide the two files. The Hot Plug system will then invoke the files as a response to USB devices being added to and removed from the robot.

on_device_add

The on_device_add hook is called in response of the following events:

  • The USB device is physically added to the host.

  • System reboot.

  • URCap restart

The `on_device_add hook executable is called in the contribution’s container with a single JSON string as an argument. The JSON string (see format specification in the “URCap hook JSON” section below) contains information about the added device, the logical devices, etc.

The backend contribution must indicate whether it wants ownership of the device described in the JSON string. This is done using the return value of the on_device_add hook:

  • Return code 0: The URCap accepts the device and will be its exclusive owner in the future. The device will stay mounted in the URCap’s container.

  • Return code not 0: Interpreted as the URCap rejecting the device. The device will subsequently be removed from the container WITHOUT a call to the on_device_remove hook. Afterward, the device is offered to other URCaps.

on_device_add Timeout

on_device_add must return within a timeout period of 10 seconds or will be forcefully terminated. Giving a fairly generous timeout and already having the device mounted into the container gives the URCap the possibility to communicate with the device to identify it. The “USB device types” section below contains information on how the different devices is made available in the container.

on_device_remove

The on_device_remove hook is only called when the USB device is physically removed from the host. The executable is called to inform the URCap about the removal of the device before the logical devices are removed. However, in cases where a device is physically removed, the communication to the device will likely fail BEFORE on_device_remove is called.

on_device_remove will be called with a single JSON string as an argument. The JSON string will have the same format as the call to on_device_add, containing information about the removed device.

on_device_remove is purely informative and will time out after 1 second.

Using USB Hot Plug in URCaps

The sample usb-hotplug is a minimum implementation that is needed to access USB devices using Hot Plug. It provides a bash implementation of the two Hot Plug event hooks: on_device_add and on_device_remove. The two hooks simply writes the events to a log file.

The following description will be based on the usb-hotplug sample. However for a more comprehensive sample that showcase implementing event hooks in Python and using an USB device from code can be found in the devices sample.

Three main components is needed to access USB devices in a backend contribution:

  • Subscribe the USB device type in the Manifest.

  • Copy Event hooks to the root of the contribution container.

  • Write event hooks for handling the Hot Plug events.

  • Access the device in the backend contribution container.

Manifest

The example below is taken from the usb-hotplug sample. It is the devices section that will make the contribution subscribe to an USB device. In this case it subscribes to USB to Ethernet devices. Supported device types are:

  • serial: USB to serial device

  • video: USB to video device

  • network: USB to ethernet device

artifacts:
  containers:
    - id: "usb-hotplug-backend"
      image: "usb-hotplug-backend:latest"
      mounts:
        - mount: tmpfs:/data/temporary
          access: rw
      devices:
        - type: network

Note: The device type `ttyTool is not a USB device, see Serial communication via the Tool Connector on how to work with this device type

Dockerfile

The backend contribution dockerfile is a convenient place to make sure that the event hooks can be called from the root of the container. First the two files must be made executable, then they must be made available for the Hot Plug system. In the usb-hotplug sample the two files a made available in the root folder of the container by creating symbolic links. This is how it is done in the sample.

# Assign executable permissions to the scripts
RUN chmod +x /usr/src/app/on_device_add.sh && \
    chmod +x /usr/src/app/on_device_remove.sh

# Create a symlink in /
RUN ln -s /usr/src/app/on_device_add.sh /on_device_add && \
    ln -s /usr/src/app/on_device_remove.sh /on_device_remove

Event hooks

The executable event hook files: on_device_add and on_device_remove can be written in any language that can be executed in the container. For a python implementation see the devices sample, the usb-hotplugsample uses bash.

The event hook files will receive information about the device in a JSON parameter described in the URCap hook JSON format section below. The JSON data can then be parsed by the event hook to retrieve the wanted information. For example in the usb-hotplug sample, jq is used in on_device_add to query the device type being added:

# Expected values: NETWORK | SERIAL | VIDEO 
TYPE=$(echo $JSON | jq -r .urDeviceType)
echo "device type $TYPE" >> $FILE

Accessing devices

When an on_device_add event have been received and the device have been accepted, the URCap can start to use the device. How a device is accessed depends on the device type. In general video and serialdevices can be accessed as Linux devices in the containers /dev folder and network devices can be accessed as a new ethernet interface that can be configured and brought up.

Video and serial device example

When on_device_add is called for a video or serial device, the location of the device is also provided in the JSON parameter deviceNode. In the following snippet the device location is extracted from JSON using jq in bash

# Example location value: /dev/ttyUSB0 
LOCATION=$(echo $JSON | jq -r .deviceNode)
echo "device can be found at $LOCATION" 

The backend contribution can then start using the device found at this location in the container.

network example

When on_device_add is called for a network, the new ethernet interface can be seen by calling ip addr show. The new interface is now ready in the container to be configured and used. Running ip addr show will output:

1: lo: .....
22: eth0@if23: ......
29: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 7c:c2:c6:47:ad:c2 brd ff:ff:ff:ff:ff:ff

Where eth1 is the USB ethernet interface.

URCap hook JSON format

Note that the format can be extended in the future, so URCaps should implement a way of parsing the JSON-payload that discards unknown fields to be future-proof. Example:

{
"idProduct": "23a3",
"idVendor": "067b",
"logicalDevices": [
    { "deviceNode": "/dev/ttyUSB0",
        "major": 188,
        "minor": 0
    },
    ...
],
"manufacturer": "Prolific Technology Inc.",
"product": "ATEN USB to Serial Bridge",
"serial": "DNCIb114J20",
"urDeviceType": "serial",
"urDeviceAPIVersion": "0.1"
}

Notes on the format:

Guaranteed and Non-guaranteed behavior

  • A device with a device file mounted in a URCap at e.g. “/dev/ttyUSB0” is NOT guaranteed to be mounted at the same path again after a reboot. If unique identification of devices is required, the URCap must implement their own identification based on the information passed to the on_device_add hook.

  • A URCap is guaranteed to get a call to its on_device_add and on_device_remove if a device is added or removed (and the criteria for ownership at met).

  • A URCap is guaranteed to have exclusive access to a device as long as it is accessible in the container.

  • Attached USB devices have proper, unique serial numbers (per vendor and product), i.e., the tuple of (idProduct, idVendor, serial number) must be unique.