For the first post on my blog I decided to publish some project I had done in my spare time few months ago. Global Positioning System, satellites, navigation, you know it, you used it probably. Python is kind of a giant snake but also very popular programming language. Better learn it if you want to benefit from this post. And finally, Google Maps is what you use to find your own house on the Internet. The idea was simple: attach the GPS module to my PC, read the data using Python script and make it open Google map with the exact position. Very smart, you will say, you could have just bought normal GPS device and have all at once. Well, Mr. Smarty Pants, some things in life don’t come always packed in tightly closed box from Taiwan, sometimes it is useful to learn how to make them work.
This is more of “how-to” kind of post than a presentation of novel and world-changing idea. I’ll be most certainly happy if one finds it useful for one’s school/university projects. I am not usually involved in GPS engineering, but for purposes of fun and exercising my ever so rusting Python skills I decided to purchase and play with the quite simple and affordable GPS receiver module EM-406A.
EM-406A is GPS receiver module made by Global Sat, some Chinese company if I’m not wrong. This small piece of electronics is capable of talking with satellites upstairs in Earth’s orbit. That’s right, with satellites, 20 000 kilometers above our heads. Makes you think how bad person you are for not talking with your granny who is only one street away. Sensing the electromagnetic waves vibrating at 1.1 – 1.5 GHz, this small guy is able pick up positions of up to 12 satellites in orbit and by doing some math it can deliver you its own position in Earth’s coordinate system. And the bunch of other stuff as well. All this useful data you can get if you know how to talk with your receiver via those six small wires and that’s exactly what I am going to be describing here.
In the specifications it is mentioned that the EM-406A supports the NMEA data protocol. What the hack does that mean?
The National Marine Electronics Association (NMEA) has developed a set of specifications that interfaces between various pieces of marine electronic equipment. Nearly all GPS receiver communication is defined within this standard. Serial communication and hardware interface is also part of the NMEA protocol and will be described a bit later
Now you probably wonder how to connect this marvelous device to your PC. Here’s how.
Serial communication and the wizards of FTDI
You may remember how some older engineers or professors talked about how they had used to connect some things called terminals back in the old days. What the terminal is, I have no single clue, and honestly, I think no one younger than 40 has ever seem them. It is kind of a legend, an unicorn of IT. But for the sake of the story, let’s pretend it is some kind of a printer or a disco ball or whatever they had back in 70’s. However, this thing, the terminal, was supposed to be communicated with through the serial port. It means that your data is transmitted as a stream of 1s and 0s from your PC to this “terminal”. The serial protocol includes things such as baudrate, which controls the speed of data transfer, parity, which takes care that your data is uncorrupted and correct etc. Of course, almost all modern PCs have no idea what to do with this old serial protocol, ‘cause we spoiled them with our standard Universal Serial Bus (USB). That’s why we need to put some kind of translator, an adapter, something that will convert data from GPS module into the something that our computers can read through, in.e., USB port.
This translator I have found in the form of FT232R chip from FTDI (company name). The chip does exactly what was described above, converts data from serial module into the USB data. What you need to make it work are standard USB mini female connector, 7-pins strip-line connector and printed circuit board design skills. Schematics of the circuit are below. Small note: since FT232 is in SSOP or QFN package, you may want to ask someone for help with the soldering.
If it looks anything like the one on the right photo you may consider yourself doing a good job. Now, swiftly find some USB cable and plug it in your new PCB. Your computer will most likely not recognize it, but here you can find and install the official drivers that will make FT232 embrace your PC like an old fellow.
Now let’s test how it works. Some smart people have made library for Python which can work with the serial protocol very creatively called “serial”. After importing you need to set up some things mentioned in the first paragraph. For instance, NMEA standard specifies the data transfer speed of 4800 bauds, with 8 bits of data, no parity and one stop bit.For test you can just read data in an endless loop and print what you get from the GPS module. The code example is written here:
import serial #initialize serial connection def init_serial(): COMNUM = 5 # Set you COM port # here (Check it in device manager) global ser # Must be declared in each fxn used ser = serial.Serial() ser.baudrate = 4800 ser.port = COMNUM -1 # Starts at 0, so subtract 1 ser.timeout = 1 # You must specify a timeout (in seconds) so that the serial port doesn't hang ser.open() # Open the serial port # print port open or closed if ser.isOpen(): print 'Open: ' + ser.portstr</pre> init_serial() while 1: # read what is on serial port data = ser.readline() # reads in bytes followed by a newline print data
If you got some error messages, you are cursed to wander the wastes of Internet seeking for help. If not, you should see something like this (values are copied from some random Internet site):
What does this mean, find out in next chapter.
NMEA protocol is some special set of commands which enables standardized communication with the satellites above us. If you want to go deeper in the subject here is bunch of useful stuff. For me it is enough to know that GPS receiver gives me 6 – 7 different sentences. They all start with “$GP” followed by the sentence identifier. Within the sentence different GPS related data is hidden, such as latitude, longitude, altitude, UTC time, number of visible satellites etc. The one sentence I care most about is the one with the header “$GPGGA”. What’s so special about this one? It contains all the data I need for the project. Its form is
$GPGGA, hhmmss.sss, ddmm.mmmm, N/S, dddmm.mmmm, E/W, X, Ns,,Alt,M,,,,0000*18
Details are explained in the table:
|UTC time||hhmmss.sss||hours, minutes seconds and milliseconds|
|Latitude||ddmm.mmmm||degrees and minutes|
|N/S indicator||N/S||N = North, S = South|
|Longitude||dddmm.mmmm||degrees and minutes again|
|E/W indicator||E/W||E = East, W = West|
|Position fix indicator||X||Can be 0,1,2 or 3|
|Satellites Used||Ns||from 0 to 12|
|HDOP||Horizontal dilution of precision (wtf?)|
|MSL Altitude||Alt||in meters|
|Units||M||it means “in meters”|
|Geoid separation||No idea what to do with this|
|I give up, don’t know|
|Have no idea|
|Checksum||Begins with * and takes care that everything’s OK with the transfer|
From now on it is quite straightforward: you’ll receive the string sentence from your module, and you can cut the useful data out of it. But merciful gods of Python have decided to make it easy for us again. They made a library called “pynmea” which you can use for parsing the given NMEA sentence. Basically, you’ll construct the class which will parse the string and its attributes are gonna be desired data such as latitude, longitude, etc. Code example looks like this:
while 1: # read what is on serial port data = ser.readline() #reads bytes followed by a newline if data[0:6] == '$GPGGA': print data #print the sentence to the console gpgga = nmea.GPGGA() # class constructor gpgga.parse(data) # method for parsing the sentence lats = gpgga.latitude lat_dir = gpgga.lat_direction longs = gpgga.longitude long_dir = gpgga.lon_direction time = gpgga.timestamp alt = gpgga.antenna_altitude
This part is so simple it hurts. It is like a toddler of all parts. If the lyrics of “Call me maybe” could incarnate as a Python code, it would be this part. But sneer do not. Behind these simple lines of code stand thousands of experts and years of developing the most exhausting scripts and facing the most challenging programming requirements. Kind of just like behind “Call me maybe”.
I guess there are bunch of more intelligent ways of working with Google maps. In my research, I stumbled upon different examples of geocoding using official Google API, but I found this as overkill for my simple project. That’s why I decided that will be enough just to open my web browser with URL address containing my GPS data. To do this, you’ll need to import library called “webbrowser” and specify the URL address as a string. The address should start as ‘http://www.google.com/maps/place/‘ and than follow the GPS coordinates in the form like in this example: ‘ 40°15’49.7“N16°34’25.2“E ‘. Funny part was to embed characters ‘ ‘ ‘ and ‘ “ ‘ in the Python string, cause, yeah, we all know what these mean for the string construction. How I solved it, find in the following code:
import webbrowser import string def to_degrees(lats, longs): # Convert string forms from ddmm.mmmm to dd°mm'ss.ss“ lat_deg = lats[0:2] lat_mins = lats[2:4] lat_secs = round(float(lats[5:])*60/10000, 2) lat_str = lat_deg + u'°'+ lat_mins + string.printable + str(lat_secs) + string.printable lon_deg = longs[0:3] lon_mins = longs[3:5] lon_secs = round(float(longs[6:])*60/10000, 2) lon_str = lon_deg + u'°'+ lon_mins + string.printable + str(lon_secs) + string.printable return [lat_str, lon_str] def open_google_maps(GPS_coordinates): # Open google map in web browser with URL of my position gmaps = 'http://www.google.com/maps/place/' webbrowser.open(gmaps+GPS_coordinates)
Finally, the whole code is available here. And here we have some results of the given code.
# -*- coding: utf-8 -*- """ Created on Thu Mar 20 22:02:51 2014 @author: Pero """ import serial from pynmea import nmea import string import webbrowser #####Global Variables###################################### #be sure to declare the variable as 'global var' in the fxn ser = 0 #####FUNCTIONS############################################# def scan(): #scan for available ports. return a list of tuples (num, name) available =  for i in range(256): try: s = serial.Serial(i) available.append( (i, s.name)) s.close() # explicit close 'cause of delayed GC in java except serial.SerialException: pass return available #initialize serial connection def init_serial(): print "Found Ports:" for n,s in scan(): print "%s" % s print " " COMNUM = 5 #set you COM port # here global ser #must be declared in each fxn used ser = serial.Serial() ser.baudrate = 4800 ser.port = COMNUM -1#starts at 0, so subtract 1 ser.timeout = 1 #you must specify a timeout (in seconds) so that the serial port doesn't hang ser.open() #open the serial port ser.isOpen() # print port open or closed if ser.isOpen(): print 'Open: ' + ser.portstr def to_degrees(lats, longs): lat_deg = lats[0:2] lat_mins = lats[2:4] lat_secs = round(float(lats[5:])*60/10000, 2) lat_str = lat_deg + u'°'+ lat_mins + string.printable + str(lat_secs) + string.printable lon_deg = longs[0:3] lon_mins = longs[3:5] lon_secs = round(float(longs[6:])*60/10000, 2) lon_str = lon_deg + u'°'+ lon_mins + string.printable + str(lon_secs) + string.printable return [lat_str, lon_str] def open_google_maps(GPS_coordinates): gmaps = 'http://www.google.com/maps/place/' webbrowser.open(gmaps+GPS_coordinates) def display_time(time): hours = time[0:2] mins = time[2:4] sec = time[4:6] print 'UTC Time: ' + hours + ':' + mins + ':' + sec init_serial() #####MAIN LOOP############################################ while 1: # read what is on serial port data = ser.readline() #read4s in bytes followed by a newline if data[0:6] == '$GPGGA': print data#print to the console gpgga = nmea.GPGGA() gpgga.parse(data) lats = gpgga.latitude lat_dir = gpgga.lat_direction longs = gpgga.longitude long_dir = gpgga.lon_direction time = gpgga.timestamp alt = gpgga.antenna_altitude display_time(time) if lats == '': print 'GPS coordinates not available' else: display_time(time) print 'Your altitude is ' + alt + 'm' lat_lon = to_degrees(lats, longs) print lat_lon+lat_dir print lat_lon+long_dir open_google_maps(lat_lon+lat_dir+lat_lon+long_dir) break
The guys from Spark Fun did a really good job in developing some GPS related projects:
- Here is what you need to know when buying your first GPS module
- And here is some comparison between couple of different GPS module
- And here is really cool project which I shamelessly ripped off to learn how to make this post.
Finally, if you really like boring things, here is one lovely description of NMEA standards.