Reverse Geocache box - add functionality questions

Hi all,

So sorry for the beginner questions here. I’m after some help trying to alter a code to add some extra functionality and have some ideas on what needs to be done but no real idea on how to implement it.

So basically my project is a ‘reverse geocache’ box. It uses an LCD screen, a gps module and a small servo. The electronics are mounted in a box, which, when taken to a particular GPS co-ord will open the box (using the servo).

All that is working perfectly - but I was wondering if I could make it so there were multiple checkpoints/waypoints along the way. EG - Take box to coordA, once A is reached - print to LCD point reached, and change to needing to reach point B (etc…) - and the final GPS point will open the box.

Here is the current code:

#include <math.h>
#include <LiquidCrystal.h>
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>
SoftwareSerial mySerial(3,2);
Adafruit_GPS GPS(&mySerial);
#define GPSECHO false												     //make true to debug GPS
boolean usingInterrupt = false;
void useInterrupt(boolean);

//Servo
#include <PWMServo.h>
PWMServo servoLatch;

//Declarations
const float deg2rad = 0.01745329251994;
const float rEarth = 6371000.0;                                          //can replace with 3958.75 mi, 6370.0 km, or 3440.06 NM
float range = 3000;                                                      // distance from HERE to THERE
String here;                                                             // read from GPS

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(7, 8, 6, 10, 11, 12);

int gpsWasFixed = HIGH;                                                  // did the GPS have a fix?
int ledFix = 4;                                                          // pin for fix LED
int servoPin = 9;                                                        // pin for servo
int servoLock = 115;                                                     // angle (deg) of "locked" servo
int servoUnlock = 50;                                                     // angle (deg) of "unlocked" servo

String there = "S34 08.371, E151 07.279";                                //Desired Location goes here. Make sure you use the same syntax and number of characters

void setup()
{
  servoLatch.attach(SERVO_PIN_A);
  servoLatch.write(servoLock);
  delay(50);
  lcd.begin(16, 2);
  Serial.begin(115200);
  Serial.println("Debug GPS Test:");

  GPS.begin(9600);
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);                          // RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);                             // 1 Hz update rate
  useInterrupt(true);                                                    // reads the steaming data in a background
  delay(1000);
  

}

void loop(){
  // Parse GPS and recalculate RANGE
  if (GPS.newNMEAreceived()) {
    if (!GPS.parse(GPS.lastNMEA()))                                      // also sets the newNMEAreceived() flag to false
      return;                                                            // We can fail to parse a sentence in which case we should just wait for another
  }
    if (GPS.fix) {
    gpsWasFixed = HIGH;
    digitalWrite(ledFix, HIGH);

  
    here = gps2string ((String) GPS.lat, GPS.latitude, (String) GPS.lon, GPS.longitude);
    range = haversine(string2lat(here), string2lon(here), string2lat(there), string2lon(there));
    Serial.print("Here: ");                                        //for GPS debug
    Serial.print(here);
    Serial.print("There: ");
    Serial.println(there);
    Serial.print("Range: ");
    Serial.print(range);
    Serial.println("m");
    
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Distance to Target");
    //lcd.setCursor(0,1);
    //lcd.print("                ");
    lcd.setCursor(0,1);
    lcd.print(range);

    delay(500);
  }
  else {                                                              //No GPS fix- take box outside
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Hi Licia!");
    lcd.setCursor(0,1);
    lcd.print("Take me outside!");
    delay(200);
  }
  
  if (range < 20.0){
    servoLatch.write(servoUnlock);
    delay(1000);
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Box Unlocked");
    lcd.setCursor(0,1);
    lcd.print("Take a look inside");
    delay(5000);
  }
}


SIGNAL(TIMER0_COMPA_vect) {
  // Interrupt is called once a millisecond, looks for any new GPS data, and stores it
  char c = GPS.read();
  if (GPSECHO)
    if (c) UDR0 = c;  
}

void useInterrupt(boolean v) {
  if (v) {
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

String int2fw (int x, int n) {
  // returns a string of length n (fixed-width)
  String s = (String) x;
  while (s.length() < n) {
    s = "0" + s;
  }
  return s;
}

String gps2string (String lat, float latitude, String lon, float longitude) {
  // returns "Ndd mm.mmm, Wddd mm.mmm";
  int dd = (int) latitude/100;
  int mm = (int) latitude % 100;
  int mmm = (int) round(1000 * (latitude - floor(latitude)));
  String gps2lat = lat + int2fw(dd, 2) + " " + int2fw(mm, 2) + "." + int2fw(mmm, 3);
  dd = (int) longitude/100;
  mm = (int) longitude % 100;
  mmm = (int) round(1000 * (longitude - floor(longitude)));
  String gps2lon = lon + int2fw(dd, 3) + " " + int2fw(mm, 2) + "." + int2fw(mmm, 3);
  String myString = gps2lat + ", " + gps2lon;
  return myString;
};
/*
float string2radius (String myString) {
  // returns a floating-point number: e.g. String myString = "Radius: 005.1 NM";
  float r = ((myString.charAt(8) - '0') * 100.0) + ((myString.charAt(9) - '0') * 10.0) + ((myString.charAt(10) - '0') * 1.0) + ((myString.charAt(12) - '0') * 0.10);
  return r;
};*/

float string2lat (String myString) {
  // returns radians: e.g. String myString = "N38 58.892, W076 29.177";
  float lat = ((myString.charAt(1) - '0') * 10.0) + (myString.charAt(2) - '0') * 1.0 + ((myString.charAt(4) - '0') / 6.0) + ((myString.charAt(5) - '0') / 60.0) + ((myString.charAt(7) - '0') / 600.0) + ((myString.charAt(8) - '0') / 6000.0) + ((myString.charAt(9) - '0') / 60000.0);
  //Serial.print("float lat: ");
  //Serial.println(lat);
  lat *= deg2rad;
  if (myString.charAt(0) == 'S')
    lat *= -1;                                                           // Correct for hemisphere
  return lat;
};

float string2lon (String myString) {
  // returns radians: e.g. String myString = "N38 58.892, W076 29.177";
  float lon = ((myString.charAt(13) - '0') * 100.0) + ((myString.charAt(14) - '0') * 10.0) + (myString.charAt(15) - '0') * 1.0 + ((myString.charAt(17) - '0') / 6.0) + ((myString.charAt(18) - '0') / 60.0) + ((myString.charAt(20) - '0') / 600.0) + ((myString.charAt(21) - '0') / 6000.0) + ((myString.charAt(22) - '0') / 60000.0);
  //Serial.print("float lon: ");
  //Serial.println(lon);
  lon *= deg2rad;
  if (myString.charAt(12) == 'W')
    lon *= -1;                                                           // Correct for hemisphere
  return lon;
};


float haversine (float lat1, float lon1, float lat2, float lon2) {
  // returns the great-circle distance between two points (radians) on a sphere
  float h = sq((sin((lat1 - lat2) / 2.0))) + (cos(lat1) * cos(lat2) * sq((sin((lon1 - lon2) / 2.0))));
  float d = 2.0 * rEarth * asin (sqrt(h)); 
  //Serial.println(d);
  return d;
  
};

I’m assuming I’ll need to use an array for the ‘there’ string - but am unsure how to go about this. For example, if I change it to something like:
coordinates thereArray={(“S34 08.371, E151 07.279”),(“example cord”),(“example cord2”)};

It would then stuff up the GPS calculations later in the code.

Anyway - hope this all makes sense!

Any help or suggestions would be greatly appreciated!

Thanks heaps!

Jon

Will the device be powered up the entire time? If not, you need to use something like EEPROM to keep track of which step in the series you are currently working on.

In setup() if the EEPROM entry is outside the range of steps (0 to n-1) you should set it and CurrentStep to 0. Otherwise set CurrentStep to the number in EEPROM.

In place of "there" use "there[CurrentStep]".

When the destination is reached, increment CurrentStep and store it in EEPROM. If CurrentStep == n, open the box.

Thanks heaps for that!

The box will be powered on the whole time, but the EEPROM may be a good fail safe in case it needs to be reset for any reason.

Will give this a go. Thanks for the suggestions.

The box will be powered on the whole time, but the EEPROM may be a good fail safe in case it needs to be reset for any reason.

I'd be more concerned about a flat battery, than "a need to reset".

Argh - don't give me nightmares! ;)

Luckily it will be used for a fairly specific amount of time so I can do plenty of testing first to make sure it will go the distance alright.

If it was to be used as just a 'general' reverse geocache box - the ability to change battery externally might be a good feature to add!