top of page

Employee Check-In Station & Scream Canister

By: Adele Parsons & Allyson Kline

Project Description

 

Our site is the employee check-in station and scream canister. The check-in station contains a list of all the employee names which is displayed on an LCD. The employee (called a scarer) who is going into the room, selects their name from the list of employees using a series of buttons to parse through the list and make their selection so the command center (mission control) is aware of who is going into the room. In the event of a contamination, an alert message is displayed on the employee name panel. The scream canister is a container that harvests the energy emitted from the sound of a child screaming. The scream canister contains a sound sensor that senses the amplitude of the scream.  The louder the scream, the higher the level of energy collected which is reflected on the front LCD panel of the scream canister. Once that energy is collected, mission control is notified of how much energy was collected. The canister can then be reset to harvest more energy.

System Diagram

Employee Check-In Station

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

Scream Canister

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

Breadboard Layout

Employee Check-In Station

​

// Library code:
#include "Wire.h"
#include "Adafruit_LiquidCrystal.h"
#include <Button.h>
#include <SoftwareSerial.h>
#include <Adafruit_NeoPixel.h>

​

// Declare buttons
Button backwards (2, INPUT_PULLUP);
Button forwards (5, INPUT_PULLUP);
Button enter (7, INPUT_PULLUP);
Button clr (3, INPUT_PULLUP);

​

// Connect via i2c, default address #0 (A0-A2 not jumpered)
Adafruit_LiquidCrystal lcd(0);

​

// Connect the NeoPixel strip
Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, PIN, NEO_GRB + NEO_KHZ800);
#define PIN 6
#define NUM_LEDS 60

​

// Initialize the XBee
SoftwareSerial xBee(8, 9); // (TX, RX) :
const int MAX_FIELDS = 4;
const String myNodeName = "EmployeeNames";
const byte TAB_CHAR = 0x09;
const byte NEWLINE_CHAR = 0x0A;

​

// A list of employee names from the movie
String employeeNames[] = {"< Sullivan     >", "< Randall      >", "< Ranft        >", "< Luckey       >", "< Rivera       >", "< Peterson     >", "< Jones       >", "< Sanderson    >", "< Plesuski     >", "< Schmidt      >",  "< Mike        >"};
int currIndex = -1;


void setup() {
  // Set up the LCD's first message
  lcd.begin(16, 2);
  lcd.print("Welcome to the");
  lcd.setCursor(0, 1);
  lcd.print("Scare Factory");
  Serial.begin(9600);

​

  // Begin the NeoPixel strip
  strip.begin();
  strip.show();
  Twinkle(0, 0, 255, 30, 100);
  colorWipe(strip.Color(0, 0, 255), 5); // Blue

  lcd.clear();
  lcd.print("< Select Name  >");
}


void loop() {
  int action = backwards.checkButtonAction();
  int action2 = forwards.checkButtonAction();
  int action3 = enter.checkButtonAction();
  int action4 = clr.checkButtonAction();

​

  // Iterate backwards if the back button is pressed
  if (action == Button::PRESSED) {
    if (currIndex == 0) {
      currIndex = 10;
      lcd.clear();
      lcd.print(employeeNames[currIndex]);
      colorSelect();
    } else {
      currIndex --;
      lcd.clear();
      lcd.print(employeeNames[currIndex]);
      colorSelect();
    }
  }

​

  // Iterate forwards if the forward button is pressed
  if (action2 == Button::PRESSED) {
    if (currIndex == 10) {
      currIndex = 0;
      lcd.clear();
      lcd.print(employeeNames[currIndex]);
      strip.show();
      colorSelect();
    } else {
      currIndex ++;
      lcd.clear();
      lcd.print(employeeNames[currIndex]);
      strip.show();
      colorSelect();
    }
  }

​

  // Sends the selected employee name to mission control
  if (action3 == Button::PRESSED) {
    String msg = myNodeName + "\t5555\t" + currIndex + "\n";
    xBee.begin(9600);
    xBee.print(msg);
    currIndex = -1;
    lcd.clear();
    lcd.setCursor(16, 0);
    lcd.print("Welcome back to");
    lcd.setCursor(16, 1);
    lcd.print("the scare floor");
    for (int positionCounter = 0; positionCounter < 16; positionCounter++) {
      // scroll one position left:
      lcd.scrollDisplayLeft();
      if (positionCounter % 5 == 0) {
        rainbowCycle(1);
      }
    }
  }

​

  // Back to "home" (Select Name)
  if (action4 == Button::PRESSED) {
    lcd.clear();
    lcd.print("< Select Name  >");
    currIndex = -1;
    Twinkle(0, 0, 255, 5, 100);
    colorWipe(strip.Color(0, 0, 255), 5); // Blue
  }

​

  // Receiving an XBee message
  String msg = checkMessageReceived();

​

  if (msg.length() > 0) {
    String msgFields[MAX_FIELDS];
    for (int i = 0; i < MAX_FIELDS; i++) {
      msgFields[i] = "";
    }

    int fieldsFound = 0;
    String buf = "";

    for (int i = 0; i < msg.length(); i++) {
      if (((msg.charAt(i) == TAB_CHAR) ||
           (msg.charAt(i) == NEWLINE_CHAR)) &&
          (fieldsFound < MAX_FIELDS)) {
        msgFields[fieldsFound] = buf;
        buf = "";
        fieldsFound++;
      }
      else {
        buf += msg.charAt(i);
      }
    }

​

    Serial.print("found fields = ");
    Serial.println(fieldsFound);
    for (int i = 0; i < fieldsFound; i++) {
      Serial.println(msgFields[i]);
    }

​

    int aNumber = msgFields[1].toInt();
    Serial.println(aNumber);

​

    switch (aNumber) {
      // All Clear
      case 9999:
        lcd.clear();
        lcd.print("All Clear");
        lcd.clear();
        Twinkle(0, 0, 255, 30, 100);
        colorWipe(strip.Color(0, 0, 255), 15); // Blue
        lcd.print("< Select Name  >");
        break;

      case 2319:
        // Contamination
        lcd.clear();
        lcd.print("CONTAMINATION");
        lcd.setCursor(0, 1);
        lcd.print("ALERT 2319");
        strip.show();
        Strobe(255, 0, 0, 30, 100, 1000);
        break;
    }

  }

}

​

// Checks for a message recievied by XBee
String checkMessageReceived () {

  static String msgBuffer = "";
  String returnMsg = "";

  if (xBee.available()) {

    byte ch = xBee.read();
    msgBuffer += char(ch);

    // Checks for the message terminator
    if (ch == NEWLINE_CHAR) {
      // If so, then return the completed message
      returnMsg = msgBuffer;
      // and clear out the buffer for the next message
      msgBuffer = "";
    }
    else {
      // The message isn't complete yet, so just return a null string to the caller
    }
  }

  else {
    // Nothing has been received, so
    // return a null string to the caller
  }

  return returnMsg;

}

​

// Clears the NeoPixel
void showStrip() {
#ifdef ADAFRUIT_NEOPIXEL_H
  strip.show();
#endif
#ifndef ADAFRUIT_NEOPIXEL_H
  FastLED.show();
#endif
}

​

// Sets the pixel color for the NeoPixel
void setPixel(int Pixel, byte red, byte green, byte blue) {
#ifdef ADAFRUIT_NEOPIXEL_H
  strip.setPixelColor(Pixel, strip.Color(red, green, blue));
#endif
#ifndef ADAFRUIT_NEOPIXEL_H
  leds[Pixel].r = red;
  leds[Pixel].g = green;
  leds[Pixel].b = blue;
#endif
}

​

void setAll(byte red, byte green, byte blue) {
  for (int i = 0; i < NUM_LEDS; i++ ) {
    setPixel(i, red, green, blue);
  }
  showStrip();
}

​

// NeoPixel cycles through the rainbow
void rainbowCycle(int num) {
  uint16_t i, j;

  for (j = 0; j < 256 * num; j++) {
    for (i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
  }
}

​

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);
  }
}

​

// NeoPixel pattern for interating through the employee list
void colorSelect() {
  strip.show();
  colorWipe(strip.Color(0, 255, 0), 5); // Green
  colorWipe(strip.Color(0, 0, 255), 5); // Blue
}

void colorWipe(uint32_t c, uint8_t wait) {
  for (uint16_t i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

​

// NeoPixel strobes red for a contamination
void Strobe(byte red, byte green, byte blue, int StrobeCount, int FlashDelay, int EndPause) {
  for (int j = 0; j < StrobeCount; j++) {
    setAll(red, green, blue);
    showStrip();
    delay(FlashDelay);
    setAll(0, 0, 0);
    showStrip();
    delay(FlashDelay);
  }
  delay(EndPause);
}

​

// NeoPixel twinkles as the station loads
void Twinkle(byte red, byte green, byte blue, int Count, int SpeedDelay) {
  setAll(0, 0, 0);

  for (int k = 0; k < Count; k++) {
    setPixel(random(NUM_LEDS), red, green, blue);
    showStrip();
    delay(SpeedDelay);
  }
  delay(SpeedDelay);
}
 

Scream Canister

​

// Library code:
#include "Wire.h"
#include "Adafruit_LiquidCrystal.h"
#include <Button.h>
#include <SoftwareSerial.h>

​

//Declare button

Button butt (11, INPUT_PULLUP);

​

// Connect via i2c, default address #0 (A0-A2 not jumpered)
Adafruit_LiquidCrystal lcd(0);

​

// Define hardware connections for sound sensor
#define PIN_GATE_IN 2
#define IRQ_GATE_IN  0
#define PIN_LED_OUT 13
#define PIN_ANALOG_IN A0
boolean soundOn = false;
boolean doneListening = false;

​

// Initialize the XBee
SoftwareSerial xBee(8, 9); // (TX, RX) :
const int MAX_FIELDS = 4;
const String myNodeName = "ScreamCanister";
const byte TAB_CHAR = 0x09;
const byte NEWLINE_CHAR = 0x0A;

​

// Character for the LCD
byte entire[8] = {
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11111
};

​

void setup() {
  xBee.begin(9600);
  Serial.begin(9600);
  lcd.begin(16, 2);

​

  // Set up the LCD
  lcd.createChar(0, entire);

​

  // Set up the sound sensor
  pinMode(PIN_GATE_IN, INPUT);
  attachInterrupt(IRQ_GATE_IN, soundISR, CHANGE);

​

  Serial.println("Initialized");
}


void loop() {
  int action = butt.checkButtonAction();

​

  // Initialize sound sensor value
  int value;
  value = analogRead(PIN_ANALOG_IN);

​

  // Receiving an XBee message
  String msg = checkMessageReceived();

  if (msg.length() > 0) {
    String msgFields[MAX_FIELDS];


    for (int i = 0; i < MAX_FIELDS; i++) {
      msgFields[i] = "";
    }

​

    int fieldsFound = 0;
    String buf = "";

​

    for (int i = 0; i < msg.length(); i++) {
      if (((msg.charAt(i) == TAB_CHAR) ||
           (msg.charAt(i) == NEWLINE_CHAR)) &&
          (fieldsFound < MAX_FIELDS)) {
        msgFields[fieldsFound] = buf;
        buf = "";
        fieldsFound++;
      } else {
        buf += msg.charAt(i);
      }
    }

​

    Serial.print("found fields = ");
    Serial.println(fieldsFound);
    for (int i = 0; i < fieldsFound; i++) {
      Serial.println(msgFields[i]);
    }

​

    int aNumber = msgFields[1].toInt();
    Serial.println(aNumber);

    switch (aNumber) {
      case 2100:
        // Begin sound sensor
        lcd.clear();
        Serial.println("begin sensing sound");
        soundOn = true;
        break;
    }

​

    if (soundOn == true) {
      Serial.println("Sensing now");


      if (value <= 30) {
        Serial.println("1");
        xBee.print(myNodeName + "\t1234\t1\n");
        fillUp(0);
        soundOn = false;
      } else if ((value > 30) && ( value <= 50)) {
        Serial.println("2");
        xBee.print(myNodeName + "\t1234\t2\n");
        fillUp(0);
        fillUp(4);
        soundOn = false;
      } else if ((value > 50) && (value <= 80)) {
        Serial.println("3");
        xBee.print(myNodeName + "\t1234\t3\n");
        fillUp(0);
        fillUp(4);
        fillUp(8);
        soundOn = false;
      } else if (value > 80) {
        Serial.println("4");
        xBee.print(myNodeName + "\t1234\t4\n");
        fillUp(0);
        fillUp(4);
        fillUp(8);
        fillUp(12);
        soundOn = false;
      }
    }
  }

​

  if ((action == Button::CLICKED)) {
    lcd.clear();
    xBee.print(myNodeName + "\t9999\n"); // reset the system
    Serial.println("Cleared");
  }
}

​

String checkMessageReceived () {
  static String msgBuffer = ""; 
  String returnMsg = "";       

​

  if (xBee.available()) {
    byte ch = xBee.read();
    msgBuffer += char(ch);

​

    // Checks for the message terminator
    if (ch == NEWLINE_CHAR) {
      // If so, then return the completed message
      returnMsg = msgBuffer;
      // and clear out the buffer for the next message
      msgBuffer = "";
    }
    else {
      // the message isn't complete yet, so just return a null string to the caller
    }
  }

  else {
    // nothing has been received, so
    // return a null string to the caller
  }

  return returnMsg;

}

​

// For the sound sensor
void soundISR() {
  int pin_val;
  pin_val = digitalRead(PIN_GATE_IN);
}

​

// Fills up the LCD row by row
void fillUp(int startingRow) {
  for (int x = startingRow; x < startingRow + 4; x++) {
    lcd.setCursor(x, 0);
    lcd.write(0);
    lcd.setCursor(x, 1);
    lcd.write(0);
    lcd.setCursor(x, 0);
    lcd.write(0);
    lcd.setCursor(x, 1);
    lcd.write(0);
    lcd.setCursor(x, 0);
    lcd.write(0);
    lcd.setCursor(x, 1);
    lcd.write(0);
    lcd.setCursor(x, 0);
    lcd.write(0);
    lcd.setCursor(x, 1);
    lcd.write(0);
  }
};
 

Software

Gallery

Research/Planning

​

We first began by conducting some background research on the movie in order to make sure we could represent the technology in the most accurate way possible. After looking at some clips and screenshots of the movie, we were able to have a basic understanding of how the technology and overall system worked.  We sketched some initial designs for our site, and some basic diagramming of what types of electronics we would incorporate into our system. 

Prototyping/Building Our Circuits

​

After all our planning and coordinating with the other two teams, we began prototyping each part of our site.  We began by coding the LCD for the scream canister to “fill up” with energy.  We created custom LCD characters to fill up the screen and hooked up 5 buttons to test the different levels of energy collected. We then worked on understanding the sound sensor we purchased online. We learned about the different factors it measured and how to toggle its sensitivity. After successfully getting the screen to fill up at different levels and gaining a better understanding of how the sound sensor operated, we worked to combine the LCD, the sound sensor, and the xBee system together.  We created temporary messages to be sent through the xBees to test the ability for the sound sensor to listen on cue, cause the LCD screen to fill up based on the volume level heard, and send the corresponding energy level through the wireless messaging system.

Creating Enclosures

​

Due to our limited amount of time, we felt it would not be practical to focus on 3D modeling and printing since it would require a fair amount of review and practice.  Our main focus was on ensuring our entire system worked together cohesively. To house our Arduinos, we utilized boxes and painted/decorated them. 

 

For our scream canister, we ideated on what types of cylindrical containers currently existed.  A Pringles can turned out to be too narrow to house our Arduino and a liter soda bottle was too large for our liking. We eventually came across a cylindrical wine gift box that was the perfect diameter to house our Arduino and components. Using movie stills as reference, we painted and decorated the box to mimic the scream canisters in the movie.  We also made cut outs in the container to make room for our buttons and LCD screen.

 

For the employee check-in station, we decided not to directly re-create what was in the movie because there seemed to be a lot of functionality that was not clearly communicated. We decided on a simple interaction and chose to house our Arduino in the original box our Arduino and components came in. We painted it blue and made cut-outs for our LCD and button interface components. 

For the employee check-in station, we began by creating the employee list based off the employee leaderboard displayed in the movie and displayed the names on the LCD.  We then added in buttons so a user could parse through the list of names, start over, or press enter if that is their correct name.  After getting the basic functionality to work, we then worked on the xBee communication to send the correct employee name to mission control. We incorporated a light strip and created different color settings based on user input for added interest.

Successes/Limitations

​

One of our initial stretch goals was creating the employee check-in station entirely. With the two of us working together, we knew we could complete both the canister and check-in station by the end of the quarter. In the end, we were successfully able to complete both sites. However, we tried to push ourselves with the employee check-in station. After getting the basic functionality of it working, we wanted to incorporate an RFID scanner to have a way for users to scan a card and have the LCD tell them which child they would be scaring today. In the movie, there is a card scanner on their employee station that brings a specific kid’s door around to the scare floor for the employee to work on next. Unfortunately, the protocol from the RFID scanner and the protocol from the LCDs were not working together properly and we did not have enough time to get an I2C backpack for the scanner. Another major issue we ran into was wires breaking on us, thus causing a lot of connectivity issues. All in all, we are proud of how well everything came together and we cannot wait to work on more fun projects in the future!

Process and Implementation

Project Description
System Diagram
Breadboard Layout
Software
Gallery
Process

Employee Check-In Station

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

Scream Canister

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

519889F3-087D-4632-8A69-F39BD17B3F7A
E2754B4D-4799-4F96-A09A-2E7276330730
IMG_8776
DSC_0654
DSC_0664
6CC3164A-059C-468A-B497-C697F443CC08
bottom of page