ThunderBolt84
ThunderBolt84

Reputation: 33

How to mount an FTDI USB device to a docker container?

I would like to be able to access an FTDI serial-to-usb bridge device plugged into a host computer from within a Docker container. From there, I am using a Python script with the pyusb and libusb libraries to process the USB output. That way, I can plug the FTDI board with its attached devices into some computer, run the docker container, and process the data. The container is built using docker-compose.

How we got here

As an earlier test on my host computer, I wrote the following Python script which will run at the start of my design:

import usb.core
import usb.util

# Find the USB device
device = usb.core.find(idVendor = 0x0403, idProduct = 0x601c)

# Check if the device was found. If not, raise an error. If the device was found, print out its info. 
if (device == None):
    raise ValueError('Device not found')
else:
    print(device)

This printed out all the information on the board as expected - manufacturer, interfaces, endpoints, ect. (I was able to find the vendor and product IDs using lsusb -D /dev/bus/usb/003/007, where the numbers were the bus and device numbers given by lsusb).

I then went into my docker container in vscode, installed the two libraries, and ran the same script. This time, I got an error:

usb.core.NoBackendError: No backend available

I went to the pyusb FAQ at https://github.com/pyusb/pyusb/blob/master/docs/faq.rst and made sure that I didn't have one of the common causes of the error. The error persisted even after I used usb.backend.libusb1.get_backend(...) to specify the backend library by hand.

I came to the realization that a root cause of the problem was that the docker container had no way to access the FTDI USB device in the first place. With a week's worth of experience in Docker, I think that I need to mount the USB device on my host computer to the container using

What has been tried

In my service in the docker-compose file, I've tried to specify the mounting location of the device using the following:

    devices:
      - "/dev/serial/by-id/<link>:/dev/ttyUSB0"
      privileged: true

To find , I went into the /dev/serial/by-id/ directory and used dmesg | grep tty. It displayed a new entry whenever I plugged in a different USB device (Arduino), but did not have any new entries when I plugged in the FTDI board. Because of this, I doubt that my FTDI board is a TTY device, which most of the existing threads seem to focus around. I am not sure how else to give docker-compose what it needs to mount the device.

Because the pyusb library will find my device if it's given several USB devices, simply mounting all of the host USB ports should also solve my problem. I searched around this set of keywords as well, but didn't find much useful information.

TL:DR

How can I mount either an individual FTDI Serial-to-USB bridge device or all of the USB devices on my host computer to a docker container? I'd like to avoid using privileged if possible. I've been working with Ubuntu, Docker, and Python for about a week so I may need it spelled out. Let me know if any more information is needed.

Thanks!

Upvotes: 3

Views: 2188

Answers (1)

Trygve
Trygve

Reputation: 621

(Edited a few times as I've learned more.)

Did you ever figure this out? I just undertook a very similar exercise and it's working for me! My board is the FT232H (using the breakout from Adafruit). I mostly just followed the directions on Adafruit here.

I think the "no backend" error is about not having libusb installed. Not sure why that is, as you say you installed it... But here's the steps I took, as far as I think they're relevant:

  1. In my Dockerfile I installed libusb. My base image was Alpine, so this was RUN apk update && apk add libusb. On full Ubuntu it would be RUN apt update && apt install libusb-1.0 as in the Adafruit guide.
  2. Create udev rules. I do not understand fully what these do, but maybe not having these is why your board wasn't showing up in dev/serial/by-id? (Now I'm not actually sure this step is necessary unless you also need non-root access in the container.) On the host, create the file /etc/udev/rules.d/99-ftdi.rules with the contents:
# /etc/udev/rules.d/99-ftdi.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6001", GROUP="plugdev", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6011", GROUP="plugdev", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6010", GROUP="plugdev", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6014", GROUP="plugdev", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6015", GROUP="plugdev", MODE="0666"
  1. Now when I unplug and replug the device I get a new entry in /dev/serial/by-usb that I can add to the compose file as you expected. Those lines in my file are:
devices:
  - "/dev/serial/by-id/usb-0403_6014-if00-port0:/dev/ftdi"

Now the device is visible to pyusb in the container (for me).

  1. The above worked for me when pyusb was run by root in the container. I recently switched to using a non-admin account in the container and had to do one more step: create the group mentioned in the rules file and add the container user to the group. If the container user will be "abc", then in the dockerfile:
RUN groupadd plugdev && usermod -a -G plugdev abc

I also don't think you need privileged: true since you're using devices. Haven't tested this yet, but I plan to.

You might also check out the rest of the Adafruit guide linked above. It uses a couple more Python libraries (pyftdi and adafruit-blinka) that were exactly what I needed to control a GPIO pin with a single line of Python. But your use case might be much more complicated than mine!

Upvotes: 0

Related Questions