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-hotplug
sample 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 serial
devices 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:
Apart from urDeviceType, and urDeviceAPIVersion, device information are from the Linux device subsystem, see List of USB ID’s
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.