Practical daily usage of smsd+set of USB-modems (19 items) shows, that /dev/ttyUSB1 can point to different modem after this modem has been reinserted or inserted into another USB hole at motherboard (usb-hub). Also, 2-port USB-device MODEM_A can occupy /devttyUSB3 and /dev/ttyUSB5 instead of /dev/ttyUSB0+/dev/ttyUSB1.
As a result, we have strong ravel in smsd.conf. Also, keep in mind, that Huawei 3G USB modems have 1 hardware port but 2 software (/dev/ttyUSB) ports.

The only reliable way to uniquely define modem in OS - is its IMEI. Other ways (e.g. udev+ATTRS{serial}) are not reliable and depend on capability of modem to show its serial number. We will create another virtual device with its personal number, a /dev/modemx (which actually is a symlink to /dev/ttyUSBy , interested for us) and set this /dev/modemx as a "device" parameter in section [GSMx] in smsd.conf . List of "IMEI-to-logical name" relations is stored in /etc/udev/rules.d/IDS.lst in following format:

Here is sample of how to link modem to its IMEI and define obtained device in smsd.conf permanently.

We use udev mechanism to catch event of device's insertion/removal:

KERNEL=="ttyUSB*" ACTION=="remove" , RUN+="/etc/udev/rules.d/eventer.sh remove"
KERNEL=="ttyUSB*" ACTION=="add" , RUN+="/etc/udev/rules.d/eventer.sh add"
'smsdconf' Syntax Highlight powered by GeSHi

As we see, during insertion|removal event, OS calls /etc/udev/rules.d/eventer.sh with parameters "add" or "remove". Of couse, this parameter can be replaced with $ACTION environment variable, but let it stay like this. Except $ACTION, udev sets many env vars during insertion|removal, which provide many info about connected device, and eventer.sh user some of them.

Here is eventer.sh , who logs into /etc/udev/rules.d/eventer.log for better debugging. Keep in mind, that udev launches 2 eventer.sh according to number of /dev/ttyUSB's appeared in OS during insertion (for Huawei modem):


echo $dt Device $DEVNAME, Maker is $ID_VENDOR_FROM_DATABASE was $1 >> /etc/udev/rules.d/eventer.log   #simple example of using env vars, pointed to device, provided by udev

if [ "$1" == "add" -a "$SUBSYSTEM" == "tty" ]  # if exactly modem has been inserted , not usb-drive etc. To find it out , we take 2 env vars, which have been set just by udev engine. You can take "$ACTION" var  instead of $1
    echo "We do it all under user $iam"  >> /etc/udev/rules.d/eventer.log
    echo Modem insertion detected  >> /etc/udev/rules.d/eventer.log
    echo Finding out IMEI for $DEVNAME >> /etc/udev/rules.d/eventer.log
#debug     chmod 777 $DEVNAME  #you may want to do it or not, it's optional
    echo ---Calling  "/etc/udev/rules.d/poll_modem.py $DEVNAME | grep IMEI | cut -d " " -f 2 | tr -d \\r\\n"  >> /etc/udev/rules.d/eventer.log  #here we call special Python script who communicates to modem and asks for IMEI via AT-coomands (listed below)
    imei=`/etc/udev/rules.d/poll_modem.py $DEVNAME | grep IMEI | cut -d " " -f 2 | tr -d "\r"`
# is  modem didn't answer instantly  - we keep trying to poll it
    if [ -z $imei ]
        echo "---IMEI was not found, retry 1..." >> /etc/udev/rules.d/eventer.log
        imei=`/etc/udev/rules.d/poll_modem.py $DEVNAME | grep IMEI | cut -d " " -f 2 | tr -d "\r"`

    if [ -z $imei ]
        echo "---IMEI was not found, retry 2..." >> /etc/udev/rules.d/eventer.log
        imei=`/etc/udev/rules.d/poll_modem.py $DEVNAME | grep IMEI | cut -d " " -f 2 | tr -d  "\r"`

    if [ -z $imei ]
        echo "---IMEI was not found, retry 3..." >> /etc/udev/rules.d/eventer.log
        imei=`/etc/udev/rules.d/poll_modem.py $DEVNAME | grep IMEI | cut -d " " -f 2 | tr -d  "\r"`

    if [ -z $imei ]
        echo "$dt +++ERROR+++ IMEI was not found, i give up. Please reinsert modem $ID_VENDOR_FROM_DATABASE. Exit." >> /etc/udev/rules.d/eventer.log

    echo "Found IMEI $imei for $DEVNAME">> /etc/udev/rules.d/eventer.log

# as said before, 1 Huawei USB-modem has 2 virtual modems. I use first to send sms via smsd,  and second is to get helpful info about modem's life. First one has ID_USB_INTERFACE_NUM=00, second one - ID_USB_INTERFACE_NUM=01. Here I call 00 and 01 "a role".
    echo "Finding out role of $DEVNAME (main/help)..."  >> /etc/udev/rules.d/eventer.log
    echo "---ID_USB_INTERFACE_NUM for $DEVNAME is $ID_USB_INTERFACE_NUM"  >> /etc/udev/rules.d/eventer.log

#now we try to make symlink for device
    echo "Trying to find device alias for $imei (subdevice $ID_USB_INTERFACE_NUM) in /etc/udev/rules.d/IDS.lst"  >> /etc/udev/rules.d/eventer.log
#grepping IDS.lst for IMEI and "role":
    alias=`grep $imei /etc/udev/rules.d/IDS.lst | grep $ID_USB_INTERFACE_NUM | cut -d " " -f 3`
    if [ -z $alias ]
        echo "$dt +++ERROR+++ No record for $imei and $ID_USB_INTERFACE_NUM was found in /etc/udev/rules.d/IDS.lst, check it. Exit." >> /etc/udev/rules.d/eventer.log
    echo "Found alias - $alias"  >> /etc/udev/rules.d/eventer.log
    #set symlink in OS
    echo "Setting alias in OS..." >> /etc/udev/rules.d/eventer.log
    echo "---Executing ln -s $DEVNAME /dev/$alias"  >> /etc/udev/rules.d/eventer.log
    ln -s $DEVNAME /dev/$alias
     echo "Done. All finished. Exit."  >> /etc/udev/rules.d/eventer.log

###action if  modem is removed. We need to remove earlier created symlinks
if [ "$1" == "remove" -a "$SUBSYSTEM" == "tty" ]
    echo "$dt $DEVNAME Device  has been removed"  >> /etc/udev/rules.d/eventer.log
    echo "$dt $DEVNAME Finding symlink for this device and removing it (them)..."  >> /etc/udev/rules.d/eventer.log
    ls -l /dev/modem* | tr "\n"  ";" | sed -e s/";"/";\n"/g | grep "$DEVNAME;" | ls -l /dev/modem* | tr "\n"  ";" | sed -e s/";"/";\n"/g | grep "$DEVNAME;" | cut -d "/" -f 2,3 | cut -d  " " -f 1 | sed -e s/"dev"/"\/dev"/g | xargs rm
    echo "$dt $DEVNAME Removing done."  >> /etc/udev/rules.d/eventer.log
'bash' Syntax Highlight powered by GeSHi

Important part of this process is a Python script which polls modem, sending there ATI command and reading output with IMEI. Python has been selected after trying:
* bash+minicom - failed. (minicom -D /dev/ttyUSB0 -S poll.min -C /tmp/result.txt writes empty
* bash+socat - failed. unpredictable output.
* php - failed. (php_dio.so extension unstable works in my PHP 5.4-5.6)

don't forget to install pyserial module.

import serial
import sys
#print 'Num of parameters passed '+str(len(sys.argv))
if len(sys.argv) < 2:
    print('No modem path specified. Use '+sys.argv[0]+' /dev/ttyUSBx')
#print 'Polling '+sys.argv[1]
    ser = serial.Serial(sys.argv[1],9600,dsrdtr=True,rtscts=True) #Mandatorily set dsrdtr=True,rtscts=True. In case of unsetting them, You can get successfull polling /dev/ttyUSB0, but failed /dev/ttyUSB1
    sys.exit("ERROR. Unable to open "+sys.argv[1]+' at speed 115200')
#print ser.isOpen()
response =  ser.read(100)
print response
'smsdconf' Syntax Highlight powered by GeSHi

Now, as a result, we can 1 000 times reinsert modem with IMEI 1111111 into different holes in M/B or USB-hub, reboot server 10000 times - device with IMEI 1111111 will always be /dev/modem1

« Last edit by kontrolsson on Mon Jul 04, 2016 12:19, 40 months ago. »