Reverse geocache

I have been trying to make this reverse geo cache with multiple locations and messages that displayed. But I am struggling all of this is pretty new for me. I think I am having problems getting the gps to connect I am using a neo 6 and an Arduino uno r4 wifi. I hope everything attaches correctly. Right now I get the longitude and latitude and the numbers change and do not stay consistent. I have gone outside with the same result.

#include <TinyGPS++.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>
#include <ArduinoBLE.h>
#include <SoftwareSerial.h>

// GPS setup
#define GPS_BAUD 9600 // Set the GPS baud rate to 9600

// Create a SoftwareSerial object for GPS
SoftwareSerial gpsSerial(2, 3); // RX, TX for GPS (use pins 2 and 3)
TinyGPSPlus gps; // Create an instance of the GPS

// Initialize the LiquidCrystal library with the pins connected to the LCD 
LiquidCrystal lcd(12, 11, 5, 4, 6, 7); // RS, E, D4, D5, D6, D7

const int servoPin = 9; // Pin for the servo
bool unlocked = false;
int foundLocations = 0; // Counter for found locations

// Define maximum number of locations 
const int maxLocations = 3; 
double locations[maxLocations][2] = {
  {38.309705, -104.623113}, // Location 1 
  {38.320914, -104.616595}, // Location 2 
  {38.135981, -105.473027}  // Location 3 
};

// Location-specific messages 
const char* locationMessages[maxLocations] = {
  "Where we first met.", 
  "Where we would talk late at night", 
  "The city near an adventure hike to the clouds." 
};

// Arrival messages for each location 
const char* arrivalMessages[maxLocations] = {
  "We miss you here!", 
  "Go have your favorite drink of the night.", 
  "Beautiful, just one more puzzle." 
};

// EEPROM addresses for storage 
const int latStartAddr = 0; 
const int lngStartAddr = latStartAddr + (maxLocations * sizeof(double)); 

unsigned long gpsUpdateTime = 0; // Time for GPS update

void setup() {
  pinMode(servoPin, OUTPUT); // Set the servo pin as output
  lcd.begin(16, 2); // Initialize the LCD with 16 columns and 2 rows
  Serial.begin(9600); // Start the hardware serial for debugging
  gpsSerial.begin(GPS_BAUD); // Start the GPS serial

  // Display welcome message
  lcd.print("Brittany,");
  lcd.setCursor(0, 1);
  lcd.print("welcome to your");
  delay(2000); 
  lcd.setCursor(0, 1);
  lcd.print("treasure box");
  delay(2000); 
  lcd.clear();

  // Load locations from EEPROM 
  loadLocations();
}

void loop() {
  // Read GPS data
  while (gpsSerial.available()) {
    gps.encode(gpsSerial.read());
  }

  // Check if the GPS location is valid
  if (gps.location.isUpdated() && gps.location.isValid()) {
    // Get latitude and longitude
    double latitude = gps.location.lat();
    double longitude = gps.location.lng();

    // Print to Serial Monitor
    Serial.print("GPS Signal Acquired:\n");
    Serial.print("Latitude: ");
    Serial.print(latitude, 6);
    Serial.print(" Longitude: ");
    Serial.println(longitude, 6);

    // Display on LCD
    lcd.clear(); // Clear the LCD
    lcd.setCursor(0, 0); // Set cursor to the first line
    lcd.print("Lat: ");
    lcd.print(latitude, 6); // Print latitude
    lcd.setCursor(0, 1); // Move to the second line
    lcd.print("Lng: ");
    lcd.print(longitude, 6); // Print longitude

    // Calculate distances to each location
    for (int i = 0; i < maxLocations; i++) {
      double distanceMeters = gps.distanceBetween(latitude, longitude, locations[i][0], locations[i][1]);
      double distanceFeet = distanceMeters * 3.28084; // Convert meters to feet

      Serial.print("Distance to location ");
      Serial.print(i + 1); // Display location number
      Serial.print(": ");
      Serial.print(distanceFeet);
      Serial.println(" ft");

      // Check if within 500 feet
      if (distanceFeet < 500) { // If within 500 feet
        foundLocations++;
        lcd.clear();
        lcd.print("Clue: ");
        lcd.print(locationMessages[i]); // Show the clue for the location
        delay(5000); // Show clue for 5 seconds
        break; // Exit the loop after processing one location
      }
    }

    // Check if all locations have been found
    if (foundLocations >= maxLocations && !unlocked) {
      unlockBox();
    }
  } else {
    // If GPS signal is not available or invalid
    Serial.println("No GPS Signal or Invalid Location");
    lcd.clear();
    lcd.print("No GPS Signal");
    lcd.setCursor(0, 1);
    lcd.print("Go Outside!");
  }

  delay(1000); // Wait for a second before the next loop iteration
}

void unlockBox() {
  if (!unlocked) {
    // Manually control the servo to unlock (90 degrees)
    for (int pos = 0; pos <= 90; pos++) { // Sweep from 0 to 90 degrees
      analogWrite(servoPin, map(pos, 0, 180, 0, 255)); // Map position to PWM value
      delay(15); // Wait for the servo to reach the position
    }
    lcd.clear();
    lcd.print("Box Unlocked!");
    delay(2000);
    unlocked = true; // Set unlocked state
  }
}

void lockBox() {
  if (unlocked) {
    // Manually control the servo to lock (0 degrees)
    for (int pos = 90; pos >= 0; pos--) { // Sweep from 90 to 0 degrees
      analogWrite(servoPin, map(pos, 0, 180, 0, 255)); // Map position to PWM value
      delay(15); // Wait for the servo to reach the position
    }
    lcd.clear();
    lcd.print("Box Locked!");
    delay(2000);
    unlocked = false; // Set locked state
  }
}

void loadLocations() {
  for (int i = 0; i < maxLocations; i++) {
    EEPROM.get(latStartAddr + i * sizeof(double), locations[i][0]);
    EEPROM.get(lngStartAddr + i * sizeof(double), locations[i][1]);
  }
}

void saveCurrentLocation(double latitude, double longitude) {
  if (foundLocations < maxLocations) {
    EEPROM.put(latStartAddr + foundLocations * sizeof(double), latitude);
    EEPROM.put(lngStartAddr + foundLocations * sizeof(double), longitude);
  }
}

Welcome to the forum

How much do the Lat and Long change ?
Please post some examples

Welcome to the forum.

I can't help just now, but is this due on 14 February 2025?

a7

No it is not due by the 14th no timeline to completion. I tried to upload a video with the changes in latitude and longitude but it doesnt let me I will hook it to serial monitor later and upload the information

How consistent are you expecting the Lat/Long to be considering the possible accuracy and consistency of location data provided by GPS ?

I hope it can narrow down to 500 ft to each location. but the program does not continue after displaying longitude latitude.

You originally said

Which is it ?

1 Like

Well, hardware wise all I can say from that mess of wires is that you don't seem to have any voltage level shifting between the 3.3V GPS module (yes, I know it'll take 5V on Vcc, but the signal levels still have to be 3.3V) and the 5V R4. That's not going to help your cause. A good schematic certainly would.

If you were running with a 3.3V processor, like in the schematic fragment below, then you don't need level translation to the GPS module. It's a bit more work to set up initially but it makes interfacing a breeze from there on out.

3.3V all the way

The actual test setup I used during development

Code wise, you ought to be checking for a 3D fix, and a maximum HDOP before even looking at the latitutde and longitude. I use an upper limit of 1.6. Much more than that and your coordinates are at best, "somewhere in the neighbourhood... maybe". Finally, I check for a maximum displacement from the desired coordinates, and use a value of 2.5 metres. That's the kind of accuracy you can easily get from even a Neo 6M under open skies. I should note that the modules I'm using are not recent ones and seem to perform better than what's on the market now. That's just my impression though.

1 Like

Its both I get the longitude and latitude and the numbers do not stay consistent, but the program should then display distance to first location after that and it never does.

Your project looks amazing. So because the R4 runs 5v that could be causing me problems gaining the signal from it? If I ran the gps off of the 3v from the r4 direct to the gps for power would that help?

Absolutely not.

You can power the GPS module by 5V. It has an onboard 3.3V regulator.

But the GPS itself is a 3.3V device. The signal levels produced and expected by the GPS are 3.3V. When you hook it directly up to the R4 you're feeding it a 5V signal on its Rx line. That is operating it way, way outside its Maximum Maximum Ratings. Way outside.

If you're going to hook up to its Rx line, as you appear to be doing, you need voltage level translation on it. It can be as simple as a 1.2K/2.2K resistor voltage divider. But something has to be there. A voltage divider, a level shifter, something. It is not optional. You will damage the GPS module without it. You may have already.

There is no need to have the GPS modules RX pin connected, if you are only reading GPS data.
If not connected, it won't get damaged by 5V output level.

1 Like

And as there's nothing in the OP's sketch sending commands back to the GPS, no reason to have Rx hooked up.

In mine I turn off all sentences that my GPS library isn't looking for to cut down on overhead, so I did have the need.

Also can't understand why they're using SoftwareSerial when there's a perfectly good unused hardware Serial1 on pins 0 and 1. But to each their own.

If I do not hook the gps rx do I need to change it in the code at all or can I leave the code as it is?

Yes, you can leave the code as it is.
SoftwareSerial still has to have 2 pins allocated for it, even if you are only using one.

I'm still having no luck with this set up. I have tried it with several boards and still can not get a signal for the gps. I need suggestions of how I should wire this and what I should use to even just get the gps to connect and then I can worry about the rest. I have an arduino mega board, adafruit metro, and the arduino uno r4 wifi is one better for this project. I also switched to another of the neo 6 gps because I was concerned I had messed up the other one from running too much power to it.

Sorry for being a pain this is all new to me.

I was having problems so I reworked the code. The code now displays my current gps but not the distance to the first location. can some one relook over the code and make any suggestions.

#include <math.h>
#include <LiquidCrystal.h>
#include <TinyGPS++.h> // Include the TinyGPS++ library
#include <SoftwareSerial.h> // Include SoftwareSerial for GPS communication
#include <EEPROM.h> // Include EEPROM for storing locations

// Define pins for GPS
#define RX_PIN 2  // Arduino Pin connected to the TX of the GPS module
#define TX_PIN 3  // Arduino Pin connected to the RX of the GPS module

TinyGPSPlus gps;                           // the TinyGPS++ object
SoftwareSerial gpsSerial(RX_PIN, TX_PIN);  // the serial interface to the GPS module

// Initialize the LiquidCrystal library with the pins connected to the LCD 
LiquidCrystal lcd(12, 11, 5, 4, 6, 7); // Adjust these pins as needed

// Define maximum number of locations 
const int maxLocations = 3; 
double locations[maxLocations][2] = {
  {38.309705, -104.623113}, // Location 1 
  {38.320914, -104.616595}, // Location 2 
  {38.135981, -105.473027}  // Location 3 
};

// Location-specific messages (clues)
const char* locationMessages[maxLocations] = {
  "Where we first met.", 
  "Go have your favorite drink of the night.", 
  "The city near an adventure hike to the clouds." 
};

// Arrival messages for each location 
const char* arrivalMessages[maxLocations] = {
  "We miss you here!", 
  "Enjoy your drink!", 
  "Beautiful, just one more puzzle." 
};

// EEPROM addresses for storage 
const int currentLocationIndexAddr = 0; // Address to store the current location index
const int latStartAddr = sizeof(int); // Address to store latitude (after currentLocationIndex)
const int lngStartAddr = latStartAddr + (maxLocations * sizeof(double)); // Address to store longitude

const int servoPin = 9; // Pin for the servo
bool unlocked = false; // State of the box lock
int currentLocationIndex = 0; // Track the current location index

void setup() {
  pinMode(servoPin, OUTPUT); // Set the servo pin as output
  lcd.begin(16, 2); // Initialize the LCD with 16 columns and 2 rows
  Serial.begin(9600); // Start the hardware serial for debugging
  gpsSerial.begin(9600); // Start the GPS serial

  // Load the last found location from EEPROM
  EEPROM.get(currentLocationIndexAddr, currentLocationIndex);
  if (currentLocationIndex >= maxLocations) {
    currentLocationIndex = 0; // Reset to the first location if out of bounds
  }

  // Display welcome message
  lcd.print("Brittany,");
  lcd.setCursor(0, 1);
  lcd.print("welcome to your");
  delay(2000); 
  lcd.setCursor(0, 1);
  lcd.print("treasure box");
  delay(2000); 
  lcd.clear();
}

void loop() {
  // Read GPS data
  while (gpsSerial.available()) {
    gps.encode(gpsSerial.read()); // Read the GPS data
  }

  // Check if the GPS fix is valid
  if (gps.location.isValid()) { // Check if a fix is available
    double latitude = gps.location.lat(); // Access latitude directly
    double longitude = gps.location.lng(); // Access longitude directly

    // Print to Serial Monitor
    Serial.print("GPS Signal Acquired:\n");
    Serial.print("Latitude: ");
    Serial.print(latitude, 6);
    Serial.print(" Longitude: ");
    Serial.println(longitude, 6);

    // Calculate distance to the current location in meters
    double distanceMeters = calculateDistance(latitude, longitude, locations[currentLocationIndex][0], locations[currentLocationIndex][1]);
    
    // Convert distance to miles
    double distanceMiles = distanceMeters * 0.000621371; // Convert meters to miles

    // Print the calculated distance
    Serial.print("Distance to Location ");
    Serial.print(currentLocationIndex + 1); // Display location number (1-based)
    Serial.print(": ");
    Serial.print(distanceMiles); // Print distance in miles
    Serial.println(" miles");

    // Display distance on LCD
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Dist to Loc ");
    lcd.print(currentLocationIndex + 1);
    lcd.setCursor(0, 1);
    lcd.print(distanceMiles);
    lcd.print(" mi");
    delay(2000); // Show distance for 2 seconds

    // Display the clue for the current location
    lcd.clear();
    lcd.print("Clue: ");
    lcd.print(locationMessages[currentLocationIndex]); // Show the clue for the current location
    delay(5000); // Show clue for 5 seconds

    // Check if within 500 feet (approximately 0.095 miles)
    if (distanceMiles < 0.095) { // If within 500 feet
      lcd.clear();
      lcd.print(arrivalMessages[currentLocationIndex]); // Show the arrival message
      delay(5000); // Show message for 5 seconds
      currentLocationIndex++; // Move to the next location
      EEPROM.put(currentLocationIndexAddr, currentLocationIndex); // Save the current location index to EEPROM

      // Check if all locations have been found
      if (currentLocationIndex >= maxLocations && !unlocked) {
        unlockBox();
      }
    }
  } else {
    // If GPS signal is not available or invalid
    Serial.println("No GPS Signal or Invalid Location");
    lcd.clear();
    lcd.print("No GPS Signal");
    lcd.setCursor(0, 1);
    lcd.print("Go Outside!");
  }

  delay(1000); // Wait for a second before the next loop iteration
}

double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
  // Haversine formula to calculate the distance between two GPS coordinates
  const double R = 6371000; // Radius of the Earth in meters
  double phi1 = lat1 * (M_PI / 180);
  double phi2 = lat2 * (M_PI / 180);
  double deltaPhi = (lat2 - lat1) * (M_PI / 180);
  double deltaLambda = (lon2 - lon1) * (M_PI / 180);

  double a = sin(deltaPhi / 2) * sin(deltaPhi / 2) +
             cos(phi1) * cos(phi2) *
             sin(deltaLambda / 2) * sin(deltaLambda / 2);
  double c = 2 * atan2(sqrt(a), sqrt(1 - a));

  return R * c; // Distance in meters
}

void unlockBox() {
  if (!unlocked) {
    // Manually control the servo to unlock (90 degrees)
    for (int pos = 0; pos <= 90; pos++) { // Sweep from 0 to 90 degrees
      analogWrite(servoPin, map(pos, 0, 180, 0, 255)); // Map position to PWM value
      delay(15); // Wait for the servo to reach the position
    }
    lcd.clear();
    lcd.print("Box Unlocked!");
    delay(2000);
    unlocked = true; // Set unlocked state
  }
}

void lockBox() {
  if (unlocked) {
    // Manually control the servo to lock (0 degrees)
    for (int pos = 90; pos >= 0; pos--) { // Sweep from 90 to 0 degrees
      analogWrite(servoPin, map(pos, 0, 180, 0, 255)); // Map position to PWM value
      delay(15); // Wait for the servo to reach the position
    }
    lcd.clear();
    lcd.print("Box Locked!");
    delay(2000);
    unlocked = false; // Set locked state
  }
}

void loadLocations() {
  for (int i = 0; i < maxLocations; i++) {
    EEPROM.get(latStartAddr + i * sizeof(double), locations[i][0]);
    EEPROM.get(lngStartAddr + i * sizeof(double), locations[i][1]);
  }
}

void saveCurrentLocation(double latitude, double longitude) {
  if (currentLocationIndex < maxLocations) {
    EEPROM.put(latStartAddr + currentLocationIndex * sizeof(double), latitude);
    EEPROM.put(lngStartAddr + currentLocationIndex * sizeof(double), longitude);
  }
}