Project tutorial

Mystick

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

  • 2,040 views
  • 19 comments
  • 5 respects

Components and supplies

About this project

Description

Mystick is an innovative hiking stick that promote exploration and allows you to share your hiking experiences and special findings with other users. When picked up by a user, the Mystick already has a predetermined location to which it directs using a compass and GPS to direct LED lights in the direction of the location. With the simple push of a button, the users progress to the chosen location can be sign through a proportional lighting display of the LEDs embedded along the length of the stick. Once the user arrives to the location, the Mystick glows in a full-length light show and a recording is played. Next, it is up to the user to continue exploring, and once they find a location they enjoy, they press the same button to save the spot as the new destination of the stick, and record a simple audio clip for the subsequent user. 

Motivation

We wanted to develop a transportation device that would incorporate artistic and exploratory facets with a distant socially interactive design. We chose to design a hiking stick to allow for an exploration of how technology can be utilized in the most natural of settings, a combination not often explored. 

Prototype Development Images

Step by Step Instructions

Exterior development:
- Purchase 4 foot wooden dowel, 1.125 inches in diameter for interior of hiking stick, as well as two two foot clear vinyl pipes, 1.875 inches in diameter exterior, 1.5 diameter hollow interior. Wanted to use acrylic pole initially, but the cost benefit ratio was not rational for an initial prototype.
- Use clamp to hold wooden pole in place, and use hand saw to saw 5 inches off of the end of the wooden dowel to allow for space in the stick at the top to encase the electronics and circuit boards of the design.
- In making the vinyl tubes opaque and less translucent, use 220 grain sandpaper to sand the poles, and use black buffer block to smooth off the exterior of the tubes. Initially wanted to use glass froster or stained glass paint on the tubing, but the paint wouldn't hold to the material. 

Extra Pieces:
- Using Adobe Photoshop, develop a logo for Mystick, entailing a clear but playful, and natural typeface and adding a mystical, exploratory flair with a north-star over the I. Print vinyl decals using the decal cutter, although not used for the final product.
- Using Adobe Illustrator, design bottom cap for the stick as well as stabilizer pieces to hold the wooden stick in place. We used a soft grain wood and cut one circular piece 1.875 inches in diameter, along with two rings with a 1.5 inch exterior and 1.125 inch interior diameter. Using hot glue, we glued the two rings on top of each other and then glued them to the middle of the bottom cap. This allows for the cap to be placed on the bottom of the stick, fitting snuggly but also allowing for a fit enclosure for the stick so that it would not move around inside the tubing. 
- Using Adobe Illustrator, develop a soft wooden cover for the LEDs. Cut eight identical holes in even spacing in a circular fashion to allow the LEDs to be placed in the negative space. 
- Using Adobe Illustrator, design a button for the hiking stick. We used a white sheet of acrylic, and rastered the MYSTICK logo into the center of it, utilizing a circular shape with a diameter 2mm smaller than that of the interior diameter of the LED enclosure in order to allow for easy pressing of the button without being too small. 
- Using Autodesk 123D, develop 3D modules for a two piece container to encapsulate the battery, some wiring, and the arduino board. Measure out a hole fitting the size of the speaker, and another the size of the microphone in order to allow for a fitted outlet for the pieces. 
- Using hot glue, glue the wooden LED encasement to the top portion of the 3D printed encasement.

Upload the code provided onto the arduino micro.

Solder together the electronics based off the circuit diagram.
- You have to be efficient with space. Large breadboards or prototype boards will not fit in the container.
- Follow good practice and put electrical tape or heat shrink over exposed joints.
- The wire connecting the compass should be fairly long. At least as long as the stick.
- The wire connecting the LED strip should be somewhat long, a couple of inches longer than the container's total height.

Hot glue the 8 green LEDs into the 8 holes in the container lid.
Hot glue the speaker in the speaker hole.
Place the button on top of the GPS module. Glue or tape it in place. Do not have it directly over the GPS antenna.
Place the GPS module under the LED circle, with the antenna pointing up.
Place the arduino micro under the GPS module.
Place the recorder module under the arduino with the microphone facing the microphone hole of the container. Part of the recorder module will stick out of the container (and eventually will fit inside the stick).
Glue or tape the container pieces together.
Wrap the LED strip coiling around the wooden stick (not too tightly though or you may damage the strip).
Slowly slide the frosted pieces over the wooden stick + LED strip. It should be a snug fit but not crush the strip.
Place the bottom cap  over the bottom of the stick. The wooden stick should rest on this cap. There should be some space at the top of the stick to put the battery inside the stick.
Cover the wire for the compass with nature-themed tubing, so it looks like a vine).
Wrap the tubing to coil around the outside of the stick.
Glue or tape the compass in place near the bottom of the stick (top is facing up). Test out the code and make sure the compass is oriented so that it correctly detects north. The compass must be placed far away from the electronics (particularly the speaker) to avoid interference.
When you are ready to turn on the device, plug the battery in, slip it into the stick, and glue or tape the container onto the top of the stick.

Circuit Diagram

Arduino Code

prov2.ino
Arduino Code

Warning: Embedding code files within the project story has been deprecated. To edit this file or add more files, go to the "Software" tab. To remove this file from the story, click on it to trigger the context menu, then click the trash can button (this won't delete it from the "Software" tab).

#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?
}

Case 3D Models

Observational Documentation

  • Human powered transportation devices can engage both social and antisocial behavior
  • Technology also encourages both social and antisocial behavior
  • People on campus are often in a rush, not looking up as they walk, bike, skate or scooter to class
  • We most often take the same paths to destinations we visit regularly
  • Idea of familiar strangers - many people we may recognize through sharing daily commutes, but do not actually know
  • Shoes are taken everywhere, used in most human powered transportation devices
  • On transportation devices with wheels, such as bikes, scooters, skateboards, we are often too engrossed in the direction we are heading and maintaining balance and safety to the extent we miss what is going on around us 
  • Old people using walkers are often lonely looking
  • Skaters are normally in packs, very social device
  • When walking, many people are zoned in on a technological device, immune to their surroundings or journey

Ideation Process

When identifying a human powered transportation device to alter for this project, Mystick was one of many ideas considered.

Initially, we discussed the idea of exploring the use of a transportation device to educate young children on science basics and to introduce them to the basics of units and measurements. We also thought of developing an ice skate that would be tracked via GPS to create a collaborative art piece projected above an arena using the movements of multiple skaters. We discussed developing a shoe that could project arrows ahead of the user to direct them to a specific location, while snapping photos of the path behind the user. We considered developing a skateboarding tool to develop unique pieces of art based off of the movements and path of the user. We were also curious to see how we could utilize these transportation devices to both promote social and antisocial behaviors. We considered equipping walkers with sensors to locate groups of two or more other walkers to allow the elderly to more easily locate and engage with each other. Similarly, we discussed the idea of allowing skateboarders the ability to locate the closest other skateboard in use, to facilitate the social nature of the sport. We began thinking of a device that could trace your path through a day and transmit it to a secondary interface to be looked at later in communion with others, effectively allowing you to see who and with how many people you cross paths which each day. Lastly, we explored the idea of a device that could reroute you based on the number of people you would encounter on your current path, based off of whether you wanted to socialize or be alone. 

We decided to incorporate our ideas for art, exploration, and distant sociality through the design of Mystick.

Design Process Images

Branding

Design Printing Files

Bottom cap, stabilizers

Park Sign

In Situ Demo

https://docs.google.com/presentation/d/1mhb3mas13yTVdMwsxzNhgo1QStroHDBQdb2y7KMSpaI/edit#slide=id.g1...

User Interaction

We tested our product with different demographics, with the following feedback:
- Overall positive reaction
- Great interest in Mystick as a tool to bring people closer to nature
- Visually appealing light display
- Enjoyed the simple interface of one button
- Excitation for further adaptations and curiosity for what the aesthetics of the final product would look like

User Testing

Future Adaptations

  • Allow for cross-user interactions, with tapping of sticks leading to fully lit up sticks and possible transfer of GPS destination and/or audio files
  • Allow for digital breadcrumb, allowing user to be directed back to the home base where they picked up the stick, as well as encouraging exploration through a secure path back 
  • Use in national park system
  • GPS constrained setting to shut down the stick if taken outside the vicinity of the park
  • Development of online database to track where each stick has traveled, users responses to other users' choice of location and recording, enticing a more social interaction

Code

prov2.inoC/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?
}

Custom parts and enclosures

bottom-mystick.stl

Sketchfab still processing.

top-mystick.stl

Sketchfab still processing.

Comments

Similar projects you might like

DIY Relay Outlet Arduino

Project tutorial by Ben Jones

  • 23,885 views
  • 17 comments
  • 31 respects

SketchBoard

Project tutorial by SketchBoard

  • 4,256 views
  • 20 comments
  • 3 respects

Kite Power Autopilot

Project in progress by 3 developers

  • 3,727 views
  • 15 comments
  • 7 respects

Unlock your door with a knock

Project tutorial by Ashraf Nabil

  • 19,456 views
  • 10 comments
  • 54 respects

Magoo

Project in progress by 4 developers

  • 7,271 views
  • 12 comments
  • 34 respects

Sole Searching

Project in progress by 5 developers

  • 7,227 views
  • 12 comments
  • 12 respects
Add projectSign up / Login