Thursday, October 20, 2011

Sending SMS via GPRS modem using Perl Device::SerialPort

Sometimes ago, I posted a script to send SMS using Gammu. Now, I would like to deep dive on the underlying communication to GPRS modem using AT command to send SMS.

The sequence of AT commands to use are:
  • ATZ : Reset modem 
  • ATE0 : Disable echo (not necessary)
  • AT+CMGF : Setting the SMS mode (Text or PDU) 
  • AT+CSCA : Query or set the SMS center number 
  • AT+CMGS: Send SMS 
To cut the story short, here is the example of Perl script I use to send SMS using a GPRS modem.Note that checks have been omitted for brevity.

#!/usr/bin/perl
use POSIX;
use Device::SerialPort;

$modem_dev="/dev/ttyS1";
# change it to your mobile network SMSC number.

$smsc="+62811000000";

# to read modem response
sub readModem
{
    if (!defined $_[0])
    {
        return;
    } else {
        my $tmout = 2;
        my $ret = "";
        while ($tmout>0)
        {
            my ($cnt,$saw) = $_[0]->read(255);
            if ($cnt>0)
            {
                $ret.=$saw;
            } else {
                $tmout--;
            }

            select(undef,undef,undef,0.2);
        }
        return $ret;
    }
}


$modem = Device::SerialPort->new($modem_dev);
if (defined $modem)
{
    $modem->databits(8);
    $modem->baudrate(115200);
    $modem->parity("none");
    $modem->stopbits(1);
    $modem->stty_echo(0);
    $modem->read_char_time(0); # don't wait each char
    $modem->read_const_time(500); # spend 500ms to read data from serial line
} else {
    print "Modem ".$modem_dev." is not available.\n";
    exit();
}
 

$phone = $ARGV[0];
$msg = $ARGV[1];

$modem->write("ATZ\r\n");

$str = &readModem($modem);

$modem->write("ATE0\r\n");
$str = &readModem($modem);
 

$modem->write("AT+CMGF=1\r\n");
$str = &readModem($modem);
 

$modem->write("AT+CSCA=\"$smsc\",145\r\n");
$str = &readModem($modem);
 

# Some modems requires \r\n  for this command
$modem->write("AT+CMGS=\"".$phone."\"\n");
#$modem->write("AT+CMGS=\"".$phone."\"\r\n");
$str = &readModem($modem);
# wait for 2 second for the prompt to appear
select(undef,undef,undef,2);\" : ".$str."\n";

$modem->write($msg.pack('c',26));

sleep(2);
$str = &readModem($modem);

$modem->close();


To use this script, you need to pass 2 arguments which are phone number as the first argument and the message as the second argument.
e.g:
# ./sendSMS "+62812344556" "Test SMS via Perl"

I hope this could help.

Thursday, October 13, 2011

3G/GPRS connection using pppd and chat for M2M communication

In most cases, connecting to the Internet using 3G modem on normal desktop Linux is fairly easy because the networking support framework is quite mature.

However, for small embedded system, having a full blown networking support framework is a luxury. And usually the desktop networking framework is developed for interactive human intervention whereas for some embedded cases the 3G network connection is on demand and automated by other application request.

For M2M cases, usually a data connection is requested on demand. A specific task sometimes requests to transfer data over the Internet to the report server. To ensure the task is completed, the data connection must be established beforehand and terminated right after the task completed.

Now, the challenge is: can we establish data connection on demand through an application?

The answer is YES you can and it can be done fairly simple. I read a great post here (http://freshfoo.com/articles/using_optus3g_with_linux.html) using pppd and chat and I did some experiment with this.

In order to connect to 3G, there are 2 main and 2 additional configuration files required to establish the data connection.
  1. the PPP peers configuration 
  2. the chat negotiation script 
  3. the pap-secret file 
  4. the chap-secret file 
I have tested this on a Telkomsel Flash unlimited package. Since there is no authentication whatsoever required by Telkomsel Flash connection, I will ignore item number 3 and 4.

PPP peers configuration

I named the peer as "internet", so I created a file /etc/ppp/peers/internet with the following entry:

/dev/ttyS1
115200
lock
crtscts
modem
noauth
defaultroute
replacedefaultroute
user guest
password guest
connect "/usr/sbin/chat -V -f /etc/ppp/chat-internet"
noipdefault
usepeerdns 


The above configuration says that ppp will connect through the second serial port with 115200 baudrate and the rest you can check its meaning at pppd manual. Beware on "replacedefaultroute", not all ppp package support this option.

In order to connect to the other end, pppd will call using chat with a script defined on /etc/ppp/chat-internet. The following is the chat script I use (defined at /etc/ppp/chat-internet):

ABORT BUSY 
ABORT 'NO CARRIER' 
ABORT ERROR 
REPORT CONNECT 
TIMEOUT 10 
"" "ATZ" 
OK "AT&F" 
OK 'AT+CGDCONT=1,"IP","internet"' 
SAY "Calling...\n" 
TIMEOUT 120 
OK "ATD*99***1#" 
CONNECT \c

It says that it will dial to APN "internet". In order to test if it is working, I call the pppd manually using the following command:

# pppd call internet nodetach debug dump

It will show the debugging information and all the ppp negotiation. You can terminate this connection using Ctrl+c or by killing pppd process. To establish connection normally, we call pppd without nodetach debug dump option.

# pppd call internet

If it works OK, then we can proceed to the next step which is using this connection on demand.  

Automating connect, transfer and disconnect process

As you have seen above, we can connect to 3G/GPRS via command line, thus we should be able to call it from a shell script.

Now, in order to check if the connection has been established and the default route has been changed to go through ppp link the "netstat -nr" command can be used. If the default route has been changed to go via ppp link, there will be a line which starts with "0.0.0.0" which means default route and ends with "ppp0" which is the network link device it goes through. The following command will check that condition.

# netstat -nr | grep -e ^0.0.0.0.*ppp0$

If it produces a non-empty string, then the link is established. Now we can check this condition within shell script.

The following example script I had written can be used as then skeleton to implement the automation assuming only one ppp network link is used. I put it at /opt/bin/connect-uploaddata-disconnect.sh

#!/bin/bash
echo "Initiating ppp connection"
/usr/sbin/pppd call internet

cnt=0
res=`netstat -nr | grep -e ^0.0.0.0.*ppp0$`
while [ -z "$res" ] && [ $cnt -lt 10 ]; do
    echo "Wait for another 3 second."
    sleep 3
    cnt=$[cnt+1]
    res=`netstat -nr | grep -e ^0.0.0.0.*ppp0$`
done

if [ -n "$res" ]; then

   # data transfer simulation
    echo "Lets fetch from www.google.co.id using curl."
    data=`/usr/bin/curl -s http://www.google.co.id`
    echo $data

    # Do some more network related task here then terminate pppd
    echo "Terminating ppp connection"
    killall -9 pppd
else
    echo "PPP connection is not available within 10 second"
    echo "Cleaning up any stall pppd process"
    killall -9 pppd
fi


Now, we should have a script to perform a complete cycle of a task.

Scheduling the task.

To perform this task periodically, usually we define a cron entry. Let say we would like the M2M agent to execute the task at every hour, all day, at minute 1. Then, we need to add the cron entry using "crontab -e" with the following entry:

1 * * * * /opt/bin/connect-uploaddata-disconnect.sh

And then save it. Now we should have our M2M agent to perform this task automatically. I hope this help anyone who faces similar issue.

Tuesday, April 19, 2011

Linux kernel modules configuration on Yocto Project Bernard

For the last two weeks, I have been working on developing custom Linux OS image using Yocto Project system. One of the requirement is that the device should support V4L(1|2) and various USB webcam. However the default kernel configuration does not contain the required options.

I have been trying to:
- add defconfig file into recipes but it is always been ignored by Bitbake
- add partial kernel config fragment into recipes but not applied by Bitbake
- overriding defconfig in the build source directly but ignored by Bitbake

Finally I could do that by using "menuconfig" command. Here is the step:

$ bitbake -f -c compile virtual/kernel

let it finish, call menuconfig and recompile

$ bitbake -f -c menuconfig virtual/kernel

Select the appropriate drivers you require

$ bitbake -f -c compile virtual/kernel
$ bitbake -f -c deploy virtual/kernel

Go to the "tmp/deploy/images", check your modules-xxx.tgz to see if you can find your modules. For me, the way I check is like this:

$ tar -ztf modules-xxx.tgz | grep v4l

Hope this is useful.