Requirements for this script:
Make available: An easy to discover and understand, new-enough, and accurate-enough GPS location to other software
Process:
For this exercise we will write the scripts needed directly to the beaglebone using vim. That way we may run and debug the scripts directly. Backing up work is very simple and can be done when needed using SCP.
Hardware:
- Beaglebone rev A6, setup using this tutorial
- Desktop PC running Ubuntu 11.10
- USB GPS dongle
- Linksys Nano WiFi Router
- 4 port USB hub, model unknown
Software on Beaglebone:
- Ubuntu 12.10
- Redis
- easy-install, pip, pyserial
Step One - log into Beaglebone
$ screen /dev/ttyUSB1 115200
Wait a few seconds then press 'enter'
Login using your credentials ie: 'ubuntu' , 'temppwd'
Step Two - set-up the Beaglebone working environment
$ sudo apt-get install vim
Configure vim to be use-able. Either SCP your current .vimrc file over to the beaglebone, or create a new one. I only need a couple of small changes to vim so will create a new one.
cd ~/
mdkir tmp
vim .vimrc
Add these lines to the file.
set tabstop=2
set softtabstop=2
set expandtab
et autoindent
set smarttab
set noswapfile
colorscheme pablo
These settings will shrink the tabs to two spaces. Give you line numbers. Make the tabs actually spaces (to avoid indentation errors) Stop vim from annoying you with .swp files and give you a colorscheme that works OK with python. Save the file and quit vim.
Step Three - set-up USB Devices
Plug in USB hub.
Then into the hub plug in the GPS dongle and Router.
***My USB hub had issues (Unable to enumerate USB device) - fixed here. Then restart Beaglebone
Step Four - use python to access GPS data
We need to find out the serial/USB port that the data will be coming into.
$ ls /dev | less
This give us a readable output of the /dev directory, press 'enter' until we find a result that looks promising like 'ttyUSB0' then press 'q'. Depending on your hardware set-up this may be different.
Create a .py file to hold our script, named with it's intended use in mind.
$ vim read_and_store_gps_data.py
Lets create the skeletion of this file, add the following lines:
#!/usr/bin/python
import sys
import serial
print 'hello world'
Save the file and quit vim ':wq!' Now lets test the file to see if everything might work.
$ ./read_and_store_gps_data.py
Great, time to read data from the GPS
We will want to make this script useful for different types of GPS units using different serial ports and baud rates. Let's leave something to remind us how to use the script. Replace the line print 'hello world' with:
print 'example usage: ./read_and_store_gps_data.py -baud 4800 -port /dev/ttyUSB0'
Now that we can use the system args, we will check them and use them to connect to the GPS.
Edit the file to look like this
:
#!/usr/bin/python
import sys
import serial
def read_line_from_serial():
reading = serial_port.readline()
if reading[:6] == '$GPGGA':
print reading
def start_serial_connection(port,baud,timeout):
try:
serial_port = serial.Serial(port, baud, timeout=timeout)
except Exception, e:
print e
sys.exit()
def is_a_number(thing):
try:
return float(thing)
except Exception, e:
print e
return None
def start_gps_reader():
baud = None
port = None
timeout = None
args_length = len(sys.argv)
for i in range(0,args_length-1):
if (i+1) <= args_length:
if sys.argv[i] == '-baud':
baud = is_a_number(sys.argv[i+1])
elif sys.argv[i] == '-timeout':
timeout = is_a_number(sys.argv[i+1])
elif sys.argv[i] == '-port':
port = sys.argv[i+1]
if baud and port and timeout:
start_serial_connection(baud=baud,port=port,timeout=timeout)
else:
print 'example usage: ./read_and_store_gps_data.py -baud 4800 -port /dev
/ttyUSB0 -timeout 0.5'
Now test the file
$ ./read_and_store_gps_data.py -baud 4800 -port /dev/ttyUSB0 -timeout 0.5
Test the file using some incorrect details to see what happens. If all is running thus far - we can now extract the useful data from the GPS. GPS units output strings called NMEA strings. An excellent reference to these strings is in this
link. For now we only want the GPGGA string.
Here is a breakdown of the GPGGA string:
Name | Example Data | Description |
Sentence Identifier | $GPGGA | Global Positioning System Fix Data |
Time | 170834 | 17:08:34 Z |
Latitude | 4124.8963, N | 41d 24.8963' N or 41d 24' 54" N |
Longitude | 08151.6838, W | 81d 51.6838' W or 81d 51' 41" W |
Fix Quality:
- 0 = Invalid
- 1 = GPS fix
- 2 = DGPS fix | 1 | Data is from a GPS fix |
Number of Satellites | 05 | 5 Satellites are in view |
Horizontal Dilution of Precision (HDOP) | 1.5 | Relative accuracy of horizontal position |
Altitude | 280.2, M | 280.2 meters above mean sea level |
Height of geoid above WGS84 ellipsoid | -34.0, M | -34.0 meters |
Time since last DGPS update | blank | No last update |
DGPS reference station id | blank | No station id |
Checksum | *75 | Used by program to check for transmission errors |
Next we break down the string into its parts. According to the table above. The string is separated by commas. We add a method to parse the GPGGA string, and a class to hold our newly found position. And refactor a little.
#!/usr/bin/python
import sys
import serial
class Position:
def __init__(self,time,latitude,lat_cardinal,longitude,
lon_cardinal,quality,altitude,precision):
self.time = time
self.latitude = latitude
self.lat_cardinal = lat_cardinal
self.longitude = longitude
self.lon_cardinal = lon_cardinal
self.quality = quality
self.altitude = altitude
self.precision = precision
def parse_gpgga_string(gpgga_string):
position = None
gpgga_parts = gpgga_string.split(',')
if not len(gpgga_parts) == 15:
print 'malformed gpgga string'
return False
else:
position = Position(time = gpgga_parts[1],
latitude = gpgga_parts[2],
lat_cardinal = gpgga_parts[3],
longitude = gpgga_parts[4],
lon_cardinal = gpgga_parts[5],
quality = gpgga_parts[6],
altitude = gpgga_parts[9],
precision = gpgga_parts[8])
return position
def get_gpgga_string_from_serial(serial_port):
reading = serial_port.readline()
if reading[:6] == '$GPGGA':
return str(reading)
else:
return False
def start_serial_connection(port,baud,timeout):
try:
serial_port = serial.Serial(port, baud, timeout=timeout)
return serial_port
except Exception, e:
print e
sys.exit()
def is_a_number(thing):
try:
return float(thing)
except Exception, e:
print e
return None
def get_serial_connection_with_system_args():
baud = None
port = None
timeout = None
serial_port = None
args_length = len(sys.argv)
for i in range(0,args_length-1):
if (i+1) <= args_length:
if sys.argv[i] == '-baud':
baud = is_a_number(sys.argv[i+1])
elif sys.argv[i] == '-timeout':
timeout = is_a_number(sys.argv[i+1])
elif sys.argv[i] == '-port':
port = sys.argv[i+1]
if baud and port and timeout:
serial_port = start_serial_connection(baud=baud,
port=port,timeout=timeout)
return serial_port
def start_gps_reader():
serial_port = get_serial_connection_with_system_args()
if serial_port:
while True:
try:
response = get_gpgga_string_from_serial(serial_port)
if response:
position = parse_gpgga_string(str(response))
print "latitude %s" % position.latitude
print "lat cardinal %s" % position.lat_cardinal
print "longitude %s" % position.longitude
print "lon cardinal %s" % position.lon_cardinal
print "altitude %s" % position.altitude
print "precision %s" % position.precision
print "quality %s" % position.quality
except Exception, e:
print e
else:
print 'example usage: ./read_and_store_gps_data.py -baud 4800 -port /dev/ttyUSB0 -timeout 0.5'
sys.exit()
start_gps_reader()
Test the script, and see we now have position objects with useful attributes.
Step Five - use redis to make the latest position available
Why Redis? I'm doing this anyway - and have been hearing about redis all the time. This script will only set the latest position. The other parts of the solution will use the position. Add to the script at the top:
import redis
import json
Now to use redis to store a position object as json, we only want a very recent position so we will set the time to live to 3 seconds. Directly underneath the start_gps_read() declaration insert the line:
redis_server = redis.Redis("localhost")
Replace the printing of the positions values in start_gps_reader() with the following lines:
redis.set("position:current",json.dumps(position.__dict__))
redis.expire("position:current",3)
Now exit vim and test the scripts' results
$ ./read_and_store_gps_data.py -baud 4800 -port /dev/ttyUSB0 -timeout 0.5 &
$ redis-cli
redis 127.0.0.1:6379> get position:current
$ ps-aux **look for the python process take note of its number
$ kill 1088 **where 1088 is the python processes number
Wait three seconds.
redis-cli
get position:current
Great there is only a 3 seconds or newer position available as position:current, Thats all for this post, if you are interested to see what comes next then come back.
Thanks! Rimu Boddy.