Monsters Inc.
Scare Factory
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
Employee Check-In Station
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
Scream Canister
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​