#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include "DFRobotDFPlayerMini.h"
#include "Rotary.h" // Include the Rotary library
#include <DmxSimple.h>
// Encoder and button setup
#define ROTARY_PIN1 2
#define ROTARY_PIN2 3
#define CLICKS_PER_STEP 4 // this number depends on your rotary encoder
#define FPSerial Serial3
#define MAX_CHANNELS 5 // Define the maximum number of DMX channels you will use
#define BR_CHANNEL 2 // Adjust to your DMX setup
#define RED_CHANNEL 3 // Adjust to your DMX setup
#define GREEN_CHANNEL 4 // Adjust to your DMX setup
#define BLUE_CHANNEL 5 // Adjust to your DMX setup
int currentIntensities[MAX_CHANNELS + 1]; // Array to hold the current intensity values for each channel
Rotary r = Rotary(ROTARY_PIN1, ROTARY_PIN2);
DFRobotDFPlayerMini myDFPlayer;
const int relayPins[] = {4, 5, 6, 7, 8, 9, 10, 11};
const int buttonPins[] = {49, 48, 47, 46, 45, 44, 43, 42};
const int busyPin = 12;
const int potPin = A1; // Replace with the actual analog pin connected to the potentiometer
const int OFF = LOW;
const int ON = HIGH;
unsigned long lastButtonDebounceTime[8] = {0};
int buttonState[8] = {LOW}; // Current state of the button
int lastButtonState[8] = {LOW}; // Last state of the button
int currentButton = -1;
int potValue = analogRead(potPin);
int mappedDelay = map(potValue, 0, 1023, 100, 1000);
volatile int currentAction = -1;
bool isMusicPlaying = false; // Global variable to track music state
int activeButton = 0;
// for 8 buttons
const unsigned long debounceDelay = 50; // Debounce delay in milliseconds
const int buttonPin = 50; // Button pin for press detection
// Enhanced debounce logic for boot-up button press
unsigned long bootTime = millis();
bool buttonPressed = false;
const unsigned long bootWaitTime = 5000; // Time window to wait for button press at boot
const unsigned long debounceDelay2 = 100; // Debounce delay in milliseconds
unsigned long lastDebounceTime = millis(); // Last time the button pin state was checked
static unsigned long previousMillis = 0;
// Global variables for menu navigation and variable adjustment
bool inSelectionMode = false;
bool inAdjustMode = false;
int currentOption = 0, displayOption = 0, currentVariable = 0;
unsigned long lastScrollTime = 0;
const long scrollInterval = 250; // Adjust scroll speed as needed
int scrollIndex = 0;
String scrollText = ""; // Will hold the text to scroll
bool setupComplete = false;
// LCD setup
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD I2C address to 0x27 for a 16 chars and 2 line display
//EEPROM
const int EEPROM_ADDR_LAST_OPTION = 0; // Use 1 byte
const byte EEPROM_VALID_DATA_FLAG = 0xA5;
const int EEPROM_INIT_FLAG_ADDR = 1; // Use 1 byte, right after the last option address
const int EEPROM_DATA_START_ADDR = 2; // Start after the flag address
// Define the Variable structure
enum VariableType {
INTEGER,
BOOLEAN,
ENUM // For simplicity, let's consider enumerated types as integers
};
enum AnimationType {
ANIMATION_NONE,
ANIMATION_AURORA,
ANIMATION_FIRE,
ANIMATION_STOP,
// Add more as needed
};
AnimationType currentAnimation = ANIMATION_NONE;
struct Variable {
VariableType type;
union {
struct {
int value;
int minValue;
int maxValue;
} integer;
bool boolean;
struct {
int enumValue;
int enumCount;
} enumeration;
};
};
// Define the MenuOption structure
struct MenuOption {
String name;
Variable* variables;
int variableCount;
void (*action)();
};
struct FadeInfo {
int currentIntensity;
int targetIntensity;
unsigned long startTime;
unsigned long duration;
int minIntensity;
int maxIntensity;
bool reset; // Flag to indicate if the fade needs to be reset
};
// Global variable definitions
FadeInfo redFade, greenFade, blueFade;
// Variables for each option
Variable option1Vars[] = {
{INTEGER, {{10, 0, 20}}},
{INTEGER, {{15, 0, 25}}}
};
Variable option2Vars[] = {
{INTEGER, {{30, 10, 40}}},
{INTEGER, {{35, 20, 45}}}
};
Variable option3Vars[] = {
{BOOLEAN, {.boolean = true}}
};
Variable option4Vars[] = {
{ENUM, {.enumeration = {0, 3}}}
};
void actionOption1();
void actionOption2();
void actionOption3();
void actionOption4();
void loadAllVariablesFromEEPROM();
void saveAllVariablesToEEPROM();
void updateLCD();
void handleButtonPress();
void enterSetupMode();
void runLastSelectedProgram();
void adjustVariable(Variable& var, int increment);
MenuOption menuOptions[] = {
{"Water Dragon", option1Vars, sizeof(option1Vars) / sizeof(Variable), actionOption1},
{"Option 2", option2Vars, sizeof(option2Vars) / sizeof(Variable), actionOption2},
{"Option 3", option3Vars, sizeof(option3Vars) / sizeof(Variable), actionOption3},
{"Option 4", option4Vars, sizeof(option4Vars) / sizeof(Variable), actionOption4},
};
bool nonBlockingDelay(unsigned long &previousMillis, unsigned long interval) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis; // Reset the timer
return true; // Interval has passed
}
return false; // Interval has not passed yet
}
// Handler called on any rotation
void onRotation(Rotary& r) {
// Check if we are in selection mode or adjustment mode
if (inSelectionMode) {
// Change menu options based on the rotation direction
if (r.getDirection() == RE_RIGHT) {
displayOption++;
if (displayOption >= sizeof(menuOptions) / sizeof(MenuOption)) {
displayOption = 0; // Wrap around to the first option
}
} else if (r.getDirection() == RE_LEFT) {
if (displayOption == 0) {
displayOption = sizeof(menuOptions) / sizeof(MenuOption) - 1; // Wrap around to the last option
} else {
displayOption--;
}
}
updateLCD(); // Update the display with the new option
} else if (inAdjustMode) {
// Adjust the current variable based on the encoder's rotation
Variable* var = &menuOptions[currentOption].variables[currentVariable];
int increment = r.getDirection() == RE_RIGHT ? 1 : -1;
adjustVariable(*var, increment);
updateLCD(); // Reflect the adjustment on the display
}
}
void onRotationLR(Rotary& r) {
Serial.println(r.directionToString(r.getDirection()));
}
// Action functions for each option (placeholders)
void actionOption1() {
if (!isMusicPlaying && digitalRead(busyPin) == HIGH) { // Corrected condition check
myDFPlayer.loop(1);
Serial.println("PLAY!");
isMusicPlaying = true; // Set flag to true when music starts playing
}
checkButtons(); // Assuming this checks which button was pressed and sets currentAction
switch (currentAction) {
case 0:
button1Function();
break;
case 1:
button2Function();
break;
case 2:
button3Function();
break;
case 3:
button4Function();
break;
case 4:
button5Function();
break;
case 5:
button6Function();
break;
case 6:
button7Function();
break;
case 7:
button8Function();
break;
default:
digitalWrite(relayPins[0], OFF);
digitalWrite(relayPins[1], OFF);
digitalWrite(relayPins[2], OFF);
digitalWrite(relayPins[3], OFF);
digitalWrite(relayPins[4], OFF);
digitalWrite(relayPins[5], OFF);
digitalWrite(relayPins[6], OFF);
digitalWrite(relayPins[7], OFF);
break;
}
}
void actionOption2() {
Serial.println("Option 2 Action");
// Add your action code here
}
void actionOption3() {
Serial.println("Option 3 Action");
if (menuOptions[2].variables[0].type == BOOLEAN) {
if (menuOptions[2].variables[0].boolean) {
Serial.println("Boolean variable is true");
} else {
Serial.println("Boolean variable is false");
}
}
} // This closing brace was missing
void actionOption4() {
Serial.println("Option 4 Action");
if (menuOptions[3].variables[0].type == ENUM) {
int enumValue = menuOptions[3].variables[0].enumeration.enumValue;
Serial.print("Enum variable is set to option: ");
Serial.println(enumValue + 1);
}
}
void setup() {
Serial.begin(115200);
FPSerial.begin(9600);
lcd.init(); // Initialize the LCD
lcd.backlight(); // Turn on the backlight
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("SEACON EVENT");
lcd.setCursor(0, 1);
lcd.print("Booting up...");
DmxSimple.usePin(17);
DmxSimple.write(BR_CHANNEL, 255);
// 2 - Brightness
// 3 - Red
// 4 - Green
// 5 - Blue
// 6 - Strobe
pinMode(potPin, INPUT);
pinMode(buttonPin, INPUT_PULLUP); // Initialize button pin with pull-up resistor
r.begin(ROTARY_PIN1, ROTARY_PIN2, CLICKS_PER_STEP);
r.setChangedHandler(onRotation);
r.setLeftRotationHandler(onRotationLR);
r.setRightRotationHandler(onRotationLR);
if (!myDFPlayer.begin(FPSerial, /*isACK = */true, /*doReset = */true)) { //Use serial to communicate with mp3.
Serial.println(F("Unable to begin:"));
lcd.clear(); // Clear any previous content on the LCD
lcd.setCursor(0, 0); // Set cursor at the beginning of the first line
lcd.print("Unable to begin:"); // Display message on LCD
Serial.println(F("1.Please recheck the connection!"));
lcd.setCursor(0, 1); // Move to the next line of the LCD
lcd.print("Check connection!");
delay(2000); // Wait a bit to let the user read the message
lcd.clear(); // Clear the LCD for the next message
lcd.setCursor(0, 0); // Set cursor at the beginning of the first line again
Serial.println(F("2.Please insert the SD card or USB drive!"));
lcd.print("Insert SD/USB");
delay(2000); // Wait a bit to let the user read the message
while(true){
delay(0); // Code to compatible with ESP8266 watch dog.
}
} else {
Serial.println(F("DFPlayer Mini online."));
myDFPlayer.enableDAC();
myDFPlayer.volume(30);
lcd.clear(); // Clear any previous content on the LCD
lcd.setCursor(0, 0); // Set cursor at the beginning of the first line
lcd.print("DFPlayer Mini");
lcd.setCursor(0, 1); // Move to the next line of the LCD
lcd.print("online.");
delay(2000); // Wait a bit to let the user read the message
}
//8 Buttons and 8Relays
{
// Set button pins as inputs
for (int i = 0; i < 8; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
// Set relay pins as outputs
for (int i = 0; i < 8; i++) {
pinMode(relayPins[i], OUTPUT);
}
}
pinMode(busyPin, INPUT);
byte initFlag;
EEPROM.get(EEPROM_INIT_FLAG_ADDR, initFlag);
if (initFlag == EEPROM_VALID_DATA_FLAG) {
// Load the last option and run it
currentOption = EEPROM.read(EEPROM_ADDR_LAST_OPTION);
runLastSelectedProgram(); // Ensure this sets `setupComplete` to true
} else {
// No valid option saved, enter setup mode or run a default function
enterSetupMode();
}
while (millis() - bootTime < bootWaitTime && !buttonPressed) {
if (digitalRead(buttonPin) == LOW) {
// Button state is LOW, indicating a press. Check if it's stable.
if ((millis() - lastDebounceTime) > debounceDelay2) {
// Button press is considered stable after the debounce delay
buttonPressed = true;
}
} else {
// Button is not pressed, reset the debounce timer
lastDebounceTime = millis();
}
}
if (buttonPressed) {
// Button was pressed during boot-up, enter setup/menu mode
enterSetupMode();
} else {
// Button was not pressed within the time window, proceed with last selected program
runLastSelectedProgram();
}
}
void enterSetupMode() {
// Code to initialize or enter setup mode
inSelectionMode = true; // Enable selection mode
setupComplete = false; // Indicate setup is not complete
// Additional setup for entering setup mode can be added here
Serial.println("Setup Mode Entered");
// Update the LCD or other display elements as needed
}
void runLastSelectedProgram() {
// Load settings and run the last selected program or default state
loadAllVariablesFromEEPROM(); // Load last known settings
// Assuming currentOption has been loaded from EEPROM earlier in setup()
// Check if currentOption is within bounds to avoid accessing menuOptions out of bounds
if (currentOption >= 0 && currentOption < sizeof(menuOptions) / sizeof(menuOptions[0])) {
lcd.setCursor(0, 0);
Serial.print("Running ");
Serial.println(menuOptions[currentOption].name);
menuOptions[currentOption].action();
// Update the LCD display
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Running function");
lcd.setCursor(0, 1);
lcd.print(menuOptions[currentOption].name);
} else {
// Handle error or invalid option index
Serial.println("Invalid option index. Running default program.");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Running Default");
}
setupComplete = true; // Indicate setup phase is complete
// If there's a specific action associated with the option, you might want to call it here
// For example: menuOptions[currentOption].action();
}
void updateLCD() {
// Clear the display with each update
lcd.clear();
if (inSelectionMode) {
// Display the currently selected option without scrolling
lcd.print("Select: ");
lcd.print(menuOptions[displayOption].name);
// Reset scrolling text if previously in a different mode
resetScroll(""); // Optionally clear scrolling setup if coming from scrolling text
} else if (inAdjustMode) {
// Display the variable being adjusted
lcd.print("Adjust: ");
lcd.print(menuOptions[currentOption].name);
lcd.setCursor(0, 1); // Move to the second row for variable value
lcd.print("Var ");
lcd.print(currentVariable + 1);
lcd.print(": ");
// Display variable value; adjust for variable type if necessary
switch (menuOptions[currentOption].variables[currentVariable].type) {
case INTEGER:
lcd.print(menuOptions[currentOption].variables[currentVariable].integer.value);
break;
case BOOLEAN:
lcd.print(menuOptions[currentOption].variables[currentVariable].boolean ? "True" : "False");
break;
case ENUM:
// For ENUM, you might display a string based on the enumValue
// This is an example and may need adjustment based on your ENUM handling
lcd.print("Option ");
lcd.print(menuOptions[currentOption].variables[currentVariable].enumeration.enumValue + 1);
break;
}
} else {
// For modes that might require scrolling text (e.g., displaying long running status or messages)
// Call resetScroll with the new message if not already set or if the message changes
String newScrollText = menuOptions[currentOption].name + " is running!"; // Customize this text as needed
if (scrollText != newScrollText) {
resetScroll(newScrollText); // Initialize scrolling with the new text
}
// Note: The actual call to update scrolling text happens in the main loop to continuously update
}
}
void loop() {
r.loop();
handleButtonPress(); // Handle button actions
potValue = analogRead(potPin);
mappedDelay = map(potValue, 0, 1023, 100, 1000);
if (setupComplete) {
menuOptions[currentOption].action(); // Continuously run the current option's action
}
checkButtons();
updateScrollingText(); // If using scrolling text, continue updating it as needed
updateCurrentAnimation();
}
void adjustVariable(Variable& var, int increment) {
switch (var.type) {
case INTEGER:
var.integer.value += increment;
var.integer.value = max(var.integer.minValue, min(var.integer.value, var.integer.maxValue));
break;
case BOOLEAN:
if (increment != 0) var.boolean = !var.boolean;
break;
case ENUM:
var.enumeration.enumValue += increment;
if (var.enumeration.enumValue >= var.enumeration.enumCount) {
var.enumeration.enumValue = 0; // Wrap to the first value
} else if (var.enumeration.enumValue < 0) {
var.enumeration.enumValue = var.enumeration.enumCount - 1; // Wrap to the last value
}
break;
}
updateLCD(); // Update the display with the new variable value
}
void handleButtonPress() {
static unsigned long buttonPressTime = 0;
bool currentButtonState = digitalRead(buttonPin);
static bool buttonPreviouslyPressed = false;
if (currentButtonState == LOW) {
if (!buttonPreviouslyPressed) {
buttonPreviouslyPressed = true;
buttonPressTime = millis();
}
} else if (buttonPreviouslyPressed) {
unsigned long pressDuration = millis() - buttonPressTime;
if (pressDuration > 1000) { // Long press detected
if (setupComplete) {
// Exiting continuous execution mode and returning to setup mode
setupComplete = false;
inSelectionMode = true;
inAdjustMode = false;
Serial.println("Exiting function. Returning to selection menu.");
myDFPlayer.stop();
isMusicPlaying = false;
lcd.clear();
lcd.print("Exiting function.");
delay(1000); // Optional delay to allow message reading
updateLCD();
}
} else { // Short press detected
if (!setupComplete) {
if (inSelectionMode && !inAdjustMode) {
inSelectionMode = false;
currentOption = displayOption;
if (menuOptions[currentOption].variableCount > 0) {
inAdjustMode = true;
currentVariable = 0;
updateLCD();
} else {
setupComplete = true;
lcd.clear();
lcd.print("Starting function");
delay(1000); // Optional delay to allow message reading
updateLCD();
updateCurrentOption(currentOption);
menuOptions[currentOption].action();
}
} else if (inAdjustMode) {
currentVariable++;
if (currentVariable >= menuOptions[currentOption].variableCount) {
inAdjustMode = false;
currentVariable = 0;
Serial.println("Setup complete. Starting function execution.");
lcd.clear();
lcd.print("Setup complete.");
lcd.setCursor(0, 1); // Move to the second row if your display supports it
lcd.print("Starting execution");
delay(1000); // Optional delay to allow message reading before action starts
updateCurrentOption(currentOption);
menuOptions[currentOption].action();
saveAllVariablesToEEPROM();
setupComplete = true; // Mark setup as complete after running the action
updateLCD();
} else {
// Prompt for next variable adjustment if not the last variable
updateLCD();
// Potentially call updateLCD() to show adjusting next variable info
}
}
}
}
buttonPreviouslyPressed = false;
}
}
void updateCurrentOption(int newOption) {
// Save the current option to EEPROM
EEPROM.update(EEPROM_ADDR_LAST_OPTION, newOption);
currentOption = newOption; // Update after successful write
Serial.print(F("Current option updated to: "));
Serial.println(newOption);
}
void updateScrollingText() {
if (millis() - lastScrollTime > scrollInterval && scrollText.length() > 16) {
String displayText = scrollText.substring(scrollIndex, min(scrollIndex + 16, scrollText.length())) + " "; // Add space for readability at wrap-around
lcd.setCursor(0, 1); // Assuming scrolling on the second row
lcd.print(displayText + String(" ").substring(0, max(0, 16 - displayText.length()))); // Pad with spaces to clear leftover characters
scrollIndex++;
if (scrollIndex >= scrollText.length()) scrollIndex = 0; // Loop back
lastScrollTime = millis();
}
}
void resetScroll(String newText) {
scrollText = newText;
scrollIndex = 0;
lastScrollTime = millis();
// Optionally clear the row where scrolling text displays to start fresh
lcd.setCursor(0, 1);
lcd.print(" "); // Clear the row for new text
}
void saveVariableToEEPROM(int address, int& currentValue, const int& newValue) {
if (currentValue != newValue) {
EEPROM.put(address, newValue);
currentValue = newValue; // Update the current value to reflect the change
}
}
void saveAllVariablesToEEPROM() {
int address = EEPROM_DATA_START_ADDR;
for (int i = 0; i < sizeof(menuOptions) / sizeof(MenuOption); i++) {
for (int j = 0; j < menuOptions[i].variableCount; j++) {
Variable& var = menuOptions[i].variables[j];
switch (var.type) {
case INTEGER:
EEPROM.put(address, var.integer.value);
address += sizeof(var.integer.value);
break;
case BOOLEAN:
EEPROM.put(address, var.boolean);
address += sizeof(var.boolean);
break;
case ENUM:
EEPROM.put(address, var.enumeration.enumValue);
address += sizeof(var.enumeration.enumValue);
break;
}
}
}
// Set the initialization flag at the end
EEPROM.update(EEPROM_INIT_FLAG_ADDR, EEPROM_VALID_DATA_FLAG);
Serial.println(F("Variables saved to EEPROM."));
}
void loadAllVariablesFromEEPROM() {
byte initFlag;
EEPROM.get(EEPROM_INIT_FLAG_ADDR, initFlag);
if (initFlag != EEPROM_VALID_DATA_FLAG) {
Serial.println(F("No valid data in EEPROM."));
return; // Exit if no valid data
}
int address = EEPROM_DATA_START_ADDR;
for (int i = 0; i < sizeof(menuOptions) / sizeof(MenuOption); i++) {
for (int j = 0; j < menuOptions[i].variableCount; j++) {
Variable& var = menuOptions[i].variables[j];
switch (var.type) {
case INTEGER:
EEPROM.get(address, var.integer.value);
address += sizeof(var.integer.value);
break;
case BOOLEAN:
EEPROM.get(address, var.boolean);
address += sizeof(var.boolean);
break;
case ENUM:
EEPROM.get(address, var.enumeration.enumValue);
address += sizeof(var.enumeration.enumValue);
break;
}
}
}
Serial.println(F("Variables loaded from EEPROM."));
}
/*-----------------------------------------------------------------------*/
// Button 1 function
void button1Function() {
static int step = 0; // Make step static so it retains its value across function calls
static unsigned long previousMillis = 0; // Also make previousMillis static for the same reason
unsigned long currentMillis = millis(); // Declare currentMillis at the start of the function
activeButton = 1;
if (step == 0) {
Serial.println("Button 1 Pressed!");
updateCurrentAnimation();
previousMillis = currentMillis; // Reset timer at the start
step++; // Advance to the first step
}
if (step == 1 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[0], ON);
setRelayState(relayPins[1], ON);
setRelayState(relayPins[2], ON);
setRelayState(relayPins[3], ON);
setRelayState(relayPins[4], ON);
setRelayState(relayPins[5], ON);
setRelayState(relayPins[6], ON);
setRelayState(relayPins[7], ON);
step = 0;
}
}
// Button 2 function
void button2Function() {
static int step = 0; // Make step static so it retains its value across function calls
static unsigned long previousMillis = 0; // Also make previousMillis static for the same reason
unsigned long currentMillis = millis(); // Declare currentMillis at the start of the function
activeButton = 2;
if (step == 0) {
Serial.println("Button 2 Pressed!");
updateCurrentAnimation();
step++; // Advance to the first step
previousMillis = currentMillis; // Reset timer at the start
return;
}
if (step == 1 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[0], ON); // tree
setRelayState(relayPins[1], OFF); // //eyes and water jet
step++;
previousMillis = currentMillis;
}
if (step == 2 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[2], ON); //fountain1
step++;
previousMillis = currentMillis;
}
if (step == 3 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[3], ON); //fountain2
step++;
previousMillis = currentMillis;
}
if (step == 4 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[4], ON);//fountain3
step++;
previousMillis = currentMillis;
}
if (step == 5 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[5], ON); //fountain4
step++;
previousMillis = currentMillis;
}
if (step == 6 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[6], ON); //fountain5
step++;
previousMillis = currentMillis;
}
if (step == 7 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[7], ON); //fountain6
step++;
previousMillis = currentMillis;
}
if (step == 8 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[1], ON); //eyes and water jet
step++;
previousMillis = currentMillis;
}
if (step == 9 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[2], OFF); //fountain1
step++;
previousMillis = currentMillis;
}
if (step == 10 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[3], OFF); //fountain2
step++;
previousMillis = currentMillis;
}
if (step == 11 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[4], OFF); //fountain3
step++;
previousMillis = currentMillis;
}
if (step == 12 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[5], OFF); //fountain4
step++;
previousMillis = currentMillis;
}
if (step == 13 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[6], OFF); //fountain5
step++;
previousMillis = currentMillis;
}
if (step == 14 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[7], OFF); //fountain6
step++;
previousMillis = currentMillis;
}
if (step == 15 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[1], OFF); //eyes and water jet
step = 0;
previousMillis = currentMillis;
}
}
// Button 3 function
void button3Function() {
static int step = 0; // Make step static so it retains its value across function calls
static unsigned long previousMillis = 0; // Also make previousMillis static for the same reason
unsigned long currentMillis = millis(); // Declare currentMillis at the start of the function
activeButton = 3;
if (step == 0) {
Serial.println("Button 3 Pressed!");
updateCurrentAnimation();
previousMillis = currentMillis; // Reset timer at the start
step++; // Advance to the first step
}
if (step == 1 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[0], ON); // tree
setRelayState(relayPins[1], ON); //eyes and water jet
setRelayState(relayPins[2], OFF); //fountain1
setRelayState(relayPins[3], ON); //fountain2
setRelayState(relayPins[4], ON); //fountain3
setRelayState(relayPins[5], ON); //fountain4
setRelayState(relayPins[6], ON); //fountain5
setRelayState(relayPins[7], ON); //fountain6
step++;
previousMillis = currentMillis; // Reset timer for next action
}
if (step == 2 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[2], ON); //fountain1
setRelayState(relayPins[3], OFF); //fountain2
setRelayState(relayPins[4], ON);//fountain3
setRelayState(relayPins[5], ON); //fountain4
setRelayState(relayPins[6], ON); //fountain5
setRelayState(relayPins[7], ON); //fountain6
step++;
previousMillis = currentMillis; // Reset timer for next action
}
if (step == 3 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[2], ON); //fountain1
setRelayState(relayPins[3], ON); //fountain2
setRelayState(relayPins[4], OFF);//fountain3
setRelayState(relayPins[5], ON); //fountain4
setRelayState(relayPins[6], ON); //fountain5
setRelayState(relayPins[7], ON); //fountain6
step++;
previousMillis = currentMillis; // Reset timer for next action
}
if (step == 4 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[2], ON); //fountain1
setRelayState(relayPins[3], ON); //fountain2
setRelayState(relayPins[4], ON);//fountain3
setRelayState(relayPins[5], OFF); //fountain4
setRelayState(relayPins[6], ON); //fountain5
setRelayState(relayPins[7], ON); //fountain6
step++;
previousMillis = currentMillis; // Reset timer for next action
}
if (step == 5 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[2], ON); //fountain1
setRelayState(relayPins[3], ON); //fountain2
setRelayState(relayPins[4], ON);//fountain3
setRelayState(relayPins[5], ON); //fountain4
setRelayState(relayPins[6], OFF); //fountain5
setRelayState(relayPins[7], ON); //fountain6
step++;
previousMillis = currentMillis; // Reset timer for next action
}
if (step == 6 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[2], ON); //fountain1
setRelayState(relayPins[3], ON); //fountain2
setRelayState(relayPins[4], ON);//fountain3
setRelayState(relayPins[5], ON); //fountain4
setRelayState(relayPins[6], ON); //fountain5
setRelayState(relayPins[7], OFF); //fountain6
step = 0;
previousMillis = currentMillis; // Reset timer for next action
}
}
// Button 4 function
void button4Function() {
static int step = 0; // Make step static so it retains its value across function calls
static unsigned long previousMillis = 0; // Also make previousMillis static for the same reason
unsigned long currentMillis = millis(); // Declare currentMillis at the start of the function
activeButton = 4;
if (step == 0) {
Serial.println("Button 4 Pressed!");
updateCurrentAnimation();
previousMillis = currentMillis; // Reset timer at the start
step++; // Advance to the first step
}
if (step == 1 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[0], ON); // tree
setRelayState(relayPins[1], ON); //eyes and water jet
setRelayState(relayPins[2], OFF); //fountain1
setRelayState(relayPins[3], ON); //fountain2
setRelayState(relayPins[4], OFF); //fountain3
setRelayState(relayPins[5], ON); //fountain4
setRelayState(relayPins[6], OFF); //fountain5
setRelayState(relayPins[7], ON); //fountain6
step++;
previousMillis = currentMillis;
}
if (step == 2 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[2], ON); //fountain1
setRelayState(relayPins[3], OFF); //fountain2
setRelayState(relayPins[4], ON);//fountain3
setRelayState(relayPins[5], OFF); //fountain4
setRelayState(relayPins[6], ON); //fountain5
setRelayState(relayPins[7], OFF); //fountain6
step++;
previousMillis = currentMillis;
}
}
// Button 5 function
void button5Function() {
static int step = 0; // Make step static so it retains its value across function calls
static unsigned long previousMillis = 0; // Also make previousMillis static for the same reason
unsigned long currentMillis = millis(); // Declare currentMillis at the start of the function
activeButton = 5;
if (step == 0) {
Serial.println("Button 5 Pressed!");
updateCurrentAnimation();
previousMillis = currentMillis; // Reset timer at the start
step++; // Advance to the first step
}
if (step == 1 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[0], ON);
setRelayState(relayPins[1], ON);
setRelayState(relayPins[2], ON);
setRelayState(relayPins[3], ON);
setRelayState(relayPins[4], ON);
setRelayState(relayPins[5], ON);
setRelayState(relayPins[6], ON);
setRelayState(relayPins[7], ON);
step = 0;
previousMillis = currentMillis;
}
// Implement the desired relay pattern for button 5
// ... (adjust the relay pattern for button 5)
}
// Button 6 function
void button6Function() {
static int step = 0; // Make step static so it retains its value across function calls
static unsigned long previousMillis = 0; // Also make previousMillis static for the same reason
unsigned long currentMillis = millis(); // Declare currentMillis at the start of the function
activeButton = 6;
if (step == 0) {
Serial.println("Button 6 Pressed!");
updateCurrentAnimation();
previousMillis = currentMillis; // Reset timer at the start
step++; // Advance to the first step
}
if (step == 1 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[0], ON);
setRelayState(relayPins[1], ON);
setRelayState(relayPins[2], ON);
setRelayState(relayPins[3], ON);
setRelayState(relayPins[4], ON);
setRelayState(relayPins[5], ON);
setRelayState(relayPins[6], ON);
setRelayState(relayPins[7], ON);
step = 0;
previousMillis = currentMillis;
}
}
// Button 7 function
void button7Function() {
static int step = 0; // Make step static so it retains its value across function calls
static unsigned long previousMillis = 0; // Also make previousMillis static for the same reason
unsigned long currentMillis = millis(); // Declare currentMillis at the start of the function
activeButton = 7;
if (step == 0) {
Serial.println("Button 7 Pressed!");
updateCurrentAnimation();
previousMillis = currentMillis; // Reset timer at the start
step++; // Advance to the first step
}
if (step == 1 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[0], ON);
setRelayState(relayPins[1], ON);
setRelayState(relayPins[2], ON);
setRelayState(relayPins[3], ON);
setRelayState(relayPins[4], ON);
setRelayState(relayPins[5], ON);
setRelayState(relayPins[6], ON);
setRelayState(relayPins[7], ON);
step = 0;
previousMillis = currentMillis;
}
}
// Button 8 function
void button8Function() {
static int step = 0; // Make step static so it retains its value across function calls
static unsigned long previousMillis = 0; // Also make previousMillis static for the same reason
unsigned long currentMillis = millis(); // Declare currentMillis at the start of the function
activeButton = 8;
if (step == 0) {
Serial.println("Button 8 Pressed!");
updateCurrentAnimation();
previousMillis = currentMillis; // Reset timer at the start
step++; // Advance to the first step
}
if (step == 1 && nonBlockingDelay(previousMillis, mappedDelay)) {
setRelayState(relayPins[0], OFF);
setRelayState(relayPins[1], OFF);
setRelayState(relayPins[2], OFF);
setRelayState(relayPins[3], OFF);
setRelayState(relayPins[4], OFF);
setRelayState(relayPins[5], OFF);
setRelayState(relayPins[6], OFF);
setRelayState(relayPins[7], OFF);
step = 0;
previousMillis = currentMillis;
}
}
void setRelayState(int relayPin, bool state) {
digitalWrite(relayPin, state);
Serial.print("Relay ");
Serial.print(relayPin);
Serial.print(" turned ");
Serial.println(state ? "ON" : "OFF");
}
/*---------------------------------------------------------------------------------*/
void checkButtons() {
for (int i = 0; i < 8; i++) {
int reading = digitalRead(buttonPins[i]);
if (reading != lastButtonState[i]) {
lastButtonDebounceTime[i] = millis();
}
if ((millis() - lastButtonDebounceTime[i]) > debounceDelay && reading != buttonState[i]) {
buttonState[i] = reading;
if (buttonState[i] == LOW) {
// Update currentAction only if a different button is pressed
if (currentAction != i) {
currentAction = i;
Serial.print("Button ");
Serial.print(i + 1);
Serial.println(" pressed. Switching action.");
// Update the LCD with the current pattern name
//lcd.clear(); // Clear the LCD for a new message
lcd.setCursor(0, 0); // Set cursor at the beginning of the first line
lcd.print("Pattern "); // Print a common prefix
lcd.print(i + 1); // Display the pattern number
lcd.print(" Active"); // Suffix to indicate activation
// Optionally, if you have specific names for each pattern, you can use a switch-case or array
// to map button indices to pattern names and display those names instead.
// Example:
// String patternNames[8] = {"Pattern 1 Name", "Pattern 2 Name", ...};
// lcd.print(patternNames[i]);
}
}
}
lastButtonState[i] = reading;
}
}
void resetFade(FadeInfo& fade, int minIntensity, int maxIntensity) {
// No need to read from DmxSimple, as we're tracking currentIntensity ourselves
fade.targetIntensity = random(minIntensity, maxIntensity + 1);
fade.startTime = millis();
fade.duration = random(100, 1000); // Adjust duration as needed
fade.minIntensity = minIntensity;
fade.maxIntensity = maxIntensity;
fade.reset = true; // Indicate that this fade info should be reset
}
void setFadeRange(FadeInfo& fade, int minIntensity, int maxIntensity) {
fade.minIntensity = minIntensity;
fade.maxIntensity = maxIntensity;
}
void updateFade(int channel, FadeInfo& fade, unsigned long currentTime) {
if (fade.reset) {
// Reset logic already handled in resetFade, just clear the flag
fade.reset = false;
}
if (currentTime - fade.startTime >= fade.duration) {
fade.currentIntensity = fade.targetIntensity;
fade.targetIntensity = random(fade.minIntensity, fade.maxIntensity + 1);
fade.startTime = currentTime;
fade.duration = random(mappedDelay/10, mappedDelay*2);
}
int newIntensity = map(currentTime - fade.startTime, 0, fade.duration, fade.currentIntensity, fade.targetIntensity);
newIntensity = constrain(newIntensity, fade.minIntensity, fade.maxIntensity);
DmxSimple.write(channel, newIntensity);
}
void updateCurrentAnimation() {
unsigned long currentTime = millis();
switch(activeButton) {
case 1: auroraAnimation();
updateFade(RED_CHANNEL, redFade, currentTime);
updateFade(GREEN_CHANNEL, greenFade, currentTime);
updateFade(BLUE_CHANNEL, blueFade, currentTime);
break;
case 2: fireAnimation();
updateFade(RED_CHANNEL, redFade, currentTime);
updateFade(GREEN_CHANNEL, greenFade, currentTime);
updateFade(BLUE_CHANNEL, blueFade, currentTime);
break;
case 3: auroraAnimation();
updateFade(RED_CHANNEL, redFade, currentTime);
updateFade(GREEN_CHANNEL, greenFade, currentTime);
updateFade(BLUE_CHANNEL, blueFade, currentTime);
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8: stopAnimation();
break;
default: stopAnimation();
break;
}
}
void stopAnimation() {
DmxSimple.write(3,0);
DmxSimple.write(4,0);
DmxSimple.write(5,0);
// Remember to update these intensities in your main loop or wherever you handle fading
}
void auroraAnimation() {
setFadeRange(redFade, 0, 0);
setFadeRange(greenFade, 200, 256);
setFadeRange(blueFade, 50, 150);
}
void fireAnimation() {
setFadeRange(redFade, 200, 255);
setFadeRange(greenFade, 100, 200);
setFadeRange(blueFade, 0, 0);
}