gps – software

Programming

gps – software

where am i?

Check out my previous posts to get up to speed with extracting latitude and longitude from generated nmea sentences using awk, and then with setting up an aruidino with a ublox gps module to get live, real gps data.

Now that we have working gps hardware, we want to develop some basic software to handle the incoming data. If you’re not interested in a “DIY-hack” approach to gps, I strongly suggest you check out gpsd.

Our main goals for this post are to:

  • Read live nmea data from a real gps
  • Perform basic validation on GPGGA sentences. Validate the checksum
  • Log the data based on a simple criteria. Latitude and longitude are non-zero, fix quality is 1 or greater
  • Probe the log file for the most recent validated, non-zero latitude and longitude

Let’s dive straight into the implementation with awk.

$ vim whereami.awk
#!/usr/bin/awk -f

BEGIN {
	FS=",|*" ;	
	ascii_map=""; for(i=0; ++i < 256;) ascii_map = ascii_map sprintf("%c", i);
}

function ctoi(ch) {
	return index(ascii_map, ch);	
}

#returns 1 if checksum matches, else 0
function is_valid_sentence(sentence){
	n = split(sentence, s_arr, "*");
	if(n == 1){
		print "No checksum separator";
		return 0; #no checksum separator
	}
	chksm = s_arr[2];
	if(length(chksm) != 2){
		print "malformed checksum, must be 2 chars."; 
		return 0;
	}
	split(s_arr[1], chars, "");
		
	if(chars[1] != "$"){
		print "first char not $"
		return 0;
	}
	calc = ctoi(chars[2]);
	for (i=3; i < length(sentence); i++){
		c = chars[i];
		if(c == "*"){
			break;
		}
		calc = xor(calc, ctoi(c));
	}
	calc = sprintf("%02X", calc);
	return (calc == chksm);
}

/GPGGA/ {
	sub(/\r/,""); #remove trailing return carriage
	if (!is_valid_sentence($0)) {
		print "failed validation"
		fflush();
		next;
	}
	
	type=$1;
	time=$2;
	lat=($4 == "S" ? -1 : 1) * (int($3 / 100) + ($3 % 100) / 60);
	lon=($6 == "W" ? -1 : 1) * (int($5 / 100) + ($5 % 100) / 60);
	fix=$7;
	sats=$8;
	hdp=$9;
	alt=$10;
	#alt_unit=$11;
	geoid=$12;
	#geoid_unit=$13;
	chksm=$16;
	
	if (lat == 0 || lon == 0) {
		print "failed lat or lon"
		fflush();
		next;
	}
	if (fix == 0) {
		print "failed fix"
		fflush();
		next;
	}	
	
	l = "log/gga.log";
	print "type " type > l;
	print "time " time > l;
	print "lat " lat > l;
	print "lon " lon > l;
	print "fix " fix > l;
	print "sats " sats > l;
	print "hdp " hdp > l;
	print "alt " alt > l;
	print "geoid " geoid > l;
	close(l);
	print "wrote to log/gga.log";

	fflush();
}

$ chmod +x whereami.awk
$ ./whereami.awk somewhere.nmea

My gps uses \r\n to symbolise end-of-line. Use th sub function to remove the return carriage symbol. After writing a log, we call close, failing to do so will cause nothing to be written to the file (as awk buffers its output). In addition, its sensible to fflush after printing.

We define a function called is_valid_sentence which checks the structure of the sentence, then calculates and compares the checksum. To pass this function:

  • The first character must match “$”
  • The sentence must contain “*” followed by a 2 digit hexadecimal checksum
  • The calculated checksum (XOR of each character between, but not including, the $ and *) must match the two digit hexadecimal checksum proceeding the *

Once we know we have a valid checksum, we populate each variable (calculating the latitude and longitude as we’ve done previously). Next, we  print the values to a log file, overwriting the previous contents.

Any program which wishes to know the most recent, validated and non-zero GPGGA data now only needs to query this log file. For example, to extract the latitude and longitude:

$ awk '/lat|lon/ {print}' log/gga.log
lat 30.0822
lon 391.286

There’s a side effect to this method. If the gps stops reporting data for any reason, there will always be a valid log stored in the log file. This can be good for the client program, the client will nearly always have valid gps data. The drawback is that this data could be severely out of date. It is recommended that the client program read the modify date of the log file to decide whether or not to use it.

$ awk '/lat/ {printf $2 ","} /lon/ {print $2}' log/gga.log
30.0822,391.286

It’s mildly verbose, but considering it gives us data which has been validated and cleaned, its not too bad.

With real hardware, such as an arduino + ublox gps module discussed earlier, we simply pass the device name to the awk script. Because the device never sends EOF, the script will continue parsing GPGGA sentences and updating the log file endlessly. Initially, there are a lot of ‘failed validation’ messages, due to the gps not receiving a fix.

$ stty -F /dev/ttyACM0 raw 9600 -hupcl
$ ./whereami.awk /dev/ttyACM0
failed validation
failed validation
failed validation
...
wrote to log/gga.log

To silence the program, redirect its output to /dev/null

$ ./whereami.awk /dev/ttyACM0 > /dev/null

Given this template, it should be straightforward to implement the same for other nmea sentences. Next time we’ll talk through storing gps data in a simple database.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.