vrijdag 6 februari 2015

Howto: persistent device names on Raspberry Pi

This text explains how to set up a persistent device name in Raspbian Linux. If you want to get cooking straight away, skip to the steps in detail.


By design, a USB device is assigned a unique address by its host, but that address may change from one session (bootup) to another. Software using the device, on the other hand, is much easier to make if the address does not change. In Linux, the solution to this problem is a persistent device name. How to make that is explained below.


USB-enumeration for dummies

Elsewhere on Loket Diversen is a post about setting up Raspberry Pi (RPi) as SMS Gateway. I'm using a Huawei GSM/UMTS modem on RPi's USB port. Gammu or Gnokii deal with the modem. You specify in a config file where to find that modem: /dev/ttyUSBx (with x a number). The problem is that x may change unpredictably.

It turns out that this is by design. The process of USB address assignment is called enumeration. In a nutshell, it's a conversation between Device, Hub and Host and it goes something like this (metaphorically speaking):

Device: Wheehee, I'm plugged in!
Hub:   Host? Hey host, wake up, there's a customer!
Host:  (waking up) What? Oh, OK, well, enable a port, would you?
Hub:  (enables port)
Host:  (in a formal tone of voice) Device! Reset!
Device: Aye-aye Sir. I'm all Reset.
Host:  Device, what is your Descriptor?
Device: I'm X, from Y.
Host:  (muttering to self) Uhm, er.. where'd I leave the free addresses list? Oh here.
     (clears throat; formal voice:) Device X from Y, you shall be on address P.

Note that P is given out in order; the first Device gets ttyUSB0, the second ttyUSB1, and so on. But more importantly, note how timing isn't always the same! When booting up with two devices plugged in, one device might just respond slightly faster than the other. And next time it may be the other way around. That is why the number for x in /dev/ttyUSBx is unpredictable.[1]

Gnokii, Gammu and others

StackOverflow has a nice little overview of various open source SMS Gateway packages. I don't need large-scale SMS-messaging, so my choice was between Gnokii and Gammu. Gnokii is small and very simple. Gammu is a fork of Gnokii that took off on a life of its own. Gammu is much, much more rich in features and possibilities than Gnokii. Documentation, unfortunately, is less than perfect.

Gnokii's SMSreader-command just listens for SMS-messages, full stop. You could probably shove that into the background, but there's more to a well-behaved background process, also known in Linux/Unix as a daemon. Building a daemon is not trivial. But Gammu comes with a well-behaved daemon already, it's called Gammu-smsd. Making a message handler for that is a lot less work. That's why I chose Gammu.

A persistent device name, step by step.

One FAQ on Gammu is Device name always changes on Linux, how to solve that?  The answer given there is not very adequate. It took me quite some googling and trial & error to get it working. Hence this text.

We're using udev, Linux' device manager. It supports persistent device naming and it runs by default on any Linux distro. Just to verify:

ps -A | grep udev

should respond with something like

 156 ?      00:00:00 udevd

Yup, it's running, process ID 156.

Find out a unique enough identifier set for the device.


lists all USB devices. This is the output in my case:

Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 005: ID 12d1:1003 Huawei Technologies Co., Ltd. E220 HSDPA Modem / E230/E270/E870 HSDPA
/HSUPA Modem Bus 001 Device 004: ID 7392:7811 Edimax Technology Co., Ltd EW-7811Un 802.11n Wireless Adapter [Realtek

OK, so the modem is connected and it's USB device 005 (this time). Note the numbers. 12d1 is the Vendor ID (Huawei Technologies) and 1003 is the Product ID, this particular model modem that they make. In this case, the combination Vendor ID + Product ID is already a unique enough identifier set. (So you could skip the next two steps.)

In other cases, things may be different. For instance, lots of devices use FTDI's USB-to-serial converter, all with the same Vendor and Product ID and you may want to have them all connected to you RPi. So you'll need some more information to distinguish one from the other(s).

To find that extra bit of information, enter

dmesg | grep usb

the response might be a long list. Somewhere in there, you'll find something like this:

[  523.057081] usb 1-1.2: new high-speed USB device number 5 using dwc_otg
[  523.168297] usb 1-1.2: New USB device found, idVendor=12d1, idProduct=1003
[  523.168330] usb 1-1.2: New USB device strings: Mfr=2, Product=1, SerialNumber=0
[  523.168346] usb 1-1.2: Product: HUAWEI Mobile
[  523.168362] usb 1-1.2: Manufacturer: HUAWEI Technology

Note idVendor and idProduct. We'll use them later on. Note also SerialNumber=0. I don't trust serialnumbers that are zero. With other devices you should see a non-zero serial number, usually on a line of its own.

dmesg | grep ttyUSB

will list all ttyUSB devices, like so:

[    6.546558] usb 1-1.2: GSM modem (1-port) converter now attached to ttyUSB0
[    6.571094] usb 1-1.2: GSM modem (1-port) converter now attached to ttyUSB1

My modem occupies 2 ttyUSB's. By trial and error, I found out that sending and receiving works on ttyUSB1. I don't know why that is.

To get an idea of all the attriombutes of a USB-device, try:

udevadm info --name=/dev/ttyUSB1 --attribute-walk

This walks along a branch of USB devices and lists, for each device, all possible attributes in the udev rules key format.[2]. Try it! If your device has a serial number, you will see it there, and you will also see what it's called. That's important, because you'll have to call it by that name in the next step. Also, I always find it reassuring when different sources report the same information.

Almost done.

Create a file /etc/udev/rules.d/99-usb-serial.rules (yes you'll need root rights) with something like this line in it:

SUBSYSTEM=="tty", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1003", SYMLINK+="kpn_dongel"

with, of course, your values for idVendor, idProduct and the symlink. You may need to add ATTR{serial}==blahblah to make it unique. Add a similar line for each device you want a symlink for. Note the comma's to separate each item. Forget one, and your rule won't work.

Now load the new rule. Rebooting is the crass way, this is much more elegant:

sudo udevadm trigger

Finally, test it. Three useful commands:

ls -l /dev/kpn_dongel

will give something like:

lrwxrwxrwx 1 root root 7 Jan 17 21:48 /dev/kpn_dongel -> ttyUSB1
ls -l /dev/ttyUSB1

Will give something similar to:

crw-rw---T 1 root dialout 188, 1 Jan 17 21:48 /dev/ttyUSB1

Note the ownerships and group memberships. Software will have to be aware of this, chown and chgrp may come in handy. But that's a different subject.

Finally, the output of this should point to the appropriate ttyUSB, and you'll recognise vendor name in there too.

udevadm test -a -p  $(udevadm info -q path -n /dev/kpn_dongel)

That's it! In gammu's config file, you can now refer to the device by persistent name /dev/kpn_dongel (for this example). And it won't change.

Final notes

Convenient file editing on RPi

My Pi is the early 256MB version. Anything you run on the GUI over VNC will lag. So I'm using it headless over SSH. Does it make sense to be working in 256 or even 512 MB using Pi's rather primitive nano editor, when I'm actually operating a nice laptop with 4000 times more memory and all the tools that I'm familiar with? I think not. So I used Samba to make a shared directory on my Pi, and I use Notepad++ on my laptop to edit the files in that directory. The only thing I need to take care of is ANSI-encoding and Linux line ending, but NPP can handle that for me.
And then all I need to do on Pi in a case like this is something like

sudo cp [myshare]/filename /dev/udev/rules.d/ .

I wrote this post because I never found a single text with all the necessary information in one place. It's useful for me, I hope it's useful for you.

Sources and references

1. The conversation was paraphrased from an Powerpoint by Atmel that is no longer online. Instead, Atmel have a nice Application Note that also explains enumeration in detail. back.

2. Source: debian wiki on udev. back.

3. Thanks to Michael Ludvig's Hintshop for the syntax in step 6 and the ls tests in step 8.

4 opmerkingen:

  1. Good Day Sir, I have found this article very useful, thanks. I would like to ask if you can elaborate on your statement
    "My modem occupies 2 ttyUSB's. By trial and error, I found out that sending and receiving works on ttyUSB1."
    What did the trial and error involve? My modem has 3 ttyUSB's and I need a better way of identifying which one to use.

  2. Using Gnokii (or Gammu), you just send a text message from /dev/ttyUSBx to your cell phone. Then you set Gnokii into listening mode, and you send a reply text message from your cell phone. Try that on all three TTYUSBx (with x = 1, 2 and 3 of course). See also here.

    If you plug the modem into a Windows machine, you'll probably see three devices as well, and you can inspect them with Windows Explorer. One usually contains the drivers or some other OEM software, another often some amount of memory (i.e. a memory stick). The other is the modem. But your device may be different. And in Windows, you don't see what x is in USBx. But it may help.

  3. Thank you for your excellent article. I am using a USB Audio device on a Raspberry Pi (Raspbian linux) and also using gnuradio. I found the following process yielded a persistent name in that environment.
    for USB audio input (from a dongle):
    arecord -L
    find the entry such as:
    USB Audio Device, USB Audio
    Direct hardware device without any conversions
    use hw:CARD=Device,DEV=0 as the device name

    for audio output:
    aplay -L
    find the entry such as: (this is for HDMI sound)
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Direct hardware device without any conversions
    use hw:CARD=ALSA,DEV=1 as the device name

    I thought you might like to know, since documentation is lacking.

    - Barry

  4. Hoi Rolf,
    goed verhaal.
    Ik was op zoek naar dit om een radio via usbaudio stick aan te sluiten
    Natuurlijk na reboot moet het wel weer werken.
    gr Martin