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:
/etc/udev/rules.d/50.modems.rules:
KERNEL=="ttyUSB*" ACTION=="remove" , RUN+="/etc/udev/rules.d/eventer.sh remove"
KERNEL=="ttyUSB*" ACTION=="add" , RUN+="/etc/udev/rules.d/eventer.sh add"
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):
#!/bin/bash
dt=`date`
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
then
iam=`whoami`
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 ]
then
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"`
fi
if [ -z $imei ]
then
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"`
fi
if [ -z $imei ]
then
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"`
fi
if [ -z $imei ]
then
dt=`date`
echo "$dt +++ERROR+++ IMEI was not found, i give up. Please reinsert modem $ID_VENDOR_FROM_DATABASE. Exit." >> /etc/udev/rules.d/eventer.log
exit
fi
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 ]
then
dt=`date`
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
exit
fi
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
fi
###action if modem is removed. We need to remove earlier created symlinks
if [ "$1" == "remove" -a "$SUBSYSTEM" == "tty" ]
then
dt=`date`
echo "$dt $DEVNAME Device has been removed" >> /etc/udev/rules.d/eventer.log
dt=`date`
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
dt=`date`
echo "$dt $DEVNAME Removing done." >> /etc/udev/rules.d/eventer.log
fi
dt=`date`
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
then
iam=`whoami`
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 ]
then
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"`
fi
if [ -z $imei ]
then
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"`
fi
if [ -z $imei ]
then
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"`
fi
if [ -z $imei ]
then
dt=`date`
echo "$dt +++ERROR+++ IMEI was not found, i give up. Please reinsert modem $ID_VENDOR_FROM_DATABASE. Exit." >> /etc/udev/rules.d/eventer.log
exit
fi
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 ]
then
dt=`date`
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
exit
fi
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
fi
###action if modem is removed. We need to remove earlier created symlinks
if [ "$1" == "remove" -a "$SUBSYSTEM" == "tty" ]
then
dt=`date`
echo "$dt $DEVNAME Device has been removed" >> /etc/udev/rules.d/eventer.log
dt=`date`
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
dt=`date`
echo "$dt $DEVNAME Removing done." >> /etc/udev/rules.d/eventer.log
fi
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
/tmp/result.txt)
* 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.
poll_modem.py:
#!/usr/bin/python
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('Exit')
sys.exit()
#print 'Polling '+sys.argv[1]
try:
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
except:
sys.exit("ERROR. Unable to open "+sys.argv[1]+' at speed 115200')
#print ser.isOpen()
ser.write("ATI1\r")
response = ser.read(100)
print response
ser.close()
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('Exit')
sys.exit()
#print 'Polling '+sys.argv[1]
try:
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
except:
sys.exit("ERROR. Unable to open "+sys.argv[1]+' at speed 115200')
#print ser.isOpen()
ser.write("ATI1\r")
response = ser.read(100)
print response
ser.close()
'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, 113 months ago. »

I don't think there is another mechanism for identifying the correct ttyUSBy device to use when inserting USB GSM modems in random order that works quite as well. If something else works better than this please let us know.