From c9c4b1c1c51434de1f28000e410c14125f01e8e0 Mon Sep 17 00:00:00 2001 From: Robin Cerny Date: Fri, 18 Apr 2025 10:15:02 +0200 Subject: [PATCH] refactored main loop, added ready countdown to unpause --- .../ROFLS_Arena_Controller.ino | 306 ++++++++++++------ 1 file changed, 201 insertions(+), 105 deletions(-) diff --git a/ROFLS_Arena_Controller/ROFLS_Arena_Controller.ino b/ROFLS_Arena_Controller/ROFLS_Arena_Controller.ino index 71c45ca..68edd40 100644 --- a/ROFLS_Arena_Controller/ROFLS_Arena_Controller.ino +++ b/ROFLS_Arena_Controller/ROFLS_Arena_Controller.ino @@ -58,10 +58,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 = 30; // 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 = 15; // 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 +79,7 @@ int BLINK_INTERVAL = 200; bool ARENA_READY = false; bool REDTEAM_READY = false; bool BLUETEAM_READY = false; +bool resumeFight = false; //------------------------------------------------------------------------------------ // ESP-NOW config @@ -189,6 +190,14 @@ void sendTimeDisplay(int MINUTES, int SECONDS, int RED, int GREEN, int BLUE, int } } +// Global markers for tap out states (non‐blocking sequences) +bool redTapOutActive = false; +unsigned long redTapOutStartTime = 0; +int redTapOutStage = 0; + +bool blueTapOutActive = false; +unsigned long blueTapOutStartTime = 0; +int blueTapOutStage = 0; //------------------------------------------------------------------------------------ @@ -280,81 +289,77 @@ 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; 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; ReadyCountDown.start(countdownToFightTIME); } - } + } else { - // resume logic + // Resume (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; + ReadyCountDown.start(countdownToFightTIME); + } + else if (switchRUMBLE.on()) { buttonREDTEAMvar = false; buttonBLUETEAMvar = false; + resumeFight = true; countdownPAUSED = false; - rumbleTIME.start(); + ARENA_READY = 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; - ReadyCountDown.start(countdownToFightTIME); - } else if (switchRUMBLE.on()) { - //rumbleTIME.start(); - ARENA_READY = true; - ReadyCountDown.start(countdownToFightTIME); - } + ARENA_READY = true; + REDTEAM_LED(true); + BLUETEAM_LED(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 +371,181 @@ 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. + } + } + 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()) { + sendTimeDisplay((ReadyCountDown.remaining()/60 % 60), (ReadyCountDown.remaining()%60), 255, 165, 0, CLOCK_LED_BRIGHTNESS); + } else if (switchRUMBLE.on()) { + 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 { + sendTimeDisplay((FightCountDown.remaining()/60 % 60), (FightCountDown.remaining()%60), 0, 255, 0, CLOCK_LED_BRIGHTNESS); + } + } +} + +// Redesigned 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 10'000 ms. + const unsigned long cycleDuration = 10000; + // 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); + } + } +} + +// Redesigned 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 10'000 ms. + const unsigned long cycleDuration = 10000; + // 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 loop() { + // Poll and update low-level I/O and LEDs. + pollInput(); + checkPIT(); + arenaLIGHT(); + statusLEDs(); + updateTEAMLEDs(); + + // Process button events. + handleStartButton(); + handleForcedStartButton(); + handlePauseButton(); + handlePitButton(); + handleAutoPitRelease(); + + // Process non-blocking tap-out sequences. + handleRedTapOut(); + handleBlueTapOut(); + + // Handle reset if (buttonRESETvar) { buttonRESETvar = false; PITreleased = false; ESP.restart(); } - // Ready Countdown to Fight Countdown transition - if (ReadyCountDown.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 ReadyCountDown to the appropriate fight countdown. + handleCountdownTransition(); - // If either team has tapped out, skip updating the display. - if (buttonREDTEAMtapout || buttonBLUETEAMtapout) { - return; - } - - // Display the Ready CountDown timer if it is running. - if (ReadyCountDown.isRunning()) { - sendTimeDisplay((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, update the display. + if (!redTapOutActive && !blueTapOutActive) { + updateDisplay(); } }