Do you want to be able to access GPS coordinates of anything, flying or not? Well, you can do just that by wirelessly sending that data to be seen from a computer on Device Cloud. This will use the Arduino microcontroller and XBee radio. After all, Arduino and XBee are best friends in the electrical engineering world! Why else would an XBee shield exist?
Parts:
- One Arduino Uno board (MS MKSP4, SFE DEV-09950, AF 50)
- One XBee Pro radio (Series 2/ZB firmware) configured as a Zigbee Router API mode (Digi: XBP24BZ7WIT-004, DK 602-1181-ND)
- Hookup wire or jumper wire kit (MS MKSEEED3, AF 153, DK 923351-ND, SFE PRT-00124)
- USB A-to-B cable for Arduino (AF 62, DK 88732-9002, SFE CAB-00512)
- XBee USB serial adapter (XBee Explorer or Digi Evaluation board) (AF 247, SFE WRL-08687)
- One XBee shield (Seeed Studio SLD01103P, Arduino A000065, SF WRL-10854)
- Wire strippers (AF 147, DK PAL70057-ND, SFE TOL-08696)
- One ConnectPort X4-ZB with cellular capability (Digi X2-Z11-EC-A)
- One Cellular sim card for data
- One GPS Module (Cirocomm 595K)
- An adventurous mind
Using the Arduino IDE
This project uses the Arduino software, known as the IDE, in order to program the arduino uno microcontroller. You can download it for free from the Arduino website software section at http://www.arduino.cc/en/Main/Software. There are many versions, so be sure to download the correct one. A basic guide to how to use it can be found at http://arduino.cc/en/Guide/HomePage.
Prepare and Configure your ConnectPort X2-ZB Cellular on Device Cloud
- Login to your Device Cloud account and add your ConnectPort device to it. Make sure that it indicates that it has joined. You can use either an Ethernet cable or cellular data to connect to the internet.
- Configure the ConnectPort radio with a PAN. Make sure you note this ID so that you can set up your router radio with the same one.
Figure 1
Prepare and Configure your Router Radio
- Use X-CTU to set the designated router radio in AT mode.
- Configure the router radio with the PAN ID you set the ConnectPort with.
- Set JV to 1 to ensure that your router attempts to rejoin the coordinator on startup.
- Enter in the High and Low destination addresses of your ConnectPort.
- Set the Baud Rate to 4800.
Construct the Arduino GPS System
Figure 2
Figure 3
- Insert your Router XBee Radio onto the XBee shield, and then insert the shield onto the Arduino board.
- For ease of space, it is recommended that you place your GPS module on top of the open space on the shield like so in Figures 2 and 3. *Figures 2 and 3 use an EM-406A GPS model.*
- Connect the GPS TX to the Arduino pin 0.
- Connect the GPS ground to Arduino ground.
- Connect the GPS power to Arduino 5 volts (recommended power supply).
Program the Arduino-controlled GPS
*when uploading the program to the Arduino board, make sure you switch the TX (connected to pin 1 on Arduino) on the XBee off. This can be done on the XBee shield.*
Important: This version is intended for Arduino 1.0 IDE. It will not compile in earlier versions. Be sure the following files are present in the folder with this sketch:
TinyGPS.h
TinyGPS.cpp
keywords.txt
Upload the following program to the Arduino controlled security monitor:
GPS_Arduino_10 Arduino File:
#include "./TinyGPS.h" // Special version for 1.0
TinyGPS gps;
void setup() {
Serial.begin(9600);
}
void loop() {
bool newdata = false;
unsigned long start = millis();
while (millis() - start < 1000) { // Update every second
if (feedgps())
newdata = true;
}
if (newdata) {
gpsdump(gps);
}
}
// Get and process GPS data
void gpsdump(TinyGPS &gps) {
float flat, flon;
unsigned long age;
gps.f_get_position(&flat, &flon, &age);
Serial.print(flat, 4);
Serial.print(", ");
Serial.println(flon, 4);
}
// Feed data as it becomes available
bool feedgps() {
while (Serial.available()) {
if (gps.encode(Serial.read()))
return true;
}
return false;
}
TinyGPS.cpp:
/*
TinyGPS - a small GPS library for Arduino providing basic NMEA parsing
Based on work by and "distance_to" courtesy of Maarten Lamers.
Copyright (C) 2008-2011 Mikal Hart
All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
**This is an unofficial version. It is altered to allow use in Arduino 1.0**
*/
#include "Arduino.h"
#include "TinyGPS.h"
#define _GPRMC_TERM "GPRMC"
#define _GPGGA_TERM "GPGGA"
TinyGPS::TinyGPS()
: _time(GPS_INVALID_TIME)
, _date(GPS_INVALID_DATE)
, _latitude(GPS_INVALID_ANGLE)
, _longitude(GPS_INVALID_ANGLE)
, _altitude(GPS_INVALID_ALTITUDE)
, _speed(GPS_INVALID_SPEED)
, _course(GPS_INVALID_ANGLE)
, _last_time_fix(GPS_INVALID_FIX_TIME)
, _last_position_fix(GPS_INVALID_FIX_TIME)
, _parity(0)
, _is_checksum_term(false)
, _sentence_type(_GPS_SENTENCE_OTHER)
, _term_number(0)
, _term_offset(0)
, _gps_data_good(false)
#ifndef _GPS_NO_STATS
, _encoded_characters(0)
, _good_sentences(0)
, _failed_checksum(0)
#endif
{
_term[0] = '';
}
//
// public methods
//
bool TinyGPS::encode(char c)
{
bool valid_sentence = false;
++_encoded_characters;
switch(c)
{
case ',': // term terminators
_parity ^= c;
case 'r':
case 'n':
case '*':
if (_term_offset < sizeof(_term))
{
_term[_term_offset] = 0;
valid_sentence = term_complete();
}
++_term_number;
_term_offset = 0;
_is_checksum_term = c == '*';
return valid_sentence;
case '$': // sentence begin
_term_number = _term_offset = 0;
_parity = 0;
_sentence_type = _GPS_SENTENCE_OTHER;
_is_checksum_term = false;
_gps_data_good = false;
return valid_sentence;
}
// ordinary characters
if (_term_offset < sizeof(_term) - 1)
_term[_term_offset++] = c;
if (!_is_checksum_term)
_parity ^= c;
return valid_sentence;
}
#ifndef _GPS_NO_STATS
void TinyGPS::stats(unsigned long *chars, unsigned short *sentences, unsigned short *failed_cs)
{
if (chars) *chars = _encoded_characters;
if (sentences) *sentences = _good_sentences;
if (failed_cs) *failed_cs = _failed_checksum;
}
#endif
//
// internal utilities
//
int TinyGPS::from_hex(char a)
{
if (a >= 'A' && a <= 'F')
return a - 'A' + 10;
else if (a >= 'a' && a <= 'f')
return a - 'a' + 10;
else
return a - '0';
}
unsigned long TinyGPS::parse_decimal()
{
char *p = _term;
bool isneg = *p == '-';
if (isneg) ++p;
unsigned long ret = 100UL * gpsatol(p);
while (gpsisdigit(*p)) ++p;
if (*p == '.')
{
if (gpsisdigit(p[1]))
{
ret += 10 * (p[1] - '0');
if (gpsisdigit(p[2]))
ret += p[2] - '0';
}
}
return isneg ? -ret : ret;
}
unsigned long TinyGPS::parse_degrees()
{
char *p;
unsigned long left = gpsatol(_term);
unsigned long tenk_minutes = (left % 100UL) * 10000UL;
for (p=_term; gpsisdigit(*p); ++p);
if (*p == '.')
{
unsigned long mult = 1000;
while (gpsisdigit(*++p))
{
tenk_minutes += mult * (*p - '0');
mult /= 10;
}
}
return (left / 100) * 100000 + tenk_minutes / 6;
}
// Processes a just-completed term
// Returns true if new sentence has just passed checksum test and is validated
bool TinyGPS::term_complete()
{
if (_is_checksum_term)
{
byte checksum = 16 * from_hex(_term[0]) + from_hex(_term[1]);
if (checksum == _parity)
{
if (_gps_data_good)
{
#ifndef _GPS_NO_STATS
++_good_sentences;
#endif
_last_time_fix = _new_time_fix;
_last_position_fix = _new_position_fix;
switch(_sentence_type)
{
case _GPS_SENTENCE_GPRMC:
_time = _new_time;
_date = _new_date;
_latitude = _new_latitude;
_longitude = _new_longitude;
_speed = _new_speed;
_course = _new_course;
break;
case _GPS_SENTENCE_GPGGA:
_altitude = _new_altitude;
_time = _new_time;
_latitude = _new_latitude;
_longitude = _new_longitude;
break;
}
return true;
}
}
#ifndef _GPS_NO_STATS
else
++_failed_checksum;
#endif
return false;
}
// the first term determines the sentence type
if (_term_number == 0)
{
if (!gpsstrcmp(_term, _GPRMC_TERM))
_sentence_type = _GPS_SENTENCE_GPRMC;
else if (!gpsstrcmp(_term, _GPGGA_TERM))
_sentence_type = _GPS_SENTENCE_GPGGA;
else
_sentence_type = _GPS_SENTENCE_OTHER;
return false;
}
if (_sentence_type != _GPS_SENTENCE_OTHER && _term[0])
switch((_sentence_type == _GPS_SENTENCE_GPGGA ? 200 : 100) + _term_number)
{
case 101: // Time in both sentences
case 201:
_new_time = parse_decimal();
_new_time_fix = millis();
break;
case 102: // GPRMC validity
_gps_data_good = _term[0] == 'A';
break;
case 103: // Latitude
case 202:
_new_latitude = parse_degrees();
_new_position_fix = millis();
break;
case 104: // N/S
case 203:
if (_term[0] == 'S')
_new_latitude = -_new_latitude;
break;
case 105: // Longitude
case 204:
_new_longitude = parse_degrees();
break;
case 106: // E/W
case 205:
if (_term[0] == 'W')
_new_longitude = -_new_longitude;
break;
case 107: // Speed (GPRMC)
_new_speed = parse_decimal();
break;
case 108: // Course (GPRMC)
_new_course = parse_decimal();
break;
case 109: // Date (GPRMC)
_new_date = gpsatol(_term);
break;
case 206: // Fix data (GPGGA)
_gps_data_good = _term[0] > '0';
break;
case 209: // Altitude (GPGGA)
_new_altitude = parse_decimal();
break;
}
return false;
}
long TinyGPS::gpsatol(const char *str)
{
long ret = 0;
while (gpsisdigit(*str))
ret = 10 * ret + *str++ - '0';
return ret;
}
int TinyGPS::gpsstrcmp(const char *str1, const char *str2)
{
while (*str1 && *str1 == *str2)
++str1, ++str2;
return *str1;
}
/* static */
float TinyGPS::distance_between (float lat1, float long1, float lat2, float long2)
{
// returns distance in meters between two positions, both specified
// as signed decimal-degrees latitude and longitude. Uses great-circle
// distance computation for hypothetical sphere of radius 6372795 meters.
// Because Earth is no exact sphere, rounding errors may be up to 0.5%.
// Courtesy of Maarten Lamers
float delta = radians(long1-long2);
float sdlong = sin(delta);
float cdlong = cos(delta);
lat1 = radians(lat1);
lat2 = radians(lat2);
float slat1 = sin(lat1);
float clat1 = cos(lat1);
float slat2 = sin(lat2);
float clat2 = cos(lat2);
delta = (clat1 * slat2) - (slat1 * clat2 * cdlong);
delta = sq(delta);
delta += sq(clat2 * sdlong);
delta = sqrt(delta);
float denom = (slat1 * slat2) + (clat1 * clat2 * cdlong);
delta = atan2(delta, denom);
return delta * 6372795;
}
TinyGPSh:
/*
TinyGPS - a small GPS library for Arduino providing basic NMEA parsing
Based on work by and "distance_to" courtesy of Maarten Lamers.
Copyright (C) 2008-2011 Mikal Hart
All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
**This is an unofficial version. It is altered to allow use in Arduino 1.0**
*/
#ifndef TinyGPS_h
#define TinyGPS_h
#include "Arduino.h"
#define _GPS_VERSION 10 // software version of this library
#define _GPS_MPH_PER_KNOT 1.15077945
#define _GPS_MPS_PER_KNOT 0.51444444
#define _GPS_KMPH_PER_KNOT 1.852
#define _GPS_MILES_PER_METER 0.00062137112
#define _GPS_KM_PER_METER 0.001
//#define _GPS_NO_STATS
class TinyGPS
{
public:
TinyGPS();
bool encode(char c); // process one character received from GPS
TinyGPS &operator << (char c) {encode(c); return *this;}
// lat/long in hundred thousandths of a degree and age of fix in milliseconds
inline void get_position(long *latitude, long *longitude, unsigned long *fix_age = 0)
{
if (latitude) *latitude = _latitude;
if (longitude) *longitude = _longitude;
if (fix_age) *fix_age = _last_position_fix == GPS_INVALID_FIX_TIME ?
GPS_INVALID_AGE : millis() - _last_position_fix;
}
// date as ddmmyy, time as hhmmsscc, and age in milliseconds
inline void get_datetime(unsigned long *date, unsigned long *time, unsigned long *fix_age = 0)
{
if (date) *date = _date;
if (time) *time = _time;
if (fix_age) *fix_age = _last_time_fix == GPS_INVALID_FIX_TIME ?
GPS_INVALID_AGE : millis() - _last_time_fix;
}
// signed altitude in centimeters (from GPGGA sentence)
inline long altitude() { return _altitude; }
// course in last full GPRMC sentence in 100th of a degree
inline unsigned long course() { return _course; }
// speed in last full GPRMC sentence in 100ths of a knot
unsigned long speed() { return _speed; }
#ifndef _GPS_NO_STATS
void stats(unsigned long *chars, unsigned short *good_sentences, unsigned short *failed_cs);
#endif
inline void f_get_position(float *latitude, float *longitude, unsigned long *fix_age = 0)
{
long lat, lon;
get_position(&lat, &lon, fix_age);
*latitude = lat / 100000.0;
*longitude = lon / 100000.0;
}
inline void crack_datetime(int *year, byte *month, byte *day,
byte *hour, byte *minute, byte *second, byte *hundredths = 0, unsigned long *fix_age = 0)
{
unsigned long date, time;
get_datetime(&date, &time, fix_age);
if (year)
{
*year = date % 100;
*year += *year > 80 ? 1900 : 2000;
}
if (month) *month = (date / 100) % 100;
if (day) *day = date / 10000;
if (hour) *hour = time / 1000000;
if (minute) *minute = (time / 10000) % 100;
if (second) *second = (time / 100) % 100;
if (hundredths) *hundredths = time % 100;
}
inline float f_altitude() { return altitude() / 100.0; }
inline float f_course() { return course() / 100.0; }
inline float f_speed_knots() { return speed() / 100.0; }
inline float f_speed_mph() { return _GPS_MPH_PER_KNOT * f_speed_knots(); }
inline float f_speed_mps() { return _GPS_MPS_PER_KNOT * f_speed_knots(); }
inline float f_speed_kmph() { return _GPS_KMPH_PER_KNOT * f_speed_knots(); }
static int library_version() { return _GPS_VERSION; }
enum {GPS_INVALID_AGE = 0xFFFFFFFF, GPS_INVALID_ANGLE = 999999999, GPS_INVALID_ALTITUDE = 999999999, GPS_INVALID_DATE = 0,
GPS_INVALID_TIME = 0xFFFFFFFF, GPS_INVALID_SPEED = 999999999, GPS_INVALID_FIX_TIME = 0xFFFFFFFF};
static float distance_between (float lat1, float long1, float lat2, float long2);
private:
enum {_GPS_SENTENCE_GPGGA, _GPS_SENTENCE_GPRMC, _GPS_SENTENCE_OTHER};
// properties
unsigned long _time, _new_time;
unsigned long _date, _new_date;
long _latitude, _new_latitude;
long _longitude, _new_longitude;
long _altitude, _new_altitude;
unsigned long _speed, _new_speed;
unsigned long _course, _new_course;
unsigned long _last_time_fix, _new_time_fix;
unsigned long _last_position_fix, _new_position_fix;
// parsing state variables
byte _parity;
bool _is_checksum_term;
char _term[15];
byte _sentence_type;
byte _term_number;
byte _term_offset;
bool _gps_data_good;
#ifndef _GPS_NO_STATS
// statistics
unsigned long _encoded_characters;
unsigned short _good_sentences;
unsigned short _failed_checksum;
unsigned short _passed_checksum;
#endif
// internal utilities
int from_hex(char a);
unsigned long parse_decimal();
unsigned long parse_degrees();
bool term_complete();
bool gpsisdigit(char c) { return c >= '0' && c <= '9'; }
long gpsatol(const char *str);
int gpsstrcmp(const char *str1, const char *str2);
};
// Arduino 0012 workaround
#undef int
#undef char
#undef long
#undef byte
#undef float
#undef abs
#undef round
#endif
keywords.txt:
#######################################
# Syntax Coloring Map for TinyGPS
##############################################################################
# Datatypes (KEYWORD1)
#######################################TinyGPS KEYWORD1#######################################
# Methods and Functions (KEYWORD2)
#######################################
encode KEYWORD2
get_position KEYWORD2
get_datetime KEYWORD2
altitude KEYWORD2
speed KEYWORD2
course KEYWORD2
stats KEYWORD2
f_get_position KEYWORD2
crack_datetime KEYWORD2
f_altitude KEYWORD2
f_course KEYWORD2
f_speed_knots KEYWORD2
f_speed_mph KEYWORD2
f_speed_mps KEYWORD2
f_speed_kmph KEYWORD2
library_version KEYWORD2
distance_between KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
GPS_INVALID_AGE LITERAL1
GPS_INVALID_ANGLE LITERAL1
GPS_INVALID_ALTITUDE LITERAL1
GPS_INVALID_DATE LITERAL1
GPS_INVALID_TIME