GPS-Stream
From Bob's Basement
[edit] Intro
GPS-Stream is a little project initially developed for Tron. The purpose of this project is to allow remote monitoring of a GPS unit with minimal overheads.
For the purposes of simplicity, we are using UDP packets to transfer the data. The client (with local GPS reciever) runs a program which reads from the GPS device through a serial port, and then transmits it to the host and port specified. Each datagram contains one complete NMEA sentence, which is more efficient to process on the receiving end.
The server will listen on a given port for incoming packets. When it receives one and is sure it's valid, it will then parse the NMEA sentence, probably using NMEAP, and update it's state accordingly.
Once this has been implemented the server side daemon will need to update a mysql database (or other such thing) with the current coordinates every time period. From here, more scripts will make use of this data. ( See Tron )
[edit] Client
This has been implemented in C, and is known to work with a generic GPS reciever hooked up with a Prolific Technology, Inc. PL2303 Serial Port
NOTE: this code may not be up to date. Please check out the Subversion to get the latest release.
/**
* GPS Stream
* v0.1
* ----------
* This program reads data from a serial NMEA device
* and sends the sentances via UDP to a destination
* where they will be processed as if the device was
* local (in theory).
*
* naxxtor [AT] bobsbasement.co.uk
* Made for a Bob's Basement Project
* BikeStalker
* http://www.bobsbasement.co.uk/Tron
*/
#include <stdio.h>
#include <stdlib.h> /* Standard libs */
#include <string.h>
#include <termios.h>
#include <fcntl.h> /* File control definitions */
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
/* GLOBALS */
int fd; /* serial connection handle */
int sock; /* socket handle */
struct sockaddr_in s_addr; /* destination address struct */
/* PROTOTYPES */
void sigproc();
int initPort(int fd);
int main(int argc, char *argv[]) {
int bufSize = 255;
char buffer[bufSize]; /* Input buffer */
int nbytes; /* Number of bytes read from device */
int port; /* Port to send to */
char* host; /* Host IP to send to */
char* device; /* Device (e.g. /dev/ttyUSB0) */
int quiet; /* Whether Quiet mode (i.e. no stdout) is enabled) */
if (argc < 3)
{
printf("Not enough parameters!\n");
printf("Usage: ./gps-stream /dev/device dest_host port [quiet]\n\n");
exit(1);
}
device = argv[1];
host = argv[2];
port = atoi(argv[3]);
quiet=0;
if (argc> 4 && (strcmp(argv[4],"-q")==0))
{
quiet = 1;
}
/* if the port number is invalid */
if (port == -1)
{
printf("Invalid port number\n");
exit(1);
}
/* TODO: make better validation of parameters */
/* Make the UDP socket destination */
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(port);
s_addr.sin_addr.s_addr = inet_addr(host);
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1)
perror("Couldn't make socket -\n");
/* Open the device and set up the connection */
/* It is a non blocking, non controlling, writable socket */
fd = open(device,O_NONBLOCK | O_NOCTTY | O_RDWR);
/* set up signal handler (to close the device properly) */
signal(SIGINT,sigproc);
/* set up the port for NMEA */
initPort(fd);
/* Do a priming read to clear out any half sentences */
read(fd,buffer, bufSize);
/* wait for it to settle */
usleep(5000);
/* Zero the buffer */
memset(buffer,0,bufSize);
while(1)
{
if ((nbytes = read(fd,buffer,bufSize)) == -1) // no data to read
{
usleep(5000);
continue; /* wait for 50ms before trying to read again */
}
buffer[nbytes]='\0'; /* null terminate */
/* send the data over the network if it's a valid sentence*/
if (buffer[0] == '$')
sendto(sock, buffer, nbytes+1, 0, (struct sockaddr *)&s_addr, sizeof(s_addr));
/* print to stdout */
if (quiet == 0)
{
printf("-->%s",buffer);
fflush(stdout);
}
}
/* this will never, ever happen, but it keeps the compilers happy */
return 0;
}
/**
* Handles Ctrl+C signal
*/
void sigproc()
{
char* exitMsg = "EXIT";
signal(SIGINT,sigproc);
close(fd); /* Close the serial connection */
printf("-- End Packet Sending --\n");
sendto(sock,exitMsg,strlen(exitMsg)+1,0,(struct sockaddr *)&s_addr,sizeof(s_addr));
printf("Exiting\n");
close(sock); /* Close the UDP socket too */
exit(0);
}
/**
* Sets up the serial port for NMEA comms
* Sets 8N1 4800BPS in canoncial mode
*/
int initPort(int fd)
{
struct termios options;
/* Get the current options for the port... */
tcgetattr(fd, &options);
/* Set the baud rates to 4800... */
cfsetispeed(&options, B4800);
cfsetospeed(&options, B4800);
/* Enable the receiver and set local mode... */
options.c_cflag |= (CLOCAL | CREAD);
/* Set 8N1 options */
options.c_oflag |= OPOST; /* Output processing */
options.c_cflag &= ~PARENB; /* No parity bit */
options.c_cflag &= ~CSTOPB; /* One stop bit */
options.c_cflag &= ~CSIZE; /* data size is ... */
options.c_cflag |= CS8; /* 8 Bits */
options.c_lflag |= ICANON; /* Puts terminal in canonical mode */
/* Set the new options for the port... */
tcsetattr(fd, TCSANOW, &options);
/* And flush out the garbled stuff (in theory anyway) */
tcflush(fd, TCIOFLUSH);
return 0;
}
[edit] Server
The server side is written in Python. You will need the Python MySQL libraries installed.
For debugging purposes, it outputs it's data to stdout, as well as inserting into a databse.
# checksumMessage, isUsefulSentence and getCoordinates written by jon_.
# UDP listener written by naxxtor
from socket import *
from time import *
import MySQLdb
## MODIFY THIS AS REQUIRED ##
# DB settings
db_host = "localhost"
db_user = "bikestalker_user"
db_passwd = "bikestalker_passwd"
db_name = "bikestalker"
# UDP listener settings
stream_addr = "0.0.0.0" # the IP to bind to (0.0.0.0 for all interfaces)
stream_port = 1337 # the port to bind to
## STOP. HAMMERTIME ##
def checksumMessage(nmea):
# Returns true if NMEA checksum is correct
checksum = ord(nmea[1])
for char in nmea[2:len(nmea)-3]:
checksum ^= ord(char)
checksum = "%X" % checksum
return checksum == nmea[-2:]
def isUsefulSentence(nmea):
# Returns true if and only if given NMEA message is a GPGGA message
# and there is at least a certain number of satellites, and
# the checksum is correct
if not nmea.startswith("$GPGGA"):
return False
minimumFix = 1
minimumSatellites = 3
parts = nmea.split(",")
fix = int(parts[6])
satellites = int(parts[7])
if minimumFix > fix or minimumSatellites > satellites:
return False
return checksumMessage(nmea)
def getCoordinates(nmea):
# Converts a GPGGA NMEA message into a pair of decimal
# coordinates in the format 12.3456N
parts = nmea.split(",")
quality = int(parts[6])
lat = float(parts[2])
lon = float(parts[4])
latD = int(lat / 100)
lonD = int(lon / 100)
latM = lat - (100 * latD)
lonM = lon - (100 * lonD)
satellites = int(parts[7])
timestamp = float(parts[1])
decimalLat = float(latD + (latM / 60))
decimalLon = float(lonD + (lonM / 60))
if (parts[3] == 'S'):
decimalLat = 0-decimalLat
if (parts[5] == 'W'):
decimalLon = 0 - decimalLon
return (timestamp,decimalLat, decimalLon,quality,satellites)
# listen for UDP data and print coordinates from 'useful' messages
host = "0.0.0.0"
port = 1337
buf = 256
addr = (host,port)
# MYSQL data connection
db = MySQLdb.connect(host=db_host, user=db_user, passwd=db_passwd,db=db_name)
# create a cursor
cursor = db.cursor()
UDPSock = socket(AF_INET,SOCK_DGRAM)
UDPSock.bind(addr)
last = time()
print "Started at "+str(last)
best = (0,0,0,0,0)
while(True):
data,addr = UDPSock.recvfrom(buf)
if (isUsefulSentence(data)):
newData = getCoordinates(data)
if (newData[3] >= best[3] and newData[4] >= best[4]):
best = newData
if ((time() - last) > 5): # outputs the best match out of the last 5 seconds
print best
cursor.execute("INSERT INTO `gps_data` (`timestamp`,`lat`,`long`,`quality`,`satelites`) VALUES('%s',%s,%s,%s,%s)",best)
db.commit()
last = time()

