I love it when a plan comes together!

Yes, my project that I’ve been working on was this awesome nod to the wall from the Netflix series “Stranger Things”. It started out as an idea for a website, but much like the Demogorgon, it manifested itself in the real world.

You’ve probably seen some of the posts I made about using the Arduino – well, this is what it was all for. I’m going to briefly outline what this project involved:

Materials:

  • Arudino Uno Microcontroller w/ 5v Adapter
  • Ethernet Shield for Arduino
  • WS8211 Individually addressable LEDs
  • WiFi Range Extender w/ ethernet port
  • Lots of wood (mostly scrounged from dumpster diving)
  • Ugly wallpaper from Amazon.com
  • Arcylic paint
  • Assorted lengths of wire

Honestly, this project was pretty much a 50/50 coding/building project as I feel that I spent equal amount of times actually constructing the wall and building out the code.

The code was pretty simple to figure out once I got the basic idea in place – originally it started as sending a text message to the wall to spell out the letters, but then I decided to just skip the middle-step and build a website that would directly input the message to the database.

Here was my initial sketch:

image1

The phone would send the message to the database, the Arduino unit would then query the database and the information would be returned as a numerical array that corresponded to the appropriate LEDs.  It was as simple a plan as I could muster.

Here’s my final Arduino Sketch:

#include <Dhcp.h>
#include <Dns.h>
#include <Ethernet.h>
#include <EthernetClient.h>
#include <EthernetServer.h>
#include <EthernetUdp.h>
//LED LIBRARY
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#include <SPI.h> // needed for Arduino versions later than 0018
#endif
#define PIN 6 //output to LED string

// NEOPIXEL
long randNumber;
long randColor1;
long randColor2;
long randColor3;
int MinBrightness = 10; //value 0-255
int MaxBrightness = 255; //value 0-255
int numLoops1 = 10;
int numLoops2 = 5;
int fadeInWait = 30; //lighting up speed, steps.
int fadeOutWait = 50; //dimming speed, steps.
int Bulbs = 100; //lights on the strip
Adafruit_NeoPixel strip = Adafruit_NeoPixel(Bulbs, 6, NEO_RGB + NEO_KHZ400);

//ETHERNET
byte server[] = { 205,196,208,154 }; //ip Address of the server you will connect to
String location = "/strangerthings/ HTTP/1.0";
// if need to change the MAC address (Very Rare)
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
////////////////////////////////////////////////////////////////////////

EthernetClient client;

char inString[64]; // string for incoming serial data
int stringPos = 0; // string index counter
boolean startRead = false; // is reading?
void setup() {
Ethernet.begin(mac);
Serial.begin(9600);
strip.begin();
strip.show(); // Initialize all pixels to 'off'
}

void loop() {
sparkler();
clearWipe();
spell();
clearWipe();
rainbowCycle(20);
clearWipe();
}

// Clear the dots one after the other
void clearWipe() {
strip.setBrightness(255);
for (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, strip.Color(0, 0, 0, 255));
strip.show();
delay(50);
}
}

//
String getValue(String data, char separator, int index)
{
int found = 0;
int strIndex[] = {
0, -1 };
int maxIndex = data.length()-1;
for(int i=0; i<=maxIndex && found<=index; i++){
if(data.charAt(i)==separator || i==maxIndex){
found++;
strIndex[0] = strIndex[1]+1;
strIndex[1] = (i == maxIndex) ? i+1 : i;
}
}
return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}
// Spell Word
void spell() {

String pageValue = connectAndRead(); //connect to the server and read the output
Serial.println(pageValue); //print out the findings.

String total = getValue(pageValue, ',', 0);
int totalCount = total.toInt();

for( int x=1; x <= totalCount; x++){

String pixel = getValue(pageValue, ',', x);

int finalPixel = pixel.toInt();

randColor1 = random(0, 255);
randColor2 = random(0, 255);
randColor3 = random(0, 255);
strip.setPixelColor(finalPixel, randColor1, randColor2, randColor3);
strip.show();
delay(2500);
strip.setPixelColor(finalPixel, 0, 0,0);
delay(100);

}

String val1 = getValue(pageValue, ',', 1);
int value1 = val1.toInt();
}

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
uint16_t i, j;

for (j = 0; j < 256 * 5; j++) { // 5 cycles of all colors on wheel
for (i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
}
strip.show();
delay(wait);
}
}

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

void sparkler(){
for (int x= 0; x < 1000; x++){
randColor1 = random(0, 255);
randColor2 = random(0, 255);
randColor3 = random(0, 255);
Sparkle(randColor1, randColor2, randColor3, random(10));
}

}

void Sparkle(byte red, byte green, byte blue, int SpeedDelay) {
int Pixel = random(Bulbs);
setPixel(Pixel,red,green,blue);
showStrip();
delay(SpeedDelay);
setPixel(Pixel,0,0,0);

}

// 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) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

//grab from web page

String connectAndRead(){
//connect to the server

Serial.println("connecting...");

//port 80 is typical of a www page
if (client.connect(server, 80)) {
Serial.println("connected");
client.print("GET ");
client.println(location);
client.println();

//Connected - Read the page
return readPage(); //go and read the output

}else{
return "connection failed";
}

}

String readPage(){
//read the page, and capture & return everything between '<' and '>'

stringPos = 0;
memset( &inString, 0, 32 ); //clear inString memory

while(true){

if (client.available()) {
char c = client.read();

if (c == '<' ) { //'<' is our begining character
startRead = true; //Ready to start reading the part
}else if(startRead){

if(c != '>'){ //'>' is our ending character
inString[stringPos] = c;
stringPos ++;
}else{
//got what we need here! We can disconnect now
startRead = false;
client.stop();
client.flush();
Serial.println("disconnecting.");
return inString;
}}}}}

The script is communicating with a PHP script on my server that looks like this:


<?php

//Include database connection details
require_once('config.php');

//Connect to mysql server
$link = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
if(!$link) {
die('Failed to connect to server: ' . mysql_error());
}

//Select database
$db = mysql_select_db(DB_DATABASE);
if(!$db) {
die("Unable to select database");
}

$qry="SELECT * FROM message WHERE _fetch != '1' ORDER BY _time ASC LIMIT 1";
$result=mysql_query($qry);

function toNumber($dest)
{

switch ($dest) {

case "A":
return 2;
break;

case "B":
return 5;
break;

case "C":
return 8;
break;

case "D":
return 11;
break;

case "E":
return 14;
break;

case "F":
return 17;
break;

case "G":
return 19;
break;

case "H":
return 22;
break;

case "I":
return 53;
break;

case "J":
return 50;
break;

case "K":
return 48;
break;

case "L":
return 45;
break;

case "M":
return 43;
break;

case "N":
return 40;
break;

case "O":
return 38;
break;

case "P":
return 34;
break;

case "Q":
return 32;
break;

case "R":
return 62;
break;

case "S":
return 65;
break;

case "T":
return 68;
break;

case "U":
return 71;
break;

case "V":
return 74;
break;

case "W":
return 78;
break;

case "X":
return 81;
break;

case "Y":
return 83;
break;

case "Z":
return 86;
break;
}
}

function convertString($msg){
$arr1 = str_split($msg);
$arrLength = count($arr1);
$outputArray = array($arrLength);
foreach ($arr1 as &$value) {
array_push($outputArray,toNumber($value));
}
$output = implode(",",$outputArray);
return $output;
}

if (mysql_num_rows($result) > 0){

for($x = 0 ; $x < mysql_num_rows($result) ; $x++){
$row = mysql_fetch_assoc($result);
$id = $row['_id'];
$message = convertString(str_replace(' ', '', $row['_message']));
echo "<".$message.">";

$finalQry = "UPDATE message SET _fetch='1' WHERE _id = '".$id."'";
$readQry=mysql_query($finalQry);
}

}

else{
$arr = array("<12,65,14,40,11,2,43,14,65,65,2,19,14>","<9,5,2,62,6,65,71,8,48,65>","<3,62,71,40>");
$random = rand (0,2);
echo $arr[$random];
}
?>

This script connects to the database and gets the next message in the queue. If there is no message waiting, then it shows one of three pre-defined messages. I put in the switch statement because the letters weren’t evenly spaced. Originally, I was thinking linearly, but when I realized the light string loops back, I had to change my thinking. Anyway, it works, and doing the conversion on the PHP side was a lot easier than having to try and figure it out on the Arduino.

The last bit of this is actually sending the message to the database. I built a simple website at http://strangerwall.com where you can send your message. I also set it up so you could send a text message to a number, but abandoned that because I would have to pay all the SMS charges and it was just cheaper for me to go with a web-based solution.

Here’s the code for that:

<?php

//Include database connection details
require_once('config.php');

//Connect to mysql server
$link = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
if(!$link) {
die('Failed to connect to server: ' . mysql_error());
}

//Select database
$db = mysql_select_db(DB_DATABASE);
if(!$db) {
die("Unable to select database");
}

//Function to sanitize values received from the form. Prevents SQL injection
function clean($str) {
$str = @trim($str);
if(get_magic_quotes_gpc()) {
$str = stripslashes($str);
}
return mysql_real_escape_string($str);
}

$msg = clean($_REQUEST['message']);

$length = strlen($msg);
$msg = strtoupper($msg);

if ($length <= 25){
$qry="INSERT INTO message (_message)
VALUES('".$msg ."')";
$result=mysql_query($qry);
echo "Your message has been sent.";
}
else{
echo "Your message exceeds 25 characters. Please try again.";
}
?>

The message was sent from a web page via AJAX request, if you understand anything I’ve written up to this point, then you should be able to figure out that last bit.

The rest of the project was pretty simple to finish once I had the LEDs working. I framed out a wall that was 6′ x 7′ using 2x2x4 lumber that I got out of a construction dumpster. I built the wall in two pieces so it would be easy to transport. I put some cheap plywood on the front as a base for the actual wall. The lower half I finished off with some manufactured wainscotting that was $12 at Home Depot. The top half I finished with wallpaper that I got on Amazon. I then added some details with some scrap trim that I had left over from when we built our house and put it all together.

Here are some photos of the progress:

This was a fun project to work on and it turned out better than I could have ever expected!