Lucas Ainsworth
Published © LGPL

Holiday Shadow Theater

Build a beautiful Holiday Shadow Theater and control the animated scene with your phone.

BeginnerFull instructions provided3.5 hours13,256

Things used in this project

Hardware components

Arduino 101
Arduino 101
SparkFun Adressable RGB LED Strip
I use this one for easy connectivity but any addressable LED strip will work (with minor adjustments.)
SparkFun Male Barrel Jack adapter
You can often find these at a local electronics store
SparkFun Female Barrel Jack Adapter
SparkFun 3xAA Battery Holder with Switch
SparkFun AA battery
This is a generic battery that can come from anywhere. I just chose SparkFun to keep the supplier list short. You need 3AA batteries.
Pololu Step Up Regulator 12v
This is a convenient way to power the Arduino 101 with the same batteries that power the LED strip.
Regular printer paper- 4 sheets 8.5x 11"
Cardstock paper 100lb, 4 sheets of 8.5 x 11"
White 3/16" Foam Core, enough for four 8.5 x11" pieces

Software apps and online services

Arduino IDE
Arduino IDE
Adafruit Neopixel Library

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)
Soldering iron (generic)
Soldering iron (generic)
Xacto knife
Spare xacto blades (#11)
Wire strippers, 12-20 AWG
Access to a printer (that can print on cardstock)
Small screwdriver to tighten the terminals on the barrel jack connectors.


Read more


Foam-Core patterns (4 pages 8x11)

Print these out on regular printer paper to use as guides for cutting foam-core. You can place them on top and trace with a hobby knife to transfer the pattern through.

Cardstock Patterns

Print on 8.5 x 11" cardstock paper (4 sheets.) These make the houses, sides, and shadow-silhouettes

Cardstock Pattern for Laser Cutter or Vinyl Cutter

If you're lucky enough to have access to a laser cutter or a vinyl cutter, you can use this pattern to cut out your cardstock pieces.


Shadow Theater Arduino Sketch

This sketch uses the Adafruit NeoPixel Library and the Blynk Library. It lets you remote control LEDs in an addressable LED string to create effects in a Shadow Theater.
   Copyright (c) 2016 Intel Corporation.  All rights reserved.
   See license notice at end of file.

///////////////////////THESE ARE THE VARIABLES YOU SHOULD CHANGE TO MATCH YOUR SHADOW THEATER SETUP////////////////////////////////////////

//snow animation lights

int pos[] = {0, 1, 2, 20, 21, 22, 40, 41, 42}; // which LEDs are available for animation?  They will be called in order from left to right.

int numLeds = 9;  //how many animation pixels are in the array above?

int shadowPixel = 59;///  main shadow-casting LED at the end of the strand.

int lightHousePixel = 23; //pixel for the lights in the rear house

int firePixel = 24;  //pixel for flickering firelight for front houses.


#include <Adafruit_NeoPixel.h>
#include <BlynkSimpleCurieBLE.h>
#include <CurieBLE.h>

#define PIN 6

int v0 = 0;  //variables to hold incoming values from Blynk
int v1 = 0;
int v2 = 0;

int fadeup = 0;   //variables for light animation smoothing
int fadedown = 0;

int firetimer = 0; /// variables to make the fire flicker
int fireinterval = 50;
int flicker = 0;

unsigned long lightcounter = 0;  //variable for counting through the animating lights forever

int stp = 0;  ///light steps for ainimation.  One for the current light to turn it on, and one for the old light to turn it off.
int stpOld = 0;

unsigned long timer = 0;
int interval = 0;

char auth[] = "YourAuthToken";  ///  The Auth Token isn't used by Blynk for BLE currently. If it's turned on in the future, you can get your auth token from your Blynk App and paste it inside the "".

BLEPeripheral  blePeripheral;  ///set up the BLE connection

Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, PIN, NEO_GRB + NEO_KHZ800);  ////  set up the light strip.  If your LED strip has more or less than 60 pixels, change the first number to match your strip.

void setup() {
  timer = millis();
  strip.begin();; // Initialize all pixels to 'off'


  Blynk.begin(auth, blePeripheral);

  interval = 350;  //time interval of 350ms for snow animation

void loop() {; //Blynk stuff for real-time controll
  ////////turn on the shadow pixel and control with Blynk
  strip.setPixelColor(shadowPixel, 200 - v2, 160 - v2, 160 - v2); //turns shadow light on, v2 (sent from Blynk app) changes the light.

  if ((millis() - firetimer) > fireinterval) {
    firetimer = millis();
    flicker = random(0, 40);  //when a set time interval passes, change the brightness of the fire randomly between 0 and 40.
  if (v0 > 1) {  
    strip.setPixelColor(firePixel, 80 + v0 + flicker, v0 + flicker, 0);  // the fire = the brightness of the slider in Blink, + the flicker value, weighted towards red
  else {
    strip.setPixelColor(firePixel, 0, 0, 0);  //only turn on the fire if the Blynk slider is > 0.

  if (v1 > 1) {
    strip.setPixelColor(lightHousePixel, 200 - v1, v1, 0);  ///  if the Houselights Slider in Blynk is >0, control the house lights in the rear house. else turn off.
  else {
    strip.setPixelColor(lightHousePixel, 0, 0, 0);  

  //snow animation
  if ((millis() - timer) > interval) { //every time the interval passes, move one step forward and reset fades
    stpOld = stp;
    timer = millis();
    stp = (lightcounter % numLeds) ; /// counts over and over from zero to numLeds-1
    fadeup = 0;   //reset fade values every time the interval passes
    fadedown = 170;

  fadeup += 2;
  fadeup = constrain(fadeup, 0, 170);
  fadedown -= 2;
  fadedown = constrain(fadedown, 0, 170);
  for (int i = 0; i < numLeds; i++) {  //turn all animation LEDs off.
    strip.setPixelColor(pos[i], 0, 0, 0);
  strip.setPixelColor(pos[stp], fadeup, fadeup, fadeup);  ///Set the selected pixel to the current "fadeup" value
  strip.setPixelColor(pos[stpOld], fadedown, fadedown, fadedown);  //Set the old pixel to the current "fadedown" value;  //finally, send all the current color values out to the LED strip for display.

////  when new signals come in from Blynk, they update the global variables v0, v1, and v2.
BLYNK_WRITE(V0) //The Fireplace Slider Widget is writing to pin V0
  v0 = param.asInt();
BLYNK_WRITE(V1) //The Houselights Slider Widget is writing to pin V1
  v1 = param.asInt();
} BLYNK_WRITE(V2) //Main Shadow LED Slider Widget is writing to pin V2
  v2 = param.asInt();

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


Lucas Ainsworth

Lucas Ainsworth

2 projects • 40 followers
I'm a designer working in the Modular Innovation Group at Intel.