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);

November grey continues

I’m still learning how to interpret the cloud indicator output. When the cloud indicator (the middle graph) sticks around -2, and when there are no excursions to -5 there is either rain, haze or fog in the air. Since yesterday this is the case, this type of weather is typical for November when there is hardly any variation in the day night temperature (the upper graph).

greyday_19nov2014

I’m still working on the sleep mode problem of the 90614 IC, it simply doesn’t work so that the circuit continuously 1,5 to 2 milliamp.

Uploaded: 9:13 on 19-nov-2014

Ja maar vroeger was het toch ook warmer?

Dit argument kwam op de tafel tijdens het debat over klimaatverandering in het programma “De achterkant van het gelijk” dat gevoerd werd door Marcel van Dam op #npo2 op donderdagavond 13-nov-2014. Wat bedoeld wordt is dat er in een geologisch verleden, b.v. in het Pliocene tussen 2 en 6 miljoen jaar geleden ook hogere kooldioxide concentraties en temperaturen gemeten zijn. (Er waren toen geen mensen, de concentraties en temperaturen zijn afgeleid uit geologische gegevens, maar dat is een technisch detail). Maar, om verder te gaan met het populistische argument; daarom zou het niet erg zijn als de wereldwijde temperatuur weer opnieuw zou stijgen als gevolg een toename van kooldioxide (CO2) in de atmosfeer.

Skeptici hebben eigenlijk op voorhand al gelijk voor een publiek dat graag korte populistische statements wil horen. Je kunt de titel van dit blog namelijk in 130 karakters tweeten, het klinkt als waar, en dus valt het goed, en het vermindert het schuldgevoel als je net “The Inconvenient Truth” gezien hebt. Geologisch gezien is de uitspraak ook waar, maar helaas past de opmerking geheel niet in de tegenwoordige tijd. Mijn tegen-tweet zou kunnen zijn: “het is niet waar!”. Maar niemand gelooft je dan op twitter, er is wat meer tekst en uitleg nodig, iets waar populisten een broertje dood aan hebben.

De reden waarom dit een ontkennend argument is, is dat het zeeniveau zich uiteindelijk zal gedragen als een langzame thermometer. Als het klimaat opwarmt komt er ten eerste extra smeltwater uit Groenland en Antarctica in de oceanen; in een recent artikel heb ik laten zien dat ongeveer de helft van de zeespiegelverandering uit smeltwater bestaat. De andere helft is volumetoename omdat warmer zeewater meer volume inneemt. De 3 mm/jaar genoemd tijdens het debat genoemd door Appie Sluis is gemeten met satelliet radar hoogtemeters maar ook wereldwijd met peilschalen, we hebben het over gemiddeld 30 cm per eeuw.

De afgelopen 2000 jaar is het zeeniveau circa 60 cm gestegen, en dat is een veel kleiner getal, namelijk gemiddeld 3 cm per eeuw. Dit kunnen we onder andere zien aan archeologische bouwwerken uit de Griekse en Romeinse oudheid die tegenwoordig 60 cm onder het huidige zeeniveau staan, zoals b.v. hier beschreven in een blog van de NY Times.

tank1-blogSpan

De genoemde 30 cm per eeuw is op dit moment al 10 keer sneller dan wat we in de afgelopen 2000 jaar gezien hebben, en de verwachting van het IPCC is dat 100 cm per eeuw tot 2100 goed mogelijk zou moeten zijn, en dat is ineens 30 keer zo snel. Na 2100 gaat het proces overigens verder. Het behoeft weinig voorstellingsvermogen dat de snelheid van zeespiegelverandering hetgene is waar je je zorgen over moet maken.

Mijn stelling is dat je met deze voorkennis moet gaan oppassen met het bouwen in kustgebieden nabij het zeeniveau. Nederland kon 10 tot 20 eeuwen geleden beginnen met het bouwen in de deltas van de grote rivieren en het heeft al veel moeite gekost om een veilige maatschappij te maken. Maar hoe ziet Nederland er over 5 tot 10 eeuwen uit? Een snelheid 100 cm per eeuw is niet makkelijk omkeerbaar, of beter, zelf niet omkeerbaar.

Onderstaande infographic toont onze huidige kwetsbaarheid ten opzichte van zeespiegelstijging. 3 mm/jaar klinkt als klein, maar het is het helemaal niet. Het argument, ja maar vroeger was het warmer is mijns insziens geen valide discussiepunt, het ontkent namelijk onderstaand probleem wat niet alleen Nederland aangaat, maar meerdere grote steden in laagliggende gebieden.

Info-is-beautiful-sea-lev-001

De geologie laat ons vrij goed zien wat de relatie is tussen het zeeniveau en de gemiddelde temperatuur op aarde. De onderstaande figuur laat een geologische reconstructie zien van het zeeniveau over de laatste 700 duizend jaar welke ik hier tegen kwam. Aan de linkerkant van de grafiek zie je het huidige zeeniveau. Dit niveau is hoog omdat we de recente ijstijd vlak achter ons hebben (ca 22 duizend jaar is geologisch gezien recent). Op het koudste punt in een ijstijd zit veel oceaan water in de vorm van ijs vast op Groenland en Antarctica, dit is de dip bij 2 in de grafiek. Het zeeniveau is dan zo’n 130 meter lager dan tegenwoordig het geval is en het is dan ook een graad of 5 kouder. Deze grens kennen we nu als het continentaal plat op aarde. De relatie temperatuur ten opzichte van zeeniveau is daarom al 700 duizend jaar te volgen. Tectonisch gezien verandert de aarde niet echt veel in deze periode, dwz, de continenten blijven zo’n beetje op hun plaats liggen. Recente geologische kennis zoals hieronder weergegeven geeft dan ook aardig aan wat je in de nabije toekomst kunt verwachten voor het zeeniveau indien de temperatuur op aarde naar verwachting omhoog gaat.

Fig14.Virginia_ES_paleochannels

Misschien moet ik meer aandacht trekken met de tweet: “Zeeniveau volgt temperatuur en de kustlijn lag 2 tot 6 miljoen jaar geleden ook 30 meter hoger, wist je niet he?” Het past in 130 karakters, ik hou hem in reserve.

Very clear sky

At 8:45 on 13-nov-2014 we have a very clear sky.
At 8:45 on 13-nov-2014 we have a very clear sky. The temperature difference measured by the cloud detector in the middle graph now goes to -5.5 degree.
The infrared image from sat24.com confirms that we have a clean cloud deck, but an Atlantic depression is on its way
The infrared image from sat24.com confirms that we have a clean cloud deck, but an Atlantic depression is on its way

The cloud detector

clouddetector

When you point an infrared thermometer to the skies then soon you will find out that the temperature reading strongly depends on whether it is cloudy or not. Clear blue skies will result in temperatures far lower than the ground temperature, I measured in the evening even -20 degrees C right above me while the surrounding temperature was around 10 degrees C. A clear sky is cold, if we wouldn’t have an atmosphere you would measure the 4K remaining since the big bang. Clouds are well known to have a blanketing effect and this is something you can measure with a single thermopile.

The above graph comes from a Melexis 90614 detector looking horizontally over the hedge the garden, about 50 cm under the rain gutter that is about a half meter wide. So the detector will see both the garden but also a part of the skies. I’ve chosen for this set-up because I want to have some protection against the rain. In any case, the horizontal axis in the graph shows hours relative to midnight 11-nov-2014 and the vertical axis shows the difference between the 90614 chip temperature (also known as the die temperature) and the infrared temperature. So we measure in fact the difference between the cold sky and the ambient temperature.

The temperature difference is a measure for the level of cloudiness, we had overcast up to -18 hours in the graph, after sunrise all clouds disappeared, a nice clear day followed with some occasional high altitude cirrus clouds. Since +10 o’clock this morning we had more broken clouds and occasionally all octas were covered.

A longer article on this all: http://www.planetarduino.org/?cat=1846

Nice November weather

kouderwarmer

Temperatures last week ending at 9-nov-2014, and below that the light curve at the bottom. We are still running on the same 4 AAA NiMH penlites that were charged at the beginning of September, no signs of a significant voltage drop. The duty cycle is low, and the current use in sleep mode is very low.

Windy days result in wiggly temperature graphs, quiet days result in smooth graphs. Foggy or cloudy days give wiggly light curves and clear days give me almost square blocks in the light curve. As you can see, our days are slowly getting shorter.

Altogether this is nice November weather for the Netherlands.