Michael Concannon
Published © GPL3+

Time Traveling Elevator - Theater Prop

Our school show needed a time traveling elevator. The perfect prop was an Arduino driven prop with WS2811 LED lights and a servo.

IntermediateFull instructions provided3 hours874
Time Traveling Elevator - Theater Prop

Things used in this project

Hardware components

Arduino
×1
WS2811 Pixel Lights
×1
Servo Module (Generic)
×1
Power Supply 5V
×1

Story

Read more

Schematics

Schematic

Code

Arduino Code

C/C++
#include <Adafruit_NeoPixel.h>
#include <Servo.h> 
#include <SPI.h>
#include <Ethernet.h>

// PWM pins (3, 5, 6, 9, 10, or 11)
#define FOG_PIN 3
#define SD_CARD_BLOCK 4
#define LIGHT_PIN 5       // Control the light strip
#define ARROW_PIN 9       // Control the servo
#define N 50              // # of lights in the chain
#define FLUX_START 8      // first light in the flux
#define FLUX_END 21       // last light in the flux
#define ELEVATOR_START 26   // first light in the elevator arc
#define ELEVATOR_END 50     // not inclusive (so this is the last index + 1)
#define LIGHTS_PER_FLOOR 3

#define DEG_PER_FLOOR 22    // servo degees per floor
#define DEG_FLOOR_ZERO 180

#define FLUX_DELAY 100
#define FLOOR_DELAY 1000
#define RAINBOW_DELAY 200
#define ARROW_DELAY 1000
#define FOG_DELAY 3000
#define SERVO_DETACH_DELAY 2000   // Need to detach servo to prevent jitter

// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(N, LIGHT_PIN, NEO_RGB + NEO_KHZ800);

byte mac[] = { 0xDE, 0xAD, 0xEE, 0xEF, 0xFE, 0xEF }; //physical mac address
byte ip[] = { 192, 168, 1, 27 }; // arduino server ip in lan
EthernetServer server(80); //arduino server port

// Some predefined color values
uint32_t elevator_off = strip.Color(0, 0, 10);
uint32_t elevator_on = strip.Color(250, 0, 0);
uint32_t flux_back = strip.Color(0, 20, 0);
uint32_t flux_move = strip.Color(120, 120, 120);
uint32_t flux_back_low = strip.Color(10, 0, 10);
uint32_t flux_move_low = strip.Color(0, 20, 0);
uint32_t flux_back_high = strip.Color(0, 70, 0);
uint32_t flux_move_high = strip.Color(250, 250, 250);

// Timers allow the main thread to continue
long floorTimer = 0;
long fluxTimer = 0;
long jitterTimer = 0;
long rainbowTimer = 0;
long crazyArrowTimer = 0;
long fogTimer = 0;

// Array of the index of light that make up the flux C
byte fluxPosition[5][3]={
 {8,14,20},
 {9,15,19},
 {10,13,18},
 {11,16,17},
 {12,12,12}
};

Servo arrowServo;  // create servo object to control a servo 

int currentFloor = 0;
int destinationFloor = 0;
int fluxStep = 0;
boolean noJitter = true;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);

  // Network start
  Ethernet.begin(mac, ip);
  server.begin();
  debug("Setup complete");

  // disable SD card
  pinMode(SD_CARD_BLOCK, OUTPUT);
  digitalWrite(SD_CARD_BLOCK, HIGH);

  pinMode(FOG_PIN, OUTPUT);
  digitalWrite(FOG_PIN, HIGH);    // fog off

  strip.begin();
  setAll(strip.Color(0,0,0));

  setFloor(1);
  setFluxPower(1);
}

void loop() {
  checkNetwork();
  checkTimers();
  delay(40);
}

// Check to see if any of the timers need the next step
// Using these timers help prevent locking of the single thread
void checkTimers() {
  if(floorTimer > 0 && floorTimer < millis()) {
    floorTimer = 0;
    stepFloor();
  }
  if(fluxTimer > 0 && fluxTimer < millis()) {
    fluxTimer = 0;
    stepFlux();
  }
  if(jitterTimer > 0 && jitterTimer < millis()) {
    jitterTimer = 0;
    arrowServo.detach();
  }
  if(rainbowTimer > 0 && rainbowTimer < millis()) {
    rainbowTimer = 0;
    stepRainbow();
  }
  if(crazyArrowTimer > 0 && crazyArrowTimer < millis()) {
    stepArrow();
    crazyArrowTimer = millis() + ARROW_DELAY;
  }
  if(fogTimer > 0 && fogTimer < millis()) {
    fogTimer = 0;
    digitalWrite(FOG_PIN, HIGH);
  }
}

void debug(String s) {
  //Serial.println(s);
}

void checkNetwork() {
  int from, to;
  String readString;
  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        //read char by char HTTP request
        if (readString.length() < 100) {
          //store characters to string 
          readString += c; 
        } 

        //if this is the end of a single line
        if (c == '\n') {
          debug(readString);
          delay(1);

          // The loop ignore all string that are not a get request
          if(readString.indexOf("GET") == 0) {
            Serial.println(readString);
            debug(readString);
            from = readString.indexOf("/") + 1;
            to = readString.indexOf("/", from);
            if((to - from) == 1) {  // ignore everything but a single character URL
              c = readString.charAt(from);
  
              if(c == 'a') {  // 'a' is command for set all
                from = to+1;
                to = readString.indexOf(",", from);
                int red = parseString(readString, from, to);
  
                from = to + 1;
                to = readString.indexOf(",", from);
                int green = parseString(readString, from, to);
  
                from = to + 1;
                to = readString.indexOf(" ", from);
                int blue = parseString(readString, from, to);
  
                setAll(strip.Color(red, green, blue));
              }
              
              if(c == 'f') {  // f = floor
                from = to + 1;
                to = readString.indexOf(" ", from);
                int floor = parseString(readString, from, to);
                setFloor(floor);
              }
              if(c == 'g') {  // g = go to floor
                from = to + 1;
                to = readString.indexOf(" ", from);
                destinationFloor = parseString(readString, from, to);
                stepFloor();
              }
              
              if(c == 'c') {  // begin flux capacitor
                from = to + 1;
                to = readString.indexOf(" ", from);
                int level = parseString(readString, from, to);
                setFluxPower(level);
                stepFlux();
              }
              if(c == 'C') {  // end/stop the flux
                fluxTimer = 0;
                setFluxColor(strip.Color(0,0,0));
              }
              
              if(c == 'r') {  // r = crazy rainbow
                stepRainbow();
              }
              if(c == 'R') {  // e = end/stop the rainbow
                rainbowTimer = 0;
                setFloor(currentFloor);
              }
  
              if(c == 'z') {  // z = fog
                fog();
              }
  
              if(c == 's') {  // Random arrow
                stepArrow();
                crazyArrowTimer = millis() + ARROW_DELAY;
              }
              if(c == 'S') {  // Stop arrow
                crazyArrowTimer = 0;
                setFloor(currentFloor);
              }
  
              if(c == 'w') {  // Go wild
                // Flux power to high and turn it on
                setFluxPower(2);
                stepFlux();
                
                // Crazy arrow
                stepArrow();
                crazyArrowTimer = millis() + ARROW_DELAY;
                // Crazy rainbow
                stepRainbow();
              }
              if(c == 'W') {  // end wild
                crazyArrowTimer = 0;
                rainbowTimer = 0;
                setFluxPower(1);
                delay(200);
                setFloor(currentFloor);
              }
            }

            htmlResponseS(client);
            //stopping client
            client.stop();
            Serial.println("Stopped");
            debug("stopped");
          }
          //clearing string for next read
          readString="";
        }
      }
    }
    Serial.println("Exit");
  }
}

int parseString(String s, int from, int to) {
  char carray[6];
  s.substring(from, to).toCharArray(carray, to - from + 1);
  int i = atoi(carray);
  return i;
}

// Full html response
void htmlResponse(EthernetClient client) {
    //now output HTML data header
    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: text/html");
    client.println();
    client.println("<HTML>");
    client.println("<HEAD>");
    client.println("<TITLE>Kapers</TITLE>");
    client.println("<style>");
    client.println("body {   font-size: 75pt; text-align: center; }");
    client.println("</style>");
    client.println("</HEAD>");
    client.println("<BODY>");
    client.println("<a href=\"/c/\">Start</a> Flux Cap. <a href=\"/C/\">Stop</a><br/>");
    client.println("<a href=\"/w/\">Start</a> Wild <a href=\"/W/\">Stop</a><br/>");
    client.println("GO ");
    for(int i = 1; i <= 8; i++) {
      client.print("<a href=\"/g/");
      client.print(i);
      client.print("\">");
      client.print(i);
      client.print("</a> ");
    }
    client.println("<br/>");
    client.println("Jump ");
    for(int i = 1; i <= 8; i++) {
      client.print("<a href=\"/f/");
      client.print(i);
      client.print("\">");
      client.print(i);
      client.print("</a> ");
    }
    client.println("<br/>");
    client.println("<a href=\"/z/\">Fog</a><br/>");
    
    client.println("<br/>");
    client.println("</BODY>");
    client.println("</HTML>");
}

// Shortened response - easier when run from curl
void htmlResponseS(EthernetClient client) {
    //now output HTML data header
    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: text/html");
    client.println();
    client.println("<HTML><HEAD><BODY>Kapers</BODY></HTML>");
}

// Set all the lights to a single color
void setAll(uint32_t color) {
  for(uint16_t i=0; i < N; i++) {
      strip.setPixelColor(i, color);
  }
  strip.show();
}

void setRange(uint32_t color, int from, int to) {
  for(uint16_t i=from; i < to; i++) {
      strip.setPixelColor(i, color);
  }
  //strip.show();
}

void fog() {
  digitalWrite(FOG_PIN, LOW);
  fogTimer = millis() + FOG_DELAY;
}

// -------------------------- Elevator functions ------------------------------
// Jump to a floor
void setFloor(int floor) {
  currentFloor = floor;

  jitterTimer = millis() + SERVO_DETACH_DELAY;
  if(!arrowServo.attached()) {
    arrowServo.attach(ARROW_PIN);  // attaches the servo on a pin to the servo object 
  }
  arrowServo.write(DEG_FLOOR_ZERO - (DEG_PER_FLOOR * floor));

  // Set up to the floor (option 1)
  //setRange(elevator_on, ELEVATOR_START, ELEVATOR_START + (LIGHTS_PER_FLOOR * floor));
  //setRange(elevator_off, ELEVATOR_START + (LIGHTS_PER_FLOOR * floor), ELEVATOR_END);

  // Set only the desired floor (option 2)
  setRange(elevator_off, ELEVATOR_START, ELEVATOR_END);
  setRange(elevator_on, ELEVATOR_START + (LIGHTS_PER_FLOOR * (floor-1)), ELEVATOR_START + (LIGHTS_PER_FLOOR * floor));

  strip.show();
}

// Walk to a floor in steps - Walks one floor and sets timer if needed
void stepFloor() {
  if(currentFloor > destinationFloor) {
    setFloor(currentFloor - 1);
  }
  if(currentFloor < destinationFloor) {
    setFloor(currentFloor + 1);
  }
  if(currentFloor != destinationFloor) {
    floorTimer = millis() + FLOOR_DELAY;
  }
}

void stepRainbow() {
  int randNumber = random(300);
  
  for(int i = ELEVATOR_START; i < ELEVATOR_END; i++) {
    strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + randNumber) & 255));
  }
  strip.show();

  rainbowTimer = millis() + RAINBOW_DELAY;
}

int lastArrow = 0;
void stepArrow() {
  int randNumber = 20 + random(140);

  while(abs(lastArrow - randNumber) < 30) {
    randNumber = 20 + random(140);
  }
  
  if(!arrowServo.attached()) {
    arrowServo.attach(ARROW_PIN);  // attaches the servo on a pin to the servo object 
  }
  arrowServo.write(randNumber);
}

// ------------------------- Flux capacitor -------------------------
void stepFlux() {
  int j;

  setRange(flux_back, FLUX_START, FLUX_END);
  for(j = 0; j < 3; j++) {
    strip.setPixelColor(fluxPosition[fluxStep][j], flux_move);
  }
  strip.show();
  fluxStep++;
  if(fluxStep == 5) {
    fluxStep = 0;
  }
  fluxTimer = millis() + FLUX_DELAY;
}

void startFlux() {
  fluxTimer = millis() + FLUX_DELAY;
}

void setFluxColor(uint32_t color) {
  setRange(color, FLUX_START, FLUX_END);
  strip.show();
}

void setFluxPower(int power) {
  if(power >= 2) {
    flux_back = flux_back_high;
    flux_move = flux_move_high;
    debug("Power set to high");  
  } else {
    flux_back = flux_back_low;
    flux_move = flux_move_low;      
    debug("Power set to low");  
  }
}

// -------------------------------------------------------------------

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

Credits

Michael Concannon

Michael Concannon

1 project • 0 followers

Comments