Device Cloud GPS

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

  1. 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.
  2. 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.
gps1
Figure 1

 

Prepare and Configure your Router Radio

  1. Use X-CTU to set the designated router radio in AT mode.
  2. Configure the router radio with the PAN ID you set the ConnectPort with.
  3. Set JV to 1 to ensure that your router attempts to rejoin the coordinator on startup.
  4. Enter in the High and Low destination addresses of your ConnectPort.
  5. Set the Baud Rate to 4800.

Construct the Arduino GPS System

gps2
Figure 2

gps3
Figure 3
 
  1. Insert your Router XBee Radio onto the XBee shield, and then insert the shield onto the Arduino board.
  2. 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.*
  3. Connect the GPS TX to the Arduino pin 0.
  4. Connect the GPS ground to Arduino ground.
  5. 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