Archive for the ‘GPS Event’ Category.

GPS Event [9] – gtalk

Google's gtalk is available inside a browser (chat only) or as a separate desktop application (chat + voice). If you have a gmail or Google Apps account you are good to go.

For the example I needed two accounts — was not able to send messages from "me" to "me".  The code does not check for availability of the recipient, so make sure that you are logged to the gmail or have the gtalk running.

The three basic modules:

To select the service in the main gps_event.py module:

# select service: none, aws, rc, tweet, email, gtalk, coral8
use_service = 'gtalk'
 
# define sampling delay in seconds
sample_delay = 25

Finally, the result: 


 

GPS Event [8] – Email

Another notification service, the example actually uses Google's Gmail. There is no need for an extra email account, because you can send email from "you" to "you" — as in the example. Similar to the previous post, we need three new modules:

and to select the service in the main gps_event.py module.

# select service: none, aws, rc, tweet, email, coral8
use_service = 'email'
 
# define sampling delay in seconds
sample_delay = 180

The sample delay is set to three minutes, not to overload  the inbox.



To be continued.

 

GPS Event [7] – tweet

"When this happens, notify me." The basic expectation one could have from an EP application. Once upon a time, automated pager notification systems would cost a fortune, but times have changed for the better. Here is what it takes for our event (see pervious posts) to show up on twitter.

First we need a twitter account and a desktop client. It takes less than five minutes to get both of these, so just do it.

For this example I needed two accounts, one for me and the other one for the event generator (GPS). Instead of creating a new account for the GPS, I have simply borrowed my cat's account — just for the experiment.

Time for all that refactoring from the previous post to pay off; only four steps in adding a service now:

  1. Add template module;
  2. add authorization module;
  3. add service module;
  4. select the service in the gps_event.py.

Twitter is a short message service, so I have selected only: entity, time_stamp, latitude and longitude for this template.

"""
gps_template_tweet.py
generic tweeter template for GPS event
"
""
 
def template(pretty = False):
    """$entity_here$; $time_stamp_here$;
$latitude_here$; $longitude_here$"
""
 
    return compress_template(template.__doc__)
 
def compress_template(s):
    return ' '.join(s.split())
 
if __name__ == "__main__":   
    print template(True)
    print template()

Authorization data.

"""
gps_auth_tweet.py
Authorization strings for Tweeter
"
""
 
tw_user = 'username_here'
tw_pass = 'password_here'
 
if __name__ == "__main__":
    print 'nuser= %snpassword= %s'% (tw_user, tw_pass)

Service module sends messages.

"""
gps_service_tweet.py
Tweeter service for gps event
"
""
 
import urllib
import gps_auth_tweet
 
(tw_max, rsp) = (140, '')
 
def send(msg):
    global rsp
    if len(msg)> tw_max:
        s = msg[:tw_max-3] + '...'
    else:
        s = msg
    dta = urllib.urlencode({'status':s})
    tw_url = "http://%s:%s@twitter.com/statuses/update.xml" %
             (gps_auth_tweet.tw_user, gps_auth_tweet.tw_pass)
    try:
        rsp = urllib.urlopen(tw_url, dta)
        r = True
    except:
        r = False
    return r
 
 
if __name__ == "__main__":
    print send('Test msg')
    print rsp.read()


Select 'tweet' as a service and 180 seconds for the delay in the gps_event.py. Twitter currently limits hourly number of request to about 40, so we'll be at half of that.

# select service: none, aws, rc, tweet, email, coral8
use_service = 'tweet'
 
# define sampling delay in seconds
sample_delay = 180


Those following phoebe_cat on twitter may have seen tweets like this one lately.

To be continued.


 

GPS Event [6] – Refactoring

Time to clean up a bit. As new services are added this code is becoming bulky. I was writing it bottom-up, code as you go — no wonder. Time to stop coding and reflect on it, before it bloats out of control.

The first task is to identify code that changes with the addition of a service.

Item Description Comment
1 Collecting GPS data over the serial port Does not change.
2 Parsing collected GPS data Does not change.
3 Template formats A new template is created each time a service is added.
4 Search and replace on a template Does not change — it depends on the GPS data set.
5 Service invocation, data transfer Different for each service; code grows with each new service.
6 Main program Does not change. Read serial port, parse, fetch a template, s/r through the template, send to service, wait, loop.

 

It should be noted that — at present — multiple templates are loaded in memory, but only one is needed.  This will have to be reworked to load code and a template for only one service at a time.

So here is the task list:

  • Extract GPS data collection and parsing into a separate module or class.
  • Each template gets its own module with generic template() function to return the string.
  • Put each service in its own module with generic send()  function.
  • Load required template and service modules dynamically into the main module.
  • Try to follow Python naming conventions for function names, classes etc.
  • Use consistent naming convention for module files.
  • And first, before anything else, write a test set (unit test) to deal with bugs introduced while refactoring.

Time goes by.

Ok, done; these are new (modified) modules, after refactoring.

Services:

Templates:

Authorization data:

Other:

 

Lots of files, but keep in mind that only one set (service  + template + authorization) is loaded at a time. All files reside in the same directory. I do not list test units here, essentially each code module has a matching test_.py. You may notice that some services are not implemented yet, the modules are simply place-holders. This is one of the benefits of refactoring, it does not just clean up the code — it includes redesign to allow for seamless future changes.

With this structure in place I should be ready for the next fun episode — twitter the GPS.

To be continued.

GPS Event [5] – ruleCore

Marco graciously offered me a temporary tinker-a-bit account on the ruleCore Geofence service, so I will try to move my event objects (see previous posts) to their server and see how it goes. Again, this is a blog-as-you-go series and at this time I have only a fuzzy idea of what lies ahead.

The ruleCore API seems straightforward, I like that.

Event Name Direction Comment
Location In GPS data to the server.
ZoneEntered Out Triggered upon entering a zone.
ZoneExited Out Triggered upon exiting a zone.
LingeredInZone Out Triggered when an Entity stays in a zone for a specified period.
CrowdedZone Out Triggered when more than X Entities are present in a zone.
ThinZone Out Triggered when less than X Entities are present in a zone.

 

Each event has its own predefined XML structure; at present I am interested only in the location event. Here is an example from the API:

 

<?xml version="1.0" encoding="iso-8859-1"?>
<Location xmlns="http://www.rulecore.com/2008/user"
xmlns:base="http://www.rulecore.com/2008/base">

base:eventTimestamp="2008-12-30T20:19:31Z"
base:eventId="91ce7524-3336-4619-a9a0-e8ee32d2f609">
    <base:EventHeader>
        <base:SecurityInfo>
            <base:Credentials>account_credentials_here</base:Credentials>
        </base:SecurityInfo>
    </base:EventHeader>
    <base:EventBody>
        <EntityId>DS_GPS_001</EntityId>
        <Coordinates>43.647194999999996,-79.398236666666662,0</Coordinates>
    </base:EventBody>
</Location>

 

It obviously looks different from my event-object format:

 

<?xml version="1.0" encoding="UTF-8"?>
<event>
    <head>
        <id>91ce7524-3336-4619-a9a0-e8ee32d2f609</id>
        <type>GPS</type>
        <time_stamp>2008-12-30T20:19:31Z</time_stamp>
        <entity>DS_GPS_001</entity>
    </head>
    <body>
        <latitude>43 38.8317 N</latitude>
        <longitude>079 23.8942 W</longitude>
        <speed>00.0</speed>
        <course>000.0</course>
        <valid>True</valid>
    </body>
</event>

 

Time to summarize the difference in data sets.

Comment
id eventId Ok, both are UID strings.
type -- GPS is default for the service.
time_stamp eventTimestamp Ok, same format.
entity EntityId Ok, both are strings.
latitude -- Combined as decimal format in coordinates (mine is deg min).
longitude -- Combined as decimal format in coordinates.
speed n/a Not in ruleCore format.
course n/a Not in ruleCore format.
valid n/a Not in ruleCore format, have to make sure not to transmitt invalid records.
-- Coordinates Combines latitude, longitude and altitude in decimal formats. I do not have altitude; mine is marine format, always zero.
n/a Credentials I do not have this; will be part of the template.

 

Not bad, almost everything is here — it just needs a new template and search/replace will do the rest.

The ruleCore service requires ordered events. The Amazon SQS bus (see the previous post) does not guarantee ordered arrival of messages, hence I will have to transfer data directly to ruleCore.

Once I get events over to the server I will think about setting up zone rules and output events.

 

To be continued.

 

Disclosure:

Everything I write about ruleCore is my own opinion. There is no contract nor any kind of active business relationship between ruleCore and me (Damir Systems Inc). The following is from Marco's email:

... there's no need for an NDA, you can write whatever you like about ruleCore. ...

 

GPS Event [4] – Go

With the data bus in place, it is time to start moving event objects (see previous posts).

The first module — qps_aws_auth.py — contains only authorization data for the Amazon SQS service, which brings us to the first problem. One can have many queues registered, however there is only one user-password pair for all the queues and for all operations on these queues. This is an equivalent of having a database allowing for only one user with admin privileges — not quite designed for sharing.

Note:

A recent entry in the SQS forum indicates that Amazon is working on queue sharing between different AWS accounts, which would remedy the problem.

 

"""
gps_aws_auth.py
Authorization strings for AWS SQS
"
""
 
awsId = 'AWS_ACCESS_KEY_ID'
awsKey = 'AWS_SECRET_ACCESS_KEY'
Queue = ['QUEUE_1_NAME', 'QUEUE_2_NAME', 'QUEUE_3_NAME']
 
if __name__ == "__main__":
    print 'n id= %snkey= %snqueue= %s'%
          (awsId, awsKey, ', '.join(Queue))

 

Boto library handles all ASW communications, so the code is short; name the module gps_aws.py.

 

"""
gps_aws.py
Connecting to Amazon SQS
"
""
 
from boto.sqs.connection import SQSConnection
from boto.sqs.message import Message
import time
import gps_aws_auth
 
conn = None
myQueue = None
 
def ConnectQueue(QueueName=gps_aws_auth.Queue[0]):
    global conn, myQueue
    r = False
    if not conn:
        conn = SQSConnection(gps_aws_auth.awsId,
                             gps_aws_auth.awsKey)
 
    myQueue = conn.lookup(QueueName)
    if myQueue:
        r = True
    return r
 
def SendToQueue(msg):
    """ Send the string to the queue;
        it will be base64 encoded"
""
    global conn, myQueue   
    if not conn:
        r1 = ConnectQueue()
    if myQueue:
        m = Message()
        m.set_body(msg)
        r = myQueue.write(m)
    else:
        r = False
    return r

 

Only sendToCep(xst) function in the gps_event.py module (see previous post) has to be modified.

 

def sendToCep(xst):
    """ Send XML string to EP/CEP/BUS """
    global msg_cnt
    r = gps_aws.SendToQueue(xst)
    print 'Send result: %s; msg %d' % (r, msg_cnt)
    if r:
        print xst
        msg_cnt += 1

 

This is all it takes to move our GPS events to a globally available message bus. Having a sensor-only device, we do not need to read from the bus in this application, but here is the read code for debug purposes. Note that Boto uses base64 encoding, so in order to exchange data between SQS Scratchpad and Boto a base64 encoder/decoder is needed, like this one.

Add the following code to the end of the qps_aws.py module.

def ReadFromQueue(delMsg = False,
                  prnMsg = False, maxMsg = 10):
    """ Read up to 10 messages,
        return list of message strings "
""
    global conn, myQueue   
    (li, td) = ([], 3)
    if not conn:
        r1 = ConnectQueue()
   
    rs = myQueue.get_messages(num_messages=maxMsg,
                              visibility_timeout=60)
    if len(rs) == 0:
        time.sleep(td)
        rs = myQueue.get_messages(num_messages=maxMsg,
                                  visibility_timeout=60)
 
    if len(rs)> 0:
        for m in rs:
            li.append(m.get_body())
            if prnMsg:
                print li[-1]
        if delMsg:
            for m in rs:
                myQueue.delete_message(m)
    return li
 
 
if __name__ == "__main__":
    print 'connection status: %s' % (ConnectQueue(),)
 
    # Load test messages
    # for i in range(10):
    #    print SendToQueue('Test message %d' % (i,))
 
    # Read
    li = ReadFromQueue(delMsg = True,
                       prnMsg = False, maxMsg = 10)
    for m in li:
        print 'n%sn%s' % ('-' * 20,m)

 

Once in the bus, messages are available globally to any application which can communicate to Amazon SQS; libraries are available for almost all software platforms. Messages expire after four days.

The code should run unchanged on Windows, Linux and Mac — as long as your GPS transmits the $GPMRC NMEA message. Some experimentation may be needed with serial to USB converters for laptops without a serial port.

Coming up next: transfer to one of EP/CEP servers.

 

GPS Event [3] – Bus

Now that I have some events flowing — see the previous post — it is time to route them somewhere. I was going to start working on Coral8, but this morning I saw this article on Amazon Simple Queue Service (SQS); so I thought, why not? I always wanted a message bus — hey, everyone should have one; holiday season and all.

Here is what it takes to get a message queue these days:

Step 1 - administer:

  • Sign up for an Amazon AWS account.
  • Sign up for the SQS service — this is where you need your Visa card.
  • Obtain AWS access keys from 'Your Account' menu on the AWS services page.
  • Download the Amazon SQS Scratchpad so you can start tinkering immediately.

Step 2 - set it up:

  • Start the SQS Scratchpad.
  • Enter access keys.
  • Select Create Queue from the Explore API list.
  • Fill in the Queue Name and leave the Visibility Timeout blank — default is 30seconds.
  • Click Invoke Request, the service returns the queue URL — jot this down.
  • It takes some time for this request to propagate through AWS, so have a coffee — tea will do too.

Step 3 - tinker:

  • Select List Queues from the Explore API list;
  • click Invoke Request;
  • the service responds with something like this:

<?xml version="1.0" ?>
<ListQueuesResponse xmlns="http://queue.amazonaws.com/doc/2008-01-01/">
    <ListQueuesResult>
    <QueueUrl>http://queue.amazonaws.com/EventQueue</QueueUrl>
  </ListQueuesResult>
    <ResponseMetadata>
        <RequestId>02e1a2e9-288f-44f3-9962-08a436cdb1e8</RequestId>
    </ResponseMetadata>
</ListQueuesResponse>

And that's it, wasn't bad at all. There is also a nice manual to guide you along the way.

Now you can try writing to and reading from the queue — use the SQS Scratchpad. The Scratchpad exposes the SQS API, so it is possible to manually explore the API commands before one starts programming. Libraries are available for Java, PHP, VB.NET, C#, Perl, Python.

Here are a few things one learns very soon:

  • Messages are stored redundantly across several servers — that's nice.
  • Maximum number of messages that can be returned in one read is 10.
  • If the queue has less than 1000 messages, the read service may actually return less than 10.
  • If the queue has only few messages, the read service may return none of them — so the read has to be repeated for service to scan different servers.
  • The order of messages is not preserved.
  • According to the manual, it is possible to receive a message more than once if all the servers containing the message are not communicating at the time of delete-message operation.

For example — regarding the order of messages — I wrote 10 messages to the queue [Msg 0, Msg 1,..., Msg 9] and read them back.

Read Attempt Returned Messages
1 none
2 4, 7
3 5
4 2, 6
5 1
6 0, 3, 8, 9

In each read attempt I was asking for 10 messages, the maximum number. Is this good or bad? Depends on the application, I guess.

One more detail, when exploring the SQS Scratchpad use Firefox or IE, Google Chrome does not seem to render XML trees nicely.

Next step is to use Python library and start transferring GPS data to the SQS queue.

To be continued.

GPS Event [2] – Object

After some tinkering I was able to cook up some Python code to get the GPS data. So, a Magellan Meridian Gold GPS is connected over the serial port to a PC running Windows XP and Python. The Python version I am using is 2.52 and if you want to play with the code you may want to install the pySerial library before starting.

First, here is the result—cut & paste—from Python Shell.

<?xml version="1.0" encoding="UTF-8"?>
<event>
    <head>
        <id>91ce7524-3336-4619-a9a0-e8ee32d2f609</id>
        <type>GPS</type>
        <time_stamp>2008-12-30T20:19:31Z</time_stamp>
        <entity>DS_GPS_001</entity>
    </head>
    <body>
        <latitude>43 38.8317 N</latitude>
        <longitude>079 23.8942 W</longitude>
        <speed>00.0</speed>
        <course>000.0</course>
        <valid>True</valid>
    </body>
</event>

<?xml version="1.0" encoding="UTF-8"?>
<event>
    <head>
        <id>aed3e5b7-f98a-49d1-b950-4434331df8ae</id>
        <type>GPS</type>
        <time_stamp>2008-12-30T20:19:42Z</time_stamp>
        <entity>DS_GPS_001</entity>
    </head>
    <body>
        <latitude>43 38.8289 N</latitude>
        <longitude>079 23.8965 W</longitude>
        <speed>00.0</speed>
        <course>000.0</course>
        <valid>True</valid>
    </body>
</event>

 

The program reads the serial port every 10-12 seconds, extracts the position and speed data from a $GPRMC message and packages the data into XML form of an event object.

The Python code has two modules (files), the first one— gps_event_template.py —contains only a template for XML and JSON strings; the main module— gps_event.py —has all the working code.

The template has placeholders—like $time_stamp_here$ —which are simply later replaced by GPS data.

"""
gps_event_template.py
XML & JSON templates for GPS event
"
""
 
def xml_template():
    """<?xml version="1.0" encoding="UTF-8"?>
<event>
    <head>
        <id>$id_here$</id>
        <type>$type_here$</type>
        <time_stamp>$time_stamp_here$</time_stamp>
        <entity>$entity_here$</entity>
    </head>
    <body>
        <latitude>$latitude_here$</latitude>
        <longitude>$longitude_here$</longitude>
        <speed>$speed_here$</speed>
        <course>$course_here$</course>
        <valid>$valid_here$</valid>
    </body>
</event>
"
""
 
 
def json_template():
    """{
"
event":{
        "
head":{"id":"$id_here$",
                "
type":"$type_here$",
                "
time_stamp":"$time_stamp_here$",
                "
entity":"$entity_here$"
               },
        "
body":{"latitude":"$latitude_here$",
                "
longitude":"$longitude_here$",
                "
speed":"$speed_here$",
                "
course":"$course_here$",
                "
valid":"$valid_here$"
               }
        }
}
"
""   
 
 
def compress_template(s):
    """ Compress XML or JSON template """
    rli = {'{ ':'{', ' }':'}', ', "':',"', '> <':'><'}
    st = ' '.join(s.split())
    for (k, v) in rli.items():
        st = st.replace(k, v)
    return st
 
   
if __name__ == "__main__":
    print xml_template.__doc__   
    print json_template.__doc__
    print compress_template(xml_template.__doc__)
    print compress_template(json_template.__doc__)

 

Here is the main module; if running from Python Shell, start this one. Python is an easy read, so you should not have trouble following. The sendToCep(xst) function simply prints the XML to the Shell; I need to tinker with the CEP server before implementing this one. Keep in mind that this is a blog-as-you-go project.

 

"""
gps_event.py
 
Reads position and speed data from Magellan Meridian Gold GPS
--connected on COM 1-- every 10-12 seconds.
 
Packages GPS data into XML and sends to a CEP server.
"
""
 
from datetime import datetime
import time
import serial
import uuid
import gps_event_template
import gps_aws
 
ser = serial.Serial()
ser_mode = True
sample_delay = 10
msg_cnt = 1
 
obj = {'id':'','type':'GPS','time_stamp':'','entity':'DS_GPS_001',
       'latitude':'', 'longitude':'','speed':'', 'course':'', 'valid':''}
 
 
def gps_main():
    """ Read GPS every 10-12 seconds, send XML to CEP """
    (ser.port, ser.baudrate, ser.timeout) = (0, 4800, 4)
    ser.open()
    try:
        while 1:
            st = readGps()
            if st:
                sendToCep(st)
            else:
                # break
                pass
            time.sleep(sample_delay)
    finally:
        ser.close()
        return '*** ABORTING gps_event.py ***'
 
def readGps():
    """ Read GPS, create XML if valid reading """
    global ser_mode
    ser.flushInput()
    li = readCom(ser_mode)         
    if li[0] == '$GPRMC':
        obj['id'] = str(uuid.uuid4())
        parseGpsData(li)
        st = makeOutputString(fmt = 'X', pretty = 0)
    else:
        st = None
    return st
 
 
def makeOutputString(fmt = 'X', pretty = 0):
    """ Make XML or JSON string """
    if fmt == 'J':
        st = gps_event_template.json_template.__doc__
    else:
        st = gps_event_template.xml_template.__doc__
 
    if pretty == 0:
        st = gps_event_template.compress_template(st)
 
    for (k,v) in obj.items():
            st = st.replace('$%s_here$'%(k,), v)
 
    return st
 
 
def parseGpsData(li):
    """ Parse GPS list to object """
    obj['time_stamp'] = make_ts(li[1],li[9])
    obj['latitude'] = make_ll(li[3],li[4])
    obj['longitude'] = make_ll(li[5],li[6])
    obj['course'] = li[8]
    obj['speed'] = li[7]
    obj['valid'] = 'True' if li[2] == 'A' else 'False'
 
 
def readCom(sm = False):
    """ Read COM port, split GPS string to a list """
    (i, st) = (0, ['?'])
    while (i <25):
        i = i + 1
        if sm:
            ln = ser.readline()
        else:
            ln = "$GPRMC,172021.20,A,4338.8243,N,"
            ln = ln + "07923.9149,W,00.0,000.0,291208,11,W*48"
        if ln:
            s1 = ln.split(",")
            # print s1
            if s1[0] == '$GPRMC':
                st = s1
                break 
    return st
 
 
def make_ts(tim, dte):
    """ Make time-stamp """
    return datetime(int('20' + dte[4:]), int(dte[2:4]), int(dte [0:2]),
           int(tim[0:2]), int(tim[2:4]), int(tim[4:6])).isoformat() + 'Z'
 
 
def make_ll(c, s):
    """ Make lattitude and longitude """
    i = len(c) - 7
    return ' '.join([c[:i], c[i:], s])
 
 
def sendToCep(xst):
    """ Send XML string to EP/CEP/BUS """
    print xst # just print for now
 
 
if __name__ == "__main__":
    print gps_main()

 

So the next step is a CEP server—install, configure, communicate, do something with.

To be continued.