Phil Hilger
Published © LGPL

Car or Motorbike Tracker

A GPS tracker for your car, motorcycle, bike and flying saucer, with data storage and map visualization. Not too hard, but no easy either...

AdvancedWork in progress2 hours4,543
Car or Motorbike Tracker

Things used in this project

Hardware components

Particle 3G Asset Tracker Kit (with Data Subscription)
Be careful to choose the right kit based on your location (Americas/Australia or Europe/Asia/Africa)
×1

Software apps and online services

AWS Lambda
Amazon Web Services AWS Lambda
AWS DynamoDB
Amazon Web Services AWS DynamoDB
Amazon Web Services AWS API Gateway
AWS EC2
Amazon Web Services AWS EC2
Optional: You can set up your own web server on EC2 or use the provider of your choice. All you'll need is to host a few file you can access through HTTP. In the cheapest case, you can just have the files on your PC and open them locally in your browser.
Google Maps
Google Maps
You'll need a google API (free) to draw the map and the paths

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Only if you want external push buttons to control/reset the box without opening it

Story

Read more

Code

GPSTracker.ino

C/C++
This is the main code for the firmware, it was developed starting from the GPS tracker sample but pretty much totally rewritten... don't change timeouts for no good reason, unless you want to get a surprisingly higher than expected bill from Particle ;-)
/*-------------------------------------------------------------------
Vehicle Tracker firmware for Electron
(c) Copyright 2016, Philippe Hilger

This code is provided with NO WARRANTIES of any kind (usual
disclaimers apply) for non-commercial applications. If you change it
and start incurring high data usage, blame yourself :)
--------------------------------------------------------------------*/

/*SYSTEM_THREAD(ENABLED);*/

// Getting the library
#include "AssetTracker.h"
#include "math.h"

#define MAX_GPS_POINTS 4 // careful, no more than 255 bytes of data!

int soft_reset = D0;
int soft_mode = D1;
int hard_reset = D2;
int LED7 = D7;

long reset_required = 0;
long mode_required = 0;
volatile bool upgrade_mode = false;

// Set whether you want the device to publish data to the internet by default here.
// 1 will Particle.publish AND Serial.print, 0 will just Serial.print
// Extremely useful for saving data while developing close enough to have a cable plugged in.
// You can also change this remotely using the Particle.function "tmode" defined in setup()
int transmittingData = 1;

// Used to keep track of the last time we published data
long lastGPSPublish = 0;
long lastAccelPublish = 0;
long lastAccelDetected = 0;
long previousAccelDetected = 0;
long lastRead = 0;
long lastActive = 0;

// How many minutes between publishes? 10+ recommended for long-time continuous publishing!
long delayAccelMinutes = 5;
long delayGPSMinutes = 60;
long delayIdleSleepMinutes = 30;
long lastGPSDetected = 0;
long lastGPSRead = 0;
long lastGPSUpdate = 0;
long gpsReadSeconds = 15;


// Threshold to trigger a publish
// 9000 is VERY sensitive, 12000 will still detect small bumps
int accelThreshold = 12000;
int maxAccel = 0;
int maxX=0,
  maxY=0,
  maxZ=0;
String pubAccel = "undefined";

float lat[MAX_GPS_POINTS], lon[MAX_GPS_POINTS];
long ts[MAX_GPS_POINTS];

int gpsCnt = 0;
float sumLat = 0.0,
  sumLon = 0.0,
  lastLat = 0.0,
  lastLon = 0.0,
  avgLat = 0.0,
  avgLon = 0.0;
int latLonCnt;
float gpsThreshold = 0.0005; // 55.5m
float gpsError = 0.01; // 1.1km

int gpsTrigger = 0;

// Creating an AssetTracker named 't' for us to reference
AssetTracker t = AssetTracker();

// A FuelGauge named 'fuel' for checking on the battery state
FuelGauge fuel;

void checkGPS(void);

// setup() and loop() are both required. setup() runs once when the device starts
// and is used for registering functions and variables and initializing things
void setup() {

    pinMode(soft_mode,INPUT_PULLUP);
    pinMode(soft_reset,INPUT_PULLUP);
    pinMode(hard_reset,INPUT_PULLUP);
    pinMode(LED7,OUTPUT);

    // Sets up all the necessary AssetTracker bits
    t.begin();

    // Opens up a Serial port so you can listen over USB
    Serial.begin(9600);

    // Enable the GPS module. Defaults to off to save power.
    // Takes 1.5s or so because of delays.
    t.gpsOn();

    // These three functions are useful for remote diagnostics. Read more below.
    Particle.function("tmode", transmitMode);
    Particle.function("batt", batteryStatus);
    Particle.function("gps", gpsPublish);
    Particle.function("aThresh",accelThresholder);
    Particle.function("reset",do_reset);
    lastActive = millis();
    reset_required = millis();
    mode_required = millis();
    /*attachInterrupt(soft_reset, do_reset, RISING);*/
}

// loop() runs continuously
void loop() {
    // You'll need to run this every loop to capture the GPS output
    t.updateGPS();

    if (digitalRead(soft_reset) && !digitalRead(soft_mode)) {
      if (millis()-reset_required>200 && millis()-mode_required<2000) {
        if (gpsTrigger == 1) {
          gpsTrigger = 2;
        } else if (gpsTrigger == 3) {
          transmittingData = false;
          Serial.println("Triggered No Tracking");
          gpsTrigger = 0;
        }
      }
      digitalWrite(LED7, (millis()-reset_required)%200>100);
      if (millis()-reset_required>3000) {
        pinMode(hard_reset,OUTPUT);
        pinResetFast(hard_reset);
        /*System.reset();*/
      }
    } else {
      reset_required = millis();
    }
    if (digitalRead(soft_mode)) {
      // special handling of mode-reset-mode to publish GPS
      if (millis()-mode_required>200 && millis()-reset_required<2000) {
        if (gpsTrigger == 2) {
          lastGPSPublish = 0;
          gpsTrigger = 0;
          Serial.println("Triggered GPS Publish");
        } else {
          gpsTrigger = 1;
        }
      }
      if (millis()-mode_required>10000) {
        gpsTrigger = 3;
      } else if (millis()-mode_required>3000) {
        gpsTrigger = 0;
        if (digitalRead(soft_reset)) {
          /*attachInterrupt(soft_reset, do_reset, RISING);*/
          upgrade_mode = true;
          digitalWrite(LED7, LOW);
          Cellular.listen();
        } else {
          digitalWrite(LED7, (millis()-mode_required)%100>50);
        }
      } else {
        digitalWrite(LED7, (millis()-mode_required)%500>250);
      }
    } else {
      mode_required = millis();
    }

    if (!digitalRead(soft_reset) && !digitalRead(soft_mode)) {
      // switch led off
      if (transmittingData) {
        digitalWrite(LED7, LOW);
      } else {
      digitalWrite(LED7, millis()%100>50 ? HIGH : LOW);
      }
    }

    if (upgrade_mode) {
      digitalWrite(LED7, (millis()-mode_required)%1000>500 ? HIGH : LOW);
    }

    /*if (millis()-lastActive<1000 || upgrade_mode) {
      return;
    }*/

    int newAccel = t.readXYZmagnitude();
    if (newAccel>maxAccel) {
      maxAccel = newAccel;
      maxX=t.readX();
      maxY=t.readY();
      maxZ=t.readZ();
      pubAccel = String::format("{\"x\":%d,\"y\":%d,\"z\":%d}",maxX,maxY,maxZ);
      Serial.println("- acceleration: "+String(maxAccel)+" - "+pubAccel);
    }

    // Check if there's been a big acceleration or we published recently
    if(maxAccel > accelThreshold) {
      lastAccelDetected = millis();
    }

    if (lastAccelDetected>0
      && (previousAccelDetected == 0
        ||lastAccelDetected-previousAccelDetected > delayAccelMinutes*60*1000)) {

        previousAccelDetected = lastAccelDetected;
        // reset max
        maxAccel=0;

        // If it's set to transmit AND it's been at least delayMinutes since the last one...
        if (lastAccelPublish == 0
          || millis()-lastAccelPublish >= delayAccelMinutes*60*1000){
            // remember last supposed publish
            lastAccelPublish = millis();
            if (transmittingData) {
              Particle.publish("A", pubAccel, 60, PRIVATE);
            }
        }
    }

    checkGPS();

    if (millis()-lastActive>=1000) {
      /*Serial.println("millis: "+String(millis())
      +", LastGPS: "+String(lastGPSPublish)
      +", LastAccel: "+String(lastAccelPublish));*/
      lastActive = millis();
    }
    /*if ((millis()-lastActive) > (delayIdleSleepMinutes*60*1000)
      && (millis()-lastGPSPublish) < (delayGPSMinutes*60*1000)
      && (millis()-lastAccelPublish) < (delayAccelMinutes*60*1000)) {
        // sleep until wakeup or next publish possible
        long sleepDuration = min(
          delayGPSMinutes*60-(millis()-lastGPSPublish)/1000,
          delayAccelMinutes*60-(millis()-lastAccelPublish)/1000
          );
        Serial.println("sleeping for " + String(sleepDuration) + " seconds");
        System.sleep(SLEEP_MODE_DEEP,sleepDuration);
    }*/
}

void checkGPS() {
  // Dumps the full NMEA sentence to serial in case you're curious
  //Serial.println(t.preNMEA());
  if (t.gpsFix()) {
    float newLat = t.readLatDeg();
    float newLon = t.readLonDeg();

    if (lastGPSPublish == 0
      || sqrt((newLat-lastLat)*(newLat-lastLat)+(newLon-lastLon)*(newLon-lastLon)) > gpsThreshold
      || millis()-lastAccelDetected < 1000) {
      Serial.println(String::format("newLat: %f, newLon: %f > Threshold",newLat,newLon));
      if ((lastLat == 0.0 && lastLon == 0.0)
        || sqrt((newLat-lastLat)*(newLat-lastLat)+(newLon-lastLon)*(newLon-lastLon)) <= gpsError) {

        // update average latLon
        /*sumLat += newLat;
        sumLon += newLon;
        latLonCnt++;
        avgLat = sumLat/latLonCnt;
        avgLon = sumLon/latLonCnt;
        Serial.println(String::format("newAvg: %f|%f",sumLat/latLonCnt,sumLon/latLonCnt));*/

        if (millis()-lastGPSRead >= gpsReadSeconds*1000
          && (millis()-lastAccelDetected <= gpsReadSeconds*1000
          || lastGPSPublish == 0
          || millis()-lastGPSPublish > (delayGPSMinutes*60*1000))) {
          lat[gpsCnt] = newLat; //sumLat/latLonCnt;
          lon[gpsCnt] = newLon; //sumLon/latLonCnt;
          ts[gpsCnt] = millis()-lastGPSPublish;
          Serial.println(String::format("Adding %f|%f to table[%d]",
            lat[gpsCnt],lon[gpsCnt],gpsCnt));
          gpsCnt++;

          lastGPSRead = millis();
          /*sumLat=sumLon=avgLat=avgLon=0.0;*/
          latLonCnt=0;
        }
        lastLat = newLat;
        lastLon = newLon;
      }
    }
    if (gpsCnt>0
      && (lastGPSPublish == 0
      || gpsCnt >= MAX_GPS_POINTS
      || (millis()-lastGPSPublish) > (delayGPSMinutes*60*1000))) {
        if(transmittingData){
          // Remember when we are supposed to publish (even if we don't)
          lastGPSPublish = millis();
          String pubGPS = "";
          for (int i=0; i<gpsCnt; i++) {
            if (pubGPS!="") {
              pubGPS+=",";
            }
            pubGPS+=String::format("{\"t\":%d,\"l\":%.5f,\"L\":%.5f}",ts[i],lat[i],lon[i]);
          }
          Serial.println("-GPS: ["+pubGPS+"]");
          // Only publish if we're in transmittingData mode 1;
          Particle.publish("G", "["+pubGPS+"]", 60, PRIVATE);
        }
        gpsCnt=0;
      }
  }
}

// Allows you to remotely change whether a device is publishing to the cloud
// or is only reporting data over Serial. Saves data when using only Serial!
// Change the default at the top of the code.
int transmitMode(String command){
    transmittingData = atoi(command);
    return 1;
}

// Actively ask for a GPS reading if you're impatient. Only publishes if there's
// a GPS fix, otherwise returns '0'
int gpsPublish(String command){
  lastGPSPublish = 0; // force publishing
  checkGPS();
  return t.gpsFix() ? 1 : 0;
}

// Lets you remotely check the battery status by calling the function "batt"
// Triggers a publish with the info (so subscribe or watch the dashboard)
// and also returns a '1' if there's >10% battery left and a '0' if below
int batteryStatus(String command){
    // Publish the battery voltage and percentage of battery remaining
    // if you want to be really efficient, just report one of these
    // the String::format("%f.2") part gives us a string to publish,
    // but with only 2 decimal points to save space
    Particle.publish("B",
          String::format("{\"v\":%.2f,\"c\":%.2f}",fuel.getVCell(),fuel.getSoC()),
          60, PRIVATE
    );
    // if there's more than 10% of the battery left, then return 1
    if(fuel.getSoC()>10){ return 1;}
    // if you're running out of battery, return 0
    else { return 0;}
}

// Remotely change the trigger threshold!
int accelThresholder(String command){
    accelThreshold = atoi(command);
    return 1;
}

int do_reset(String command) {
  /*detachInterrupt(soft_reset);*/
  upgrade_mode = false;
  System.reset();
  return 1;
}

AssetTracker.cpp

C/C++
(modified) AssetTracker Library
#include "AssetTracker.h"
#include "math.h"

//----------------- Tracker ----------------//

//#define mySerial Serial1
//Adafruit_GPS gps(&mySerial);
Adafruit_GPS gps = Adafruit_GPS();
Adafruit_LIS3DH accel = Adafruit_LIS3DH(A2, A5, A4, A3);

AssetTracker::AssetTracker(){

}

void AssetTracker::begin(){
    accel.begin(LIS3DH_DEFAULT_ADDRESS);

    // Default to 5kHz low-power sampling
    accel.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ);

    // Default to 4 gravities range
    accel.setRange(LIS3DH_RANGE_4_G);

    // Turn on the GPS module
    // gpsOn();
}

float AssetTracker::readLat(){
    return gps.latitude;
}

float AssetTracker::readLon(){
    return gps.longitude;
}

float AssetTracker::readLatDeg(){
    return gps.latitudeDegrees;
}

float AssetTracker::readLonDeg(){
    return gps.longitudeDegrees;
}

String AssetTracker::readLatLon(){
    String latLon = String::format("%f,%f",gps.latitudeDegrees,gps.longitudeDegrees);
    return latLon;
}

void AssetTracker::gpsOn(){
    // Power to the GPS is controlled by a FET connected to D6
    pinMode(D6,OUTPUT);
    digitalWrite(D6,LOW);
    gps.begin(9600);
    gps.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
    delay(500);
    // Default is 1 Hz update rate
    gps.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
    delay(500);
    gps.sendCommand(PGCMD_NOANTENNA);
    delay(500);
}

void AssetTracker::gpsOff(){
    digitalWrite(D6,HIGH);
}

char* AssetTracker::preNMEA(){
    return gps.lastNMEA();
}

bool AssetTracker::gpsFix(){
    if(gps.latitude == 0.0){
        return false;
    }
    else {
        return true;
    }
    //return gps.fix;
}

// char AssetTracker::checkGPS(){
//     char c = gps.read();
//     return c;
// }

void AssetTracker::updateGPS(){
    char c = gps.read();
      // if a sentence is received, we can check the checksum, parse it...
  if (gps.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences!
    // so be very wary if using OUTPUT_ALLDATA and trytng to print out data
    //Serial.println(gps.lastNMEA());   // this also sets the newNMEAreceived() flag to false

    if (!gps.parse(gps.lastNMEA()))   {
      // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another
    }
  }
}

int AssetTracker::readX(){
    accel.read();
    return accel.x;
}

int AssetTracker::readY(){
    accel.read();
    return accel.y;
}

int AssetTracker::readZ(){
    accel.read();
    return accel.z;
}

int AssetTracker::readXYZmagnitude(){
    accel.read();
    int magnitude = sqrt((accel.x*accel.x)+(accel.y*accel.y)+(accel.z*accel.z));
    return magnitude;
}



/***********************************
Adafruit's Ultimate GPS Library

Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!

Written by Limor Fried/Ladyada for Adafruit Industries.
BSD license, check license.txt for more information
All text above must be included in any redistribution
****************************************/
#ifdef __AVR__
  // Only include software serial on AVR platforms (i.e. not on Due).
  #include <SoftwareSerial.h>
#endif
//#include <Adafruit_GPS.h>

// how long are max NMEA lines to parse?
#define MAXLINELENGTH 120

// we double buffer: read one line in and leave one for the main program
volatile char line1[MAXLINELENGTH];
volatile char line2[MAXLINELENGTH];
// our index into filling the current line
volatile uint8_t lineidx=0;
// pointers to the double buffers
volatile char *currentline;
volatile char *lastline;
volatile boolean recvdflag;
volatile boolean inStandbyMode;
//Serial1 gpsHwSerial;


boolean Adafruit_GPS::parse(char *nmea) {
  // do checksum check

  // first look if we even have one
  if (nmea[strlen(nmea)-4] == '*') {
    uint16_t sum = parseHex(nmea[strlen(nmea)-3]) * 16;
    sum += parseHex(nmea[strlen(nmea)-2]);

    // check checksum
    for (uint8_t i=2; i < (strlen(nmea)-4); i++) {
      sum ^= nmea[i];
    }
    if (sum != 0) {
      // bad checksum :(
      return false;
    }
  }
  int32_t degree;
  long minutes;
  char degreebuff[10];
  // look for a few common sentences
  if (strstr(nmea, "$GPGGA")) {
    // found GGA
    char *p = nmea;
    // get time
    p = strchr(p, ',')+1;
    float timef = atof(p);
    uint32_t time = timef;
    hour = time / 10000;
    minute = (time % 10000) / 100;
    seconds = (time % 100);

    milliseconds = fmod(timef, 1.0) * 1000;

    // parse out latitude
    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      strncpy(degreebuff, p, 2);
      p += 2;
      degreebuff[2] = '\0';
      degree = atol(degreebuff) * 10000000;
      strncpy(degreebuff, p, 2); // minutes
      p += 3; // skip decimal point
      strncpy(degreebuff + 2, p, 4);
      degreebuff[6] = '\0';
      minutes = 50 * atol(degreebuff) / 3;
      latitude_fixed = degree + minutes;
      latitude = degree / 100000 + minutes * 0.000006F;
      latitudeDegrees = (latitude-100*int(latitude/100))/60.0;
      latitudeDegrees += int(latitude/100);
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      if (p[0] == 'S') latitudeDegrees *= -1.0;
      if (p[0] == 'N') lat = 'N';
      else if (p[0] == 'S') lat = 'S';
      else if (p[0] == ',') lat = 0;
      else return false;
    }

    // parse out longitude
    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      strncpy(degreebuff, p, 3);
      p += 3;
      degreebuff[3] = '\0';
      degree = atol(degreebuff) * 10000000;
      strncpy(degreebuff, p, 2); // minutes
      p += 3; // skip decimal point
      strncpy(degreebuff + 2, p, 4);
      degreebuff[6] = '\0';
      minutes = 50 * atol(degreebuff) / 3;
      longitude_fixed = degree + minutes;
      longitude = degree / 100000 + minutes * 0.000006F;
      longitudeDegrees = (longitude-100*int(longitude/100))/60.0;
      longitudeDegrees += int(longitude/100);
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      if (p[0] == 'W') longitudeDegrees *= -1.0;
      if (p[0] == 'W') lon = 'W';
      else if (p[0] == 'E') lon = 'E';
      else if (p[0] == ',') lon = 0;
      else return false;
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      fixquality = atoi(p);
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      satellites = atoi(p);
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      HDOP = atof(p);
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      altitude = atof(p);
    }

    p = strchr(p, ',')+1;
    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      geoidheight = atof(p);
    }
    return true;
  }
  if (strstr(nmea, "$GPRMC")) {
   // found RMC
    char *p = nmea;

    // get time
    p = strchr(p, ',')+1;
    float timef = atof(p);
    uint32_t time = timef;
    hour = time / 10000;
    minute = (time % 10000) / 100;
    seconds = (time % 100);

    milliseconds = fmod(timef, 1.0) * 1000;

    p = strchr(p, ',')+1;
    // Serial.println(p);
    if (p[0] == 'A')
      fix = true;
    else if (p[0] == 'V')
      fix = false;
    else
      return false;

    // parse out latitude
    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      strncpy(degreebuff, p, 2);
      p += 2;
      degreebuff[2] = '\0';
      long degree = atol(degreebuff) * 10000000;
      strncpy(degreebuff, p, 2); // minutes
      p += 3; // skip decimal point
      strncpy(degreebuff + 2, p, 4);
      degreebuff[6] = '\0';
      long minutes = 50 * atol(degreebuff) / 3;
      latitude_fixed = degree + minutes;
      latitude = degree / 100000 + minutes * 0.000006F;
      latitudeDegrees = (latitude-100*int(latitude/100))/60.0;
      latitudeDegrees += int(latitude/100);
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      if (p[0] == 'S') latitudeDegrees *= -1.0;
      if (p[0] == 'N') lat = 'N';
      else if (p[0] == 'S') lat = 'S';
      else if (p[0] == ',') lat = 0;
      else return false;
    }

    // parse out longitude
    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      strncpy(degreebuff, p, 3);
      p += 3;
      degreebuff[3] = '\0';
      degree = atol(degreebuff) * 10000000;
      strncpy(degreebuff, p, 2); // minutes
      p += 3; // skip decimal point
      strncpy(degreebuff + 2, p, 4);
      degreebuff[6] = '\0';
      minutes = 50 * atol(degreebuff) / 3;
      longitude_fixed = degree + minutes;
      longitude = degree / 100000 + minutes * 0.000006F;
      longitudeDegrees = (longitude-100*int(longitude/100))/60.0;
      longitudeDegrees += int(longitude/100);
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      if (p[0] == 'W') longitudeDegrees *= -1.0;
      if (p[0] == 'W') lon = 'W';
      else if (p[0] == 'E') lon = 'E';
      else if (p[0] == ',') lon = 0;
      else return false;
    }
    // speed
    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      speed = atof(p);
    }

    // angle
    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      angle = atof(p);
    }

    p = strchr(p, ',')+1;
    if (',' != *p)
    {
      uint32_t fulldate = atof(p);
      day = fulldate / 10000;
      month = (fulldate % 10000) / 100;
      year = (fulldate % 100);
    }
    // we dont parse the remaining, yet!
    return true;
  }

  return false;
}

char Adafruit_GPS::read(void) {
  char c = 0;

  if (paused) return c;

#ifdef __AVR__
  if(gpsSwSerial) {
    if(!gpsSwSerial->available()) return c;
    c = gpsSwSerial->read();
  } else
#endif
  {
    if(!Serial1.available()) return c;
    c = Serial1.read();
  }

  //Serial.print(c);

 if (c == '$') {         //please don't eat the dollar sign - rdl 9/15/14
    currentline[lineidx] = 0;
    lineidx = 0;
 }
  if (c == '\n') {
    currentline[lineidx] = 0;

    if (currentline == line1) {
      currentline = line2;
      lastline = line1;
    } else {
      currentline = line1;
      lastline = line2;
    }

    //Serial.println("----");
    //Serial.println((char *)lastline);
    //Serial.println("----");
    lineidx = 0;
    recvdflag = true;
  }

  currentline[lineidx++] = c;
  if (lineidx >= MAXLINELENGTH)
    lineidx = MAXLINELENGTH-1;

  return c;
}

#ifdef __AVR__
// Constructor when using SoftwareSerial or NewSoftSerial
#if ARDUINO >= 100
Adafruit_GPS::Adafruit_GPS(SoftwareSerial *ser)
#else
Adafruit_GPS::Adafruit_GPS(NewSoftSerial *ser)
#endif
{
  common_init();     // Set everything to common state, then...
  gpsSwSerial = ser; // ...override gpsSwSerial with value passed.
}
#endif

// Constructor when using HardwareSerial
Adafruit_GPS::Adafruit_GPS() {
  common_init();  // Set everything to common state, then...
  //gpsHwSerial = ser; // ...override gpsHwSerial with value passed.
}

// Initialization code used by all constructor types
void Adafruit_GPS::common_init(void) {
#ifdef __AVR__
  gpsSwSerial = NULL; // Set both to NULL, then override correct
#endif
  //gpsHwSerial = NULL; // port pointer in corresponding constructor
  recvdflag   = false;
  paused      = false;
  lineidx     = 0;
  currentline = line1;
  lastline    = line2;

  hour = minute = seconds = year = month = day =
    fixquality = satellites = 0; // uint8_t
  lat = lon = mag = 0; // char
  fix = false; // boolean
  milliseconds = 0; // uint16_t
  latitude = longitude = geoidheight = altitude =
    speed = angle = magvariation = HDOP = 0.0; // float
}

void Adafruit_GPS::begin(uint16_t baud)
{
#ifdef __AVR__
  if(gpsSwSerial)
    gpsSwSerial->begin(baud);
  else
    Serial1.begin(baud);
#endif
  Serial1.begin(baud);
  delay(10);
}

void Adafruit_GPS::sendCommand(const char *str) {
#ifdef __AVR__
  if(gpsSwSerial)
    gpsSwSerial->println(str);
  else
#endif
    Serial1.println(str);
}

boolean Adafruit_GPS::newNMEAreceived(void) {
  return recvdflag;
}

void Adafruit_GPS::pause(boolean p) {
  paused = p;
}

char *Adafruit_GPS::lastNMEA(void) {
  recvdflag = false;
  return (char *)lastline;
}

// read a Hex value and return the decimal equivalent
uint8_t Adafruit_GPS::parseHex(char c) {
    if (c < '0')
      return 0;
    if (c <= '9')
      return c - '0';
    if (c < 'A')
       return 0;
    if (c <= 'F')
       return (c - 'A')+10;
    // if (c > 'F')
    return 0;
}

boolean Adafruit_GPS::waitForSentence(const char *wait4me, uint8_t max) {
  char str[20];

  uint8_t i=0;
  while (i < max) {
    if (newNMEAreceived()) {
      char *nmea = lastNMEA();
      strncpy(str, nmea, 20);
      str[19] = 0;
      i++;

      if (strstr(str, wait4me))
	return true;
    }
  }

  return false;
}

boolean Adafruit_GPS::LOCUS_StartLogger(void) {
  sendCommand(PMTK_LOCUS_STARTLOG);
  recvdflag = false;
  return waitForSentence(PMTK_LOCUS_STARTSTOPACK);
}

boolean Adafruit_GPS::LOCUS_StopLogger(void) {
  sendCommand(PMTK_LOCUS_STOPLOG);
  recvdflag = false;
  return waitForSentence(PMTK_LOCUS_STARTSTOPACK);
}

boolean Adafruit_GPS::LOCUS_ReadStatus(void) {
  sendCommand(PMTK_LOCUS_QUERY_STATUS);

  if (! waitForSentence("$PMTKLOG"))
    return false;

  char *response = lastNMEA();
  uint16_t parsed[10];
  uint8_t i;

  for (i=0; i<10; i++) parsed[i] = -1;

  response = strchr(response, ',');
  for (i=0; i<10; i++) {
    if (!response || (response[0] == 0) || (response[0] == '*'))
      break;
    response++;
    parsed[i]=0;
    while ((response[0] != ',') &&
	   (response[0] != '*') && (response[0] != 0)) {
      parsed[i] *= 10;
      char c = response[0];
      if (isDigit(c))
        parsed[i] += c - '0';
      else
        parsed[i] = c;
      response++;
    }
  }
  LOCUS_serial = parsed[0];
  LOCUS_type = parsed[1];
  if (isAlpha(parsed[2])) {
    parsed[2] = parsed[2] - 'a' + 10;
  }
  LOCUS_mode = parsed[2];
  LOCUS_config = parsed[3];
  LOCUS_interval = parsed[4];
  LOCUS_distance = parsed[5];
  LOCUS_speed = parsed[6];
  LOCUS_status = !parsed[7];
  LOCUS_records = parsed[8];
  LOCUS_percent = parsed[9];

  return true;
}

// Standby Mode Switches
boolean Adafruit_GPS::standby(void) {
  if (inStandbyMode) {
    return false;  // Returns false if already in standby mode, so that you do not wake it up by sending commands to GPS
  }
  else {
    inStandbyMode = true;
    sendCommand(PMTK_STANDBY);
    //return waitForSentence(PMTK_STANDBY_SUCCESS);  // don't seem to be fast enough to catch the message, or something else just is not working
    return true;
  }
}

boolean Adafruit_GPS::wakeup(void) {
  if (inStandbyMode) {
   inStandbyMode = false;
    sendCommand("");  // send byte to wake it up
    return waitForSentence(PMTK_AWAKE);
  }
  else {
      return false;  // Returns false if not in standby mode, nothing to wakeup
  }
}




/**************************************************************************/
/*!
    @file     Adafruit_LIS3DH.cpp
    @author   K. Townsend / Limor Fried (Adafruit Industries)
    @license  BSD (see license.txt)

    This is a library for the Adafruit LIS3DH Accel breakout board
    ----> https://www.adafruit.com/products/????

    Adafruit invests time and resources providing this open source code,
    please support Adafruit and open-source hardware by purchasing
    products from Adafruit!

    @section  HISTORY

    v1.0  - First release
*/
/**************************************************************************/

 //#include "application.h"


//#include <Wire.h>
//#include "Adafruit_LIS3DH.h"


/**************************************************************************/
/*!
    @brief  Instantiates a new LIS3DH class in I2C or SPI mode
*/
/**************************************************************************/
// I2C
Adafruit_LIS3DH::Adafruit_LIS3DH()
  : _cs(-1), _mosi(-1), _miso(-1), _sck(-1), _sensorID(-1)
{
}

Adafruit_LIS3DH::Adafruit_LIS3DH(int8_t cspin)
  : _cs(cspin), _mosi(-1), _miso(-1), _sck(-1), _sensorID(-1)
{ }

Adafruit_LIS3DH::Adafruit_LIS3DH(int8_t cspin, int8_t mosipin, int8_t misopin, int8_t sckpin)
  : _cs(cspin), _mosi(mosipin), _miso(misopin), _sck(sckpin), _sensorID(-1)
{ }



/**************************************************************************/
/*!
    @brief  Setups the HW (reads coefficients values, etc.)
*/
/**************************************************************************/
bool Adafruit_LIS3DH::begin(uint8_t i2caddr) {
  _i2caddr = i2caddr;


  if (_cs == -1) {
    // i2c
    Wire.begin();
  } else {
    digitalWrite(_cs, HIGH);
    pinMode(_cs, OUTPUT);

    if (_sck == -1) {
      // hardware SPI
      SPI.begin();
    } else {
      // software SPI
      pinMode(_sck, OUTPUT);
      pinMode(_mosi, OUTPUT);
      pinMode(_miso, INPUT);
    }
  }

  /* Check connection */
  uint8_t deviceid = readRegister8(LIS3DH_REG_WHOAMI);
  if (deviceid != 0x33)
  {
    /* No LIS3DH detected ... return false */
    //Serial.println(deviceid, HEX);
    return false;
  }

  // enable all axes, normal mode
  writeRegister8(LIS3DH_REG_CTRL1, 0x07);
  // 400Hz rate
  setDataRate(LIS3DH_DATARATE_400_HZ);

  // High res enabled
  writeRegister8(LIS3DH_REG_CTRL4, 0x08);

  // DRDY on INT1
  writeRegister8(LIS3DH_REG_CTRL3, 0x10);

  // Turn on orientation config
  //writeRegister8(LIS3DH_REG_PL_CFG, 0x40);

  // enable adc & temp sensor
  writeRegister8(LIS3DH_REG_TEMPCFG, 0xC0);


  for (uint8_t i=0; i<0x30; i++) {
    Serial.print("$");
    Serial.print(i, HEX); Serial.print(" = 0x");
    Serial.println(readRegister8(i), HEX);
  }


  return true;
}


void Adafruit_LIS3DH::read(void) {
  // read x y z at once

  if (_cs == -1) {
    // i2c
    Wire.beginTransmission(_i2caddr);
    Wire.write(LIS3DH_REG_OUT_X_L | 0x80); // 0x80 for autoincrement
    Wire.endTransmission();

    Wire.requestFrom(_i2caddr, 6);
    x = Wire.read(); x |= ((uint16_t)Wire.read()) << 8;
    y = Wire.read(); y |= ((uint16_t)Wire.read()) << 8;
    z = Wire.read(); z |= ((uint16_t)Wire.read()) << 8;
  } else {
    if (_sck == -1)
      //SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0));
      SPI.setBitOrder(MSBFIRST);
      SPI.setClockSpeed(500000);
      SPI.setDataMode(SPI_MODE0);

    digitalWrite(_cs, LOW);
    spixfer(LIS3DH_REG_OUT_X_L | 0x80 | 0x40); // read multiple, bit 7&6 high

    x = spixfer(); x |= ((uint16_t)spixfer()) << 8;
    y = spixfer(); y |= ((uint16_t)spixfer()) << 8;
    z = spixfer(); z |= ((uint16_t)spixfer()) << 8;

    digitalWrite(_cs, HIGH);
    if (_sck == -1)
      SPI.end();              // release the SPI bus

  }
  uint8_t range = getRange();
  uint16_t divider = 1;
  if (range == LIS3DH_RANGE_16_G) divider = 2048;
  if (range == LIS3DH_RANGE_8_G) divider = 4096;
  if (range == LIS3DH_RANGE_4_G) divider = 8190;
  if (range == LIS3DH_RANGE_2_G) divider = 16380;

  x_g = (float)x / divider;
  y_g = (float)y / divider;
  z_g = (float)z / divider;

}

/**************************************************************************/
/*!
    @brief  Read the auxilary ADC
*/
/**************************************************************************/

uint16_t Adafruit_LIS3DH::readADC(uint8_t adc) {
  if ((adc < 1) || (adc > 3)) return 0;
  uint16_t value;

  adc--;

  uint8_t reg = LIS3DH_REG_OUTADC1_L + adc*2;


  if (_cs == -1) {
    // i2c
    Wire.beginTransmission(_i2caddr);
    Wire.write(reg | 0x80);   // 0x80 for autoincrement
    Wire.endTransmission();
    Wire.requestFrom(_i2caddr, 2);
    value = Wire.read();  value |= ((uint16_t)Wire.read()) << 8;
  } else {
    if (_sck == -1)
      //SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0));
    SPI.setBitOrder(MSBFIRST);
      SPI.setClockSpeed(500000);
      SPI.setDataMode(SPI_MODE0);
    digitalWrite(_cs, LOW);
    spixfer(reg | 0x80 | 0x40); // read multiple, bit 7&6 high

    value = spixfer(); value |= ((uint16_t)spixfer()) << 8;

    digitalWrite(_cs, HIGH);
    if (_sck == -1)
      SPI.end();              // release the SPI bus
  }

  return value;
}

/**************************************************************************/
/*!
    @brief  Sets the g range for the accelerometer
*/
/**************************************************************************/
void Adafruit_LIS3DH::setRange(lis3dh_range_t range)
{
  uint8_t r = readRegister8(LIS3DH_REG_CTRL4);
  r &= ~(0x30);
  r |= range << 4;
  writeRegister8(LIS3DH_REG_CTRL4, r);
}

/**************************************************************************/
/*!
    @brief  Sets the g range for the accelerometer
*/
/**************************************************************************/
lis3dh_range_t Adafruit_LIS3DH::getRange(void)
{
  /* Read the data format register to preserve bits */
  return (lis3dh_range_t)((readRegister8(LIS3DH_REG_CTRL4) >> 4) & 0x03);
}

/**************************************************************************/
/*!
    @brief  Sets the data rate for the LIS3DH (controls power consumption)
*/
/**************************************************************************/
void Adafruit_LIS3DH::setDataRate(lis3dh_dataRate_t dataRate)
{
  uint8_t ctl1 = readRegister8(LIS3DH_REG_CTRL1);
  ctl1 &= ~(0xF0); // mask off bits
  ctl1 |= (dataRate << 4);
  writeRegister8(LIS3DH_REG_CTRL1, ctl1);
}

/**************************************************************************/
/*!
    @brief  Sets the data rate for the LIS3DH (controls power consumption)
*/
/**************************************************************************/
lis3dh_dataRate_t Adafruit_LIS3DH::getDataRate(void)
{
  return (lis3dh_dataRate_t)((readRegister8(LIS3DH_REG_CTRL1) >> 4)& 0x0F);
}

/**************************************************************************/
/*!
    @brief  Gets the most recent sensor event
*/
/**************************************************************************/
bool Adafruit_LIS3DH::getEvent(sensors_event_t *event) {
  /* Clear the event */
  memset(event, 0, sizeof(sensors_event_t));

  event->version   = sizeof(sensors_event_t);
  event->sensor_id = _sensorID;
  event->type      = SENSOR_TYPE_ACCELEROMETER;
  event->timestamp = 0;

  read();

  event->acceleration.x = x_g;
  event->acceleration.y = y_g;
  event->acceleration.z = z_g;
}

/**************************************************************************/
/*!
    @brief  Gets the sensor_t data
*/
/**************************************************************************/
void Adafruit_LIS3DH::getSensor(sensor_t *sensor) {
  /* Clear the sensor_t object */
  memset(sensor, 0, sizeof(sensor_t));

  /* Insert the sensor name in the fixed length char array */
  strncpy (sensor->name, "LIS3DH", sizeof(sensor->name) - 1);
  sensor->name[sizeof(sensor->name)- 1] = 0;
  sensor->version     = 1;
  sensor->sensor_id   = _sensorID;
  sensor->type        = SENSOR_TYPE_ACCELEROMETER;
  sensor->min_delay   = 0;
  sensor->max_value   = 0;
  sensor->min_value   = 0;
  sensor->resolution  = 0;
}


/**************************************************************************/
/*!
    @brief  Low level SPI
*/
/**************************************************************************/

uint8_t Adafruit_LIS3DH::spixfer(uint8_t x) {
  if (_sck == -1)
    return SPI.transfer(x);

  // software spi
  //Serial.println("Software SPI");
  uint8_t reply = 0;
  for (int i=7; i>=0; i--) {
    reply <<= 1;
    digitalWrite(_sck, LOW);
    digitalWrite(_mosi, x & (1<<i));
    digitalWrite(_sck, HIGH);
    if (digitalRead(_miso))
      reply |= 1;
  }
  return reply;
}


/**************************************************************************/
/*!
    @brief  Writes 8-bits to the specified destination register
*/
/**************************************************************************/
void Adafruit_LIS3DH::writeRegister8(uint8_t reg, uint8_t value) {
  if (_cs == -1) {
    Wire.beginTransmission((uint8_t)_i2caddr);
    Wire.write((uint8_t)reg);
    Wire.write((uint8_t)value);
    Wire.endTransmission();
  } else {
    if (_sck == -1)
      //SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0));
      SPI.setBitOrder(MSBFIRST);
      SPI.setClockSpeed(500000);
      SPI.setDataMode(SPI_MODE0);
    digitalWrite(_cs, LOW);
    spixfer(reg & ~0x80); // write, bit 7 low
    spixfer(value);
    digitalWrite(_cs, HIGH);
    if (_sck == -1)
      SPI.end();              // release the SPI bus
  }
}

/**************************************************************************/
/*!
    @brief  Reads 8-bits from the specified register
*/
/**************************************************************************/
uint8_t Adafruit_LIS3DH::readRegister8(uint8_t reg) {
  uint8_t value;

  if (_cs == -1) {
    Wire.beginTransmission(_i2caddr);
    Wire.write((uint8_t)reg);
    Wire.endTransmission();

    Wire.requestFrom(_i2caddr, 1);
    value = Wire.read();
  }  else {
    if (_sck == -1)
      //SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0));
      SPI.setBitOrder(MSBFIRST);
      SPI.setClockSpeed(500000);
      SPI.setDataMode(SPI_MODE0);
    digitalWrite(_cs, LOW);
    spixfer(reg | 0x80); // read, bit 7 high
    value = spixfer(0);
    digitalWrite(_cs, HIGH);
    if (_sck == -1)
      SPI.end();              // release the SPI bus
  }
  return value;
}

AssetTracker.h

C/C++
Header for (modified) AssetTracker Library
#include "application.h"
#include "math.h"

#ifndef AssetTracker_h
#define AssetTracker_h

class AssetTracker {

 public:

  AssetTracker();

  void
    begin(void),
    updateGPS(void),
    gpsOn(void),
    gpsOff(void);
  int
    readX(void),
    readY(void),
    readZ(void),
    readXYZmagnitude(void);
  float
    readLat(void),
    readLon(void),
    readLatDeg(void),
    readLonDeg(void);

  bool
    gpsFix(void);
  char
    checkGPS(void),
    *preNMEA(void);
  String
    readLatLon(void);
 private:

};

#endif

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software< /span>
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* Update by K. Townsend (Adafruit Industries) for lighter typedefs, and
 * extended sensor support to include color, voltage and current */



#ifndef _ADAFRUIT_SENSOR_H
#define _ADAFRUIT_SENSOR_H

/* Intentionally modeled after sensors.h in the Android API:
 * https://github.com/android/platform_hardware_libhardware/blob/master/include/hardware/sensors.h */

/* Constants */
#define SENSORS_GRAVITY_EARTH             (9.80665F)              /**< Earth's gravity in m/s^2 */
#define SENSORS_GRAVITY_MOON              (1.6F)                  /**< The moon's gravity in m/s^2 */
#define SENSORS_GRAVITY_SUN               (275.0F)                /**< The sun's gravity in m/s^2 */
#define SENSORS_GRAVITY_STANDARD          (SENSORS_GRAVITY_EARTH)
#define SENSORS_MAGFIELD_EARTH_MAX        (60.0F)                 /**< Maximum magnetic field on Earth's surface */
#define SENSORS_MAGFIELD_EARTH_MIN        (30.0F)                 /**< Minimum magnetic field on Earth's surface */
#define SENSORS_PRESSURE_SEALEVELHPA      (1013.25F)              /**< Average sea level pressure is 1013.25 hPa */
#define SENSORS_DPS_TO_RADS               (0.017453293F)          /**< Degrees/s to rad/s multiplier */
#define SENSORS_GAUSS_TO_MICROTESLA       (100)                   /**< Gauss to micro-Tesla multiplier */

/** Sensor types */
typedef enum
{
  SENSOR_TYPE_ACCELEROMETER         = (1),   /**< Gravity + linear acceleration */
  SENSOR_TYPE_MAGNETIC_FIELD        = (2),
  SENSOR_TYPE_ORIENTATION           = (3),
  SENSOR_TYPE_GYROSCOPE             = (4),
  SENSOR_TYPE_LIGHT                 = (5),
  SENSOR_TYPE_PRESSURE              = (6),
  SENSOR_TYPE_PROXIMITY             = (8),
  SENSOR_TYPE_GRAVITY               = (9),
  SENSOR_TYPE_LINEAR_ACCELERATION   = (10),  /**< Acceleration not including gravity */
  SENSOR_TYPE_ROTATION_VECTOR       = (11),
  SENSOR_TYPE_RELATIVE_HUMIDITY     = (12),
  SENSOR_TYPE_AMBIENT_TEMPERATURE   = (13),
  SENSOR_TYPE_VOLTAGE               = (15),
  SENSOR_TYPE_CURRENT               = (16),
  SENSOR_TYPE_COLOR                 = (17)
} sensors_type_t;

/** struct sensors_vec_s is used to return a vector in a common format. */
typedef struct {
    union {
        float v[3];
        struct {
            float x;
            float y;
            float z;
        };
        /* Orientation sensors */
        struct {
            float roll;    /**< Rotation around the longitudinal axis (the plane body, 'X axis'). Roll is positive and increasing when moving downward. -90<=roll<=90 */
            float pitch;   /**< Rotation around the lateral axis (the wing span, 'Y axis'). Pitch is positive and increasing when moving upwards. -180<=pitch<=180) */
            float heading; /**< Angle between the longitudinal axis (the plane body) and magnetic north, measured clockwise when viewing from the top of the device. 0-359 */
        };
    };
    uint8_t status;
    uint8_t reserved[3];
} sensors_vec_t;

/** struct sensors_color_s is used to return color data in a common format. */
typedef struct {
    union {
        float c[3];
        /* RGB color space */
        struct {
            float r;       /**< Red component */
            float g;       /**< Green component */
            float b;       /**< Blue component */
        };
    };
    uint32_t rgba;         /**< 24-bit RGBA value */
} sensors_color_t;

/* Sensor event (36 bytes) */
/** struct sensor_event_s is used to provide a single sensor event in a common format. */
typedef struct
{
    int32_t version;                          /**< must be sizeof(struct sensors_event_t) */
    int32_t sensor_id;                        /**< unique sensor identifier */
    int32_t type;                             /**< sensor type */
    int32_t reserved0;                        /**< reserved */
    int32_t timestamp;                        /**< time is in milliseconds */
    union
    {
        float           data[4];
        sensors_vec_t   acceleration;         /**< acceleration values are in meter per second per second (m/s^2) */
        sensors_vec_t   magnetic;             /**< magnetic vector values are in micro-Tesla (uT) */
        sensors_vec_t   orientation;          /**< orientation values are in degrees */
        sensors_vec_t   gyro;                 /**< gyroscope values are in rad/s */
        float           temperature;          /**< temperature is in degrees centigrade (Celsius) */
        float           distance;             /**< distance in centimeters */
        float           light;                /**< light in SI lux units */
        float           pressure;             /**< pressure in hectopascal (hPa) */
        float           relative_humidity;    /**< relative humidity in percent */
        float           current;              /**< current in milliamps (mA) */
        float           voltage;              /**< voltage in volts (V) */
        sensors_color_t color;                /**< color in RGB component values */
    };
} sensors_event_t;

/* Sensor details (40 bytes) */
/** struct sensor_s is used to describe basic information about a specific sensor. */
typedef struct
{
    char     name[12];                        /**< sensor name */
    int32_t  version;                         /**< version of the hardware + driver */
    int32_t  sensor_id;                       /**< unique sensor identifier */
    int32_t  type;                            /**< this sensor's type (ex. SENSOR_TYPE_LIGHT) */
    float    max_value;                       /**< maximum value of this sensor's value in SI units */
    float    min_value;                       /**< minimum value of this sensor's value in SI units */
    float    resolution;                      /**< smallest difference between two values reported by this sensor */
    int32_t  min_delay;                       /**< min delay in microseconds between events. zero = not a constant rate */
} sensor_t;

class Adafruit_Sensor {
 public:
  // Constructor(s)
  Adafruit_Sensor() {}
  virtual ~Adafruit_Sensor() {}

  // These must be defined by the subclass
  virtual void enableAutoRange(bool enabled) {};
  virtual bool getEvent(sensors_event_t*) = 0;
  virtual void getSensor(sensor_t*) = 0;

 private:
  bool _autoRange;
};

#endif

/***********************************
This is the Adafruit GPS library - the ultimate GPS library
for the ultimate GPS module!

Tested and works great with the Adafruit Ultimate GPS module
using MTK33x9 chipset
    ------> http://www.adafruit.com/products/746
Pick one up today at the Adafruit electronics shop
and help support open source hardware & software! -ada

Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!

Written by Limor Fried/Ladyada  for Adafruit Industries.
BSD license, check license.txt for more information
All text above must be included in any redistribution
****************************************/
// Fllybob added lines 34,35 and 40,41 to add 100mHz logging capability

#ifndef _ADAFRUIT_GPS_H
#define _ADAFRUIT_GPS_H

#ifdef __AVR__
  #if ARDUINO >= 100
    #include <SoftwareSerial.h>
  #else
    #include <NewSoftSerial.h>
  #endif
#endif

// different commands to set the update rate from once a second (1 Hz) to 10 times a second (10Hz)
// Note that these only control the rate at which the position is echoed, to actually speed up the
// position fix you must also send one of the position fix rate commands below too.
#define PMTK_SET_NMEA_UPDATE_100_MILLIHERTZ  "$PMTK220,10000*2F" // Once every 10 seconds, 100 millihertz.
#define PMTK_SET_NMEA_UPDATE_200_MILLIHERTZ  "$PMTK220,5000*1B"  // Once every 5 seconds, 200 millihertz.
#define PMTK_SET_NMEA_UPDATE_1HZ  "$PMTK220,1000*1F"
#define PMTK_SET_NMEA_UPDATE_5HZ  "$PMTK220,200*2C"
#define PMTK_SET_NMEA_UPDATE_10HZ "$PMTK220,100*2F"
// Position fix update rate commands.
#define PMTK_API_SET_FIX_CTL_100_MILLIHERTZ  "$PMTK300,10000,0,0,0,0*2C" // Once every 10 seconds, 100 millihertz.
#define PMTK_API_SET_FIX_CTL_200_MILLIHERTZ  "$PMTK300,5000,0,0,0,0*18"  // Once every 5 seconds, 200 millihertz.
#define PMTK_API_SET_FIX_CTL_1HZ  "$PMTK300,1000,0,0,0,0*1C"
#define PMTK_API_SET_FIX_CTL_5HZ  "$PMTK300,200,0,0,0,0*2F"
// Can't fix position faster than 5 times a second!


#define PMTK_SET_BAUD_57600 "$PMTK251,57600*2C"
#define PMTK_SET_BAUD_9600 "$PMTK251,9600*17"

// turn on only the second sentence (GPRMC)
#define PMTK_SET_NMEA_OUTPUT_RMCONLY "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29"
// turn on GPRMC and GGA
#define PMTK_SET_NMEA_OUTPUT_RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28"
// turn on ALL THE DATA
#define PMTK_SET_NMEA_OUTPUT_ALLDATA "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28"
// turn off output
#define PMTK_SET_NMEA_OUTPUT_OFF "$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28"

// to generate your own sentences, check out the MTK command datasheet and use a checksum calculator
// such as the awesome http://www.hhhh.org/wiml/proj/nmeaxor.html

#define PMTK_LOCUS_STARTLOG  "$PMTK185,0*22"
#define PMTK_LOCUS_STOPLOG "$PMTK185,1*23"
#define PMTK_LOCUS_STARTSTOPACK "$PMTK001,185,3*3C"
#define PMTK_LOCUS_QUERY_STATUS "$PMTK183*38"
#define PMTK_LOCUS_ERASE_FLASH "$PMTK184,1*22"
#define LOCUS_OVERLAP 0
#define LOCUS_FULLSTOP 1

#define PMTK_ENABLE_SBAS "$PMTK313,1*2E"
#define PMTK_ENABLE_WAAS "$PMTK301,2*2E"

// standby command & boot successful message
#define PMTK_STANDBY "$PMTK161,0*28"
#define PMTK_STANDBY_SUCCESS "$PMTK001,161,3*36"  // Not needed currently
#define PMTK_AWAKE "$PMTK010,002*2D"

// ask for the release and version
#define PMTK_Q_RELEASE "$PMTK605*31"

// request for updates on antenna status
#define PGCMD_ANTENNA "$PGCMD,33,1*6C"
#define PGCMD_NOANTENNA "$PGCMD,33,0*6D"

// how long to wait when we're looking for a response
#define MAXWAITSENTENCE 5


class Adafruit_GPS {
 public:
  void begin(uint16_t baud);

#ifdef __AVR__
  #if ARDUINO >= 100
    Adafruit_GPS(SoftwareSerial *ser); // Constructor when using SoftwareSerial
  #else
    Adafruit_GPS(NewSoftSerial  *ser); // Constructor when using NewSoftSerial
  #endif
#endif
  Adafruit_GPS(); // Constructor when using HardwareSerial

  char *lastNMEA(void);
  boolean newNMEAreceived();
  void common_init(void);

  void sendCommand(const char *);

  void pause(boolean b);

  boolean parseNMEA(char *response);
  uint8_t parseHex(char c);

  char read(void);
  boolean parse(char *);
  void interruptReads(boolean r);

  boolean wakeup(void);
  boolean standby(void);

  uint8_t hour, minute, seconds, year, month, day;
  uint16_t milliseconds;
  // Floating point latitude and longitude value in degrees.
  float latitude, longitude;
  // Fixed point latitude and longitude value with degrees stored in units of 1/100000 degrees,
  // and minutes stored in units of 1/100000 degrees.  See pull #13 for more details:
  //   https://github.com/adafruit/Adafruit-GPS-Library/pull/13
  int32_t latitude_fixed, longitude_fixed;
  float latitudeDegrees, longitudeDegrees;
  float geoidheight, altitude;
  float speed, angle, magvariation, HDOP;
  char lat, lon, mag;
  boolean fix;
  uint8_t fixquality, satellites;

  boolean waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE);
  boolean LOCUS_StartLogger(void);
  boolean LOCUS_StopLogger(void);
  boolean LOCUS_ReadStatus(void);

  uint16_t LOCUS_serial, LOCUS_records;
  uint8_t LOCUS_type, LOCUS_mode, LOCUS_config, LOCUS_interval, LOCUS_distance, LOCUS_speed, LOCUS_status, LOCUS_percent;
 private:
  boolean paused;

  uint8_t parseResponse(char *response);
#ifdef __AVR__
  #if ARDUINO >= 100
    SoftwareSerial *gpsSwSerial;
  #else
    NewSoftSerial  *gpsSwSerial;
  #endif
#endif
  //Serial1 gpsHwSerial;
};


#endif




/**************************************************************************/
/*!
    @file     Adafruit_LIS3DH.h
    @author   K. Townsend / Limor Fried (Adafruit Industries)
    @license  BSD (see license.txt)

    This is a library for the Adafruit LIS3DH Accel breakout board
    ----> https://www.adafruit.com/products/????

    Adafruit invests time and resources providing this open source code,
    please support Adafruit and open-source hardware by purchasing
    products from Adafruit!

    @section  HISTORY

    v1.0  - First release
*/
/**************************************************************************/

 //#include "application.h"


//#include <Wire.h>
//#include <SPI.h>
//#include "Adafruit_Sensor.h"

/*=========================================================================
    I2C ADDRESS/BITS
    -----------------------------------------------------------------------*/
    #define LIS3DH_DEFAULT_ADDRESS  (0x18)    // if SDO/SA0 is 3V, its 0x19
/*=========================================================================*/

#define LIS3DH_REG_STATUS1       0x07
#define LIS3DH_REG_OUTADC1_L     0x08
#define LIS3DH_REG_OUTADC1_H     0x09
#define LIS3DH_REG_OUTADC2_L     0x0A
#define LIS3DH_REG_OUTADC2_H     0x0B
#define LIS3DH_REG_OUTADC3_L     0x0C
#define LIS3DH_REG_OUTADC3_H     0x0D
#define LIS3DH_REG_INTCOUNT      0x0E
#define LIS3DH_REG_WHOAMI        0x0F
#define LIS3DH_REG_TEMPCFG       0x1F
#define LIS3DH_REG_CTRL1         0x20
#define LIS3DH_REG_CTRL2         0x21
#define LIS3DH_REG_CTRL3         0x22
#define LIS3DH_REG_CTRL4         0x23
#define LIS3DH_REG_CTRL5         0x24
#define LIS3DH_REG_CTRL6         0x25
#define LIS3DH_REG_REFERENCE     0x26
#define LIS3DH_REG_STATUS2       0x27
#define LIS3DH_REG_OUT_X_L       0x28
#define LIS3DH_REG_OUT_X_H       0x29
#define LIS3DH_REG_OUT_Y_L       0x2A
#define LIS3DH_REG_OUT_Y_H       0x2B
#define LIS3DH_REG_OUT_Z_L       0x2C
#define LIS3DH_REG_OUT_Z_H       0x2D
#define LIS3DH_REG_FIFOCTRL      0x2E
#define LIS3DH_REG_FIFOSRC       0x2F
#define LIS3DH_REG_INT1CFG       0x30
#define LIS3DH_REG_INT1SRC       0x31
#define LIS3DH_REG_INT1THS       0x32
#define LIS3DH_REG_INT1DUR       0x33
#define LIS3DH_REG_CLICKCFG      0x38
#define LIS3DH_REG_CLICKSRC      0x39
#define LIS3DH_REG_CLICKTHS      0x3A
#define LIS3DH_REG_TIMELIMIT     0x3B
#define LIS3DH_REG_TIMELATENCY   0x3C
#define LIS3DH_REG_TIMEWINDOW    0x3D

typedef enum
{
  LIS3DH_RANGE_16_G         = 0b11,   // +/- 16g
  LIS3DH_RANGE_8_G           = 0b10,   // +/- 8g
  LIS3DH_RANGE_4_G           = 0b01,   // +/- 4g
  LIS3DH_RANGE_2_G           = 0b00    // +/- 2g (default value)
} lis3dh_range_t;


/* Used with register 0x2A (LIS3DH_REG_CTRL_REG1) to set bandwidth */
typedef enum
{
  LIS3DH_DATARATE_400_HZ     = 0b0111, //  400Hz
  LIS3DH_DATARATE_200_HZ     = 0b0110, //  200Hz
  LIS3DH_DATARATE_100_HZ     = 0b0101, //  100Hz
  LIS3DH_DATARATE_50_HZ      = 0b0100, //   50Hz
  LIS3DH_DATARATE_25_HZ      = 0b0011, //   25Hz
  LIS3DH_DATARATE_10_HZ      = 0b0010, // 10 Hz
  LIS3DH_DATARATE_1_HZ       = 0b0001, // 1 Hz
  LIS3DH_DATARATE_POWERDOWN  = 0,
  LIS3DH_DATARATE_LOWPOWER_1K6HZ  = 0b1000,
  LIS3DH_DATARATE_LOWPOWER_5KHZ  =  0b1001,

} lis3dh_dataRate_t;

class Adafruit_LIS3DH : public Adafruit_Sensor {
 public:
  Adafruit_LIS3DH(void);
  Adafruit_LIS3DH(int8_t cspin);
  Adafruit_LIS3DH(int8_t cspin, int8_t mosipin, int8_t misopin, int8_t sckpin);

  bool       begin(uint8_t addr = LIS3DH_DEFAULT_ADDRESS);

  void read();
  uint16_t readADC(uint8_t a);

  void setRange(lis3dh_range_t range);
  lis3dh_range_t getRange(void);

  void setDataRate(lis3dh_dataRate_t dataRate);
  lis3dh_dataRate_t getDataRate(void);

  bool getEvent(sensors_event_t *event);
  void getSensor(sensor_t *sensor);

  uint8_t getOrientation(void);

  int16_t x, y, z;
  float x_g, y_g, z_g;


 private:
  uint8_t readRegister8(uint8_t reg);
  void writeRegister8(uint8_t reg, uint8_t value);
  uint8_t spixfer(uint8_t x = 0xFF);


  int32_t _sensorID;
  int8_t  _i2caddr;

  // SPI
  int8_t _cs, _mosi, _miso, _sck;
};

Lambda electron_gps

JavaScript
This is the lambda function that is called by your API Gateway - for more details, see tutorial
This is a first draft, based on a dynamoDB skeleton, providing basic STORE (create) and RETRIEVE (query) functions (for PUT and GET methods) - other functions (echo/ping) are just for test purpose
'use strict';
console.log('Loading function');

let doc = require('dynamodb-doc');
let dynamo = new doc.DynamoDB();

/**
 * Provide an event that contains the following keys:
 *
 *   - operation: one of the operations in the switch statement below
 *   - tableName: required for operations that interact with DynamoDB
 *   - payload: a parameter to pass to the operation being performed
 */
exports.handler = (event, context, callback) => {
    console.log('Received event:', JSON.stringify(event, null, 2));

    const operation = event.operation;

    switch (operation) {
        case 'create':
            event.payload.TableName = event.tableName;
            if (event.payload.Item.item === "") {
                callback(new Error(`Missing parameters.`));
            }
            console.log(event.payload.Item.value.replace(/&quot;/g,'"'));
            event.payload.Item.value = JSON.parse(event.payload.Item.value.replace(/&quot;/g,'"'));
            event.payload.Item.timestamp = (new Date()).getTime();
            dynamo.putItem(event.payload, callback);
            break;
        case 'query':
            var payload = {
                "TableName": event.tableName,
                "KeyConditionExpression": "#item = :item and #ts >= :start",
                "ExpressionAttributeNames": {
                    "#item": "item",
                    "#ts": "timestamp"
                },
                "ExpressionAttributeValues": {
                    ":item": event.item,
                    ":start": (new Date()).getTime()-event.period*1000
                }
            };
            dynamo.query(payload, function(err, data) {
                var newData = [];
                data.Items.forEach(function(item) {
                    var ts=item.timestamp;
                    console.log(item.value);
                    if (item.value[0].t === undefined) {
                        return;
                    }
                    var lastTs = item.value[item.value.length-1].t;
                    var l=0, L=0;
                    item.value.forEach(function(value) {
                        if ((l===0 && L===0)
                            || Math.sqrt((value.l-l)*(value.l-l)+(value.L-L)*(value.L-L)) < 0.1) {
                            newData.push({
                                t: ts-lastTs+value.t,
                                l: value.l,
                                L: value.L
                            });
                        }
                    });
                });
                callback(err,newData);
            });
            break;
        case 'echo':
            callback(null, event.payload);
            break;
        case 'ping':
            callback(null, 'pong');
            break;
        default:
            callback(new Error(`Unrecognized operation "${operation}"`));
    }
};

GPSTrack payload mapping template

JSON
This is used by the API Gateway to format the payload with data to graph on the map
#set($inputRoot = $input.path('$'))
[
#foreach($elem in $inputRoot)
 {
  "t" : $elem.t,
  "l" : $elem.l,
  "L" : $elem.L
} 
#if($foreach.hasNext),#end
#end
]

DynamoDB table definition

Markdown
This are the table name and primary partition and sort keys the table needs to have. All other fields are created on the fly when data is added
*Table name*	| electron
*Primary partition key* |	item | (String)
*Primary sort key* |	timestamp | (Number)

ParticleEvent - Model for API Gateway

JSON
This model is used by the API Gateway to parse data coming from Particle's webhook
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "ParticleEvent",
  "type": "object",
  "properties": {
    "data": { "type": "string" },
    "ttl": { "type": "string" },
    "published_at": { "type": "string" },
    "coreid": { "type": "string" },
    "event": { "type": "string" }
  }
}

index.html

HTML
Tracking web page
<!DOCTYPE html>
<html>
  <head>
    <title>Where's My Bike</title>
    <meta name="viewport" content="initial-scale=1.0">
    <meta charset="utf-8">
    <style>
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
      #map {
        height: 100%;
      }
    </style>
    <script src="bower_components/jquery/dist/jquery.min.js"></script>
  </head>
  <body>
    <div id="map"></div>
    <script>
      var map;
      var API = '***your_api_gateway_url_here***';
      var API_KEY = '***your_api_gateway_key_here***';
      var GOOGLE_API_KEY = '***your_google_api_key_here***';
      var track = [];
      var paths = [];

        function animateCircle(line) {
            var count = 0;
            window.setInterval(function() {
              count = (count + 1) % 200;

              var icons = line.get('icons');
              icons[0].offset = (count / 2) + '%';
              line.set('icons', icons);
          }, 500);
        }

        $.ajax(API, {
            method: 'GET',
            data: {
                p: 24*3600,
                t: 'G'
            },
            headers:{
                'x-api-key': API_KEY
            },
            success: drawPaths
        });

        function drawPaths(data, status, jqXHR) {
            var latlng;
            var ts = 0;
            var avglatlng = {lat:0,lng:0};
            var points = [];
            var pointCount = 0;

            var path;
            var color=0;
            var colorString;
            var lineSymbol;

            data.forEach(function(coord) {
                colorString = getColor(color);
                if (coord.t-ts>30*60*1000 && ts!==0) {
                    track.push(points);
                    lineSymbol = {
                        path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
                        scale: 5,
                        strokeColor: colorString,
                        strokeWeight: 2,
                        strokeOpacity: 1.0,
                        fillColor: 'white',
                        fillOpacity: 1.0
                    };
                    path = new google.maps.Polyline({
                        path: points,
                        geodesic: true,
                        strokeColor: colorString,
                        strokeOpacity: 1.0,
                        strokeWeight: 4,
                        icons: [{
                          icon: lineSymbol,
                          offset: '100%'
                        }]
                      });
                    if (points.length>5) {
                        animateCircle(path);
                        path.setMap(map);
    //                    runSnapToRoad(path.getPath());
                        paths.push(path);
                        color++;
                    }
                    points = [];
                    // keep last point for continuous line
                    points.push(latlng);

                }
                ts = coord.t;
                latlng = {
                        lat: coord.l,
                        lng: coord.L
                };
                avglatlng.lat += coord.l;
                avglatlng.lng += coord.L;
                points.push(latlng);
                pointCount++;
                var marker = new google.maps.Marker({
                    position: latlng,
                    title: (new Date(coord.t)).toTimeString(),
                    map: map,
                    icon: {
                        path: google.maps.SymbolPath.CIRCLE,
                        scale: 2,
                        strokeWeight: 1,
                        strokeColor: 'white',
                        strokeOpacity: 1.0,
                        fillColor: 'black',
                        fillOpacity: 1.0,
                        zIndex:9
                    }
                  });
            });
            colorString = getColor(color);

            if (points.length>0) {
                track.push(points);

                lineSymbol = {
                    path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
                    strokeColor: colorString,
                    scale: 5,
                    strokeWeight: 2,
                    strokeOpacity: 1.0,
                    fillColor: 'white',
                    fillOpacity: 1.0
                };

                path = new google.maps.Polyline({
                    path: points,
                    geodesic: true,
                    strokeColor: colorString,
                    strokeOpacity: 1.0,
                    strokeWeight: 4,
                    icons: [{
                      icon: lineSymbol,
                      offset: '100%'
                    }]
                  });
                animateCircle(path);
                path.setMap(map);
//                    runSnapToRoad(path.getPath());
                paths.push(path);
            }
            if (pointCount>0) {
                avglatlng.lat /= pointCount;
                avglatlng.lng /= pointCount;
                map.setCenter(avglatlng);
            }
            // last known points
            var marker = new google.maps.Marker({
                position: latlng,
                title: new Date(ts).toTimeString(),
                map: map,
                icon: {
                    path: google.maps.SymbolPath.CIRCLE,
                    scale: 5,
                    strokeColor: 'red',
                    zIndex:10
                }
              });
        }

        function getColor(color) {
            var initColor = 0xff1e00;
            return '#'+new String("000000"+(Math.random(0x1000000)).toString(16)).substr(-6,6);
        }
        
        function runSnapToRoad(path) {
          var pathValues = [];
          for (var i = 0; i < path.getLength(); i++) {
            pathValues.push(path.getAt(i).toUrlValue());

            if (pathValues.length>90) {
                $.get('https://roads.googleapis.com/v1/snapToRoads', {
                interpolate: true,
                key: GOOGLE_API_KEY,
                path: pathValues.join('|')
              }, function(data) {
                processSnapToRoadResponse(data);
                drawSnappedPolyline();
              });
              pathValues = [];
            }
          }
          if (pathValues.length>0) {
            $.get('https://roads.googleapis.com/v1/snapToRoads', {
                interpolate: true,
                key: GOOGLE_API_KEY,
                path: pathValues.join('|')
              }, function(data) {
                processSnapToRoadResponse(data);
                drawSnappedPolyline();
              });
          }
        }

        // Store snapped polyline returned by the snap-to-road method.
        function processSnapToRoadResponse(data) {
          snappedCoordinates = [];
          for (var i = 0; i < data.snappedPoints.length; i++) {
            var latlng = new google.maps.LatLng(
                data.snappedPoints[i].location.latitude,
                data.snappedPoints[i].location.longitude);
            snappedCoordinates.push(latlng);
          }
        }

        // Draws the snapped polyline (after processing snap-to-road response).
        function drawSnappedPolyline() {
          var snappedPolyline = new google.maps.Polyline({
            path: snappedCoordinates,
            strokeColor: 'yellow',
            strokeWeight: 6,
            strokeOpacity: 1.0
          });

          snappedPolyline.setMap(map);
        }

      function initMap() {
        map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: 49.21508, lng: -123.11061},
          zoom: 13
        });
      }
    </script>
    <script src="https://maps.googleapis.com/maps/api/js?key=***your_google_api_key_here***&callback=initMap"
    async defer></script>
  </body>
</html>

Credits

Phil Hilger

Phil Hilger

1 project • 6 followers
Contact

Comments