Project showcase

Magic Cauldron

A "Magic Cauldron" that detects spell casting and responds with dynamic lighting effects.

  • 4,033 views
  • 3 comments
  • 18 respects

Components and supplies

Apps and online services

About this project

Another piece requested by my wife, this time to enhance a reading she did at the Ross Creek Centre for the Arts. Her new novel is called the Witches of New York and she wanted a "fortune telling" contraption that created a lighting effect [the reading was planned for outdoors around a fire, but weather forced it inside]. My solution was a "magic cauldron", that lit up when a special spell was cast [hand movements above it]. It was a huge hit and she has used it in other appearances [with some coding changes] to engage the public and her readers.

How it Works

In a nutshell, an old brass cauldron is given a false bottom, under which I hide the arduino and batteries. The US sensor is mounted under the false bottom [covered in Silver aluminum tape for reflectivity] and "looks" up through two camouflaged holes. We program it to detect a pattern of movements or just a "X time at X distance" affair [we found this worked better].

Once triggered the neopixel ring, mounted above the false bottom and under a mirror [to better reflect the light down and out the sides] activated. It would "play" a pattern that emulated fire or a specific colour that enhanced the performance.

Here are two videos of it in action....

*Maker's Note

My projects are generally larger scale with multiple systems and some arty components. As such, I rarely do the "full instruction" kind of project share. However, I am very open to answering specific questions left in comments about how/why I may have done something seen in the project posted here. I hope to inspire and possibly assist with these project showcases, so please feel free to post your comments/questions and I will do my best to address them.

More details coming for this one. I believe I took some pics to document it....

I had planned to use a capacitive touch sensor to activate the device, but the brass cauldron and battery power made pulling to ground a real issue.

Code

Magic Cauldron - storytellingArduino
It starts in "flickering flame" mode, that looks a lot like burning embers. Each time you hold your hand over the cauldron it displays a colour from a pre-determined sequence for a short time. It then makes a slow transition back to "flame" and waits until it is activated again.
// Source code for the Cauldron Project (2016)
// Written by Jonah McKay
//
//
// A Monkey Dream Monkey Do project
// http://www.monkeydreammonkeydo.com
// Contact e-mail: ian@amimckay.com

#include <Adafruit_NeoPixel.h>

#ifdef __AVR__
  #include <avr/power.h>
#endif

//PIN VARIABLES
const int neopixelPin = 5;
const int triggerPin = 9;
const int pingPin = A1;
const int debugPin = 10;

//NEOPIXEL COUNT
const int neopixelCount = 24;

float neopixelVariation[neopixelCount] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; //24 zeroes, count em

//ULTRASONIC VARIABLES
int ultrasonicDistance = 0;
boolean ultrasonicReady = false;
long timeSincePing = 0;
boolean ultrasonicPrimed = false;

boolean ultrasonicGesture = false;
long timeSinceGesture = 0;
long timeSincePrime = 0;

//PHASE VARIABLES
long transitionProgress = 0;
int currentPhase = 0;
long timeSinceTransitionUpdate = 0;

//BEGINNING START VARIABLES
int cyclesHeld = 0;
boolean started = false;

// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest
// example for more information on possible values.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(neopixelCount, neopixelPin, NEO_GRB + NEO_KHZ800);


void setup() {
  // put your setup code here, to run once:
  pixels.begin(); // This initializes the NeoPixel library.

  pinMode(pingPin, OUTPUT);
  pinMode(triggerPin, OUTPUT);
  pinMode(debugPin, INPUT_PULLUP);
  Serial.begin(9600);
}

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

  if (started) {
    
  
  if (digitalRead(debugPin) == LOW)
  {
      while (digitalRead(debugPin) == LOW)
      {
          delay(10);
      }
      nextPhase();
  }
  runCurrentPhase(currentPhase, transitionProgress);
  changeVariation(200, 10);
  delay(random(1, 100));
  if (millis() > timeSinceTransitionUpdate+100)
  {
    timeSinceTransitionUpdate = millis();
    transitionProgress += 1;
  }
  if (millis() > timeSincePing+500)
  {
    ultrasonicDistance = ping();
    //Serial.println(ultrasonicDistance);
    if (ultrasonicDistance < 60 && ultrasonicReady)
    {
        cyclesHeld++;
        if (cyclesHeld > 4)
        {
          nextPhase();
          cyclesHeld = 0;
          ultrasonicReady = false;
        }
    }
    
    if (ultrasonicDistance > 67)
    {
        ultrasonicReady = true;
        cyclesHeld = 0;
        
    }
    timeSincePing = millis();
    
  }
  }
  else //haven't started
  {
    readyState();
    changeVariation(200, 10);
    delay(random(1, 100));
    //Serial.println(ultrasonicDistance);
    if (millis() > timeSincePing+500)
    {
    ultrasonicDistance = ping();
    timeSincePing = millis();
    if (ultrasonicDistance < 60)
    {
      cyclesHeld++;
      if (cyclesHeld > 4)
      {
        //Serial.println("STARTING");
        started = true;
        nextPhase();
        cyclesHeld = 0;
      }
    }
    else
    {
      cyclesHeld = 0;
    }
    }
  }
}

long ping() {
  digitalWrite(triggerPin, LOW);
  delayMicroseconds(2);
  digitalWrite(triggerPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggerPin, LOW);
  long duration;

  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);

  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(pingPin, INPUT);
  duration = pulseIn(pingPin, HIGH);

  return duration / 29 / 2; //return in centimeters
}


/////////////////////////////////////////
//      FUNCTIONS FOR NEOPIXELS       ///
//                                    ///
//  Includes the variations, and the  ///
//              "phases"              ///
/////////////////////////////////////////

void changeVariation(int bounds, float shave) {
    for (int i=0;i<neopixelCount;i++){
    float upperBound = (bounds-(neopixelVariation[i]/2))/shave;
    float lowerBound = -( (bounds + (neopixelVariation[i]/2) ) / shave);
    neopixelVariation[i] += random(lowerBound, upperBound);
    
    }
}
float noNegative(float value)
{
    if (value < 0) {
     return 0;
    }
    else
    {
return value;
    }
}



void setWhiteLight(int i) {

  pixels.setPixelColor(i, pixels.Color(150+neopixelVariation[i],150+neopixelVariation[i],150+neopixelVariation[i])); // White
    pixels.show();
}
void safeState() {
  //Sets the lights to a clean, white colour.
  for(int i=0;i<neopixelCount;i++){
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    setWhiteLight(i);
   }
}

void safeTransitionState(int chance) {
    for(int i=0;i<neopixelCount;i++){
    if (random(0, 100) < chance-20)
    {
      setWhiteLight(i);
    }
    else if (random(0, 100) < chance)
    {
        pixels.setPixelColor(i, pixels.Color(50, 50, 50)); // Green Fire
        pixels.show();
    }
    else
    {
      setReadyLight(i);
    }
    }
   }

void safePhase(long spot) {
    if (spot < 200)
    {
        safeState();
    }
    else if (spot < 300)
    {
        safeTransitionState(100 - (spot-200));
    }
    else
    {
        readyState();
    }
}

void setFireLight(int i) {
    pixels.setPixelColor(i, pixels.Color(160+(neopixelVariation[i]*2), 40-neopixelVariation[i]/4,0)); // Fire
    pixels.show();
}
void fireState() {
  //Sets the lights to a firey, orange-yellow-red colour.
  for(int i=0;i<neopixelCount;i++){
        setFireLight(i);
    
   }
}

void firePhase(long spot) {
    fireState();
}

void setGreenLight(int i) {
    pixels.setPixelColor(i, pixels.Color(abs(neopixelVariation[i]/2), 200-abs(neopixelVariation[i]/2), noNegative(neopixelVariation[i]/2))); // Green Fire
    pixels.show();
}

void greenState() {
  //Sets the lights to a green flame colour
  for(int i=0;i<neopixelCount;i++){
    setGreenLight(i);
   }
}

void greenTransitionState(int chance) {
    for(int i=0;i<neopixelCount;i++){
    if (random(0, 100) < chance-20)
    {
      setGreenLight(i);
    }
    else if (random(0, 100) < chance)
    {
        pixels.setPixelColor(i, pixels.Color(150, 100-abs(neopixelVariation[i]/2), noNegative(neopixelVariation[i]/2))); // Green Fire
        pixels.show();
    }
    else
    {
      setReadyLight(i);
    }
    }
   }
void greenPhase(long spot)
{

    if (spot < 200)
    {
        greenState();
    }
    else if (spot < 300)
    {
        greenTransitionState(100 - (spot-200));
    }
    else
    {
        readyState();
    }
}

void setBlueLight(int i)
{
    pixels.setPixelColor(i, pixels.Color(abs(neopixelVariation[i]), 30+noNegative(neopixelVariation[i]), 200-(abs(neopixelVariation[i])*2))); // Blue Fire
    pixels.show();
}
void blueState() {
  //Sets the lights to a blue flame colour
  for(int i=0;i<neopixelCount;i++){
        setBlueLight(i);
    
   }
}

void blueTransitionState(int chance) {
    for(int i=0;i<neopixelCount;i++){
    if (random(0, 100) < chance-20)
    {
      setBlueLight(i);
    }
    else if (random(0, 100) < chance)
    {
        pixels.setPixelColor(i, pixels.Color(20-abs(neopixelVariation[i]/5), noNegative(neopixelVariation[i])/4, 100-abs(neopixelVariation[i]/5))); //blue transition fire
        pixels.show();
    }
    else
    {
      setReadyLight(i);
    }
    }
   }
void bluePhase(long spot)
{
    if (spot < 200)
    {
        blueState();
    }
    else if (spot < 300)
    {
        blueTransitionState(100 - (spot-200));
    }
    else
    {
        readyState();
    }
}
void setRedLight(int i)
{
    pixels.setPixelColor(i, pixels.Color(200-abs(neopixelVariation[i]/5), noNegative(neopixelVariation[i]/10), 0)); // Red Fire
    pixels.show();
}
void redState() {
  //Sets the lights to a blue flame colour
  for(int i=0;i<neopixelCount;i++){
        setRedLight(i);
    
   }
}

void redTransitionState(int chance) {
    for(int i=0;i<neopixelCount;i++){
    if (random(0, 100) < chance-20)
    {
      setRedLight(i);
    }
    else if (random(0, 100) < chance)
    {
        pixels.setPixelColor(i, pixels.Color(220-abs(neopixelVariation[i]/5), noNegative(neopixelVariation[i]/4), 80-abs(neopixelVariation[i]/3))); //Red transition fire
        pixels.show();
    }
    else
    {
      setReadyLight(i);
    }
    }
   }
void redPhase(long spot)
{

    if (spot < 200)
    {
        redState();
    }
    else if (spot < 300)
    {
        redTransitionState(100 - (spot-200));
    }
    else
    {
        readyState();
    }
}


void setPurpleLight(int i)
{
    pixels.setPixelColor(i, pixels.Color(200-abs(neopixelVariation[i]/3), 0, 0)); // Purple Fire
    pixels.show();
}
void purpleState() {
  //Sets the lights to a blue flame colour
  for(int i=0;i<neopixelCount;i++){
        setPurpleLight(i);
    
   }
}

void purpleTransitionState(int chance) {
    for(int i=0;i<neopixelCount;i++){
    if (random(0, 100) < chance-20)
    {
      setPurpleLight(i);
    }
    else if (random(0, 100) < chance)
    {
        pixels.setPixelColor(i, pixels.Color(200-abs(neopixelVariation[i]/5), 0, 100-abs(neopixelVariation[i]/5))); //Purple transition fire
        pixels.show();
    }
    else
    {
      setReadyLight(i);
    }
    }
   }
void purplePhase(long spot)
{

    if (spot < 200)
    {
        purpleState();
    }
    else if (spot < 300)
    {
        purpleTransitionState(100 - (spot-200));
    }
    else
    {
        readyState();
    }
}

void setReadyLight(int i) {
  pixels.setPixelColor(i, pixels.Color(5, 0, 0)); //red light
  pixels.show();
}


void readyState()
{
   int lightCount = random(1, 4);
   for(int i=0;i<neopixelCount;i++){
        
        if (i < lightCount)
        {
        setReadyLight(i);
        }
        else
        {
             pixels.setPixelColor(i, pixels.Color(0, 0, 0)); //blank
             pixels.show();
        }
    }
}

/////////////////////////////////////////
//         FUNCTIONS FOR ORDER        ///
//                                    ///
// Includes functions get which phase ///
//              we're in.             ///
/////////////////////////////////////////

void runCurrentPhase(int phase, long spot)
{
    if (phase == 0)
    {
        safePhase(spot);
    }
    else if (phase == 1)
    {
        greenPhase(spot);
    }
    else if (phase == 2)
    {
        bluePhase(spot);
    }
    else if (phase == 3)
    {
        redPhase(spot);
    }
    else
    {
        currentPhase = 0;
        safePhase(spot);
    }
}




void nextPhase()
{
    currentPhase++;
    transitionProgress = 0;
}

void skipToFire()
{
    transitionProgress = 300;
}

Comments

Similar projects you might like

Pac-Man LED Pixel Panel Costume

Project tutorial by Ben Muller

  • 5,178 views
  • 4 comments
  • 90 respects

LoRa Gateway for DeviceHive

Project tutorial by DeviceHive IoT team

  • 1,366 views
  • 2 comments
  • 17 respects

IoT Bird Feeder with Sigfox and Tweeter

Project showcase by Gaël Porté

  • 413 views
  • 0 comments
  • 7 respects

Raspberry Pi and Arduino Laptop

Project tutorial by Dante Roumega

  • 17,945 views
  • 6 comments
  • 45 respects

Arduino-Based Automatic Guitar Tuner

Project tutorial by Ben Overzat

  • 3,739 views
  • 0 comments
  • 12 respects
Add projectSign up / Login