DIY Wireless GPS tracker

The features of this project

So this is what I accomplished so far

  • GPS C/A code tracking
  • Stores trackpoints every 10 seconds
  • Remote control options:
  •   Download GPS data from flash
  •   Real-time monitoring
  •   Status flash memory receiver
  • Three indicator LEDs to help the user
  • Operates for about 20 hours on a full battery
  • Includes software for moteino R4 board
  • matlab m file to generate KML for google Earth
  • works great for vehicle tracking

Hardware

We have the following shipping list:

  • Moteino R4 board with 4mb flash chip, no USB required
  • Adafruit ultimate GPS V3 board
  • 5 resistors, 1 NTC, 3 LEDs, Hammond box, Velleman PCB,
  • switch, 4 aaa NiMh penlights, 5 mm plug

Schematic

download
Schematic GPS wireless tracker

 

The design is pretty straightforward, the moteino controls the Adafruit GPS board from which I use the serial output, and the pps (the pulse per second line). In addition there are three LEDs on the board, a thermistor and a voltage divider (I log the temperature and battery voltage). The three control LEDs do this:

  • Green flashing LED (on the left for the user) means that there are GPS NMEA records in the GPS output stream, and here there are enough satellites in view to determine a position
  • Yellow LED to indicate that there is either data stored in flash (in this case it briefly blinks every 10 seconds when data is stored) or to indicate that there is no valid GPS data to store, in the latter case the yellow LED is continuously on. This happens when the green led is off and there is no GPS reception, or when the receiver is booting from a cold start without any satellite ephemeris and clock parameters. This can take up to thirty minutes when you are not lucky, but normally there is at least something stored in the receiver so that a fast start (better than 30 seconds) is possible. The more you use the GPS the better. A slow start can also mean that the cr1220 cell under the Adafruit board is empty, they run for like a month or six to a year.
  • Red LED to briefly flash when there is contact with the gateway, so to indicate handshaking. While data is downloaded this LED is permanently on. The download takes about 5 minutes for all data in flash, not super fast but good enough for what we are doing.
  • During startup the LEDs briefly cycle to show that the unit is starting

Mounted in Hammond box

gpsrfm_box gpsrfm_open

Examples of recorded tracks

So this is what you get after downloading the data at the gateway and processing the text in a matlab script. That output is converted to Google Earth. The waypoints are made by the matlab script when a GPS track starts or stops. trip_141026_141101Another examples (it is a few weeks worth of data between home, work and offsite places). NovemberTracks

Receiver code, this is what I have for the moteino board.

//
// GPS logger via RFM
//
// updates: 
// yellow LED should blink when we are in acquisition
// only valid data should be logged, thus fix is on, minimally 4 sats, lat lon not equal to zero
//
#include <RFM69.h>               // radio library
#include <SPI.h>                 // we need SPI
#include <SPIFlash.h>            // maybe we need also flash
#include <Adafruit_GPS.h>        // GPS receiver
#include <SoftwareSerial.h>      // GPS needs software serial
#define GPSECHO false

#define GATEWAY     1
#define NODEID      90   // ID of this station
#define NETWORKID   50
#define FREQUENCY   RF69_868MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define KEY         "thisIsEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!

RFM69 radio;
bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network
SPIFlash flash(8, 0xEF30);

// Hardware model 

const int gpspps = 5;   // PPS pin on GPS receiver
const int txpin  = 4;   // TX pin on GPS receiver
const int rxpin  = 3;   // RX pin on GPS receiver
const int led    = 9;   // moteino led
const int ledr   = 6;   // red led, used for the one second pips
const int ledy   = 7;   // yellow led
const int ledg   = led; // green led, one when there was recent communication
const int batpin = A0;  // this pin reads the battery voltage as 10k/(10k+33k) * Vbattery
const int ntcpin = A1;  // this pin reads the voltage over the NTC as (NTC/(NTC+10K)) * 3.3Volt

//int rstatus = 0;  
//int gstatus = 0;
//int ystatus = 0;
//unsigned long int rtimer = 0;
//unsigned long int gtimer = 0;
//unsigned long int ytimer = 0;

// other constants
const int TimeZone = 2;                // assume UTC + 2 hours, 5-5-2014
const int c2000 = 2000;                // the year 2000
const long c86400 = 86400;             // seconds in a day
const long c3600 = 3600;               // seconds in an hour
const int amaand[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
const int omaand[] = { 0,31,59,90,120,151,181,212,243,273,304,334 };

// start the software serial interface to the GPS receiver
SoftwareSerial mySerial(txpin, rxpin); 
Adafruit_GPS GPS(&mySerial);
boolean usingInterrupt = false;
void useInterrupt(boolean); 

long mjdnum,secnum;              // mjd and seconds
short temperature;               // raw temperature in 0.1 C
long pressure;                   // raw pressure in hpa
int csec,psec;                   // NMEA sentence counter
int cpps,ppps;                   // GPS PPS counter
int cpin,ppin;                   // current and previous pin value
int screen,pscreen;              // what screen are we displaying
unsigned long int done;          // timer variable

int year,month,day,hour,minute,second,r1,r2,milliseconds;

const int n60 = 60;
char topstr[n60];
unsigned long int tttt1;  // updated when there is a pip or a raw
long major1,major2,minor1,minor2;

unsigned long ctimer,lastack,ptimer,ptimer2;
boolean DEBUG = false;
boolean USELED = true;

struct GPSDataPackage {  // everything that comes out of the GPS RFM device
  byte yr; // year
  byte mo; // month
  byte dy; // day
  byte hh; // hours
  byte mm; // minutes
  byte ss; // seconds
  int msec; // milliseconds;
  int latmaj; long latmin; // latitude,  remainder
  int lonmaj; long lonmin; // longitude, remainder
  int hgt1; int hgt2; // height in meters, remainder in decimeters
  byte dop;   // hdop value
  int velmaj; int velmin;   // velocity
  int crsmaj; int crsmin;   // course
  byte sats;  // number of satellites
  byte fix;   // whether there is a fix
  byte qua;   // quality indicator
  unsigned long int systemtime; // our system time in milli seconds
  byte stat;  // in what part of the loop are we? 0=init 1=RAW 2=PIP
  int bat;    // ntc voltage
  int ntc;    // battery voltage
};
GPSDataPackage data;

//----------------------------------------------------------------------------------------------------------

// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
#ifdef UDR0
  if (GPSECHO)
    if (c) UDR0 = c;  
    // writing direct to UDR0 is much much faster than Serial.print 
    // but only one character can be written at a time. 
#endif
}

//----------------------------------------------------------------------------------------------------------

void useInterrupt(boolean v) {
  if (v) {
    // Timer0 is already used for millis() - we'll just interrupt somewhere
    // in the middle and call the "Compare A" function above
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    // do not call the interrupt function COMPA anymore
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

//----------------------------------------------------------------------------------------------------------

long DayNum( long year, long month, long day ) 
{
   //
   // are we in a leap year?
   //
   int leap = 0;
   if ( ((year % 4) == 0) & ( ((year % 100) != 0) | ((year % 400) == 0) ) ) leap = 1;
   //Serial.println(leap);
   //
   // what is the day of year number
   //
   long doy = day + omaand[month-1];   
   if ( (leap == 1) & (month > 2) ) doy = doy + 1;
   //Serial.println(doy);
   //
   // what is the year since 1584 number
   //
   long t1 = year * 365;
   long t2 = year / 400;
   long t3 = year / 100;
   long t4 = year / 4;
   long yday = t1 + t2 - t3 + t4;
   if (leap == 1) yday = yday - 1;
   yday = yday - 578543;                        
   long iday = doy - 1 + yday;       // day number at start of the year;
   //
   // convert into the more often used MJD number
   // 
   long mjd = iday - 100397;
   //
   // return the answer
   //
   return mjd;
}

//----------------------------------------------------------------------------------------------------------

long DayNumber ( int jaar, int maand, int dag ) 
{
   long year = c2000; year = year + long(jaar);
   long month = maand;
   long day = dag;
   if ((jaar != -1) && (month != -1) && (day != -1)) {
     long mjd = DayNum( year, month, day );
     return mjd;
   } else {
     return -1;
   }
}

//----------------------------------------------------------------------------------------------------------

long DaySecond( int uur, int minuut, int seconde ) 
{
   long hh = uur;
   long mm = minuut;
   long ss = seconde;
   if ((hh != -1) && (mm != -1) && (seconde >= 0) && (seconde <= 59)) {
     long ds;
     ds = hh * c3600;
     ds += mm * 60;
     ds += ss;
     return ds;
   } else {
     return -1;
   } 
}

//----------------------------------------------------------------------------------------------------------

class Calendar
{
   private:
     int year;
     int month;
     int day;
     long lastmjd;
     long year0; long off0;
     long year1; long off1;
     long yearoffset;
   private:
     int leapyear() {
       int leap = 0;
       if ( ((year % 4) == 0) & ( ((year % 100) != 0) | ((year % 400) == 0) ) ) leap = 1;
       return leap;
     }
   public:
     Calendar() { 
       year = 0; 
       month = 0; 
       day = 0; 
       lastmjd = 0; 
       year0 = 1950;
       year1 = 2050;
       off0 = DayNum( year0, 1, 1);
       off1 = DayNum( year1, 1, 1);
       yearoffset = 0;
     }
     void Decode( long input ) {
       if (input == lastmjd) return;
       long guessyear = (input - off0) / 365; guessyear = guessyear + year0; year = guessyear;
       int ndays = 365;
       if (leapyear() == 1) ndays = 366;
       int ok = 0;
       long Delta;
       while (ok == 0) {
         yearoffset = DayNum( guessyear, 1, 1 );
         Delta = input - yearoffset;
         if (Delta < 0) guessyear = guessyear - 1;
         if (Delta > ndays) guessyear = guessyear + 1;
         if ((Delta >=0) && (Delta <= ndays)) ok = 1;
         year = guessyear;
         ndays = 365;
         if (leapyear() == 1) ndays = 366;         
       }
       month = 1; 
       day = 1;
       for (int mmm=1; mmm<=12; mmm++) {
          int daysinmonth = amaand[mmm-1];
          int monthoffset = omaand[mmm-1];
          if ( (ndays == 366) && (mmm == 2) ) daysinmonth = daysinmonth + 1;
          if ( (ndays == 366) && (mmm > 2)  ) monthoffset = monthoffset + 1;
          int DD = (Delta - monthoffset);
          if ((DD < daysinmonth) && (DD >= 0)) { month = mmm; day = DD + 1; }
       }
       // Serial.print(input); Serial.print(" ");  Serial.print(guessyear); Serial.print(" ");  Serial.print(Delta); Serial.print(" ");   
       // Serial.print(year); Serial.print(" ");  Serial.print(month); Serial.print(" ");  Serial.println(day);
       lastmjd = input;
     }
     int Year() { return year; }
     int Month() { return month; }
     int Day() { return day; }
};

//----------------------------------------------------------------------------------------------------------

void d2i( double input, double factor, long &major, long &minor ) 
{
  long sign = 1; if (input < 0) sign = -1;
  input = fabs(input); // use fabs here, and not abs
  long leading = long(input);
  double remain = input; remain = remain - double(leading); 
  remain = remain * factor;
  long remaining = long(remain);
  major = sign*leading; 
  minor = remaining;
}  

//---------------------------------------------------------------------------------------------------------

long min2dec( long minor ) {
  double minute = minor;
  minute = minute / 100.0;
  minute = minute / 60.0;
  minute = minute * 10000.0;
  return long(minute);
}

//---------------------------------------------------------------------------------------------------------

Calendar Engine;  // calendar converter

//---------------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------
//
// The logger stores its data in flash, here is a class to access it, the 
// help function is adapted relative to its source on the gateway.
//
//--------------------------------------------------------------------------

class Logger {
  private:
    long int counter;  // this is where the address pointer is in flash memory
    long int offset;   // this is the offset of the latest string
    long int flashsize;
    long int maxlogsize;  // look down the code
    long int currblock;   // the memory block we are currently writing in
    boolean showdata;
    boolean showradio;
    long firstbyte;
    long lastbyte;
    long numrec;
  public:
    //
    // The constructor only sets the most important variables
    //
    Logger() {
      showdata = false;
      flashsize = 4L * 1024L * 1024L / 8L;
      maxlogsize = 128; 
      counter = 0;
      offset = 0;
      currblock = -1;
      showradio = false;
    }
    //
    long Synchronize() {
      Serial.println("% Synchronize");
      byte blurp=0; byte blurp2=0;
      long k=0; long block = 0;
      counter = k;
      offset = counter;
      blurp = flash.readByte(k); 
      while (blurp != 0xFF) {    // you only need to check 4Kb offsets, begin and end byte should <> erased to increase
        if (flash.readByte(k+4095L) == 0xFF) {
          int el=0; int ll=0;
          for (el=1; el<4096; el++) {
            if ((flash.readByte(k + long(el)) == 0xFF) && (ll == 0)) ll = el;
          } 
          counter = k + long(ll);
          Serial.print("% counter ="); Serial.println(counter);
          currblock = counter / 4096L;
          offset = counter; 
          return counter;
        }
        counter = k;
        currblock = counter / 4096L;
        offset = counter;
        k = k + 4096L;
        if (k >= flashsize) break;
        blurp = flash.readByte(k);       
      }   
      if (k >= flashsize) {
        counter = 0;
        offset = 0;
        currblock = -1; // this also means that it is erased
      }
      return counter;
    }
    //
    // Status
    //
    int Status() {
      // read the analog values for the Battery and the NTC:
      int BatValue = analogRead(batpin);   
      int NTCValue = analogRead(ntcpin);       
      Serial.println(F("% Logger status:"));
      Serial.print(F("% counter    = ")); Serial.println(counter);
      Serial.print(F("% offset     = ")); Serial.println(offset);
      Serial.print(F("% flashsize  = ")); Serial.println(flashsize);
      Serial.print(F("% maxlogsize = ")); Serial.println(maxlogsize);
      Serial.print(F("% currblock  = ")); Serial.println(currblock);
      Serial.print(F("% echo       = ")); Serial.println(showdata);
      Serial.print(F("% radio      = ")); Serial.println(showradio);   
      Serial.print(F("% bat        = ")); Serial.println(BatValue);
      Serial.print(F("% ntc        = ")); Serial.println(NTCValue);       
      return 0;
    }
    //
    // Status
    //
    int StatusByRadio() {
      // read the analog values for the Battery and the NTC:
      int BatValue = analogRead(batpin); 
      int NTCValue = analogRead(ntcpin); 
      digitalWrite(ledr,HIGH);
      sprintf(topstr,"%% Logger status:");                radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));
      sprintf(topstr,"%% counter    = %ld",counter);      radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));
      sprintf(topstr,"%% offset     = %ld",offset);       radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));
      sprintf(topstr,"%% flashsize  = %ld",flashsize);    radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));
      sprintf(topstr,"%% maxlogsize = %ld",maxlogsize);   radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));
      sprintf(topstr,"%% currblock  = %ld",currblock);    radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));
      sprintf(topstr,"%% echo       = %d",showdata);      radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));
      sprintf(topstr,"%% radio      = %d",showradio);     radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));  
      sprintf(topstr,"%% bat        = %d",BatValue);      radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));
      sprintf(topstr,"%% ntc        = %d",NTCValue);      radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));      
      digitalWrite(ledr,LOW);
      return 0;
    }    
    //
    // Status
    //
    int RecordStatus() {
      Serial.println(F("% "));
      List(false);
      Serial.print(F("% Last record        : ")); Serial.println(firstbyte);
      Serial.print(F("% First record       : ")); Serial.println(lastbyte);
      Serial.print(F("% Number of records  : ")); Serial.println(numrec);
      Serial.println(F("%  ")); 
      Serial.print(F("% Begin: ")); DisplayRecord(firstbyte,1);
      Serial.print(F("% End  : ")); DisplayRecord(lastbyte,-1);
      Serial.println(F("%  ")); 
      return 0;
    }    
    //
    // DisplayRecord
    //
    int DisplayRecord( long adr, int direct ) {    
      long k=adr; long kk;
      byte blurp = 0x01;
      while (blurp != 0x00)  {
        if (blurp == 0xFF) break;
        kk = k;
        if (direct < 0) k = k - 1; 
        if (direct > 0) k = k + 1;
        if (k<0) k = k + flashsize;
        if (k>=flashsize) k = k - flashsize;
        blurp = flash.readByte(k); 
        if (direct > 0) {
          if (blurp != 0xFF) Serial.print(char(blurp));
          if (blurp == 0x00) { Serial.println(); return 0; }
        }
      }
      // Serial.print(" * ");
      k = kk; blurp = flash.readByte(k);
      while (blurp != 0x00) {
        Serial.print(char(blurp));
        k++; if (k==flashsize) k=0;
        blurp = flash.readByte(k);
      }
      Serial.println();
    }
    //
    // Erase the flash chip, we erase everything up to the end, and then we take a 200 byte offset to 
    // see what the rollover brings for us 
    //
    int Erase() {
      flash.chipErase();
      while(flash.busy());
      // counter = 128L * 4096L; counter = counter - 200L;
      counter = 0;
      offset = counter;
      currblock = 0;
      return 0;
    }
    //
    // store a string of a given length, the advance the counter
    //
    long WriteString( char *string, int len ) {
      if (len > 0) {
        byte blurp;
        int k; 
        long cc = counter; 
        TestErase(len);
        for (k=0; k<len; k++) {
          blurp = string[k];
          flash.writeByte(counter++,blurp);  // then write 
          counter = counter % flashsize;
        }
      }
      return counter;
    }
    //
    // close the string
    //
    long CloseString( ) {
      byte blurp;
      TestErase(1); 
      blurp = 0x00; flash.writeByte(counter++,blurp);
      return counter;
    }
    //
    // list everything that is in the flash memory
    //
    int List( boolean disp ) {
      if (showradio) digitalWrite(ledr,HIGH);     
      if (disp) Serial.println(F("% List data"));
      byte blurp; int ns; ns = 0; char str[n60];
      long k=0; 
      firstbyte = -1;
      lastbyte = -1;
      numrec = 0;
      while (k < flashsize) {
        long cc = counter + k;
        cc = cc % flashsize;
        blurp = flash.readByte(cc);
        if (blurp != 0xff) { // then skip this byte
          if (blurp != 0x00) {
            if (disp) Serial.print(char(blurp)); 
            if (showradio) {
              if (ns < n60-1) str[ns] = char(blurp); 
            }
            ns++;
          } else {
            if (disp) { Serial.print(" ["); Serial.print(ns); Serial.print("]"); } 
            if (disp) Serial.println(); 
            if (showradio) {
              str[ns++] = 0;
              radio.sendWithRetry(GATEWAY, str, sizeof(str)); // delay(1);
            }            
            if (firstbyte == -1) firstbyte = cc;
            lastbyte = cc;
            numrec++;
            ns = 0;            
          }
        } 
        if ((blurp == 0xff) && ((cc % 4096L) == 0)) {
          k += 4096L;
        } else {
          k++;
        }
      }
      if (disp) Serial.println(F("% ok")); 
      if (showradio) digitalWrite(ledr,LOW);
      return 0;
    }
    //
    // list everything that is in the flash memory
    //
    int Write( char *string, int code ) {   
      int len = strlen(string);
      if (len > 0) {
        if ((code >= 0) && (code <= 1)) {
          if (showdata) Serial.print(string);
          WriteString(string,len);
        }
        if (code == 1) {
          if (showdata) Serial.println();
          CloseString();
        }
      }
      return 0;
    }
    //
    // list everything that is in the flash memory
    //
    int Help() {
      Serial.println(F("% Help:"));
      Serial.println(F("% "));
      Serial.println(F("%   d: hex      -- dump of all non-zero blocks (do you really want that?)"));    
      Serial.println(F("%   e: echo     -- toggel echo status")); 
      Serial.println(F("%   h: help     -- print this text")); 
      Serial.println(F("%   l: list     -- list everything that we have stored in flash"));
      Serial.println(F("%   r: recstat  -- list the oldest and the latest record in flash"));
      Serial.println(F("%   s: status   -- what does the flash administration say")); 
      Serial.println(F("%   x: erase    -- wipe out flash memory (no warning, it simply does it)"));  
      Serial.println(F("%   z: erase    -- toggle radio status"));       
      Serial.println(F("% "));
      Serial.println(F("% End of help"));
      return 0;
    }
    //
    // list everything that is in the flash memory
    //
    int Dump() {
      long k=0;
      while (k < flashsize) {
        byte blurp = flash.readByte(k);
        if (blurp != 0xFF) {
          Serial.print("Block "); Serial.print(k/4096L); Serial.print("/"); Serial.println(k);
          for (int l=0; l<4096; l++) {
            long kl = k + long(l);
            byte blurp = flash.readByte(kl);
            if (blurp < 0x10) Serial.print(0);
            Serial.print(blurp,HEX); Serial.print(".");
            if ( ((l+1) % 64) == 0 ) Serial.println();
          }
          Serial.println();
        }
        k = k + 4096L;
      }
      Serial.println(F("% End of dump"));
      return 0;       
    }  
    //
    // Tests whether a 4Kb block should be erased or not
    //    
    int TestErase( int len ) {
      long blockstart = counter / 4096L;             blockstart = blockstart % 128L;
      long blockend = (counter + long(len))/4096L;   blockend   = blockend % 128L;
      if ((currblock == blockstart) && (blockend == blockstart)) {
        return 0; // we are in the current block, and that is ok
      } 
      //Serial.print("% Start/stop = "); Serial.print(blockstart); Serial.print("/"); Serial.println(blockend); 
      if ((currblock == blockstart) && (blockend != blockstart)) {
         long cc = blockend * 4096L;
         //Serial.print("Erase block at "); Serial.println(cc);
         flash.blockErase4K(cc);
         while(flash.busy());
         currblock = blockend;
      } else {
        if (currblock != blockstart) {
           long cc = blockstart * 4096L;
           //Serial.print("Erase block at "); Serial.println(cc);
           flash.blockErase4K(cc);
           while(flash.busy());
           currblock = blockstart;
        }
      }
      return 0;
    } 
    //
    // toggle echo on or off
    //  
    int SetEcho() {
      if (showdata) {
        Serial.println(F("% echo data off"));
        showdata = false;
      } else {
        Serial.println(F("% echo data on"));
        showdata = true;
      }
    } 
    boolean ShowEcho() { return showdata; }
    int SetRadio() {
      if (showradio) {
        Serial.println(F("% radio off"));
        showradio = false;
      } else {
        Serial.println(F("% radio on"));
        showradio = true;
      }
    }     
    boolean ShowRadio() { return showradio; }
};
Logger logger;

//----------------------------------------------------------------------------------------------------------------------

void ShowStuff( char *opt ) 
{
  if (strcmp(opt,"INI") == 0) { data.stat = 0; digitalWrite(ledy,HIGH); }
  if (strcmp(opt,"RAW") == 0) data.stat = 1;
  if (strcmp(opt,"PIP") == 0) data.stat = 2;  
  //
  //
  data.systemtime = millis();
  ctimer = data.systemtime;
  //Serial.print(ctimer); Serial.print(" "); Serial.println(ptimer); 
  if (ptimer > ctimer) ptimer = ctimer;
  if (ptimer2 > ctimer) ptimer2 = ctimer;
  //
  data.bat = analogRead(batpin);
  data.ntc = analogRead(ntcpin);
  if ((ctimer - ptimer2) > 60000) {    
    float factor; factor = (33.0+10.0)/10.0; 
    factor = factor*3.3; 
    float voltage = float(data.bat); 
    voltage = voltage * factor;
    int ivolt = voltage;
    //Serial.print(F("% Battery=")); Serial.print(ivolt);
    //Serial.print(F(" Temperature=")); Serial.println(data.ntc);
    sprintf(topstr,"%% SYS %2.2d%2.2d%2.2d%2.2d%2.2d%2.2d %d %d",data.yr,data.mo,data.dy,data.hh,data.mm,data.ss,ivolt,data.ntc); 
    logger.Write(topstr,1);
    ptimer2 = ctimer;
  }  
  //
  if ((ctimer - ptimer) > 9500) {
    //
    // Date and Time
    //
    year = GPS.year; 
    month = GPS.month;
    day = GPS.day;
    hour = GPS.hour;
    minute = GPS.minute;
    second = GPS.seconds;
    milliseconds = GPS.milliseconds;
    if (year > c2000) year = year - c2000;
    mjdnum = DayNumber( year,month,day );
    secnum = DaySecond( hour,minute,second );
    secnum = secnum + c3600 * long(TimeZone);    
    if (strcmp(opt,"PIP") == 0) secnum++;  
    if (secnum > c86400) { mjdnum++; secnum = secnum - c86400;  }
    Engine.Decode( mjdnum );
    year = Engine.Year();
    month = Engine.Month();
    day = Engine.Day();
    hour = secnum / c3600; 
    r1 = secnum - hour * c3600; 
    minute = r1 / 60; 
    r2 = r1 - minute * 60; 
    second = r2; 
    data.yr = year-c2000; data.mo = month;  data.dy = day;  
    data.hh = hour;  data.mm = minute;  data.ss = second; data.msec = milliseconds;
    // 
    // quality information
    // 
    int fix = int(GPS.fix);  
    int qua = int(GPS.fixquality); 
    int sat = int(GPS.satellites);
    data.sats = sat; data.fix = fix; data.qua = qua;
    //
    // position information
    //
    data.latmaj = 0; data.lonmaj = 0; 
    data.latmin = 0; data.lonmin = 0; 
    data.hgt1 = 0; data.hgt2 = 0; 
    data.dop = 0; 
    data.velmaj = 0; data.velmin = 0; 
    data.crsmaj = 0; data.crsmin = 0; 
    //
    double hdop = double(GPS.HDOP);   
    double gpslat = double(GPS.latitude);  gpslat = gpslat / 100.0;
    double gpslon = double(GPS.longitude);   gpslon = gpslon / 100.0;
    d2i(gpslat,100000.0,major1,minor1); 
    d2i(gpslon,100000.0,major2,minor2);  
    if (GPS.lat == 'S') major1 = -major1;
    if (GPS.lat == 'W') major2 = -major2;
    data.latmaj = int(major1); data.latmin = minor1;
    data.lonmaj = int(major2); data.lonmin = minor2; 
    long velmaj,velmin; 
    long crsmaj,crsmin; 
    d2i(double(GPS.speed*1.852),10.0,velmaj,velmin);
    d2i(double(GPS.angle),10.0,crsmaj,crsmin);  
    data.velmaj = int(velmaj); data.velmin = int(velmin); 
    data.crsmaj = int(crsmaj); data.crsmin = int(crsmin); 
    data.dop = byte(hdop*10.0);
    long int major3,minor3;
    d2i(double(GPS.altitude),10.0,major3,minor3);
    data.hgt1 = int(major3);
    data.hgt2 = int(minor3);
    //
    // print all that we have
    //
    //
    // do we have valid data?
    // 
    boolean validdata = false;
    if (fix == 1) {
      if (sat > 2) {
        if ((gpslat != 0.0) && (gpslon != 0.0)) {
          validdata = true;
        }
      }
    }  
    if (validdata) {
      digitalWrite(ledy,HIGH);
      sprintf(topstr,"%2.2d%2.2d%2.2d%2.2d%2.2d%2.2d ",data.yr,data.mo,data.dy,data.hh,data.mm,data.ss); logger.Write(topstr,0);
      sprintf(topstr,"%1d%1d%1d%2.2d ",int(data.stat),data.fix,data.qua,data.sats); logger.Write(topstr,0);
      sprintf(topstr,"%4d.%5.5ld ",data.latmaj,data.latmin); logger.Write(topstr,0);
      sprintf(topstr,"%4d.%5.5ld ",data.lonmaj,data.lonmin); logger.Write(topstr,0);
      sprintf(topstr,"%3d ",data.velmaj); logger.Write(topstr,0);
      sprintf(topstr,"%3d ",data.crsmaj); logger.Write(topstr,0); 
      sprintf(topstr,"%5d ",data.hgt1); logger.Write(topstr,0); 
      sprintf(topstr,"%3d",data.dop); logger.Write(topstr,1);
      //
      // finally send the data package
      //
      if (logger.ShowRadio()) {
        if (radio.sendWithRetry(GATEWAY, (const void*)(&data), sizeof(data))) {;
          digitalWrite(ledr,HIGH);
          delay(5);
          digitalWrite(ledr,LOW);
        }
      }   
      delay(5);
      digitalWrite(ledy,LOW);
    } else {
      digitalWrite(ledy,HIGH);
    }
    ptimer = millis();
  }
} 

//-------------------------------------------------------------------------

void setup() 
{ 
  Serial.begin(115200);
  Serial.println(F("% GPS RFM device")); 
  //
  pinMode(ledr, OUTPUT);       // initialize the digital pin as an output.
  pinMode(ledy, OUTPUT);       // initialize the digital pin as an output.
  pinMode(ledg, OUTPUT);       // initialize the digital pin as an output.
  for (int k=0; k<3; k++) {
    digitalWrite(ledr,HIGH);  
    digitalWrite(ledy,HIGH);  
    digitalWrite(ledg,HIGH); 
    delay(200); 
    digitalWrite(ledr,LOW); 
    digitalWrite(ledy,LOW);
    digitalWrite(ledg,LOW);
    delay(100);
  }
  //
  pinMode(gpspps, INPUT);     // PPS input
  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(9600);
  //GPS.begin(57600); // werkt dus niet met deze chip
  // required for sensors
  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // show everything
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_ALLDATA);
  // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
  // the parser doesn't care about other sentences at this time
  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);   // 1 Hz update rate
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz
  // Request updates on antenna status, comment out to keep quiet
  //GPS.sendCommand(PGCMD_ANTENNA);
  // the nice thing about this code is you can have a timer0 interrupt go off
  // every 1 millisecond, and read data from the GPS for you. that makes the
  // loop code a heck of a lot easier!
  useInterrupt(true);
  // Ask for firmware version
  mySerial.println(PMTK_Q_RELEASE);     
  //
  // display the date and time in the top panel (it means that you've to wait till 
  // the first NMEA sentence is received
  // 
  int rdy = false;
  while (!rdy) {
    if (GPS.newNMEAreceived()) {
      if (GPS.parse(GPS.lastNMEA())) {
        if (GPS.month > 0) rdy = true;
      }
    }
    delay(50);
  }
  // 
  // turn on the radio
  //
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  radio.promiscuous(promiscuousMode);
  radio.encrypt(KEY);
  char buff[50];
  sprintf(buff, "%% Radio operating at %d Mhz ", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
  //char string[] = "Hi base station"; int lstring = strlen(string);
  //if (radio.sendWithRetry(GATEWAY, string, lstring)) {
  //  Serial.println("Hello string sent");
  //} else {
  //  Serial.println("No response");
  //}
  Serial.println(buff);
  //
  // what do we know about the flash chip
  //
  Serial.print(F("% ")); 
  if (flash.initialize())
    Serial.println(F("SPI Flash Init OK!"));
  else
    Serial.println(F("SPI Flash Init FAIL! (is chip present?)"));  
  Serial.print(F("% DeviceID: "));  
  word jedecid = flash.readDeviceId();
  Serial.println(jedecid, HEX);  
  //
  // And say hello to the user
  //
  logger.SetEcho();   
  logger.Synchronize();
  logger.Status();
  logger.Help();  
  logger.SetEcho();
  // 
  Serial.print(F("% Data structure size = ")); Serial.println(sizeof(data));
  ShowStuff( "INI" );
  tttt1 = millis(); ctimer = tttt1; ptimer = tttt1; ptimer2 = tttt1;
  digitalWrite(ledr,LOW);  
  digitalWrite(ledy,LOW);
  digitalWrite(ledg,LOW);
  {
    sprintf(topstr,"%% GPS receiver starting"); 
    radio.sendWithRetry(GATEWAY, topstr, sizeof(topstr));  
  }
}

//----------------------------------------------------------------------------------------------------------------------

void loop() 
{
  //timer = millis();
  //
  // GPS pre-check
  //
  // in case you are not using the interrupt above, you'll
  // need to 'hand query' the GPS, not suggested :(
  if (! usingInterrupt) {
    // read data from the GPS in the 'main loop'
    char ccc = GPS.read();
    // if you want to debug, this is a good time to do it!
    if (GPSECHO)
      if (ccc) Serial.print(ccc);
  }
  // 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
      //Serial.println("Skip NMEA sentence");
      return;  // we can fail to parse a sentence in which case we should just wait for another
    } else {
      //Serial.println("Accept NMEA sentence");    
      csec = GPS.seconds;
      if (csec != psec) {
        done = millis();  
        if (tttt1 > done) tttt1 = done; 
        if ((done - tttt1) > 900) {      
          ShowStuff("RAW");
          tttt1 = done;  // mark when we printed the last raw 
        }        
      }
      psec = csec;
    }
  }  
  //
  // handle the PPS output
  //
  cpps = digitalRead(gpspps);
  delay(1);
  if ( (cpps == 1) && (ppps == 0) ) {
    digitalWrite(ledg,HIGH); 
    done = millis();    
    if (tttt1 > done) tttt1 = done;
    if (done - tttt1 > 800) {  
      ShowStuff("PIP");    
      tttt1 = done;  // mark when we printed the last pip
    }
  }
  //
  // turn led off when needed
  //
  if ((cpps == 0) && (ppps == 1)) { 
    digitalWrite(ledg,LOW);
  }
  ppps = cpps;
  //
  // radio control
  // -------------
  //
  if (radio.receiveDone()) {
    byte theNodeID = radio.SENDERID;
    int datalen = radio.DATALEN;
    int rssi = int(radio.readRSSI());    
    if (radio.ACK_REQUESTED) {
      radio.sendACK();    
      lastack = millis();
      if (DEBUG) Serial.println("% Ack sent");
      digitalWrite(ledr,HIGH);
      delay(5);
      digitalWrite(ledr,LOW);
      delay(3);
    }
    if (theNodeID == GATEWAY) {
      if (datalen > 0) {
        int j;
        for (j=0; j<datalen; j++) topstr[j] = radio.DATA[j];
        topstr[j++] = 0;
        if (topstr[0] == '*') { 
          if (strcmp(topstr,"*HELLO")==0) {
            // Serial.println(F("% Hello received from gateway"));
            // Sure, yeah, hello back, but I got tired of these messages
          } 
          if (strcmp(topstr,"*LIST")==0) {
            //Serial.println(F("% Gateway asks to list flash memory"));
            boolean turnoff = false;
            if (logger.ShowRadio() == false) { logger.SetRadio(); turnoff = true; }
            logger.List(false);
            if (turnoff) { logger.SetRadio(); }
          }      
          if (strcmp(topstr,"*STATUS")==0) {
            //Serial.println("% Gateway asks for status");
            logger.StatusByRadio();
          }   
          if (strcmp(topstr,"*ECHO")==0) {
            //Serial.println("% Gateway toggles echo on/off");
            logger.SetEcho();   
            if (logger.ShowEcho()) { 
              char str[] = "% GPS local echo on"; 
              radio.sendWithRetry(GATEWAY, str, sizeof(str));  
            } else {
              char str[] = "% GPS local echo off"; 
              radio.sendWithRetry(GATEWAY, str, sizeof(str)); 
            }     
          }   
          if (strcmp(topstr,"*RADIO")==0) {
            //Serial.println("% Gateway toggles radio on/off");
            logger.SetRadio();     
            if (logger.ShowRadio()) { 
              char str[] = "% GPS radio on"; 
              radio.sendWithRetry(GATEWAY, str, sizeof(str));  
            } else {
              char str[] = "% GPS radio off"; 
              radio.sendWithRetry(GATEWAY, str, sizeof(str)); 
            }             
          }   
          if (strcmp(topstr,"*ERASE")==0) {
            Serial.println("% Gateway erases flash");
            logger.Erase();            
          }             
        } 
      }
    }    
  }
  //
  int nb;
  if ((nb = Serial.available()) > 0) {
    char input = Serial.read();
    if (input == 'i') {
      Serial.print(F("% DeviceID: "));
      word jedecid = flash.readDeviceId();
      Serial.println(jedecid, HEX);
    }
    if (input == 'd') { logger.Dump();  }   
    if (input == 'e') { logger.SetEcho(); } 
    if (input == 'h') { logger.Help(); }  
    if (input == 'l') { logger.List(true); }  
    if (input == 's') { logger.Status(); }    
    if (input == 'r') { logger.RecordStatus();  }    
    if (input == 'x') { logger.Erase(); }      
    if (input == 'z') { logger.SetRadio(); }     
  }
}

Code in matlab to generate KML files (use a text editor to extract the GPS output from gateway, please use matlab to reformat this stuff) It generates a file output.kml that you open in google earth.

clear;
data = load(‘gps_141122.txt’);
%
date0 = datenum(2014,11,02,00,0,0);
date1 = datenum(2014,11,23,00,0,0);
%
ymd = data(:,1);
f1 = 10000000000;
f2 = 100000000;
f3 = 1000000;
f4 = 10000;
f5 = 100;
year = floor(ymd/f1);
rest = ymd – year*f1;
month = floor(rest/f2);
day = ymd – year*f1 – month*f2; day = floor(day / f3);
hms = ymd – year*f1 – month*f2 – day*f3;
hour = floor(hms/f4);
rest = hms – hour*f4;
minute = floor(rest/f5);
second = hms – hour*f4 – minute*f5;
datnum = datenum( year+2000,month,day,hour,minute,second );
idx = find( (date0 <= datnum) & (datnum <= date1) );

lat = data(idx,3); x = lat – floor(lat); x = x * 10000; x = x / 6000; xx = floor(lat) + x; lat = xx;
lon = data(idx,4); y = lon – floor(lon); y = y * 10000; y = y / 6000; yy = floor(lon) + y; lon = yy;
hgt = data(idx,7);
dates = datnum(idx);

n = size(hgt,1);
ids = (1:n)’;
fp = fopen(‘output.kml’,’w’);

% question one, where are the waypoints

% Find common points then
cmotion = zeros(n,1);
smotion = zeros(n,1);
status = 0;
counter = 0;
for i=2:n,
dlat = lat(i) – lat(i-1);
dlon = lon(i) – lon(i-1);
dy = dlat * 111.111e3;
dx = dlon * 111.111e3 * cos(lat(i)/180*pi);
distance = sqrt(dx*dx + dy*dy);
if (distance < 15),
% fprintf(‘%% no motion %f %f %d\n’,lon(i-1),lat(i-1),i-1);
if (status == 1),
counter = counter + 1;
end
status = 0;
smotion(i-1) = status;
cmotion(i-1) = counter;
else
if (status == 0),
% fprintf(‘%% no motion %f %f %d\n’,lon(i-1),lat(i-1),i-1);
cmotion(i-1) = counter;
smotion(i-1) = status;
counter = counter + 1;
end
if (status == 1),
% fprintf(‘%% motion %f %f %d\n’,lon(i-1),lat(i-1),i-1);
cmotion(i-1) = counter;
smotion(i-1) = status;
end
status = 1;
end
end
if (distance < 15),
if (status == 1),
counter = counter + 1;
end
else
if (status == 0),
counter = counter + 1;
end
end
if (status == 0),
% fprintf(‘%% no motion %f %f %d\n’,lon(n),lat(n),n);
cmotion(n) = counter;
smotion(n) = status;
end
if (status == 1),
% fprintf(‘%% motion %f %f %d\n’,lon(n),lat(n),n);
cmotion(n) = counter;
smotion(n) = status;
end
%
% all odd cmotion values should be the waypoints
%
cmin = min(cmotion);
cmax = max(cmotion);
nwp = 0;
for ipw = cmin:2:cmax,
idx = find(cmotion == ipw);
nwp = nwp + 1;
meanlat(nwp) = mean(lat(idx));
meanlon(nwp) = mean(lon(idx));
meanhgt(nwp) = mean(hgt(idx));
meandate(nwp) = mean(dates(idx));
end
meanlat = meanlat’;
meanlon = meanlon’;
meanhgt = meanhgt’;
meandate = meandate’;
meandatevec = floor(datevec(meandate));
wpidx = (1:nwp)’;
% fprintf(‘%f %f %f %d\n’,[meanlon meanlat meanhgt wpidx]’);
fprintf(fp,'<?xml version=”1.0″ encoding=”UTF-8″?>\n’);
fprintf(fp,'<kml xmlns=”http://www.opengis.net/kml/2.2&#8243; xmlns:gx=”http://www.google.com/kml/ext/2.2&#8243; xmlns:kml=”http://www.opengis.net/kml/2.2&#8243; xmlns:atom=”http://www.w3.org/2005/Atom”&gt;\n’);
fprintf(fp,'<Document>\n’);
fprintf(fp,’ <name>output.kml</name>\n’);
fprintf(fp,’ <open>1</open>\n’);
fprintf(fp,’ <StyleMap id=”linestyle”>\n’);
fprintf(fp,’ <Pair>\n’);
fprintf(fp,’ <key>normal</key>\n’);
fprintf(fp,’ <styleUrl>#linestyle0</styleUrl>\n’);
fprintf(fp,’ </Pair>\n’);
fprintf(fp,’ </StyleMap>\n’);
fprintf(fp,’ <Style id=”linestyle0″>\n’);
fprintf(fp,’ <LineStyle>\n’);
fprintf(fp,’ <color>ff0000ff</color>\n’);
fprintf(fp,’ <width>3</width>\n’);
fprintf(fp,’ </LineStyle>\n’);
fprintf(fp,’ </Style>\n’);
fprintf(fp,’ <Placemark>\n <name>WP%d</name>\n <description>%d-%d-%d %2.2d:%2.2d:%2.2d </description>\n <styleUrl>#default</styleUrl>\n <Point>\n <coordinates>%f,%f,%f</coordinates>\n </Point>\n </Placemark>\n’,[wpidx,meandatevec,meanlon,meanlat,meanhgt]’);
% question two, where are the tracks
% fprintf(‘%f %f %d %d\n’,[lon lat smotion cmotion]’);
fprintf(fp,’ <Folder>\n’);
fprintf(fp,’ <name>Tracks</name>\n’);
fprintf(fp,’ <open>1</open>\n’);
ipw = 0;
for idx = cmin:2:cmax-2,
ipw = ipw + 1;
fprintf(fp,’ <Placemark>\n’);
fprintf(fp,’ <name>Tracks_%d_%d</name>\n’,ipw,ipw+1);
fprintf(fp,’ <styleUrl>#linestyle0</styleUrl>\n’);
fprintf(fp,’ <LineString>\n’);
fprintf(fp,’ <tessellate>1</tessellate>\n’);
fprintf(fp,’ <coordinates>\n’);
startlat = meanlat(ipw);
startlon = meanlon(ipw);
stoplat = meanlat(ipw+1);
stoplon = meanlon(ipw+1);
jdx = find(cmotion == idx+1);
lats = [startlat lat(jdx)’ stoplat];
lons = [startlon lon(jdx)’ stoplon];
% x,y,0 x,y,0 now
fprintf(fp,’%f,%f,0 ‘,[lons’ lats’]’);
fprintf(fp,’ </coordinates>\n’);
fprintf(fp,’ </LineString>\n’);
fprintf(fp,’ </Placemark>\n’);
end
fprintf(fp,’ </Folder>\n’);
fprintf(fp,'</Document>\n’);
fprintf(fp,'</kml>\n’);
fclose(fp);

Advertisements

5 thoughts on “DIY Wireless GPS tracker

    1. The 25mW or 13.5dBm version, so the RFM69W because the frequency regulations do not allow more than 25mW e.r.p. in the Netherlands

    1. Approximately 50 to 100 meter depending on how many scatterers there are between the TX and the RX. Also more could be done if you would use better antenna’s

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s