PlatformIO “could not open port /dev/ttyUSB0”

This was a really annoying error that popped up initially when I started developing PlatformIO applications with the ESP8266 variants on my Ubuntu Linux laptop. I kept thinking the problem was that Ubuntu had bad drivers for the CH34x USB-to-Serial chip on the NodeMCU (and other) devices. There is lots of information on the web about patching the kernel to “fix” this problem.

Linux Background – Udev

Udev is the Linux subsystem that supplies your computer with device events. In plain English, that means it’s the code that detects when you have things plugged into your computer, like a network card, external hard drives (including USB thumb drives), mouses, keyboards, joysticks and gamepads, DVD-ROM drives, and so on. That makes it a potentially useful utility, and it’s well-enough exposed that a standard user can manually script it to do things like performing certain tasks when a certain hard drive is plugged in. This definition and a really nice writeup is here.

The bottom line is that whenever a USB device is plugged in, previously defined Udev rules will look up the USB id of the device and if it matches, it will launch a script.

Setup

I wanted a mobile development machine to for PlatformIO development on ESP8266 devices. So I install the environment on my (old) laptop with Ubuntu 22.04 following these instructions. Everything work well – until I plug my NodeMCU and to program it. Programming fails because the port “/dev/ttyUSB0” can not be found. Next, I try to determine if the device is on a different port.

$ ls /dev/ttyUSB*
ls: cannot access '/dev/ttyUSB*': No such file or directory

But no USB com ports were found.

After trying different devices and much internet searching, I find good diagnosis steps. First, I unplug the device from the USB port. I start a terminal window and display the kernel messages generated by diagnostics – this includes starting/stopping USB devices.

$ sudo dmesg -w

Now I plug in the NodeMCU and see many many messages pop up. I am picking the following lines because they point me to the next step (some lines omitted).

... usb 6-2: new full speed USB device number 16 using uhci_hcd
... usb 6-2: New USB device found, idVendor=1a86, idProduct=7523, bcdDevice=2.54
... ch341 6-2:1.0: ch341-uart converter detected
... usb 6-2: ch341-uart converter now attached to ttyUSB0
... input: BRLTTY 6.4 Linux Screen Driver Keyboard as /devices/virtual/input/input27
... usb 6-2: usbfs: interface 0 claimed by ch341 while 'brltty' sets config #1
... ch341-uart ttyUSB0: ch341-uart converter now disconnected from ttyUSB0
... ch341 6-2:1.0: device disconnected

Most of the information is not difficult to understand. The system found the device’s USB serial driver chip (CH341 uart) and connected it to ttyUSB0. Notice that the device has a USB Vendor ID of 1a86 and Product ID of 7523. This forms part of the USB id that Udev uses for device lookup

What is not clear is what is brltty and why is the CH341 being disconnected. BRLTTY is a background process (daemon) which provides access to the Linux/Unix console (when in text mode) for a blind person using a refreshable braille display. It is enabled by default due to it being in the system startup Udev /usr/lib/udev/rules.d/85-brltty.rules.

# Device: 1A86:7523
# Baum [NLS eReader Zoomax (20 cells)]
ENV{PRODUCT}=="1a86/7523/*", ENV{BRLTTY_BRAILLE_DRIVER}="bm", ...

So once the USB serial driver is loaded, Udev checks the list of rules and finds a match and runs the associated script. This script launches a service (BRLTTY) that removes the ttyUSB0 assignment and uses it for it’s own purpose. This is exactly deterministic and expected – if you knew about it already. It took me several hours to get to this level of understanding!

How to Fix the Problem

Since I don’t use Braille, I removed the BRLTTY package.

> sudo apt remove brltty

Next, I needed to install a Udev rule that automatically sets the permission mode on /dev/ttyUSB0 for the CH34x to “0666” so all system users can read and write the from/to the device. As explained in the Udev section above, we are going to load the rule published by the PlatformIO people so the devices are accessible to VS Code.

> curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/system/99-platformio-udev.rules | sudo tee /etc/udev/rules.d/99-platformio-udev.rules

I rebooted my system then PlatformIO can now flash the NodeMCU device.