@@ -3,7 +3,6 @@
# include <WiFi.h>
# include <esp_now.h> // automatically installed for ESP32 boards, I think?
# include <Preferences.h> // automatically installed for ESP32 boards
# include <FastLED.h> // https://fastled.io/
# include <avdweb_Switch.h> // https://github.com/avdwebLibraries/avdweb_Switch
# include <CountDown.h> // https://github.com/RobTillaart/CountDown
# include <StopWatch.h> // https://github.com/RobTillaart/StopWatch_RT
@@ -58,10 +57,10 @@ bool buttonBLUETEAMtapout = false;
unsigned long PITopenTimestamp = 0 ;
bool PITreleased = false ;
const long PITopenTime = 500 ; // activate solenoid for 500ms
const int countdownTIME = 180 ; // countdown timer length in seconds, actual countdown for the fight
const int countdownToFightTIME = 3 ; // countdown timer length in seconds, "ready" countdown
const int PITreleaseTime = 90 ; // automatic pit release time in seconds until end of countdown
const long PITopenTime = 500 ; // default: 500 activate solenoid for 500ms
const int countdownTIME = 180 ; // default: 180 countdown timer length in seconds, actual countdown for the fight
const int countdownToFightTIME = 3 ; // default: 3 countdown timer length in seconds, "ready" countdown
const int PITreleaseTime = 90 ; // default: 90 automatic pit release time in seconds until end of countdown
bool countdownPAUSED = false ;
CountDown FightCountDown ( CountDown : : SECONDS ) ;
CountDown ReadyCountDown ( CountDown : : SECONDS ) ;
@@ -79,6 +78,7 @@ int BLINK_INTERVAL = 200;
bool ARENA_READY = false ;
bool REDTEAM_READY = false ;
bool BLUETEAM_READY = false ;
bool resumeFight = false ;
//------------------------------------------------------------------------------------
// ESP-NOW config
@@ -163,13 +163,19 @@ void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
break ;
case 1 :
// RED team button
// ignore button input if in rumble mode
if ( ! switchRUMBLE . on ( ) ) {
buttonREDTEAMvar = receiveDATA . buttonREDTEAM ;
buttonREDTEAMtapout = receiveDATA . buttonREDTEAMtapout ;
}
break ;
case 2 :
// BLUE team button
// ignore button input if in rumble mode
if ( ! switchRUMBLE . on ( ) ) {
buttonBLUETEAMvar = receiveDATA . buttonBLUETEAM ;
buttonBLUETEAMtapout = receiveDATA . buttonBLUETEAMtapout ;
}
break ;
}
}
@@ -189,6 +195,23 @@ void sendTimeDisplay(int MINUTES, int SECONDS, int RED, int GREEN, int BLUE, int
}
}
// Global Vars for tap out states
bool redTapOutActive = false ;
unsigned long redTapOutStartTime = 0 ;
int redTapOutStage = 0 ;
bool blueTapOutActive = false ;
unsigned long blueTapOutStartTime = 0 ;
int blueTapOutStage = 0 ;
// Global Vars for Fight end
bool fightStarted = false ;
bool fightEnded = false ;
const int END_BLINK_COUNT = 3 ;
const unsigned long END_BLINK_INTERVAL = 500 ; // in milliseconds
int endBlinkTransitions = 0 ;
bool endBlinkState = false ;
unsigned long lastEndBlinkTime = 0 ;
//------------------------------------------------------------------------------------
@@ -280,81 +303,85 @@ void setup() {
esp_now_register_recv_cb ( esp_now_recv_cb_t ( OnDataRecv ) ) ;
}
void loop ( ) {
// poll all the switch/button inputs
pollInput ( ) ;
// deactivate solenoids if needed
checkPIT ( ) ;
// Arena Light
arenaLIGHT ( ) ;
// update status LEDs
statusLEDs ( ) ;
// update Teambutton LEDs
updateTEAMLEDs ( ) ;
//----------------------------------------------------------------------------------------
// start button logic
// Handler for the START button logic.
void handleStartButton ( ) {
if ( buttonSTARTvar ) {
buttonSTARTvar = false ;
// start logic
// Fresh start: no countdown is running, and the fight hasn't been paused.
if ( ! FightCountDown . isRunning ( ) & & ! rumbleTIME . isRunning ( ) & & countdownPAUSED = = false ) {
if ( switchRUMBLE . on ( ) ) {
buttonREDTEAMvar = false ;
buttonBLUETEAMvar = false ;
//rumbleTIME.start();
ARENA_READY = true ;
REDTEAM_LED ( true ) ;
BLUETEAM_LED ( true ) ;
ReadyCountDown . start ( countdownToFightTIME ) ;
} else if ( buttonREDTEAMvar & & buttonBLUETEAMvar & & ! switchRUMBLE . on ( ) ) {
}
else if ( buttonREDTEAMvar & & buttonBLUETEAMvar & & ! switchRUMBLE . on ( ) ) {
buttonREDTEAMvar = false ;
buttonBLUETEAMvar = false ;
//FightCountDown.start(countdownTIME);
ARENA_READY = true ;
REDTEAM_LED ( true ) ;
BLUETEAM_LED ( true ) ;
ReadyCountDown . start ( countdownToFightTIME ) ;
}
}
else {
// r esume logic
// R esume (unpause) branch: instead of calling FightCountDown.resume() immediately,
// we want to display ReadyCountDown first.
if ( buttonREDTEAMvar & & buttonBLUETEAMvar & & ! switchRUMBLE . on ( ) ) {
buttonREDTEAMvar = false ;
buttonBLUETEAMvar = false ;
resumeFight = true ; // Mark that we want to resume later.
countdownPAUSED = false ;
FightCountDown . resume ( ) ;
} else if ( switchRUMBLE . on ( ) ) {
ARENA_READY = true ;
REDTEAM_LED ( true ) ;
BLUETEAM_LED ( true ) ;
ReadyCountDown . start ( countdownToFightTIME ) ;
}
else if ( switchRUMBLE . on ( ) ) {
buttonREDTEAMvar = false ;
buttonBLUETEAMvar = false ;
resumeFight = true ;
countdownPAUSED = false ;
rumbleTIME . start ( ) ;
ARENA_READY = true ;
REDTEAM_LED ( true ) ;
BLUETEAM_LED ( true ) ;
ReadyCountDown . start ( countdownToFightTIME ) ;
}
}
}
// forced start of countdown
}
// Handler for the forced start button logic.
void handleForcedStartButton ( ) {
if ( buttonSTARTforced ) {
buttonSTARTforced = false ;
if ( ! FightCountDown . isRunning ( ) & & ! rumbleTIME . isRunning ( ) & & countdownPAUSED = = false ) {
buttonREDTEAMvar = false ;
buttonBLUETEAMvar = false ;
if ( ! switchRUMBLE . on ( ) ) {
//FightCountDown.start(countdownTIME);
ARENA_READY = true ;
REDTEAM_LED ( true ) ;
BLUETEAM_LED ( true ) ;
ReadyCountDown . start ( countdownToFightTIME ) ;
} else if ( switchRUMBLE . on ( ) ) {
//rumbleTIME.start();
ARENA_READY = true ;
ReadyCountDown . start ( countdownToFightTIME ) ;
}
}
else {
// forced resume logic
buttonREDTEAMvar = false ;
buttonBLUETEAMvar = false ;
resumeFight = true ;
countdownPAUSED = false ;
if ( ! switchRUMBLE . on ( ) ) {
FightCountDown . resume ( ) ;
} else if ( switchRUMBLE . on ( ) ) {
rumbleTIME . start ( ) ;
ARENA_READY = true ;
REDTEAM_LED ( true ) ;
BLUETEAM_LED ( true ) ;
ReadyCountDown . start ( countdownToFightTIME ) ;
}
}
}
// pause button logic
}
// Handler for the pause button logic.
void handlePauseButton ( ) {
if ( buttonPAUSEvar ) {
buttonPAUSEvar = false ;
if ( FightCountDown . isRunning ( ) ) {
@@ -366,90 +393,230 @@ void loop() {
rumbleTIME . stop ( ) ;
}
}
// pit button logic
}
// Handler for the pit button logic.
void handlePitButton ( ) {
if ( buttonPITvar ) {
buttonPITvar = false ;
buttonPIThold = false ;
openPITmanually ( ) ;
}
// automatic pit release
if ( FightCountDown . remaining ( ) < = PITreleaseTime & & FightCountDown . remaining ( ) ! = 0 & & ! switchRUMBLE . on ( ) & & switchPIT . on ( ) & & buttonPIThold = = false ) {
openPIT ( ) ;
}
if ( rumbleTIME . elapsed ( ) > = PITreleaseTime & & switchRUMBLE . on ( ) & & switchPIT . on ( ) & & buttonPIThold = = false ) {
openPIT ( ) ;
}
}
// tap out logic
if ( buttonREDTEAMtapout & & ! switchRUMBLE . on ( ) ) {
// check for automatic pit release.
void handleAutoPitRelease ( ) {
// Only release the pit if it hasn't already been released.
if ( ! PITreleased ) {
if ( FightCountDown . remaining ( ) < = PITreleaseTime & & FightCountDown . remaining ( ) ! = 0 & &
! switchRUMBLE . on ( ) & & switchPIT . on ( ) & & buttonPIThold = = false ) {
openPIT ( ) ;
}
else if ( rumbleTIME . elapsed ( ) > = PITreleaseTime & & switchRUMBLE . on ( ) & &
switchPIT . on ( ) & & buttonPIThold = = false ) {
openPIT ( ) ;
}
}
}
// Handler for transitioning from ReadyCountDown to the fight (or rumble) countdown.
void handleCountdownTransition ( ) {
if ( ReadyCountDown . remaining ( ) = = 0 & & ARENA_READY ) {
ARENA_READY = false ;
REDTEAM_LED ( false ) ;
BLUETEAM_LED ( false ) ;
if ( ! switchRUMBLE . on ( ) ) {
if ( resumeFight ) {
FightCountDown . resume ( ) ; // Resume the paused fight countdown.
resumeFight = false ;
}
else {
FightCountDown . start ( countdownTIME ) ; // Fresh start.
fightStarted = true ;
}
}
else if ( switchRUMBLE . on ( ) ) {
// For the rumble branch, you may decide if a resume is relevant.
// Here we simply start the rumble timer as before.
rumbleTIME . start ( ) ;
resumeFight = false ;
}
}
}
// The display update logic now considers all conditions in order.
void updateDisplay ( ) {
if ( ReadyCountDown . isRunning ( ) ) {
// Display Ready Countdown in Yellow
sendTimeDisplay ( ( ReadyCountDown . remaining ( ) / 60 % 60 ) , ( ReadyCountDown . remaining ( ) % 60 ) , 255 , 165 , 0 , CLOCK_LED_BRIGHTNESS ) ;
} else if ( switchRUMBLE . on ( ) ) {
// Display the Rumble Timer
sendTimeDisplay ( ( rumbleTIME . elapsed ( ) / 60 % 60 ) , ( rumbleTIME . elapsed ( ) % 60 ) , 0 , 255 , 255 , CLOCK_LED_BRIGHTNESS ) ;
} else if ( ! FightCountDown . isRunning ( ) & & ! countdownPAUSED ) {
// Choose green if both team buttons are active; otherwise, choose magenta.
if ( buttonREDTEAMvar & & buttonBLUETEAMvar ) {
sendTimeDisplay ( ( countdownTIME / 60 % 60 ) , ( countdownTIME % 60 ) , 0 , 255 , 0 , CLOCK_LED_BRIGHTNESS ) ;
} else {
sendTimeDisplay ( ( countdownTIME / 60 % 60 ) , ( countdownTIME % 60 ) , 255 , 0 , 255 , CLOCK_LED_BRIGHTNESS ) ;
}
} else {
// choose yellow if countdown is paused and both teams aren't ready
if ( countdownPAUSED & & ( ! buttonREDTEAMvar | | ! buttonBLUETEAMvar ) ) {
sendTimeDisplay ( ( FightCountDown . remaining ( ) / 60 % 60 ) , ( FightCountDown . remaining ( ) % 60 ) , 255 , 165 , 0 , CLOCK_LED_BRIGHTNESS ) ;
} else {
// Display the countdown in green
sendTimeDisplay ( ( FightCountDown . remaining ( ) / 60 % 60 ) , ( FightCountDown . remaining ( ) % 60 ) , 0 , 255 , 0 , CLOCK_LED_BRIGHTNESS ) ;
}
}
}
// endless cycling for Red Team tap-out.
void handleRedTapOut ( ) {
// When the red tap-out is first triggered.
if ( ! redTapOutActive & & buttonREDTEAMtapout & & ! switchRUMBLE . on ( ) & & ! blueTapOutActive ) {
buttonREDTEAMtapout = false ;
countdownPAUSED = true ;
FightCountDown . stop ( ) ;
REDTEAM_LED ( true ) ;
sendTimeDisplay ( 99 , 0 , 255 , 0 , 0 , CLOCK_LED_BRIGHTNESS ) ;
delay ( 1500 ) ;
sendTimeDisplay ( 0 , 99 , 255 , 0 , 0 , CLOCK_LED_BRIGHTNESS ) ;
delay ( 1500 ) ;
sendTimeDisplay ( ( FightCountDown . remaining ( ) / 60 % 60 ) , ( FightCountDown . remaining ( ) % 60 ) , 255 , 0 , 0 , CLOCK_LED_BRIGHTNESS ) ;
delay ( 7000 ) ;
redTapOutActive = true ;
redTapOutStartTime = millis ( ) ;
}
if ( buttonBLUETEAMtapout & & ! switchRUMBLE . on ( ) ) {
// If tap-out is active, continuously update the display in a cycle.
if ( redTapOutActive ) {
// Define a full cycle period of 8'000 ms.
const unsigned long cycleDuration = 8000 ;
// Compute how far into the current cycle we are.
unsigned long cycleTime = ( millis ( ) - redTapOutStartTime ) % cycleDuration ;
if ( cycleTime < 1500 ) {
// Stage 1 (0 - 1500ms): Display first tap-out message.
sendTimeDisplay ( 99 , 0 , 255 , 0 , 0 , CLOCK_LED_BRIGHTNESS ) ;
}
else if ( cycleTime < 3000 ) {
// Stage 2 (1500 - 3000ms): Display second tap-out message.
sendTimeDisplay ( 0 , 99 , 255 , 0 , 0 , CLOCK_LED_BRIGHTNESS ) ;
}
else {
// Stage 3 (3000 - 10'000ms): Show the remaining fight countdown.
sendTimeDisplay ( ( FightCountDown . remaining ( ) / 60 % 60 ) , ( FightCountDown . remaining ( ) % 60 ) , 255 , 0 , 0 , CLOCK_LED_BRIGHTNESS ) ;
}
}
}
// endless cycling for Blue Team tap-out.
void handleBlueTapOut ( ) {
// When the blue tap-out is first triggered.
if ( ! blueTapOutActive & & buttonBLUETEAMtapout & & ! switchRUMBLE . on ( ) & & ! redTapOutActive ) {
buttonBLUETEAMtapout = false ;
countdownPAUSED = true ;
FightCountDown . stop ( ) ;
BLUETEAM_LED ( true ) ;
sendTimeDisplay ( 99 , 0 , 0 , 0 , 255 , CLOCK_LED_BRIGHTNESS ) ;
delay ( 1500 ) ;
sendTimeDisplay ( 0 , 99 , 0 , 0 , 255 , CLOCK_LED_BRIGHTNESS ) ;
delay ( 1500 ) ;
sendTimeDisplay ( ( FightCountDown . remaining ( ) / 60 % 60 ) , ( FightCountDown . remaining ( ) % 60 ) , 0 , 0 , 255 , CLOCK_LED_BRIGHTNESS ) ;
delay ( 1500 ) ;
blueTapOutActive = true ;
blueTapOutStartTime = millis ( ) ;
}
// reset button logic
// If tap-out is active, continuously update the display in a cycle.
if ( blueTapOutActive ) {
// Define a full cycle period of 8'000 ms.
const unsigned long cycleDuration = 8000 ;
// Compute the cycle progress.
unsigned long cycleTime = ( millis ( ) - blueTapOutStartTime ) % cycleDuration ;
if ( cycleTime < 1500 ) {
// Stage 1 (0 - 1500ms): Display first tap-out message.
sendTimeDisplay ( 99 , 0 , 0 , 0 , 255 , CLOCK_LED_BRIGHTNESS ) ;
}
else if ( cycleTime < 3000 ) {
// Stage 2 (1500 - 3000ms): Display second tap-out message.
sendTimeDisplay ( 0 , 99 , 0 , 0 , 255 , CLOCK_LED_BRIGHTNESS ) ;
}
else {
// Stage 3 (3000 - 10'000ms): Show the remaining fight countdown.
sendTimeDisplay ( ( FightCountDown . remaining ( ) / 60 % 60 ) , ( FightCountDown . remaining ( ) % 60 ) , 0 , 0 , 255 , CLOCK_LED_BRIGHTNESS ) ;
}
}
}
void handleFightEnd ( ) {
// When the countdown hits 0 and the fight had started, mark it as ended.
if ( ( FightCountDown . remaining ( ) = = 0 ) & & fightStarted ) {
fightEnded = true ;
}
// Only do blinking if the fight has ended.
if ( ! fightEnded ) {
return ; // Skip the rest until fightEnded becomes true.
}
// Each full blink cycle includes an "on" and "off" state.
// Therefore, we count transitions: total transitions = END_BLINK_COUNT * 2.
const int totalTransitions = END_BLINK_COUNT * 2 + 1 ;
// If we haven't completed our full blink sequence, manage timing:
if ( endBlinkTransitions < totalTransitions ) {
unsigned long currentMillis = millis ( ) ;
if ( currentMillis - lastEndBlinkTime > = END_BLINK_INTERVAL ) {
endBlinkState = ! endBlinkState ; // Toggle between on and off states.
endBlinkTransitions + + ; // Count this toggle.
lastEndBlinkTime = currentMillis ; // Reset the timer.
}
// Depending on the blink state, update the display.
// When endBlinkState is true, use the normal brightness.
// When false, replace CLOCK_LED_BRIGHTNESS with 0 to blank the display.
if ( endBlinkState ) {
sendTimeDisplay ( 0 , 0 , 255 , 165 , 0 , CLOCK_LED_BRIGHTNESS ) ;
} else {
sendTimeDisplay ( 0 , 0 , 255 , 165 , 0 , 0 ) ;
}
} else {
// After completing the blink sequence, ensure that the display remains on.
sendTimeDisplay ( 0 , 0 , 255 , 165 , 0 , CLOCK_LED_BRIGHTNESS ) ;
}
}
//----------------------------------------------------------------------------------------
void loop ( ) {
// Poll and update low-level I/O and LEDs.
pollInput ( ) ;
checkPIT ( ) ;
arenaLIGHT ( ) ;
statusLEDs ( ) ;
updateTEAMLEDs ( ) ;
// Process button events.
handleStartButton ( ) ;
handleForcedStartButton ( ) ;
handlePauseButton ( ) ;
handlePitButton ( ) ;
handleAutoPitRelease ( ) ;
// Process tap-out sequences.
if ( ! switchRUMBLE . on ( ) ) {
handleRedTapOut ( ) ;
handleBlueTapOut ( ) ;
}
// Handle reset
if ( buttonRESETvar ) {
buttonRESETvar = false ;
PITreleased = false ;
ESP . restart ( ) ;
}
// Ready Countd own to F ight C ountdown transition
if ( Ready CountD own. remaining ( ) = = 0 & & ARENA_READY ) {
ARENA_READY = false ;
REDTEAM_LED ( false ) ;
BLUETEAM_LED ( false ) ;
if ( ! switchRUMBLE . on ( ) ) {
FightCountDown . start ( countdownTIME ) ;
} else if ( switchRUMBLE . on ( ) ) {
rumbleTIME . start ( ) ;
}
}
// Transition from ReadyCountD own to the appropriate f ight c ountdown.
handle Countd ownTransition ( ) ;
// If either team has tapped out, skip updating the display.
if ( buttonREDTEAMtapout | | buttonBLUETEAMtapout ) {
return ;
}
// End display
handleFightEnd ( ) ;
// Display the Ready CountDown timer if it is running .
if ( ReadyCountDown . isRunning ( ) ) {
sendTim eDisplay ( ( ReadyCountDown . remaining ( ) / 60 % 60 ) , ( ReadyCountDown . remaining ( ) % 60 ) , 255 , 165 , 0 , CLOCK_LED_BRIGHTNESS );
return ;
}
// When the RUMBLE switch is active, always show the rumble timer.
if ( switchRUMBLE . on ( ) ) {
sendTimeDisplay ( ( rumbleTIME . elapsed ( ) / 60 % 60 ) , ( rumbleTIME . elapsed ( ) % 60 ) , 0 , 255 , 255 , CLOCK_LED_BRIGHTNESS ) ;
return ;
}
// If the fight countdown isn't running and isn't paused, determine which countdown color to use.
if ( ! FightCountDown . isRunning ( ) & & ! countdownPAUSED ) {
// Choose green if both buttons are active; otherwise, choose magenta.
if ( buttonREDTEAMvar & & buttonBLUETEAMvar ) {
sendTimeDisplay ( ( countdownTIME / 60 % 60 ) , ( countdownTIME % 60 ) , 0 , 255 , 0 , CLOCK_LED_BRIGHTNESS ) ;
} else {
sendTimeDisplay ( ( countdownTIME / 60 % 60 ) , ( countdownTIME % 60 ) , 255 , 0 , 255 , CLOCK_LED_BRIGHTNESS ) ;
}
} else {
// Otherwise, show the fight countdown timer.
sendTimeDisplay ( ( FightCountDown . remaining ( ) / 60 % 60 ) , ( FightCountDown . remaining ( ) % 60 ) , 0 , 255 , 0 , CLOCK_LED_BRIGHTNESS ) ;
// When no team is in a tap‐ out sequence and fight hasn't ended, update the display .
if ( ! redTapOutActive & & ! blueTapOutActive & & ! fightEnded ) {
updat eDisplay( ) ;
}
}