Monday, September 9, 2013

NMEA string parser in Python - if you want to develop GPS tracker software in Python

GPS devices usually spit out location information in NMEA format. This format is quite standard. Here is the example of NMEA strings:

$GPGGA,002153.000,3342.6618,N,11751.3858,W,1,10,1.2,27.0,M,-34.2,M,,0000*5E
$GPRMC,161229.487,A,3723.2475,N,12158.3416,W,0.13,309.62,120598, ,*10

Those two lines contains geo-location coordinates, bearing, speed and also some information about the number of satellites being used.

I wrote this simple utilities to parse out those strings to kill time and hopefully this little piece of code inspires other to work on geo-location related services.

# NMEA messages decoding library
# Main decoder of the string messages
def nmea_decode(str):
ret = dict({'type': 'unknown'});
data = str.split(',');
if (len(data)>0):
# start decoding messages
if (data[0] == '$GPGGA'):
ret['type']='GGA';
ret.update(decode_gga(data));
elif (data[0] == '$GPRMC'):
ret['type']='RMC';
ret.update(decode_rmc(data));
#elif (data[0] == '$GPGSA'):
#print(data[0]+' not decoded')
#elif (data[0] == '$GPGSV'):
#print(data[0]+' not decoded')
# fall back
return ret;

def decode_gga(da):
ret = dict()
if (da[0]=='$GPGGA' and len(da)==15):
ret['utc_time']= da[1][0:2]+':'+da[1][2:4]+':'+da[1][4:]
ret['lat_deg']=int(da[2][0:2])+(float(da[2][2:])/60)
ret['lat_dm']=da[2][0:2]+' '+da[2][2:]
ret['ns']=da[3]
ret['lon_deg']=int(da[4][0:3])+(float(da[4][3:])/60)
ret['lon_dm']=da[4][0:3]+' '+da[4][3:]
ret['ew']=da[5]
ret['pfi']=da[6]
ret['sat_used']=da[7]
ret['hdop']=da[8]
ret['msl_alt']=da[9]
ret['alt_unit']=da[10]
ret['geoid_sep']=da[11]
ret['sep_unit']=da[12]
ret['age_diff_cor']=da[13]
ret['diff_ref_sta_id']=da[14][0:4]
ret['csum']=da[14][4:]
return ret;

def decode_rmc(da):
ret = dict()
if (da[0]=='$GPRMC' and len(da)==12):
ret['utc_time']= da[1][0:2]+':'+da[1][2:4]+':'+da[1][4:]
ret['status']=da[2]
ret['lat_deg']=int(da[3][0:2])+(float(da[3][2:])/60)
ret['lat_dm']=da[3][0:2]+' '+da[3][2:]
ret['ns']=da[4]
ret['lon_deg']=int(da[5][0:3])+(float(da[5][3:])/60)
ret['lon_dm']=da[5][0:3]+' '+da[5][3:]
ret['ew']=da[6]
ret['speed_knot']=da[7]
ret['speed_km']=float(da[7])*1.852
ret['course']=da[8]
ret['date']=da[9][4:]+'-'+da[9][2:4]+'-'+da[9][0:2]
ret['mode']=da[10]
ret['csum']=da[11]
return ret;




Monday, December 10, 2012

Moving Linux installation to bigger disk/SSD

I recently had an experience helping customer moving its Linux installation from 4GB CF to 16GB SSD. Normally, we can transfer existing installation from one medium to another easily if the size is the same. Unfortunately this is not the case. So, here are the high level steps to achieve do this.

1. Attach the new disk on spare SATA port.
2. Re-create new partition structure in the new disk. Maintain the partition ordering based on each function (either root, home or swap). Use fdisk.
3. Format each partition on the new disk based on its type. e.g. mkfs.ext3 /dev/sdb1 or mkswap /dev/sdb3
4. Create filesystem structure on the new partitions and copy files according to filesystem structure except /dev /proc /sys (kernel related mount points).
5. Test your new filesystem by rebooting the system and pointing the root to target disk
     e.g. on the GRUB boot screen, edit the option by changing root=/dev/sdb1 or the root partition on the new disk. Ensure the OS boots up OK on the new disk.

6. Install Grub boot loader on new disk.
In order to install the boot loader on the new disk, please ensure that /boot folder from previous installation are properly placed and mount. Then execute the grub as root.
On the GRUB prompt, set the root disk
> root hd(1,0)
(root located at secondary HDD port at at partition 1.)
> setup (hd1)
(setup boot loading scheme on secondary disk).
If there is no error, the boot loading should be installed on the secondary disk. Now shutdown the system and replace the old disk with your newly provisioned disk.

I hope this would help.


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.