WS8211 Uno and DY SV5W MEGATRON

Hello everyone. I'm working on the final details of my son's Megatron costume and I'm hitting a snag.

I am creating a sketch for Arduino UNO, ws8211 and DY-SV5W. The sketch should trigger two different cases. Case 1 is a strip of LEDs in a pattern playing while audio track 1 plays. case 1 will be triggered by a toggle switch and will repeat until the toggle is ungrounded. Case 2 is a second led pattern and audio track 2. Case 2 will be triggered by a momentarily grounded switch. Case 2 can be triggered if the toggle switch is in either position. If the momentary switch interrupts case1 while toggle is switched on, case 1 will resume after case 2 completes the audio track. if case 2 is triggered when case 1 is switched off, case 2 case 2 cannot be triggered.

However, Here’s what happens. When the Toggle is on, I can only get audio track 1 to play once, and will not repeat. I cannot get audio track two to play with the toggle on, but when I ground the momentary switch while the toggle is grounded, it resets case 1. When the toggle is off, I can trigger case 2 by grounding the toggle, which will cause the audio only to play, but cannot get the light pattern 2 to play. I only want case 2 to be able to play and interrupt case 1 when case 1 is active.

Think megaton's blaster. he "arms" it with the toggle, and then "fires" with the trigger. Then it should return to ready, armed state.


#include <Adafruit_NeoPixel.h>
#include <SoftwareSerial.h>

#define LED_PIN       6
#define NUM_LEDS      30 // Adjust to the number of LEDs in your strip
#define DFPLAYER_RX   10
#define DFPLAYER_TX   11

// Button pins
#define TOGGLE_SWITCH_PIN   4
#define MOMENTARY_BUTTON_PIN 3

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
SoftwareSerial mySerial(DFPLAYER_RX, DFPLAYER_TX);

enum CaseState {
  CASE_NONE,
  CASE_1,
  CASE_2
};

CaseState currentCase = CASE_NONE;
bool isToggleOn = false;
bool isPlayingTrack2 = false;

byte commandLength;
byte command[6];
int checkSum = 0;

//my variables for demo sketch
int trackNum = 1;
int playStatus; //0 playing 1 stopped 2 waiting to start next track
unsigned long lastCheckTime;
unsigned long currentMillis;
int busyPinstate;
int triggerPin = 2;//trigger pin

void playTrack(int soundTrack) {
  //select track
  Serial.print("soundTrack: ");
  Serial.println(soundTrack);
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x07;
  command[2] = 0x02;
  command[3] = highByte(soundTrack);//snh...track HIGH bit
  command[4] = lowByte(soundTrack);//SNL... track low bit
  checkSum = 0;
  for (int q = 0; q < 5; q++) {
    checkSum +=  command[q];
  }
  command[5] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 6;
  sendCommand();
}

//plays whatever track has been paused or 1st track if nothing selected
//May need to be selected after putting into random mode
void play() {
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x02;
  command[2] = 0x00;
  command[3] = 0xAC;
  commandLength = 4;
  sendCommand();
}

//selects random mode
void randomMode() {
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x18;
  command[2] = 0x01;
  command[3] = 0x03;//random
  checkSum = 0;
  for (int q = 0; q < 4; q++) {
    checkSum +=  command[q];
  }
  command[4] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 5;
  sendCommand();

  //play() needs to be selected if you want the random tracks to start playing instantly
  play();
}

//sets the device volume...0 - 30
void playbackVolume(int vol) {
  if (vol > 30) { //check within limits
    vol = 30;
  }
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x13;
  command[2] = 0x01;
  command[3] = vol;//volume
  checkSum = 0;
  for (int q = 0; q < 4; q++) {
    checkSum +=  command[q];
  }
  command[4] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 5;
  sendCommand();
}

//sends the command to the DY-SV5W
void sendCommand() {
  int q;
  for (q = 0; q < commandLength; q++) {
    mySerial.write(command[q]);
    Serial.print(command[q], HEX);
  }
  Serial.println("End");
}
void setup() {
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'

  mySerial.begin(9600); // Initialize DY-SV5W
  sendCommand(0x3F, 0); // Reset the module
  delay(1000); // Wait for the module to reset

  // Set button pins as input
  pinMode(TOGGLE_SWITCH_PIN, INPUT_PULLUP);
  pinMode(MOMENTARY_BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  // Check toggle switch state
  if (digitalRead(TOGGLE_SWITCH_PIN) == LOW) {
    if (!isToggleOn) {
      isToggleOn = true;
      startCase1(); // Start case 1
    }
  } else {
    if (isToggleOn) {
      isToggleOn = false;
      stopCase1(); // Stop case 1
    }
  }

  // Check momentary button state
  if (digitalRead(MOMENTARY_BUTTON_PIN)== LOW) {
    if (currentCase != CASE_2) {
      startCase2(); // Start case 2
      delay(300); // Debounce delay
    }
  }

  // Check if case 2 has finished playing
  if (isPlayingTrack2 && !mySerial.available()) {
    if (isToggleOn) {
      startCase1(); // Resume case 1 if toggle is on
    } else {
      stopCase1(); // Stop case 1 if toggle is off
    }
    currentCase = CASE_NONE; // Reset current case
    isPlayingTrack2 = false; // Reset track 2 state
  }
}

// Start case 1 (track 1 and pattern 1)
void startCase1() {
  currentCase = CASE_1;
  playTrack(1); // Play audio track 1
  lightPatternOne(); // Start light pattern one
}

// Stop case 1
void stopCase1() {
  stopTrack(); // Stop playback
  strip.clear(); // Clear LED strip
  strip.show();
}

// Start case 2 (track 2 and pattern 2)
void startCase2() {
  isPlayingTrack2 = true;
  stopTrack(); // Stop case 1 if it's playing
  playTrack(5); // Play audio track 2
  lightPatternTwo(); // Start light pattern two
  currentCase = CASE_2; // Set current case to 2

}

// Function to play a specific track
void playTrack(uint8_t track) {
  sendCommand(0x03, track); // Play specified track
}

// Function to stop the current track
void stopTrack() {
  sendCommand(0x16, 0); // Stop playback
}

// Light pattern one (rainbow effect)
void lightPatternOne() {
  static unsigned long lastUpdate = 0;
  static int j = 0;

  // Run the rainbow effect
  if (millis() - lastUpdate > 20) { // Update every 20 ms
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i + j) & 255));
    }
    strip.show();
    j++;
    lastUpdate = millis();
  }
}

// Light pattern two (breathing effect)
void lightPatternTwo() {
  static unsigned long lastUpdate = 0;
  static int brightness = 0;
  static int fadeAmount = 5;

  // Run the breathing effect
  if (millis() - lastUpdate > 30) { // Update every 30 ms
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, strip.Color(brightness, 0, 0)); // Red breathing effect
    }
    strip.show();

    brightness += fadeAmount;
    if (brightness <= 0 || brightness >= 255) {
      fadeAmount = -fadeAmount; // Reverse the direction
    }
    lastUpdate = millis();
  }
}

// Function to generate color from wheel
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
    WheelPos -= 170;
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

// Function to send commands to the DFPlayer
void sendCommand(uint8_t command, uint8_t param) {
  mySerial.write(0x7E); // Start byte
  mySerial.write(0xFF); // Version
  mySerial.write(command); // Command
  mySerial.write(param); // Parameter
  mySerial.write(0xEF); // End byte
}

I'm lost in this. 'Case' is a loaded word in C/C++ coding, because of the switch-case statement.

This seems cleaner:

It seems that the system could be modeled with three states: IDLE, ARMED, FIRING, where I suppose ARMED and FIRING would map to CASE_1 and CASE_2. I'd rename a few things and try something like these completely untested snippets:

switch (state){
   case IDLE:
     if (digitalRead(TOGGLE_SWITCH_PIN) == LOW){
       state = ARMED;
       // startCase1 stuff
     }
     break;
case ARMED:
     if (digitalRead(TOGGLE_SWITCH_PIN) == HIGH){
       state = IDLE;
       // other stuff to disarm 
     }
     if (digitalRead(MOMENTARY_BUTTON_PIN)== LOW) {
        state = FIRING;
        // startCase2 stuff 
     }
     if( !dfPlayerBusy ){
        //play random track
     }
     break;
  case FIRING:
     if(!dfPlayerBusy){
        state = ARMED;
     }
     break;
}

and you also need to detect if the DFPlayer is busy. Maybe:

dfPlayerBusy = digitalRead(DF_PLAYER_BUSY_PIN) == HIGH ? false : true;

These use a switch-case statement to maintain a Finite State Machine. switch-case is excellent for separating mutually-exclusive logic. In the aboce, the transitions are coded as if-statemets within the cases that test for the need for a transition and do the work of transitioning.

Here's one FSM tutorial. There are many other tutorials available with a search

Could it be bouncing of the "toggle" switches? Would you isolate the switches and read them to ensure every "toggle" does just that?

I don't have a dfPlayer, but if this checks for if it is busy:

I do not see where you ever mySerial.read() anything to drain out the character and change the availability.

great ideas. Yes- I was thinking of the cases exactly like how you interpreted it... switch-cases with multiple embedded if-the statements, but as I got into the code, I didn't follow through on developing those into proper state machines. I'm going to try to take those suggestions and rework some of this.

Any ideas about why the second light pattern "breathing effect" does not run at all?

this is my first go with the SV5W - why do I need to detect if it's busy? I want the momentary (FIRING) state to interrupt the ARMED state.

it's my understanding that you don't need myserial.read with the sv5W. I know you need myserial.begin and .write.

I'm a little unsure about your meaning. Could you clarify?

thanks
there's great info about this board (and a lot more) over at Digital Town

But when you are in the ARMED state, and the player becomes not-busy as a track ends, I thought you wanted to get busy on a new random track.

One important trick with the state machine coding is figuring out where all the one-shot actions happen, and the "start a new random track" happens only in ARMED state when a track ends.

I do not know the board, but the mySerial.available() is on the Arduino side and indicates the number of bytes in the Arduino's buffer waiting to be read. The number increases as new bytes are added to the buffer, and only decreases as characters are actually read() and removed from the buffer.

If !mySerial.available() is true, and then ever changes to false as a byte comes in, !mySerial.available() will never change back to true if the byte is never read.

You have two sendCoommand() functions. It does not look like one is a prototype.

Good catch... I'll pull that out when I get back to that version. What do you mean [quote="xfpd, post:9, topic:1315901"]
It does not look like one is a prototype.
[/quote]?

not a random track, I just want the armed track to repeat continuously.

Take a look at this code - it's getting closer to what I'm trying to do, but now none of the audio tracks are being triggered.

#include <Adafruit_NeoPixel.h>
#include <SoftwareSerial.h>

#define LED_PIN 6          // NeoPixel strip connected to pin 6
#define NUM_PIXELS 20      // Number of LEDs in the NeoPixel strip
#define DY_TX 10           // DY-SV5W TX pin connected to Arduino pin 10
#define DY_RX 11           // DY-SV5W RX pin connected to Arduino pin 11
#define ARMED_PIN 5        // ARMED state toggle switch
#define PRIMED_PIN 4       // PRIMED state toggle switch
#define FIRING_PIN 3       // FIRING state momentary switch

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);
SoftwareSerial mySerial(DY_RX, DY_TX);

enum States { OFF, ARMED, PRIMED, FIRING };
States currentState = OFF;
bool isFiringActive = false;

void setup() {
  pinMode(ARMED_PIN, INPUT_PULLUP);
  pinMode(PRIMED_PIN, INPUT_PULLUP);
  pinMode(FIRING_PIN, INPUT_PULLUP);
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  
  mySerial.begin(9600);
  Serial.begin(9600); // For debugging
  
  Serial.println("System Initialized");
}

void loop() {
  bool armedState = digitalRead(ARMED_PIN) == LOW;
  bool primedState = digitalRead(PRIMED_PIN) == LOW;
  bool firingState = digitalRead(FIRING_PIN) == LOW;

  // State management logic
  if (armedState) {
    if (primedState) {
      if (firingState && currentState == PRIMED) {
        currentState = FIRING;
        Serial.println("State: FIRING");
        triggerFiring();
      } else if (currentState != FIRING) {
        currentState = PRIMED;
        Serial.println("State: PRIMED");
        triggerPrimed();
      }
    } else if (currentState != FIRING) {
      currentState = ARMED;
      Serial.println("State: ARMED");
      triggerArmed();
    }
  } else {
    if (currentState != OFF) {
      currentState = OFF;
      Serial.println("State: OFF");
      turnOff();
    }
  }
  
  // Run LED patterns based on the current state
  switch (currentState) {
    case ARMED:
      purpleBreath();
      break;
    case PRIMED:
      fastPurpleRotate();
      break;
    case FIRING:
      if (isFiringActive) {
        fastTwinkleBurst();
      }
      break;
    case OFF:
      strip.clear();
      strip.show();
      break;
  }
}

void triggerArmed() {
  mySerial.write(0x16);  // Stop any playing audio track
  delay(100);            // Ensure previous audio stops completely
  strip.clear();
  strip.show();
}

void triggerPrimed() {
  mySerial.write(0xAA);  // Start command sequence
  mySerial.write(0x07);  // Play command
  mySerial.write(0x02);  // Play track 1
  delay(100);            // Allow the DY-SV5W to process command
  Serial.println("Playing Audio Track 1 (PRIMED)");
}

void triggerFiring() {
  isFiringActive = true;
  mySerial.write(0xAA);  // Start command sequence
  mySerial.write(0x07);  // Play command
  mySerial.write(0x03);  // Play track 2
  delay(100);            // Allow the DY-SV5W to process command
  Serial.println("Playing Audio Track 2 (FIRING)");

  delay(5000);  // Adjust this delay as per the length of audio track 2

  // After FIRING completes, return to PRIMED state if ARMED and PRIMED toggles are still on
  isFiringActive = false;
  if (digitalRead(ARMED_PIN) == LOW && digitalRead(PRIMED_PIN) == LOW) {
    currentState = PRIMED;
    Serial.println("Returning to PRIMED after FIRING");
    triggerPrimed();
  } else {
    currentState = OFF;
    turnOff();
  }
}

// LED Patterns

void purpleBreath() {
  static uint8_t brightness = 0;
  static int fadeAmount = 5;
  
  brightness += fadeAmount;
  if (brightness == 0 || brightness == 255) fadeAmount = -fadeAmount;

  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, strip.Color(brightness, 0, brightness)); // Purple color
  }
  strip.show();
  delay(30);
}

void fastPurpleRotate() {
  static int ledIndex = 0;
  strip.clear();

  for (int i = 0; i < strip.numPixels(); i++) {
    int pixel = (i + ledIndex) % strip.numPixels();
    strip.setPixelColor(pixel, strip.Color(128, 0, 128)); // Set pixel to purple
  }
  
  ledIndex++;
  strip.show();
  delay(50);
}

void fastTwinkleBurst() {
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, random(2) == 0 ? strip.Color(128, 0, 128) : strip.Color(0, 0, 0)); // Purple twinkle or off
  }
  strip.show();
  delay(50);
}

void turnOff() {
  strip.clear();
  strip.show();
  mySerial.write(0x16);  // Stop audio
  delay(100);
  Serial.println("Audio Stopped and LEDs turned OFF");
}

I need to go to bed and come at this tomorrow with a clear head

that line was actually from something I reused. is that what you meant about the prototype?

A prototype is an empty version of the function, preceding a call to that function, showing the return type, function name and argument types.

  int thisFunctionPrototype(int, int);

There are also two playTrack() functions...

void playTrack(int soundTrack) {
  //select track

and

// Function to play a specific track
void playTrack(uint8_t track) {

Did I get a mashup of two sketches? I don't know how that compiles.

#include <Adafruit_NeoPixel.h>
#include <SoftwareSerial.h>

#define LED_PIN       6
#define NUM_LEDS      30 // Adjust to the number of LEDs in your strip
#define DFPLAYER_RX   10
#define DFPLAYER_TX   11

// Button pins
#define TOGGLE_SWITCH_PIN   4
#define MOMENTARY_BUTTON_PIN 3

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
SoftwareSerial mySerial(DFPLAYER_RX, DFPLAYER_TX);

enum CaseState {
  CASE_NONE,
  CASE_1,
  CASE_2
};

CaseState currentCase = CASE_NONE;
bool isToggleOn = false;
bool isPlayingTrack2 = false;

byte commandLength;
byte command[6];
int checkSum = 0;

//my variables for demo sketch
int trackNum = 1;
int playStatus; //0 playing 1 stopped 2 waiting to start next track
unsigned long lastCheckTime;
unsigned long currentMillis;
int busyPinstate;
int triggerPin = 2;//trigger pin

void playTrack(int soundTrack) {
  //select track
  Serial.print("soundTrack: ");
  Serial.println(soundTrack);
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x07;
  command[2] = 0x02;
  command[3] = highByte(soundTrack);//snh...track HIGH bit
  command[4] = lowByte(soundTrack);//SNL... track low bit
  checkSum = 0;
  for (int q = 0; q < 5; q++) {
    checkSum +=  command[q];
  }
  command[5] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 6;
  sendCommand();
}

//plays whatever track has been paused or 1st track if nothing selected
//May need to be selected after putting into random mode
void play() {
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x02;
  command[2] = 0x00;
  command[3] = 0xAC;
  commandLength = 4;
  sendCommand();
}

//selects random mode
void randomMode() {
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x18;
  command[2] = 0x01;
  command[3] = 0x03;//random
  checkSum = 0;
  for (int q = 0; q < 4; q++) {
    checkSum +=  command[q];
  }
  command[4] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 5;
  sendCommand();

  //play() needs to be selected if you want the random tracks to start playing instantly
  play();
}

//sets the device volume...0 - 30
void playbackVolume(int vol) {
  if (vol > 30) { //check within limits
    vol = 30;
  }
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x13;
  command[2] = 0x01;
  command[3] = vol;//volume
  checkSum = 0;
  for (int q = 0; q < 4; q++) {
    checkSum +=  command[q];
  }
  command[4] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 5;
  sendCommand();
}

//sends the command to the DY-SV5W
void sendCommand() {
  int q;
  for (q = 0; q < commandLength; q++) {
    mySerial.write(command[q]);
    Serial.print(command[q], HEX);
  }
  Serial.println("End");
}
void setup() {
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'

  mySerial.begin(9600); // Initialize DY-SV5W
  sendCommand(0x3F, 0); // Reset the module
  delay(1000); // Wait for the module to reset

  // Set button pins as input
  pinMode(TOGGLE_SWITCH_PIN, INPUT_PULLUP);
  pinMode(MOMENTARY_BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  // Check toggle switch state
  if (digitalRead(TOGGLE_SWITCH_PIN) == LOW) {
    if (!isToggleOn) {
      isToggleOn = true;
      startCase1(); // Start case 1
    }
  } else {
    if (isToggleOn) {
      isToggleOn = false;
      stopCase1(); // Stop case 1
    }
  }

  // Check momentary button state
  if (digitalRead(MOMENTARY_BUTTON_PIN) == LOW) {
    if (currentCase != CASE_2) {
      startCase2(); // Start case 2
      delay(300); // Debounce delay
    }
  }

  // Check if case 2 has finished playing
  if (isPlayingTrack2 && !mySerial.available()) {
    if (isToggleOn) {
      startCase1(); // Resume case 1 if toggle is on
    } else {
      stopCase1(); // Stop case 1 if toggle is off
    }
    currentCase = CASE_NONE; // Reset current case
    isPlayingTrack2 = false; // Reset track 2 state
  }
}

// Start case 1 (track 1 and pattern 1)
void startCase1() {
  currentCase = CASE_1;
  playTrack(1); // Play audio track 1
  lightPatternOne(); // Start light pattern one
}

// Stop case 1
void stopCase1() {
  stopTrack(); // Stop playback
  strip.clear(); // Clear LED strip
  strip.show();
}

// Start case 2 (track 2 and pattern 2)
void startCase2() {
  isPlayingTrack2 = true;
  stopTrack(); // Stop case 1 if it's playing
  playTrack(5); // Play audio track 2
  lightPatternTwo(); // Start light pattern two
  currentCase = CASE_2; // Set current case to 2

}

// Function to play a specific track
void playTrack(uint8_t track) {
  sendCommand(0x03, track); // Play specified track
}

// Function to stop the current track
void stopTrack() {
  sendCommand(0x16, 0); // Stop playback
}

// Light pattern one (rainbow effect)
void lightPatternOne() {
  static unsigned long lastUpdate = 0;
  static int j = 0;

  // Run the rainbow effect
  if (millis() - lastUpdate > 20) { // Update every 20 ms
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i + j) & 255));
    }
    strip.show();
    j++;
    lastUpdate = millis();
  }
}

// Light pattern two (breathing effect)
void lightPatternTwo() {
  static unsigned long lastUpdate = 0;
  static int brightness = 0;
  static int fadeAmount = 5;

  // Run the breathing effect
  if (millis() - lastUpdate > 30) { // Update every 30 ms
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, strip.Color(brightness, 0, 0)); // Red breathing effect
    }
    strip.show();

    brightness += fadeAmount;
    if (brightness <= 0 || brightness >= 255) {
      fadeAmount = -fadeAmount; // Reverse the direction
    }
    lastUpdate = millis();
  }
}

// Function to generate color from wheel
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
    WheelPos -= 170;
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

// Function to send commands to the DFPlayer
void sendCommand(uint8_t command, uint8_t param) {
  mySerial.write(0x7E); // Start byte
  mySerial.write(0xFF); // Version
  mySerial.write(command); // Command
  mySerial.write(param); // Parameter
  mySerial.write(0xEF); // End byte
}

Those are two different functions, and which one is called depends on whether the argument is a uint8_t or an int.

The multiple functions with the same name works the same way as Serial.print("hello world") can print a "hello world"" string while Serial.print(PI) prints "3.14".

You likely don't need both functions.

I am not disagreeing with you @DaveX, but this (was) new news to me from the beginning of time. When I (searched) "two functions same name" the overwhelming response (was) "Can't"... but one JavaScript described "Function Overloading" where javascript "can't" but went on to point out the compile-time stuff of counting arguments.

Thank you, @DaveX - I now see these function overloading in library files, and have a clear(er) understanding... and for destroying my long-held belief of "can't"... I swear, I am not crying in my coffee... thinking of the lost decades...

Since you are using swSerial, you have to make sure the transmission is complete before you start making a call to

the Neopixel library needs to disable interrupts during show() and swSerial depends on it. Try calling

mySerial.flush();

after every transmission or at least before any call to show(). at 9600bps a byte takes about 1ms to transmitted , so waiting enough will probably also resolve the issue.

thanks - but I've tried inserting my serial as you suggested in both locations with no change.

thank you both!

Here's where I'm at: I've taken it back down to two states. if I can get the sketch running properly before halloween, I'd like to break state one into primed and armed. for now, I'm able to get the state one track triggered with the toggle, I can interrupt it with the momentary switch, but the loops don't repeat (both audio and LED). in Case1, the led pattern executes once (~1 second), but the audio track plays the entire segment (8s). In case2, the led pattern executes once (~1s) but when it finished, it cuts off the audio track and restarts case 1 when the toggle is engaged. The momentary switch activates the case2 regardless of the state of the toggle switch, which is wrong. I want it to restart case one (if the toggle is engaged) but that should happen after the track plays in entirety, or for a set amount of time (millis). I feel like this is a problem I have had in the past and it's one little segment that I'm overlooking.
Does anyone see anything obvious?

`#include <Adafruit_NeoPixel.h>
#include <SoftwareSerial.h>
#include <NeoPixelBus.h>
#include <NeoPixelAnimator.h>


#define LED_PIN       6
#define NUM_LEDS      30 // Adjust to the number of LEDs in your strip
#define DFPLAYER_RX   10
#define DFPLAYER_TX   11

// Button pins
#define TOGGLE_SWITCH_PIN   4
#define MOMENTARY_BUTTON_PIN 3
//color variables


Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
SoftwareSerial mySerial(DFPLAYER_RX, DFPLAYER_TX);

enum CaseState {
  CASE_NONE,
  CASE_1,
  CASE_2,
};

CaseState currentCase = CASE_NONE;
bool isToggleOn = false;
bool isPlayingTrack2 = false;

byte commandLength;
byte command[6];
int checkSum = 0;

//my variables for demo sketch
int trackNum = 1;
int playStatus; //0 playing 1 stopped 2 waiting to start next track
unsigned long lastCheckTime;
unsigned long currentMillis;
int busyPinstate;
int triggerPin = 2;//trigger pin

void playTrack(int soundTrack) {
  //select track
  Serial.print("soundTrack: ");
  Serial.println(soundTrack);
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x07;
  command[2] = 0x02;
  command[3] = highByte(soundTrack);//snh...track HIGH bit
  command[4] = lowByte(soundTrack);//SNL... track low bit
  checkSum = 0;
  for (int q = 0; q < 5; q++) {
    checkSum +=  command[q];
  }
  command[5] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 6;
  sendCommand();
}

//plays whatever track has been paused or 1st track if nothing selected
//May need to be selected after putting into random mode
void play() {
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x02;
  command[2] = 0x00;
  command[3] = 0xAC;
  commandLength = 4;
  sendCommand();
}

//selects random mode
void randomMode() {
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x18;
  command[2] = 0x01;
  command[3] = 0x03;//random
  checkSum = 0;
  for (int q = 0; q < 4; q++) {
    checkSum +=  command[q];
  }
  command[4] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 5;
  sendCommand();

  //play() needs to be selected if you want the random tracks to start playing instantly
  play();
}

//sets the device volume...0 - 30
void playbackVolume(int vol) {
  if (vol > 30) { //check within limits
    vol = 30;
  }
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x13;
  command[2] = 0x01;
  command[3] = vol;//volume
  checkSum = 0;
  for (int q = 0; q < 4; q++) {
    checkSum +=  command[q];
  }
  command[4] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 5;
  sendCommand();
}

//sends the command to the DY-SV5W
void sendCommand() {
  int q;
  for (q = 0; q < commandLength; q++) {
    mySerial.write(command[q]);
    Serial.print(command[q], HEX);
  }
  Serial.println("End");
}
void setup() {
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'

  mySerial.begin(9600); // Initialize DY-SV5W
  sendCommand(0x3F, 0); // Reset the module
  delay(1000); // Wait for the module to reset

  // Set button pins as input
  pinMode(TOGGLE_SWITCH_PIN, INPUT_PULLUP);
  pinMode(MOMENTARY_BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  // Check toggle switch state
  if (digitalRead(TOGGLE_SWITCH_PIN) == LOW) {
    if (!isToggleOn) {
      isToggleOn = true;
      startCase1(); // Start case 1
    }
  } else {
    if (isToggleOn) {
      isToggleOn = false;
      stopCase1(); // Stop case 1
    }
  }

  // Check momentary button state
  if (digitalRead(MOMENTARY_BUTTON_PIN)== LOW) {
    if (currentCase != CASE_2) {
      startCase2(); // Start case 2
      delay(300); // Debounce delay
    }
  }

  // Check if case 2 has finished playing
  if (isPlayingTrack2 && !mySerial.available()) {
    if (isToggleOn) {
      startCase1(); // Resume case 1 if toggle is on
    } else {
      stopCase1(); // Stop case 1 if toggle is off
    }
    currentCase = CASE_NONE; // Reset current case
    isPlayingTrack2 = false; // Reset track 2 state
  }
}

// Start case 1 (track 1 and pattern 1)
void startCase1() {
  currentCase = CASE_1;
  playTrack(1); // Play audio track 1
  primePattern(); // Start light pattern one
}

// Stop case 1
void stopCase1() {
  stopTrack(); // Stop playback
  strip.clear(); // Clear LED strip
  strip.show();
}

// Start case 2 (track 2 and pattern 2)
void startCase2() {
  isPlayingTrack2 = true;
  stopTrack(); // Stop case 1 if it's playing
  playTrack(5); // Play audio track 2
  lightPatternTwo(); // Start light pattern two
  currentCase = CASE_2; // Set current case to 2

}

// Function to play a specific track
void playTrack(uint8_t track) {
  sendCommand(0x03, track); // Play specified track
}

// Function to stop the current track
void stopTrack() {
  sendCommand(0x16, 0); // Stop playback
}

// PRIME pattern: Fast purple fading loop
void primePattern() {
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, strip.Color(128, 0, 255)); // Purple color
    strip.show();
    delay(10);
  }
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, strip.Color(0, 0, 0)); // Fade out
    strip.show();
    delay(10);

  }
}

// FIRE pattern: High-speed strobe sparkle with purple and red
void lightPatternTwo() {
  for (int i = 0; i < 100; i++) {  // Loop for a brief period
    for (int j = 0; j < strip.numPixels(); j++) {
      if (random(10) > 7) {
        strip.setPixelColor(j, strip.Color(255, 0, 0)); // Red sparkle
      } else {
        strip.setPixelColor(j, strip.Color(128, 0, 255)); // Purple sparkle
      }
    }
    strip.show();
    delay(30);  // High-speed strobe effect
    strip.clear();  // Clear for next sparkle
  }

}
// Helper function to fill entire strip with a single color
void fillStrip(uint32_t color) {
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, color);
  }
  strip.show();
}
// Function to generate color from wheel
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
    WheelPos -= 170;
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

// Function to send commands to the   Player
void sendCommand(uint8_t command, uint8_t param) { 
  mySerial.write(0x7E); // Start byte
  mySerial.write(0xFF); // Version
  mySerial.write(command); // Command
  mySerial.write(param); // Parameter
  mySerial.write(0xEF); // End byte
}`

maybe I give up and just leave it as is. it's not how I wanted it, but it kind of works.