Negah NafisiAlvin YuanMoeka TakagiSahana Rajasekar
Published

Mystick

An innovative hiking stick that helps you explore your surroundings and share your experiences with others.

Full instructions provided3,123
Mystick

Things used in this project

Story

Read more

Custom parts and enclosures

bottom-mystick.stl

Sketchfab still processing.

top-mystick.stl

Sketchfab still processing.

Code

prov2.ino

C/C++
prov2.ino
#include <string.h>
#include <ctype.h>
#include <Wire.h>
#include <HMC5883L.h>
#include "LPD8806.h"
#include "SPI.h"

/*******************************************
 * Debugging variables
 *******************************************/
int ledPin = 13;
// Preset Locations: { near Campanile, near Soda, near Berkeley BART, near College & Parker }
const float PRESET_LAT[] = { 37.8721, 37.8754, 37.8701, 37.8641 };
const float PRESET_LON[] = { -122.2584, -122.2587, -122.2679, -122.2543 };

// Debugging flags
const boolean USE_PRESET_LOCATIONS = true;
const int PRESET_STORED_LOCATION_INDEX = 1;
const int PRESET_CURRENT_LOCATION_INDEX = 0;
const int PRESET_START_ADVENTURE_LOCATION_INDEX = 2;
const boolean CONTROL_ADVENTURE_END_VIA_BUTTON = true;
boolean debug_showed_progress = false;

/*******************************************
 * GPS variables
 *******************************************/
const int rxPin = 0;
const char GPRstring[7] = "$GPRMC";
char gpsDataBuffer[300];
int gpsDataBufferIndex;
int gpsDataFieldIndices[13];

/*******************************************
 * Button variables
 *******************************************/
const int buttonPin = 12;
int buttonState;            // the current reading from the input pin
int lastButtonState = HIGH; // the previous reading from the input pin
long lastDebounceTime = 0;  // the last time the output pin was toggled

/*******************************************
 * Compass variables
 *******************************************/
HMC5883L compass;
const int compassSDApin = 2; // variable not used in code.
const int compassSCLpin = 3; // variable not used in code.
const int northPin = 4;
const int northEastPin = 5;
const int eastPin = 6;
const int southEastPin = 7;
const int southPin = 8;
const int southWestPin = 9;
const int westPin = 10;
const int northWestPin = 11;

/*******************************************
 * Record/playback variables
 *******************************************/
const int recordPlaybackPin = A1;
const int recordingPin = A4;
const long MAX_RECORD_LENGTH = 22000; // milliseconds
unsigned long recordBeginTime, recordLength;
const long INSTRUCTIONS_LENGTH = 4000;

/*******************************************
 * LED strip variables
 *******************************************/
// Number of RGB LEDs in strand:
int nLEDs = 46 - 18;

// Two output pins
int dataPin  = A2;
int clockPin = A3;

LPD8806 strip = LPD8806(nLEDs, dataPin, clockPin);


/*******************************************
 * State variables
 *******************************************/
const float DISTANCE_THRESHOLD = 20; // meters
boolean currentLocationValid = false;

float currentLat, currentLon;
float storedLat, storedLon;
float startAdventureLat, startAdventureLon;

unsigned long playbackBeginTime, showingProgressBeginTime;
boolean buttonPressed, buttonReleased;
boolean showingProgress = false;

enum State {
  ADVENTURE_BEGUN,
  PLAYBACK,
  NO_LOCATION_SELECTED,
  RECORDING,
  LOCATION_SELECTED
} state;

/*******************************************
 * setup()
 *******************************************/
void setup() {
  // GPS
  Serial.begin(4800);
  pinMode(rxPin, INPUT);
  Serial1.begin(4800);
  // Initialize buffer for received data
  gpsDataBufferIndex = 0;
  for (int i=0;i<300;i++){
    gpsDataBuffer[i]=' ';
  }

  // Button
  pinMode(buttonPin, INPUT_PULLUP);

  // Compass
  Wire.begin();
  compass = HMC5883L();
  int error = compass.SetScale(1.3); // Set the scale of the compass.
  if(error != 0) { // If there is an error, print it out.
    Serial.println(compass.GetErrorText(error));
  }
  error = compass.SetMeasurementMode(Measurement_Continuous); // Set the measurement mode to Continuous
  if(error != 0) { // If there is an error, print it out.
    Serial.println(compass.GetErrorText(error));
  }
  pinMode(northPin, OUTPUT);
  pinMode(westPin, OUTPUT);
  pinMode(eastPin, OUTPUT);
  pinMode(southPin, OUTPUT);
  pinMode(northEastPin, OUTPUT);
  pinMode(southEastPin, OUTPUT);
  pinMode(southWestPin, OUTPUT);
  pinMode(northWestPin, OUTPUT);

  // Record/Playback Module
  pinMode(recordPlaybackPin, OUTPUT);
  digitalWrite(recordPlaybackPin, LOW);
  pinMode(recordingPin, OUTPUT);
  digitalWrite(recordingPin, HIGH);

  // LED strip
  strip.begin();
  strip.show();

  // Debugging
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  if (USE_PRESET_LOCATIONS) {
    currentLat = PRESET_LAT[PRESET_CURRENT_LOCATION_INDEX];
    currentLon = PRESET_LON[PRESET_CURRENT_LOCATION_INDEX];
    storedLat = PRESET_LAT[PRESET_STORED_LOCATION_INDEX];
    storedLon = PRESET_LON[PRESET_STORED_LOCATION_INDEX];
    startAdventureLat = PRESET_LAT[PRESET_START_ADVENTURE_LOCATION_INDEX];
    startAdventureLon = PRESET_LON[PRESET_START_ADVENTURE_LOCATION_INDEX];
    currentLocationValid = true;
    Serial.println("ADVENTURE_BEGUN");
    state = ADVENTURE_BEGUN;
    recordLength = 0;
  } else {
    Serial.println("NO_LOCATION_SELECTED");
    state = NO_LOCATION_SELECTED;
  }

  Serial.println("ready");
}

/*******************************************
 * loop()
 *******************************************/
void loop() {
  // Computes currentLat, currentLon if possible
  gpsModuleLoop();
  
  if (!currentLocationValid) {
    return;
  }

  // Check for button press
  buttonPressed = false;
  buttonReleased = false;
  buttonLoop();

  // Show compass direction
  // Compass direction only shows if state == ADVENTURE_BEGUN
  compassLoop();

  // State-based behavior
  // Some state-based behavior is in compassLoop()
  float progress =   (  distanceBetweenCoords(startAdventureLat, startAdventureLon, storedLat, storedLon) 
                      - distanceBetweenCoords(currentLat, currentLon, storedLat, storedLon) )
                   / distanceBetweenCoords(startAdventureLat, startAdventureLon, storedLat, storedLon);

  // Show progress for 3 seconds at a time
  if (showingProgress && millis() - showingProgressBeginTime > 3000) {
    lightsOff();
    showingProgress = false;
  }

  switch (state) {
    case ADVENTURE_BEGUN:
      // Check if reached stored location
      if (   distanceBetweenCoords(storedLat, storedLon, currentLat, currentLon) < DISTANCE_THRESHOLD
          || (CONTROL_ADVENTURE_END_VIA_BUTTON && buttonPressed && debug_showed_progress)) {
        // Reached stored location
        showingProgress = false;
        lightsOff();
        finalLights();
        lightsOff();
        Serial.println("PLAYBACK");
        state = PLAYBACK;
        playback();
        playbackBeginTime = millis();
      } else if (buttonPressed) {
        // Show Progress if button pressed in this state
        Serial.print("Show progress: ");
        Serial.println(progress);
        colorWipe(strip.Color( 127, 127, 127), 300, progress);
        showingProgress = true;
        showingProgressBeginTime = millis();
        debug_showed_progress = true;
      }
      break;

    case PLAYBACK:
      // Check if finished playback
      Serial.print(playbackBeginTime);
      Serial.print(" ");
      Serial.print(millis() - playbackBeginTime);
      Serial.print(" ");
      Serial.println(recordLength);
      
      if (millis() - playbackBeginTime > recordLength) {
        resetRecordPlaybackState();
        Serial.println("NO_LOCATION_SELECTED");
        state = NO_LOCATION_SELECTED;
      }
      break;

    case NO_LOCATION_SELECTED:
      if (buttonPressed) {
        // Store new location
        storedLat = currentLat;
        storedLon = currentLon;
        // Begin recording
        record();
        Serial.println("RECORDING");
        state = RECORDING;
      }
      break;

    case RECORDING:
      // Check if recording finished
      if (buttonReleased) {
        stopRecord();
        Serial.println("LOCATION_SELECTED");
        state = LOCATION_SELECTED;
      }
      break;

    case LOCATION_SELECTED:
      if (buttonPressed) {
        Serial.println("ADVENTURE_BEGUN");
        state = ADVENTURE_BEGUN;
        startAdventureLat = currentLat;
        startAdventureLon = currentLon;
        debug_showed_progress = false;
      }
      break;
  }  
}


/*******************************************
 * gpsModuleLoop()
 *
 * Based on example code for connecting a Parallax GPS module to the Arduino
 * Igor Gonzalez Martin. 05-04-2007
 * igor.gonzalez.martin@gmail.com
 * English translation by djmatic 19-05-2007
 * Listen for the $GPRMC string and extract the GPS location data from this.
 *******************************************/
void gpsModuleLoop() {
  int byteGPS=Serial1.read(); // Read a byte of the serial port
  if (byteGPS == -1) { // See if the port is empty yet
    digitalWrite(ledPin, HIGH);
    delay(100);
  } else {
    digitalWrite(ledPin, LOW);
//    Serial.write(byteGPS);
    // note: there is a potential buffer overflow here!
    gpsDataBuffer[gpsDataBufferIndex++]=byteGPS; // If there is serial port data, it is put in the buffer

    if (byteGPS==13){ // If the received byte is = to 13, end of transmission
      // note: the actual end of transmission is <CR><LF> (i.e. 0x13 0x10)
      int fieldsIndex=0;

      boolean correctString = true;
      // The following for loop starts at 1, because this code is clowny and the first byte is the <LF> (0x10) from the previous transmission.
      for (int i=1;i<7;i++){ // Verifies if the received command starts with $GPR
        if (gpsDataBuffer[i] != GPRstring[i-1]){
          correctString = false;
          break;
        }
      }

      if(correctString){ // If yes, fieldsIndexinue and process the data
        // Find field delimiters
        for (int i=0;i<300;i++){
          if (gpsDataBuffer[i]==',' || gpsDataBuffer[i]=='*') {
            // note: again, there is a potential buffer overflow here!
            gpsDataFieldIndices[fieldsIndex++]=i;
          }
        }
        
        if (gpsDataBuffer[gpsDataFieldIndices[1]+1] == 'A') {
          // If status is good, get current location
          getCurrentLatAndLon();
          currentLocationValid = true;
          Serial.print(currentLat, 4);
          Serial.print(", ");
          Serial.println(currentLon, 4);
        }
      }

      // Reset buffer
      gpsDataBufferIndex=0;
      for (int i=0;i<300;i++){
        gpsDataBuffer[i]=' ';             
      }
    }
  }
}

/*******************************************
 * getCurrentLatAndLon()
 *******************************************/
void getCurrentLatAndLon() {
  const int LAT_INDEX = 2;
  const int LAT_DIRECTION_INDEX = 3;
  const int LON_INDEX = 4;
  const int LON_DIRECTION_INDEX = 5;
  
  char currentLatStr[] = "DDMM.MMMM";
  char currentLonStr[] = "DDDMM.MMMM";
  
  copyGPSDataToBuffer(LAT_INDEX, currentLatStr);
  copyGPSDataToBuffer(LON_INDEX, currentLonStr);
  
  currentLat = convertGPSModuleFormatToDegrees(currentLatStr);
  currentLon = convertGPSModuleFormatToDegrees(currentLonStr);
  
  if (gpsDataBuffer[gpsDataFieldIndices[LAT_DIRECTION_INDEX]+1] == 'S') {
    currentLat = currentLat * -1;
  }
  if (gpsDataBuffer[gpsDataFieldIndices[LON_DIRECTION_INDEX]+1] == 'W') {
    currentLon = currentLon * -1;
  }

}

/*******************************************
 * copyGPSDataToBuffer()
 *******************************************/
void copyGPSDataToBuffer(int fieldIndex, char* buffer) {
  for (int j = gpsDataFieldIndices[fieldIndex]; j < (gpsDataFieldIndices[fieldIndex+1]-1); j++) {
    buffer[j - gpsDataFieldIndices[fieldIndex]] = gpsDataBuffer[j+1];
  }
}

/*******************************************
 * convertGPSModuleFormatToDegrees()
 *******************************************/
float convertGPSModuleFormatToDegrees(char* coordString)
{
  float floatVal = atof(coordString);
  int wholeDegrees = ((int) floatVal) / 100;
  float minutes = floatVal - (float) (wholeDegrees * 100);
  float finalDegrees = (float) (wholeDegrees + minutes / 60.0);
  return finalDegrees;
}

/*******************************************
 * distanceBetweenCoords()
 *
 * Returns value in meters.
 *******************************************/
float distanceBetweenCoords( float Lat1, float Lon1, float Lat2, float Lon2 )
{
  const float EARTH_RADIUS_METERS = 6372795.0;

  float dLat = radians( Lat2 - Lat1 );
  float dLon = radians( Lon2 - Lon1 );
  
  float a = sin( dLat / 2.0f ) * sin( dLat / 2.0f ) + 
            cos( radians( Lat1 ) ) * cos( radians( Lat2 ) ) *
            sin( dLon / 2.0f ) * sin( dLon / 2.0f );
  
  float d = 2.0f * atan2( sqrt( a ), sqrt( 1.0f - a ) );
  
  return d * EARTH_RADIUS_METERS;
}

/*******************************************
 * bearingBetweenCoords()
 *
 * returns value in degrees.
 *******************************************/
float bearingBetweenCoords(float Lat1, float Lon1, float Lat2, float Lon2) {
  float dLon = radians( Lon2 - Lon1 );

  Lat1 = radians(Lat1);
  Lat2 = radians(Lat2);
  Lon1 = radians(Lon1);
  Lon2 = radians(Lon2);
    
  float y = sin(dLon)*cos(Lat2);
  float x = cos(Lat1)*sin(Lat2) - sin(Lat1)*cos(Lat2)*cos(dLon);
  
  return (((int) degrees(atan2(y,x))) + 360) % 360;
}

/*******************************************
 * buttonLoop()
 *
 * based on Debounce example
 *******************************************/
void buttonLoop() {
  const long DEBOUNCE_DELAY = 100;    // the debounce time; increase if the output flickers

  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);

  // check to see if you just pressed the button 
  // (i.e. the input went from LOW to HIGH),  and you've waited 
  // long enough since the last press to ignore any noise:  

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  } 
  
  if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == LOW) {
        buttonPressed = true;
      } else {
        buttonReleased = true;
      }
    }
  }
  
  // save the reading.  Next time through the loop,
  // it'll be the lastButtonState:
  lastButtonState = reading;
}

/*******************************************
 * compassLoop()
 *******************************************/
void compassLoop()
{
  // Retrive the raw values from the compass (not scaled).
  MagnetometerRaw raw = compass.ReadRawAxis();
  // Retrived the scaled values from the compass (scaled to the configured scale).
  MagnetometerScaled scaled = compass.ReadScaledAxis();
  
  // Values are accessed like so:
  int MilliGauss_OnThe_XAxis = scaled.XAxis;// (or YAxis, or ZAxis)
  
  // Calculate heading when the magnetometer is level, then correct for signs of axis.
  float heading = atan2(scaled.YAxis, scaled.XAxis);
  
  // Once you have your heading, you must then add your Declination Angle, which is the Error of the magnetic field in your location.
  // Find yours here: http://www.magnetic-declination.com/
  float declinationAngle = -0.1742;
  heading += declinationAngle;
  
  // Correct for when signs are reversed.
  if(heading < 0)
  heading += 2*PI;
  
  // Check for wrap due to addition of declination.
  if(heading > 2*PI)
  heading -= 2*PI;
  
  // Convert radians to degrees for readability.
  float headingDegrees = heading * 180/M_PI;
  
  Serial.println(headingDegrees); 

  // Invert headingDegrees so that instead of shining the LED
  // that corresponds to the direction, the LED shone is always
  // pointing north.
  headingDegrees = 360 - headingDegrees;

  if (state != ADVENTURE_BEGUN) {
    digitalWrite(northPin, LOW);
    digitalWrite(westPin, LOW);
    digitalWrite(eastPin, LOW);
    digitalWrite(southPin, LOW);
    digitalWrite(northEastPin, LOW);
    digitalWrite(southEastPin, LOW);
    digitalWrite(southWestPin, LOW);
    digitalWrite(northWestPin, LOW);
    return;
  } else {
    // Offset "0" to be in the direction of the stored location.
    headingDegrees = ((int) (headingDegrees + bearingBetweenCoords(currentLat, currentLon, storedLat, storedLon))) % 360;
  }
  //Light North LED
  if(headingDegrees > 337.5 or headingDegrees < 22.5)
  {
    digitalWrite(northPin, HIGH);
    digitalWrite(westPin, LOW);
    digitalWrite(eastPin, LOW);
    digitalWrite(southPin, LOW);
    digitalWrite(northEastPin, LOW);
    digitalWrite(southEastPin, LOW);
    digitalWrite(southWestPin, LOW);
    digitalWrite(northWestPin, LOW);
  }
  
  //Light South LED
  if(headingDegrees > 157.5 and headingDegrees < 202.5)
  {
    digitalWrite(northPin, LOW);
    digitalWrite(westPin, LOW);
    digitalWrite(eastPin, LOW);
    digitalWrite(southPin, HIGH);
    digitalWrite(northEastPin, LOW);
    digitalWrite(southEastPin, LOW);
    digitalWrite(southWestPin, LOW);
    digitalWrite(northWestPin, LOW);
  }
  
  //Light West LED
  if(headingDegrees > 247.5 and headingDegrees < 292.5)
  {
    digitalWrite(northPin, LOW);
    digitalWrite(westPin, HIGH);
    digitalWrite(eastPin, LOW);
    digitalWrite(southPin, LOW);
    digitalWrite(northEastPin, LOW);
    digitalWrite(southEastPin, LOW);
    digitalWrite(southWestPin, LOW);
    digitalWrite(northWestPin, LOW);
  }
  
  //Light East LED
  if (headingDegrees > 67.5 and headingDegrees < 112.5)
  {
    digitalWrite(northPin, LOW);
    digitalWrite(westPin, LOW);
    digitalWrite(eastPin, HIGH);
    digitalWrite(southPin, LOW);
    digitalWrite(northEastPin, LOW);
    digitalWrite(southEastPin, LOW);
    digitalWrite(southWestPin, LOW);
    digitalWrite(northWestPin, LOW);
  }
  
  //Light NorthEast LED
  if(headingDegrees > 22.5 and headingDegrees < 67.5)
  {
    digitalWrite(northPin, LOW);
    digitalWrite(westPin, LOW);
    digitalWrite(eastPin, LOW);
    digitalWrite(southPin, LOW);
    digitalWrite(northEastPin, HIGH);
    digitalWrite(southEastPin, LOW);
    digitalWrite(southWestPin, LOW);
    digitalWrite(northWestPin, LOW);
  }
  
    //Light SouthEast LED
  if(headingDegrees > 112.5 and headingDegrees < 157.5)
  {
    digitalWrite(northPin, LOW);
    digitalWrite(westPin, LOW);
    digitalWrite(eastPin, LOW);
    digitalWrite(southPin, LOW);
    digitalWrite(northEastPin, LOW);
    digitalWrite(southEastPin, HIGH);
    digitalWrite(southWestPin, LOW);
    digitalWrite(northWestPin, LOW);
  }
  
    //Light SouthWest LED
  if(headingDegrees > 202.5 and headingDegrees < 247.5)
  {
    digitalWrite(northPin, LOW);
    digitalWrite(westPin, LOW);
    digitalWrite(eastPin, LOW);
    digitalWrite(southPin, LOW);
    digitalWrite(northEastPin, LOW);
    digitalWrite(southEastPin, LOW);
    digitalWrite(southWestPin, HIGH);
    digitalWrite(northWestPin, LOW);
  }
  
    //Light NorthWest LED
  if(headingDegrees > 292.5 and headingDegrees < 337.5)
  {
    digitalWrite(northPin, LOW);
    digitalWrite(westPin, LOW);
    digitalWrite(eastPin, LOW);
    digitalWrite(southPin, LOW);
    digitalWrite(northEastPin, LOW);
    digitalWrite(southEastPin, LOW);
    digitalWrite(southWestPin, LOW);
    digitalWrite(northWestPin, HIGH);
  }
 
}

/*******************************************
 * record()
 *******************************************/
void record() {
  // To record, set record/playback pin low, then select message by setting message pin low
  digitalWrite(recordPlaybackPin, LOW);
  digitalWrite(recordingPin, LOW);
  recordBeginTime = millis();
  Serial.println(recordBeginTime);
}

/*******************************************
 * stopRecord()
 *******************************************/
void stopRecord() {
  resetRecordPlaybackState();
  unsigned long currentTime = millis();
  Serial.println(currentTime);
  Serial.println(recordBeginTime);
  Serial.println(currentTime - recordBeginTime);
  recordLength = currentTime - recordBeginTime;
  if (recordLength > MAX_RECORD_LENGTH) {
    recordLength = MAX_RECORD_LENGTH;
  }
  Serial.println(recordLength);
}

/*******************************************
 * playback()
 *******************************************/
void playback() {
  // To playback, set record/playback pin high, then select message by setting message pin low
  digitalWrite(recordPlaybackPin, HIGH);  
  digitalWrite(recordingPin, LOW);
}

/*******************************************
 * resetRecordPlaybackState()
 *******************************************/
void resetRecordPlaybackState() {
  digitalWrite(recordingPin, HIGH);
}

/*******************************************
 * colorWipe()
 *
 * Fill the dots progressively along the strip.
 *******************************************/
void colorWipe(uint32_t c, uint8_t wait, float progress) {
  float ledSoFar;
  
  ledSoFar = strip.numPixels()*(1-progress);
  for (int i=strip.numPixels(); i>=int(ledSoFar); i--) {
      strip.setPixelColor(i, c);
      strip.show();
      delay(wait);
 }
}

/*******************************************
 * finalLights()
 *
 * Show lights for reaching destination.
 *******************************************/
void finalLights() {

  flickerLights(strip.Color(  0,   0, 127), 200); // Blue
  flickerLights(strip.Color(  0,   127, 127), 200); // Cyan
  flickerLights(strip.Color(  127,   127, 127), 200); // White
  flickerLights(strip.Color(  0,   0, 127), 200); // Blue
  flickerLights(strip.Color(  0,   127, 127), 200); // Cyan
  flickerLights(strip.Color(  127,   127, 127), 200); // White
  
}

/*******************************************
 * flickerLights()
 *******************************************/
void flickerLights(uint32_t c, uint8_t wait) {
    for (int q=0; q < 3; q++) {
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, c);    //turn every third pixel on
      }
      strip.show();
     
      delay(wait);
     
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
}

/*******************************************
 * lightsOff()
 *******************************************/
void lightsOff() {
  for(int i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, 0);
  }
  strip.show();
  delay(200); // need delay after show?
}

Credits

Negah Nafisi

Negah Nafisi

4 projects • 2 followers
Alvin Yuan

Alvin Yuan

5 projects • 3 followers
Moeka Takagi

Moeka Takagi

4 projects • 1 follower
Sahana Rajasekar

Sahana Rajasekar

5 projects • 5 followers

Comments