Project tutorial

Wizard's Walking Staff © LGPL

Make your own gesture-controlled Wizard's Walking Staff that lights up to different spells when you perform gestures. You shall not pass!

  • 4,751 views
  • 5 comments
  • 36 respects

Components and supplies

Ardgen 101
Arduino 101 & Genuino 101
×1
$4 Pololu Voltage Regulator
×1
3x AA battery holder w/switch
×1
61vhxtmzbyl
AA Batteries
Any type is fine. You need 3.
×1
M Barrel Jack Adapter
$1.95 These make it easy to connect & disconnect power
×1
F Barrel Jack Adapter
$1.95 These make it easy to connect & disconnect power
×1
Addressable LED Strip
The example is written for any NeoPixel compatible LEDs, 60 pixel strip.
×1
Sports Tape
Any kind of cloth tape. This is used to hold the electronics on the staff and make a nice grip.
×1
Rubber Bands
To help hold parts in place on the staff until you can use tape.
×1
A Staff. (broomstick, sprinkler pipe, natural walking stick, etc)
×1

Apps and online services

About this project

This project uses the Arduino 101, a battery and an LED strip to make a gesture-controlled Wizard's Walking Staff. We'll define three different light-display "spells" you can summon by moving the staff in one of three gestures.

Tech:

The code uses the Inertial Measurement Unit in the Arduino 101 to read G-forces, but does not use the Pattern Matching Engine (for an example using the PME, check out Kitty Yeung's PME project). Instead, this code counts quick impulses in the X, Y, and Z directions and summons spells when you perform a certain combination in a short time.

Casting Spells:

Two quick vertical taps starts a spell, then you can either move the staff sharply forward, left/right, or tap a third time to select one of the three spells, then a final tap casts the spell.

Visual Feedback:

To help you understand where you are in your gesture, the example code includes visual feedback from the LED strip. When you first perform a double-tap, the lights will quickly flash white, telling you that a spell is started. You'll get visual feedback for each step so you know which spell will be selected.

Casting Spells (in detail):

Hold the staff with the Arduino 101 facing you. First double-tap the staff (gently!) on the ground, or do two quick vertical pulses in the air. When you see the white flash, move the staff sharply forward or back to select Spell 1, or tilt the staff left or right to select Spell 2, or move vertically a third time to select Spell 3. When a spell is selected, the lights will glow solid green (spell 1,) blue (spell 2) or red (spell 3.) Once the lights are solid, summon the spell with a final vertical tap. The lights will shimmer and flash in the selected color.

Gestures are time sensitive! You have only 1 second between each step in a gesture or the spell will reset. That means if you mess-up or select the wrong spell, you can just wait a second and start over. Accurately summoning spells is a little tricky to master, but the good news is that spells won't be accidentally called when you're just walking around with the staff.

Building the Electronics:

This is basically just an Arduino 101, a battery, and a strip of NeoPixel LEDs.

There are several ways to configure power. The quickest would be to use a 9v battery and run the LEDs right off the Arduino 101- but the battery life would be very short. You could also use a LiPo battery to maximize power/weight for a more "pro" build- but you would need a safe way to charge the battery. For me, a good balance between battery life and simplicity is to use 3 AA batteries and a $4 voltage regulator. This is the same electronics setup as in the Shadow Theater and Kaleidoscope Infinity Mirror.

Step 1

Collect the end harness from your LED strip by cutting about 1 cm from the end of the lights. Strip the wire ends.

Step 2

Break apart the bent header pins that came with your voltage regulator, and solder two of them to the ends of the green and yellow wires. This will let you easily plug them into the pins of the Arduino 101.

Step 3

Next solder the red wire to the VIN terminal on the voltage regulator, and solder two straight headers into the GND and VOUT pins as shown.

Step 4

Now connect the barrel jack adapters. First strip the ends of the wires coming from your battery case, then mount them in the terminals of the male barrel jack and tighten with a screwdriver. RED goes to the (+) terminal, BLACK goes to the (-) terminal.

Next connect the female barrel jack adapter onto to the loose wires coming from the beginning of the LED strip. YELLOW goes to the (-) terminal, and RED goes to the (+).

Step 5

Now connect your harness to the Arduino 101. The GREEN wire goes to PIN 6, the YELLOW wire goes to the GND pin near #13. With the two header pins on the voltage regulator, GND goes to GND and VOUT goes to the "VIN" pin on the Arduino 101. Don't get this wrong! Plugging in backwards leads to smells and burns. I know this.

Uploading the Example Code:

Step 1

With the battery-pack turned off, use a USB cable to connect your Arduino 101 to a computer, and upload the attached example code using the Arduino IDE.

If you've never programmed an Arduino 101 before, there are great instructions on the Arduino website here: https://www.arduino.cc/en/Guide/Arduino101

Once the example code is loaded successfully, you can disconnect the USB, and test the code by turning on the battery pack and trying some of the spell gestures with the USB port on the Arduino 101 facing down, and with the pins facing you. Try moving the board vertically four times to call the Red Spell.

Code Discussion:

(If you just want to use the example-code as-is, you can skip down to Building the Staff.)

The example code uses the shock-detection capability of the Arduino 101 to count shocks, while a timer runs that re-sets the shock count to zero after a short time with no new shocks. If you hit the right combination before the timer expires, a "spell" function is called.

Because a firm tap on the ground will actually register the initial shock plus several "noise" shocks from the vibrations in the staff, the first code in loop is just trying to find a dominant impulse from each batch of shocks, and categorise it simply as vertical, left/right, or front/back. I use a variable called "gesture" to hold each dominant impulse: 1 = vertical impulse, 2 = front/back impulse, 3 = left/right impulse.

Then to track combinations, the code uses a "switch" statement, and moves through it as gestures accumulate. It's sort of like a "choose your own adventure" book, where each step says something like "if the next impulse is left/right, move on to step 2, if the timer has expired, go back to zero."

Spells:

Here's where you can easily change what each spell does. These are currently just running "theater-chase" example code in different colors. You could easily add more, change the colors, or write your own light function here. The first three variables in the theaterChase function are values for red, green, and blue. The last number is the speed (higher number = slower).

/////////////////////////////////////////////////spells////////////////////// 
void tapspell() { 
 ///red theatre lights 
 theaterChase(strip.Color(127, 20, 0), 20); 
 theaterChase(strip.Color(127, 20, 50), 55); 
 strip.show(); 
} 
void leftspell() { 
 theaterChase(strip.Color(0, 0, 100), 20); 
 theaterChase(strip.Color(0, 30, 120), 75); 
 strip.show(); 
} 
void forwardspell() { 
 theaterChase(strip.Color(0, 127, 0), 30); 
 theaterChase(strip.Color(0, 127, 90), 55); 
 strip.show(); 
} 

Building the Staff:

There is lots of room for creativity here! I'll focus on the basic tech build, but have fun making a unique staff. Light diffusers such as tulle, tissue paper, or a plastic bottle over the LEDs can make very cool light displays!

Step 1: Find a Hiking Stick/Staff

You can use a broomstick, sprinkler pipe, or find a natural stick. Michael's craft stores also sell nice ones. (In the stick section- really.) I like to wrap a section with sports tape to have a clean work area for the electronics, but that's optional.

Put a few rubber-bands on the staff to make it easier to attach things.

Step 2: Place the Arduino 101 on the Staff with the USB Port Down

Use a rubber band to hold the Arduino 101 in place.

Step 3: Using a Rubber Bands to Help at Both Ends, Wrap the LEDs

Step 4: Wrap Sports Tape to Secure the Arduino 101 and Battery Pack

Be sure to leave access to the on/off switch on the battery pack. You want the battery pack secure for jostles / taps, but remember you'll need to change those batteries eventually.

Step 5: Diffuse the LED Lights and Decorate

Code

Wizard Walking Staff Example CodeArduino
Count impulses on the x, y, and z axis, certain impulse combinations summon "spells" (light displays) on a neopixel strip.
/*
   Copyright (c) 2016 Intel Corporation.  All rights reserved.
   See license notice at end of file.
*/

/*
  Arduino 101 "Wizard's Walking Staff." 
  This code uses the Adafruit Neopixel library. Library installation instructions here: https://learn.adafruit.com/adafruit-neopixel-uberguide/arduino-library-installation
  Make sure you have the latest Intel Curie Core installed.  For more info, visit https://www.arduino.cc/en/Guide/Arduino101
*/

#include "CurieIMU.h"
#include <Adafruit_NeoPixel.h>
#define PIN 6  //// what pin are the NeoPixels connected to?
Adafruit_NeoPixel strip = Adafruit_NeoPixel(60 , PIN, NEO_GRB + NEO_KHZ800);  ///  the strip is 60 pixels long.

int tr = 0;  //Some variables to hold color target and color current for smoothing...
int tg = 0;
int tb = 0;
int r = 0;
int g = 0;
int b = 0;

long int globaltimer = 0;  /// timers to keep track of gestures vs time...for things like "if timer hasn't passed 0.5sec, AND there's two taps, then..."
long int gesturetimer = 0;
long int ledtimer = 0;

int fade = 10;  /// how quickly lights fade.  Used for smoothing

int tap = 0;  //couter for vertical impulses

int lr = 0;   //couter for left/right impulses

int fb = 0;   //couter for forward/back impulses

int gesture = 0;  // 
int state = 0;  /// for our switch case...  this will keep track of each movement in a series.





void setup() {
  // put your setup code here, to run once:
  //Serial.begin(9600);

  globaltimer = millis();  // start timekeepers at current time
  gesturetimer = millis();
  ledtimer = millis();

  /* Initialise the IMU */
  CurieIMU.begin();
  CurieIMU.attachInterrupt(eventCallback);

  /* Enable Shock Detection */
  CurieIMU.setDetectionThreshold(CURIE_IMU_SHOCK, 1500); // 1.5g = 1500 mg
  CurieIMU.setDetectionDuration(CURIE_IMU_SHOCK, 50);   // milliseconds of spike required to call interupt
  CurieIMU.interrupts(CURIE_IMU_SHOCK);

  strip.begin();  //  intialize neopixel strip
  strip.show();   // Initialize all pixels to 'off'
}

void loop() {
  // put your main code here, to run repeatedly:


  ////basic filter- the IMU registers multiple shocks from rebound and counteraction.  This tries to capture the dominant shock in each gesture.

  if (millis() - globaltimer > 170) {  ///  this tries to find the dominant axis of movement for each shock..  Compares the sum of tap, left-right, and front-back movements, and picks the largest.
    if ((tap > lr) && (tap > fb)) {
      Serial.println("tap");
      gesture = 1;
      tap = 0; lr = 0; fb = 0; ////  reset values after a movement is classified.
    }
    else if ((lr > fb) && (lr > tap)) {
      Serial.println("lr");
      gesture = 2;
      tap = 0; lr = 0; fb = 0;
    }
    else if ((fb > lr) || (fb > tap)) {
      Serial.println("fb");
      gesture = 3;
      tap = 0;  lr = 0; fb = 0;
    }
  }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if (millis() - globaltimer > 1000) {  /////timeoutreset
    globaltimer = millis() - 170;
tr = 0; tg=0; tb = 0;
    state = 0;
    //gesture=0;
  }
  if (millis() - gesturetimer > 1000) {
    gesturetimer = millis() - 350;
tr = 0; tg = 0; tb = 0;
    state = 0;
     //gesture=0;
  }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  switch (state) {   // This tracks gestures

    case 0: {  /// no gestures recorded yet...  listen for tap.  If there's one, go to case 1.
        if (millis() - gesturetimer > 350) {  
          if (gesture == 1) {
            state = 1;
            gesture = 0;
            gesturetimer = millis();
          }
        }

        break;
      }
    case 1: {  ///// one tap recorded.  If a second tap happens, play a quick flash then go to step 2.
        if (millis() - gesturetimer > 350) {  
          if (gesture == 1) {
            r=10;g=10;b=10;  ///feedback flash
            state = 2;
            gesture = 0;
            gesturetimer = millis();
            
          }
        }

        break;
      }
    case 2: {  ///////Switch point -  two taps recorded.  The three spells diverge here based on the next gesture.  If it's a tap, go to case 3.  If it's L/R, go to case 4. If it's Front/Back, go to case 5.
        
        if (millis() - gesturetimer > 350) {
          if (gesture == 1) {
            state = 3;
            gesture = 0;
            gesturetimer = millis();
          }
          if (gesture == 2) {
            state = 4;
            gesture = 0;
            gesturetimer = millis();
          }
          if (gesture == 3) {
            state = 5;
            gesture = 0;
            gesturetimer = millis();
          }
        }

        break;
      }
    case 3: {  ////  three taps recorded...  we're in the tap spell, turn the staff red and listen for the final "tap" to set off the spell.
      tr = 20; tg = 0; tb = 0;
      globaltimer = millis()-250;
      if (millis() - gesturetimer > 350) {
        if (gesture == 1) {
          state = 0;
          Serial.println("tapspell!");
          tapspell();
        }
      }
        break;
      }
    case 4: {  /////  two taps and a L or R recorded...  we're in the leftspell, turn the staff blue and listen for the final "tap" to set off the spell.
      tr = 0; tg = 0; tb = 20;
      globaltimer = millis()-250;
      if (millis() - gesturetimer > 350) {;
        if (gesture == 1) {
          state = 0;
          Serial.println("leftspell!");
          leftspell();
        }
      }

        break;
      }
    case 5: {  /////  ////  two taps and a forward or back recorded...  we're in the forwardspell, turn the staff green and listen for the final "tap" to set off the spell.
      tr = 0; tg = 20; tb = 0;
      globaltimer = millis()-250;
      if (millis() - gesturetimer > 350) {
        if (gesture == 1) {
          state = 0;
          Serial.println("forwardspell!");
          forwardspell();
        }
      }

        break;
      }
defaut: {
        break;
      }
  }
  //Serial.println(tr);
  
if (millis()-ledtimer > fade){   /// only do this next step periodically every (fade value) milliseconds.  Unlike the "delay()" function, this allows other things to happen in the program in between updates.

   /// color smoothing.  Actual color moves toward target color...  If target is more than curent, move up, if less, move down.
  if (tr > r + 1) {
    r++;
  }
  if (tg > g + 1) {
    g++;
  }
  if (tb > b + 1) {
    b++;
  }
  if (tr < r) {
    r--;
  }
  if (tg < g) {
    g--;
  }
  if (tb < b) {
    b--;
  }
  //turn all the LEDS to the current r, g, b values.
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, r, g, b);
  }
  strip.show();
  ledtimer=millis();
}

}

///////////When a shock is detected, the following code interrupts the loop.

static void eventCallback(void)
{
  if (CurieIMU.getInterruptStatus(CURIE_IMU_SHOCK)) {
    if (CurieIMU.shockDetected(X_AXIS, POSITIVE)) {
      tap++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(X_AXIS, NEGATIVE)) {   /// for now, just classifying shocks based on their axis.  positive and negative shocks both are the same.
      tap++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(Y_AXIS, POSITIVE)) {
      lr++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(Y_AXIS, NEGATIVE)) {
      lr++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(Z_AXIS, POSITIVE)) {
      fb++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(Z_AXIS, NEGATIVE)) {

      fb++;
      globaltimer = millis();
    }
  }
}

/////////////////////////////////////////////////spells//////////////////////
void tapspell() {
  ///red theatre lights
  theaterChase(strip.Color(127, 20, 0), 20);
  theaterChase(strip.Color(127, 20, 50), 55);
  strip.show();
}

void leftspell() {
  theaterChase(strip.Color(0, 0, 100), 20);
  theaterChase(strip.Color(0, 30, 120), 75);
  strip.show();
}

void forwardspell() {
  theaterChase(strip.Color(0, 127, 0), 30);
  theaterChase(strip.Color(0, 127, 90), 55);
  strip.show();
}


////////////////Spell functions for lights////////// From the Adafruit Neopixel Strandtest Example
//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
  for (int j = 0; j < 10; j++) { //do 10 cycles of chasing
    for (int q = 0; q < 3; q++) {
      for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
        strip.setPixelColor(i + q, c);  //turn every third pixel on
      }
      strip.show();

      delay(wait);

      for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
        strip.setPixelColor(i + q, 0);      //turn every third pixel off
      }
    }
  }
}

  ////////////////////////////////////////////////////////////////////////////////////////////
/*
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

Comments

Similar projects you might like

Guess the Number With Arduino KeyPad

Project tutorial by Giovanni Gentile

  • 21 views
  • 2 comments
  • 3 respects

Control a 7 Segment Display with a keypad!

Project tutorial by Isaac100

  • 34 views
  • 2 comments
  • 2 respects

Launch Pad Count Down Sequence Display

Project in progress by Douglas Frey

  • 54 views
  • 0 comments
  • 2 respects

Know the Temp of Your Home From Anywhere with Arduino Cloud!

Project tutorial by Isaac100

  • 1,817 views
  • 2 comments
  • 9 respects

Safe City: A Device to Keep You Safe

Project tutorial by Patel Darshil

  • 1,094 views
  • 0 comments
  • 8 respects

Water Softener

Project tutorial by moty

  • 540 views
  • 0 comments
  • 6 respects
Add projectSign up / Login