Help me bust some ghosts!

Hello Everyone,

I am new to programming and I'm attempting to learn a little by creating a Ghostbusters trap that has lights, sounds, and smoke.

The basic idea is that like in the movie, when the trap is activated it triggers different states.

  1. Startup
  2. Open Trap
  3. Capture Ghost
  4. Reset

The code below is what I am working from. I'm currently trying to sort out a few things that I am hoping you can help me learn.

First

I am using the following code I found on these forums in an attempt to randomize the capture sound so it plays different ghost sounds when capturing.

    char trackName[ 13 ];
    int group = 4;  // can be 1 to 10 for your code
    sprintf( trackName, "trk%02d_%02d.mp3", group, random( 100 ) );
    //  output will have the form "trk09_07.mp3", assuming the random number was 7
        musicPlayer.playFullFile( trackName );
    }

I want these to play randomly, but not necessarily all the time. My plan to solve this was to add 5 instances of the base sound so it had a higher chance of playing than the special sounds. Unless there is a more elegant programming way to do this?

Goal: To have the base "clean" sound play most of the time with a few randomized "ghost" sounds thrown in for variety.

Second

The way the trap is programmed is that one of the buttons on the side, when pressed will trigger the trap. This can also be done with a foot pedal. So you can do either or if you like.

On the other side of the trap there is another button. Currently this doesn't do anything. I am wondering if it might be possible to make it a volume switch instead?

The first question is. This switch has two connections for the push and three for the rotary. How would I wire this to the Metro/Music Maker Shield so it would work, as well... what is required from a programming perspective to control the master volume?

Third

If you look at a Ghostbusters trap you'll see there is the main trap and a pedal. In this build, the LED on the pedal does nothing. I would like it to light up and blink along with the main red LED on the trap.

The problem is that the trap and pedal are connected via a hose. This hose as a red and blue wire that connects them both allowing the pedal to be a switch to activate the trap. There are no room for any other wires so this brings me to my dilemma.

I have an "itsybitsy" micro controller. Is it possible to use this, connected to the same switch to trigger and control the LED? I am assuming the switch has no power so I'm not sure how it would power the LED?

Does this make sense?

Thanks!

/********************************************************
 ECTOLABS ARDUIO CODE FOR CHARLESWORTH DYNAMICS GHOST TRAP
 
 v1.0 (EctoLabs/Dave Tremaine 2020). Modified from original
 code by Jeremy Williams 2016
 -  Using 3x RGBW NeoPixel Jewels in place of single LEDs.
 -  Added reusable trapDoor() function to open and close
    doors. Includes immediate servo.detach() after each
    movement to avoid conflict with NeoPixel library
    during LED animation.
 -  Removed sine tone and replaced with bargraph startup 
    indicator.                       
 -  Rewritten trap states to better mimic movie behaviour.
 -  Added 'millisDelay' library as alternative to delay()
    and improve timing issues.
 -  Added blue sparking FX after ghost capture.
 -  Removed extraneous unused code and test functions.

 Ghost trap kit designed by Sean Charlesworth 2016-2018
 Programming and electronics by Jeremy Williams 2016
  
 ********************************************************/

#include <millisDelay.h> 
#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <Adafruit_LEDBackpack.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <SD.h>
#include <Servo.h>
#include <Adafruit_NeoPixel.h> 

// Servos
Servo servoR;
Servo servoL;

int servoCloseL = 180;
int servoCloseR = 0;
int servoOpenL = 70;
int servoOpenR = 120;
int doorDelay = 30;

#define SERVO_RIGHT_PIN 9
#define SERVO_LEFT_PIN 10

// Pins for single LEDs
byte ledRed = 15;
byte ledYellow = 16;

// Pedal & side knob activation pin
byte activationSwitch = 5;

// Smoke machine
byte smokePin = 6;
boolean endingSmoke = false; // smoke effect after trap close?

// Bargraph
Adafruit_24bargraph bar = Adafruit_24bargraph();

// Music Maker Shield
#define SHIELD_RESET -1 // VS1053 reset pin (unused!)
#define SHIELD_CS 7 // VS1053 chip select pin (output)
#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
#define CARDCS 4 // Card chip select pin
#define DREQ 2 // VS1053 Data request, ideally an Interrupt pin

Adafruit_VS1053_FilePlayer musicPlayer = 
  Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);

// NeoPixels
#define PIXELS_PIN 5
#define PIXELS_COUNT 21
Adafruit_NeoPixel strip(PIXELS_COUNT, PIXELS_PIN, NEO_GRBW + NEO_KHZ800);

// Initial states
int trapState = 0;
boolean autoreset = false;
boolean redLEDState = true;
boolean remotePressed = false;
boolean smokeActive = false;
boolean opened = false;
boolean beeping = false;
boolean sparking = false;
unsigned long captureStart = 0;
long debounceBuffer = 0;
long redFlashTime = 0;
long whiteFlashTime = 0;
long smokeToggleTime = 0;
byte activeFlasher = 0;

// Setup non-blocking timers
millisDelay captureDelay;
millisDelay bgDelay;
millisDelay sparkDelay;
millisDelay sparkTime;

// Feature toggles
boolean smoke = true;
boolean sfx = true;

// Audio Track Random
char trackName;


/////////
// SETUP
/////////


void setup() {

  // Start debug output
  Serial.begin(57600);
  Serial.println(F("\n** STARTUP **"));

  // Bargraph red startup indicator
  bar.begin(0x70);
  for (uint8_t b = 0; b < 12; b++) {
    bar.setBar(23 - b, LED_RED);
    bar.writeDisplay();
    delay(30);
  }
  
  // Calibrate closed door position
  trapDoors("calibrate");

  // Initialise music player
  if (! musicPlayer.begin()) {
    Serial.println(F("=> Couldn't find music player, do you have the right pins defined?"));
    while (1);
  }
  Serial.println(F("=> Music player found"));
  musicPlayer.GPIO_pinMode(activationSwitch, INPUT);

  // Check SD card
  if (!SD.begin(CARDCS)) {
    Serial.println(F("=> SD failed, or not present"));
    while (1);
  }
  Serial.println(F("=> SD Card OK"));

  // Interrupt pin
  if (! musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT))
    Serial.println(F("=> DREQ pin is not an interrupt pin"));

  // Initialise static LEDs 
  pinMode (ledYellow, OUTPUT);
  pinMode (ledRed, OUTPUT);

  // Initialise NeoPixels and set to off
  strip.begin();
  strip.show();
  
  // Make sure smoke pump is inactive
  musicPlayer.GPIO_digitalWrite(smokePin, LOW);
  musicPlayer.GPIO_pinMode(smokePin, INPUT);

  // Clear bargraph
  for (uint8_t b = 0; b < 12; b++) {
    bar.setBar(23 - b, LED_OFF);
    bar.writeDisplay();
    delay(50);
  }

  // Startup complete
  if(sfx==true){
    musicPlayer.setVolume(50, 50);
    musicPlayer.startPlayingFile("start.mp3");
  }
  Serial.println(F("=> Startup complete (State: 0)"));
  Serial.println(F("\nWAITING FOR INPUT..."));
  
}


void loop() {
  
  checkRemote();

  
////////////////////
// MAIN TRAP STATES
////////////////////

  
  // OPEN TRAP
  if (remotePressed && trapState == 0) {

    // NeoPixels on
    strip.fill(strip.Color(255,102,255,0));
    strip.show();

    // Play SFX
    if(sfx==true){
      musicPlayer.stopPlaying();
      musicPlayer.setVolume(30, 30);
      musicPlayer.startPlayingFile("open.mp3");
    }
    
    // Open doors
    trapDoors("open");
    
    // Update trap state
    trapState = 1;
    Serial.println(F("\n** TRAP OPENED (State: 1) **"));
    Serial.println(F("\nWAITING FOR INPUT OR TIMEOUT..."));

    
  // CAPTURING
  } else if (remotePressed && trapState == 1) {

    captureDelay.start(7800); // time synced with capture SFX
    boolean capturing = true;
    int strobeUp = 50;
    int strobeDown = 50;
    int strobeCycle = 0;
    
    // Play capture SFX
    if(sfx==true){
      musicPlayer.stopPlaying();
      musicPlayer.setVolume(30, 30);

    char trackName[ 13 ];
    int group = 4;  // can be 1 to 10 for your code
    sprintf( trackName, "trk%02d_%02d.mp3", group, random( 100 ) );
    //  output will have the form "trk09_07.mp3", assuming the random number was 7
        musicPlayer.playFullFile( trackName );
    }

    // NeoPixels burst
    for (uint8_t i=0; i<255; i=i+5) {
      //Serial.println(i);
      if (i>102) {
        strip.fill(strip.Color(255,i,255,i));
      } else {
        strip.fill(strip.Color(255,102,255,i));
      }
      strip.show();
      delay(1);
    }
    
    while(capturing == true){
      //Serial.println(captureDelay.remaining());

      // NeoPixel strobe
      if(strobeCycle == 0){
        if(strobeUp<=251){
          strobeUp = strobeUp + 4;
        }
        if(strobeDown>=1){
          strobeDown = strobeDown - 1;
        }
      }
      strobeCycle++;
      if(strobeCycle==3){
        strobeCycle = 0;
      }
      strip.fill(strip.Color(strobeUp,strobeUp,strobeUp,strobeUp));
      strip.show();
      delay(20);
      strip.fill(strip.Color(strobeDown,strobeDown,strobeDown,strobeDown));
      strip.show();
      delay(20);

      // Smoke on for final 7 seconds
      if((captureDelay.remaining() <= 7000) && smokeActive == false && smoke == true){
        musicPlayer.GPIO_pinMode(smokePin, OUTPUT);
        musicPlayer.GPIO_digitalWrite(smokePin, HIGH);
        smokeActive = true;
      }
      
      // Close doors after 8 seconds and proceed
      if(captureDelay.justFinished()){
        trapDoors("close");
        strip.fill(strip.Color(255,255,255,255));
        strip.show(); 
        capturing = false;
      }
      
    }

    // Smoke off
    if(smoke==true){
      musicPlayer.GPIO_digitalWrite(smokePin, LOW);
      musicPlayer.GPIO_pinMode(smokePin, INPUT);
    }
    
    // NeoPixels off
    strip.fill(strip.Color(0,0,0,0));
    strip.show();
    
    // Silence for 4 seconds before indicators
    bgDelay.start(4000);
    boolean silence = true;

    while(silence == true){
      if(bgDelay.justFinished()){
        // Play bargraph SFX
        if (sfx == true){
          musicPlayer.setVolume(30, 30);
          musicPlayer.startPlayingFile("bargraph.mp3");
        }
        silence = false;
      }
    }

    // Fill bargraph
    for (uint8_t b = 0; b < 12; b++) {
      bar.setBar(23 - b, LED_YELLOW);  
      bar.writeDisplay();
      delay(20);
    }
 
    // Yellow LED on
    digitalWrite(ledYellow, HIGH);
    
    // Wait 1 second
    delay(1000);

    // Start red LED beep loop
    if (sfx == true){
      musicPlayer.stopPlaying();
      musicPlayer.setVolume(40, 40);
      musicPlayer.startPlayingFile("beeponly.mp3");
    }

    // Start timer for sparks
    sparkDelay.start(6000); 
    
    // Update trap state
    trapState = 2;
    beeping = true;
    Serial.println(F("\n** FULL TRAP (State: 2 FLASHING) **"));
    Serial.println(F("\nWAITING FOR INPUT OR TIMEOUT..."));
    

  // RESET TRAP
  } else if ((remotePressed && trapState == 2) || autoreset == true) {
  
    // Make sure doors are closed
    trapDoors("close");

    // Music player stop
    musicPlayer.stopPlaying();
    
    // Yellow and Red LEDs off
    redLEDState = 1;
    digitalWrite(ledRed, LOW);
    digitalWrite(ledYellow, LOW);
    
    // NeoPixels off
    strip.fill(strip.Color(0,0,0,0));
    strip.show();

    // Bargraph reset
    for (uint8_t b = 12; b > 0; b--) {
      bar.setBar(23 - b + 1, LED_OFF);
      bar.writeDisplay();
      delay(50);
    }

    // Make sure smoke is off
    smokeActive = false;
    smokeToggleTime = 0;
    musicPlayer.GPIO_pinMode(smokePin, INPUT);
    musicPlayer.GPIO_digitalWrite(smokePin, LOW);

    // Update trap state
    trapState = 0;
    autoreset = false;
    opened = false;
    Serial.println(F("\n** RESET TRAP (State: 0)**"));
    Serial.println(F("\nWAITING FOR INPUT..."));
    

//////////////////
// LOOPING STATES
//////////////////

  
  // OPEN LOOP
  } else if (trapState == 1) {

    if (sfx == true && musicPlayer.stopped()) {
      autoreset = true;
      Serial.println(F("\n** TIMEOUT **"));
    }

    if (opened == false) {
      for (uint8_t i=0; i<250; i=i+10) {
        strip.fill(strip.Color(255,102,255,i));
        strip.show();
        delay(1);
      }
      for (uint8_t i=240; i>0; i=i-10) {
        strip.fill(strip.Color(255,102,255,i));
        strip.show();
        delay(1);
      }
      opened = true;
    }
    
    // Flash NeoPixels randomly
    if (millis() > whiteFlashTime){
      whiteFlashTime = millis() + 50;
      strip.setPixelColor(activeFlasher, strip.Color(255,102,255,255));
      strip.show();
      delay(30);
      strip.setPixelColor(activeFlasher, strip.Color(255,102,255,0));
      strip.show();
      activeFlasher = random(0, 22);
    }


  // FULL TRAP LOOP
  } else if (trapState == 2 && beeping == true) {
    
    if (sfx == true && musicPlayer.stopped()) {

      // Solid red light once beeping stops
      digitalWrite(ledRed, HIGH);
      redLEDState = 0;

      // Update trap state
      beeping = false;
      Serial.println(F("\n** FULL TRAP (State: 2 IDLE) **"));
      Serial.println(F("\nWAITING FOR INPUT..."));
    
    } else {

      // Flash red light during beeps
      if (redLEDState) {
        digitalWrite(ledRed, HIGH);
      } else {
        digitalWrite(ledRed, LOW);
      }
      redLEDState = !redLEDState;

      // Blue sparks synched with SFX
      if (sparkDelay.justFinished()) {
        sparkTime.start(5000);
        sparking = true;
        Serial.println(F("=> Blue sparks"));
      }
      if (sparkTime.justFinished()) {
        sparking = false;
      } else if (sparking == true) { 
        activeFlasher = random(0, 22);
        strip.setPixelColor(activeFlasher, strip.Color(0,0,255,0));
        strip.show();
        delay(30);
        strip.setPixelColor(activeFlasher, strip.Color(0,0,0,0));
        strip.show();
      }
      
      delay(160);

    }
    
  }
  
}


/////////////
// FUNCTIONS
/////////////


void trapDoors(String mode) {
  if (mode=="calibrate") {
    servoR.write(servoCloseR);
    servoL.write(servoCloseL);
  }
  servoR.attach(SERVO_RIGHT_PIN);
  servoL.attach(SERVO_LEFT_PIN);
  if (mode=="open") {
    servoR.write(servoOpenR);
    servoL.write(servoOpenL);
  } else {
    servoL.write(servoCloseL);
    delay(doorDelay);
    servoR.write(servoCloseR);
  }
  delay(300);
  servoR.detach();
  servoL.detach();
}

void checkRemote() {
  if (musicPlayer.GPIO_digitalRead(activationSwitch) == HIGH && millis() > debounceBuffer) {
    debounceBuffer = millis() + 1000;
    remotePressed = true;
  }
  else remotePressed = false;
}

If there ever was a need for a diagram, it would be for this.

Thanks. What is the thing that looks like an abacus on the lower left? The connection points are not labelled so it's hard to tell what the blue/black pairs do. I'm going to take a wild guess and say the black wires are common (ground)?

Why are there dotted line connections to the micro switch?

1000 times better. I'm near to the end of my shift, but I'm sure someone can run with this...

  char trackName[ 13 ];
    int group = 4;  // can be 1 to 10 for your code
    sprintf( trackName, "trk%02d_%02d.mp3", group, random( 100 ) );
    //  output will have the form "trk09_07.mp3", assuming the random number was 7
        musicPlayer.playFullFile( trackName );
    }

Could someone break down what each line is doing so I can learn?

Read up on sprintf() function.
This line is saying, put into the char array 'trackName' "trk" then two characters (leading zero) from the first parameter, followed by an underscore character then two characters from the second parameter, finally ending with ".mp3". Then the two parameters: group and a random number from 0 to 99.

I usually start my Halloween props in June and my Christmas displays in November. What are you doing for smoke? (You could connect the Arduino to 110V and get smoke, but only once.)

Try new hardware.

:frowning_face:

Hard to help fix the problem without knowing what it looked like before, but posting the code would go a long way towards that goal.
Do you know how to make incremental backups of your work?

Hi @anon4869908. I apologize for the trouble. The forum's automated spam detection system is far from perfect. This is why human moderators review every action taken by the automated system. Unfortunately, although humans can be a bit better at differentiating spam from legitimate posts, we are also slow. So, even though I think we always manage to correct the actions taken by the automated system in error, it does cause some delays.

Duplicate topics merged

@anon4869908
Why did you created a new topic on the same subject ?

Because they were not the same topic. The other one was an issue with the SD card and why the boards themselves were not working.

This topic is about programming and making the prop work. Not related at all. Merging the two has made the topic super confusing as it has introduced an issue that was solved in the last thread.

The topics are about the same project and merging them means that anyone reading the combined topic has the full background as to what you are doing

Thanks, but as I have said you have only served to muddy the topic with a question that I had solved. I've deleted my previous replies in the hopes it doesn't confuse any more people. Yes, it is the same project, but this is specific to the project, the other was a general hardware / programming question.

You're the mod, you do what you feel is best, but I feel you may have skipped the context.

Don't let it get you down. Just the rules or there'd just be too many topics to search and respond to.

It's a good subject, if you thinks something's missing go ahead and add it back in.

I have a super long thread that's mutated a couple of time but since it's the same project I kept it going: (I might try to rename it though)
Would this power switch have a parasitic draw? - Using Arduino / General Electronics - Arduino Forum

Ghostbusters was the last cartoon I watched as a teen.

So, what were you trying to discuss with the new thread?

I understand, but what is worse in my opinion is when topics are so disjointed and full of tangents that people that come after have to weed through pages of irrelevant information to understand what is being discussed.

Nothing in that other post had anything relevance this post. It was done and self-contained.

This is the new thread. The old one was started because I thought I had messed up my hardware. Turns out after a reformat of the SD card and copying the files over again, it has worked well since. I figured since everything was working there was no reason to beat that dead horse or have new people read through and keep giving answers. I wanted to focus on actually programming this thing and getting it working. Hence the new topic, in the programming section.

This means you are declaring a char array named trackName that will hold of 12 characters.

I am not at all familiar with the shield or the library, but the difference might be the difference from: startPlayingFile() and playFullFile().

I would be guessing that playFullFile() is a blocking function and startPlayingFile() is not.

How do you physically connect the e-cig to the pump (I've never look at an e-cig). What pump do you use? How do you turn on the e-cig? If I buy an e-cig to experiment, what do I look for on Amazon?

Not quite true, and I know what you mean, but @anon4869908 might not

More correctly, char trackName[ 13 ]; declares an array of 13 chars but can only be used to hold a C style string with a maximum length of 12 characters because it needs to be terminated with a '\0' thus needing the extra space for that character

I didn't want to confuse him with the details.

Lets work on ONE issue at a time.

Since you don't have a function named final_sequence(), I don't know what you are referencing. Is "Final Sequence" in a comment that I missed?

Just a scan of your code, I saw a lot of these in your loop:

delay(1200);
delay(30);
delay(60);
delay(160);
delay(300);

Some of these are sure to screw with your timing. Whenever possible, avoid using delay() inside the loop() function.

You could use millisDelay() more liberally but it requires you to rethink your program flow. (Incidentally, I am testing a few delay libraries including millisDelay. Essay to follow soon).

There is no problem with the hardware- you could run this on an Uno. (I have never heard of the "itsybitsy" micro controller. My go-to board is the Wemos D1 Mini). ((Actually, if you use the Wemos D1 Mini you could add remote control later since the Wemos has WiFi built in)).

[quote="anon4869908, post:31, topic:891782"]
PROBLEM 2: Green LED
Any reason why this might be happening?[/quote]

Ghosts.
I've never used NeoPixels (I prefer fastLED.h), but when you get one LED lighting up during startup, it's because of noise on the data line. Maybe something is happening to D5 during startup? Can you try another IO pin?
Is it always the same LED? I don't see the LED array or how you initialize it. (Maybe it's done in the NeoPixel library?? As I said, I've never used NeoPixel). Do you have a 1K resistor in the data line going to the Neopixel?

Whattheheck is the "forward NeoPixel Jewel"? Like "final sequence", I don't find anything in your code or comments referring to "forward NeoPixel Jewel".

Then do a "black" sequence in startup().

What sound shield are you using? Don't the specs say anything about volume control? What do you mean "manually change the volume"?

What button?

Don't tell me. Put it into the comments of the sketch.
The library has a setVolume() function, you don't have to reprogram the board.

WHAT?? You aren't making a breadboard first? Big mistake. Big. Huge. Unless you are building a Heathkit, you should make everything work on a breadboard before touching a soldering iron.

Probably. Like I said, I've never used the Neopixel library.

musicPlayer.setVolume(40, 40);

This does the same but you can adjust the volume dynamically:

int leftVolume=40;
int rightVolume=40;
musicPlayer.setVolume(leftVolume, rightVolume);

If the knob is on a rotary encoder. Just read the encoder value, map it to the range of the volume parameter. (You would have to read the library code- the .h file- to see what the range is. Is it 0-100% or 0-255?)

Click? Do you mean a pushbutton? Just wire the button to a GPIO port and detect when it is pressed.

There's some button libraries that allow for long or short button presses.