Jared Andrews

My Spooky Skeleton

In 2018,

I moved to Worcester and for the first time in almost a decade, I lived in a neighborhood where kids would trick-or-treat. While I love the idea of opening the door with a scary mask on and throwing candy at children, my dog hates it. Thus, I deployed the age old trick of leaving a bowl of candy outside so the kids wouldn't knock. I bought an orange pumpkin bowl, dumped candy in it, left it outside and locked my door. This was no fun.

I soon purchased a full size plastic skeleton.

Skeleton on Motorcycle

I initially planned on placing the skeleton on my motorcycle in front of my house, with the bowl of candy cradled in his lap. But then I remembered how much anxiety I get an when an adult who knows about motorcycles touches my bike and decided against it.

In 2019,

I spiced the skeleton up for his first Halloween. I installed red LED eyes into the skeleton using something like this. I also gave him a cloak. I am embarresed to admit, that while I am usually impervous to ads on Instagram, I have fallen to many hoodie variations thru the years. While I still love to wear my Oodies at night, this asymmetrical hoodie never felt quite right on me. Fortunately, it fit the skeleton perfectly.

Skeleton on Motorcycle

In 2020,

while I think we all remember 2020. Halloween was cancelled that year in Worcester and while I'm sure some trick-or-treaters were out there, I was not home for the evening and did not even get the skeleton out of storage.

In 2021,

I wanted to give the skeleton a voice. I purchased a motion activated speaker with an SD card (something similar to this). I could never get this thing working right tho. When I put custom audio on the SD card it would sometimes play but many of the builtin audio recordings would persist in the devices library after I deleted them from the SD Card. Furthmore, the sound quality was very quiet and bad. I put the skeleton back outside, but he boasted no new features.

In 2022,

I was still hung up on the fact that I could not make the skeleton speak. I researched more out of the box motion detection solutions. None of them seemed up to par, I even went to Home Depot and tried one out there. It was time to introduce an Arduino and DO IT MYSELF.

Adding an Arduino opened a whole new world of possibilites and I am excited to add new components to it for years to come. I used an Arduino UNO because I already had one, I could see myself replacing this UNO with a IOT 33 in the future for more fun.

New Eyes

I replaced the skeletons pure red LED eyes with RGB LEDs, in the video you can see that while the eyes are primarily red they flicker and fade between a few different colors and brightness levels. The LEDs were routed thru the back of the skeletons head, which is covered up by his hood when on display.

Skeleton hole in head

The Voice

To give the skeleton a voice I used a DFPLAYER module. The DFPLAYER is essentially a stripped down MP3 player. You put an SD card in and control it with a microcontroller. A lot of DFPLAYER tutorials detail the building of an amp to attach to the DFPLAYER. I avoided this by soldering on an 1/8 jack and hooking up a pair of computer speakers which I zip tied into the skeletons rib cage. A friend of mine recorded several creepy phrases for me, which gave the skeleton a powerful presence.

Skeleton with speaker in ribs

Motion Detection

I added a Sharp GP2Y0A21YK to the candy bowl to detect when someone reached their hand in. This is a sensor we use a lot on my robotics team, so I was familiar with it and already had a few of them lying around at home. It worked perfectly and when someone reaches into the bowl it triggers the eyes and voice of the skeleton.

Distance Sensor Front Distance Sensor Back

Assembly

I decided to build a "shield" to go on top of the Arduino UNO. I didn't have the correct size of PCB so I merged two together and it came out alright. I decided to make the distance sensor and eyes connect via terminals so they remain independent from the board.

Shield Front Shield Back Shield Installed

Code

I don't think there is anything particularily interesting about this code, but I wanted to share to add to the collective knowledge of the DFPLAYER. It seems like are a lot of fake, or rather, non-compliant implementations of the DFPlayer. I ran mine against DFPlayerAnalyzer to determine that it was correct. I get the sense that a lot of issues people have with the DFPLAYER are related to fake models. I used DFRobotDFPlayerMini library to control it.

#include <SharpIR.h>
#include <SoftwareSerial.h>
#include "DFRobotDFPlayerMini.h"

// IR
#define IR_PIN A0
#define IR_MODEL SharpIR::GP2Y0A21YK0F
SharpIR distanceSensor = SharpIR(IR_MODEL, IR_PIN);

// LEDs
int R_2 = 11;
int G_2 = 10;
int B_2 = 9;

// DFPlayer
static const uint8_t PIN_MP3_TX = 2;
static const uint8_t PIN_MP3_RX = 3;
#define DF_BUSY 4
SoftwareSerial softwareSerial(PIN_MP3_RX, PIN_MP3_TX);
DFRobotDFPlayerMini player;

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(0));

  softwareSerial.begin(9600);
  // Start communication with DFPlayer Mini
  if (player.begin(softwareSerial)) {
   Serial.println("DF Player - OK");

    // Set volume to maximum (0 to 30).
    player.volume(30);
  } else {
    Serial.println("DF Player - ERROR");
  }
}

unsigned long t;
bool playing = false;
float r = 0;
int g = 0;
int b = 0;
boolean lightDirectionUp = true;
int UP = 10;

int detected = 0;

void loop() {
  int distance = distanceSensor.getDistance();

  if (!playing && distance > 5 && distance < 25) {
    detected += 1;

    if (detected > 2) {
      player.play(getNextTrack());
      t = millis();
      playing = true;
      detected = 0;
    }
  } else {
    detected = 0;
  }

  if (playing) {
     if (lightDirectionUp) {
      r += 1;
      if (r > UP) {
    lightDirectionUp = false;
      }
    } else {
      r -= 1;
      if (r <= 1) {
    lightDirectionUp = true;
      }
    }

    int g = flickerColor(random(0, 2));
    int b = flickerColor(random(0, 2));

    if (g > b) {
      b = 0;
    }

    color(int(r), g, b);
  }

  unsigned long now = millis();
  if (playing && now - t > 17000) {
    playing = false;
    color(0,0,0);
    t = 0;
    r = 0;
    lightDirectionUp = true;
  }

  delay(100);
}

const int TRACK_COUNT = 5;
int tracks[TRACK_COUNT] = {1, 2, 3, 4, 5};
int USED = -1;
int getNextTrack() {

  Serial.print("tracks: {");
  for (int i = 0; i < TRACK_COUNT; i++) {
    Serial.print(" ");
    Serial.print(tracks[i]);
    Serial.print(" ");
  }
  Serial.print("}\n");

  boolean unusedRemaining = false;
  for (int i = 0; i < TRACK_COUNT; i++) {
    if (tracks[i] != USED) {
      unusedRemaining = true;
      break;
    }
  }

  if (unusedRemaining == false) {
    Serial.println("Reseting tracks, TRACK_COUNT: " + String(TRACK_COUNT));
    for (int i = 0; i < TRACK_COUNT; i++) {
      tracks[i] = i + 1;
    }
  }

  int r = 2;
  while(tracks[r] == USED) {
    r = random(0, 5);
    Serial.println("r: " + String(r));
  }

  tracks[r] = USED;

  return r + 1;
}

int flickerColor(int n) {
  if (n > 0) {
    return random(0,2);
  }

  return 0;
}

void flickerEyes() {
  int n = 15;

  for (int i = 0; i < n; i++) {
    color(i, 0, 0);
    delay(50);
  }

  for (int j = n; j >= 0; j -= 1) {
    color(j, random(0, 2), random(0,2));
    delay(50);
  }
}

void color(int r, int g, int b) {
  analogWrite(R_2, r);
  analogWrite(G_2, g);
  analogWrite(B_2, b);                                          
}
Reaction

I was pretty stoked with how all this turned out. My favorite part was hearing kids scream as they reached in, quickly followed by parents laughter. I'm sure Halloween can be a stressful night for parents so I'm glad to provide some levity.

In 2023,

I tried to add movement, unfortunately I didn't think about it until a week before halloween. I orded some electric toothbrush motors thinking that I could attach them to the bowl and make it shake when the motion sensor was activated. This effect didn't really work out, when the motors were at full speed they were simply too fast and small, they rattled against the bowl but didn't really make it move. I tried slowing them down but that didn't work out either. I'm currently considering other options to generate movement for 2024, see you then!