Device Hotplug
A URCap can communicate with hot-pluggable devices, but only through a backend contribution. The backend contribution can access those devices within its container, with the access method varying based on the device type.
The hotplug system delivers events to the backend container at runtime when devices appear or disappear. It also takes care of device ownership so that only one URCap can access a given device at a time.
Hotplug covers:
USB serial, video, and USB-to-Ethernet adapters
Control box built-in Ethernet interfaces
ETH Tool on g-Series (Ethernet at the tool flange, not the RS-485 Tool Connector)
Hotplug Concept
Events
The Hotplug system consists of two events that can be received by URCaps, one event informing of a device being connected to the robot and another event informing that a device have been disconnected.
The Hotplug 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 Hotplug system will
then invoke the files as a response to USB devices being added to and removed from the robot, and for hot-pluggable
network interfaces subscribed as a network device (control box built-in Ethernet interfaces and g-Series ETH Tool).
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.
A hot-pluggable network interface is connected (control box built-in Ethernet interface or g-Series ETH Tool).
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.
This first-accept-wins behaviour is Automatic assignment. It is kept for backwards compatibility and is deprecated. See Device assignment.
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 “Accessing devices” 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, or when a hot-pluggable network interface (control box built-in Ethernet interface or g-Series ETH Tool) is unassigned. 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.
Device assignment
Automatic (deprecated)
The original assignment mode is Automatic. It is backwards compatible with existing URCaps and is deprecated.
In Automatic mode, each matching device is offered to subscribed URCaps through on_device_add. Returning 0 claims
exclusive ownership. Any other return value, or a timeout, rejects the device and Hotplug offers it to the next
subscriber. URCaps that only declare a device type in the manifest continue to use Automatic assignment.
New URCaps should not rely on Automatic assignment.
Using Hotplug in URCaps
The sample usb-hotplug is a minimum implementation that is needed to access devices using Hotplug. It provides
a bash implementation of the two Hotplug 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 a device from code can be found in the devices sample.
Three main components is needed to access Hotplug devices in a backend contribution:
Subscribe the device type in the Manifest.
Copy Event hooks to the root of the contribution container.
Write event hooks for handling the Hotplug 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 a device. In this case it subscribes to Ethernet interfaces with direct layer-2 ownership.
Supported device types are:
serial: USB to serial device
video: USB to video device
network: Ethernet interface with direct layer-2 ownership (control box built-in Ethernet interfaces, g-Series ETH Tool and via USB-to-Ethernet adapter)
Built-in Ethernet interfaces are included when you request a network device and are hot-pluggable. Connecting or
disconnecting the cable produces the same on_device_add / on_device_remove events as a USB-to-Ethernet adapter.
Declaring only type uses Automatic assignment, which is deprecated and kept for backwards compatibility. See
Device assignment.
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 Hotplug 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 Hotplug 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 Ethernet interface (USB-to-Ethernet adapter, control box built-in Ethernet interface, or g-Series ETH Tool). Configure and bring it up as you would a macvlan interface; see Networking - Macvlan.
Built-in Ethernet interfaces
Built-in Ethernet interfaces are offered through the same network device subscription and the
same Hotplug events. Connecting or disconnecting the cable produces on_device_add and on_device_remove, just as
for a USB-to-Ethernet adapter. After the URCap accepts the device, the interface appears in the container as a new
Ethernet interface with direct layer-2 ownership.
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.2"
}
Notes on the format:
Fields prepended with “ur” are UR fields. For USB devices all fields come from the Linux USB subsystem, see List of USB ID’s.
"urDeviceType":"serial""video""network"
"urDeviceAPIVersion": "0.2"
Built-in Ethernet (control box Ethernet interfaces and g-Series ETH Tool):
"idProduct": "builtin""idVendor": "universal-robots""deviceNode":"ethCB0""ethCB1""ethCB2""ethTool0"
"manufacturer": "Universal Robots""product": "Built-in NIC""serial":"ethCB0""ethCB1""ethCB2""ethTool0"
"urSystemId":"ethCB0""ethCB1""ethCB2""ethTool0"
"urDisplayName":"ETH 0""ETH 1""ETH 2""ETH Tool"
Built-in network example:
{
"idProduct": "builtin",
"idVendor": "universal-robots",
"logicalDevices": [
{ "deviceNode": "ethCB1",
"major": 0,
"minor": 0
}
],
"manufacturer": "Universal Robots",
"product": "Built-in NIC",
"serial": "ethCB1",
"urDeviceType": "network",
"urDeviceAPIVersion": "0.2",
"urSystemId": "ethCB1",
"urDisplayName": "ETH 1"
}
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.