13 Commits

7 changed files with 450 additions and 131 deletions

View File

@@ -25,9 +25,11 @@ void openPITmanually() {
void arenaLIGHT() {
if (switchLIGHT.on()) {
digitalWrite(LIGHT_PIN, LOW);
digitalWrite(LIGHT_PIN2, LOW);
}
else {
digitalWrite(LIGHT_PIN, HIGH);
digitalWrite(LIGHT_PIN2, HIGH);
}
}

View File

@@ -1,13 +1,18 @@
// ROFLS+ Arena Controller
#include <WiFi.h>
#include <esp_wifi.h> // Required for wifi_tx_info_t
#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
// set Arena, switches the MAC addresses out
// 1 = Arena A (Ant)
// 2 = Arena B (Beetle)
#define ARENA 1
// Hardware connections
// Buttons:
#define START_BTN_PIN 1
@@ -26,7 +31,7 @@
// Relays:
#define PIT_RELEASE_PIN 37
#define LIGHT_PIN 39
#define UNUSED_RELAY3_PIN 35
#define LIGHT_PIN2 35
#define UNUSED_RELAY4_PIN 33
const byte relayOnState = LOW;
@@ -58,10 +63,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);
@@ -69,7 +74,7 @@ CountDown ReadyCountDown(CountDown::SECONDS);
// Rumble stopwatch
StopWatch rumbleTIME(StopWatch::SECONDS);
int CLOCK_LED_BRIGHTNESS = 32; // 64 is okay
int CLOCK_LED_BRIGHTNESS = 128; // 64 is okay
int BLINK_COUNTER_REDTEAM = 0;
int BLINK_COUNTER_BLUETEAM = 0;
@@ -79,11 +84,21 @@ int BLINK_INTERVAL = 200;
bool ARENA_READY = false;
bool REDTEAM_READY = false;
bool BLUETEAM_READY = false;
bool resumeFight = false;
//------------------------------------------------------------------------------------
// ESP-NOW config
// send config, Clock:
uint8_t broadcastAddressClock[] = {0x48, 0x27, 0xE2, 0x5D, 0xB6, 0x84};
#if ARENA == 1
// A Arena
uint8_t broadcastAddressClock1[] = {0x48, 0x27, 0xE2, 0x5D, 0xB6, 0x84};
uint8_t broadcastAddressClock2[] = {0xD8, 0x3B, 0xDA, 0xC9, 0x49, 0xC6};
#elif ARENA == 2
// B Arena
uint8_t broadcastAddressClock1[] = {0xD8, 0x3B, 0xDA, 0xC8, 0xFF, 0xFA};
uint8_t broadcastAddressClock2[] = {0xD8, 0x3B, 0xDA, 0xC8, 0x95, 0x42};
#endif
// struct for clock data
typedef struct struct_message_Clock {
int sendMinutes;
@@ -98,8 +113,15 @@ typedef struct struct_message_Clock {
struct_message_Clock sendClockDATA;
// send config, pilot buttons:
uint8_t broadcastAddressREDTEAMbutton[] = {0x84, 0xFC, 0xE6, 0xC7, 0x23, 0x14};
uint8_t broadcastAddressBLUETEAMbutton[] = {0x84, 0xFC, 0xE6, 0xC7, 0x1A, 0x02};
#if ARENA == 1
// A Arena
uint8_t broadcastAddressREDTEAMbutton[] = {0x84, 0xFC, 0xE6, 0xC7, 0x23, 0x14};
uint8_t broadcastAddressBLUETEAMbutton[] = {0x84, 0xFC, 0xE6, 0xC7, 0x1A, 0x02};
#elif ARENA == 2
// B Arena
uint8_t broadcastAddressREDTEAMbutton[] = {0xD8, 0x3B, 0xDA, 0xC8, 0x95, 0x58};
uint8_t broadcastAddressBLUETEAMbutton[] = {0xD8, 0x3B, 0xDA, 0xC8, 0x95, 0x1C};
#endif
// Structure for sending data
typedef struct struct_message_send {
@@ -111,7 +133,16 @@ struct_message_send sendToREDTEAMbutton;
struct_message_send sendToBLUETEAMbutton;
// ESP-Now stuff for the Pit controller
uint8_t broadcastAddressPitController[] = {0x84, 0xFC, 0xE6, 0xC7, 0x19, 0xDE};
#if ARENA == 1
// A Arena
uint8_t broadcastAddressPitController[] = {0x84, 0xFC, 0xE6, 0xC7, 0x19, 0xDE};
#elif ARENA == 2
// B Arena
uint8_t broadcastAddressPitController[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xB0, 0x0B};
#endif
// Test Pit controller
//uint8_t broadcastAddressPitController[] = {0x94, 0xA9, 0x90, 0x0B, 0x21, 0x64};
// Structure for sending data
typedef struct struct_message_pit {
bool PIT; // LED state
@@ -122,7 +153,7 @@ esp_now_peer_info_t peerInfo;
// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
void OnDataSent(const wifi_tx_info_t *tx_info, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
@@ -163,13 +194,31 @@ 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()) {
// ignore button press while in fight
if (!FightCountDown.isRunning()) {
buttonREDTEAMvar = receiveDATA.buttonREDTEAM;
}
// ignore tapout while not in fight
if (FightCountDown.isRunning()) {
buttonREDTEAMtapout = receiveDATA.buttonREDTEAMtapout;
}
}
break;
case 2:
// BLUE team button
// ignore button input if in rumble mode
if (!switchRUMBLE.on()) {
// ignore button press while in fight
if (!FightCountDown.isRunning()) {
buttonBLUETEAMvar = receiveDATA.buttonBLUETEAM;
}
// ignore tapout while not in fight
if (FightCountDown.isRunning()) {
buttonBLUETEAMtapout = receiveDATA.buttonBLUETEAMtapout;
}
}
break;
}
}
@@ -184,11 +233,30 @@ void sendTimeDisplay(int MINUTES, int SECONDS, int RED, int GREEN, int BLUE, int
sendClockDATA.sendGREENchannel = GREEN;
sendClockDATA.sendBLUEchannel = BLUE;
sendClockDATA.sendBrightness = BRIGHTNESS;
// actually send it
esp_err_t result = esp_now_send(broadcastAddressClock, (uint8_t *) &sendClockDATA, sizeof(sendClockDATA));
// actually send it to first clock
esp_err_t result1 = esp_now_send(broadcastAddressClock1, (uint8_t *) &sendClockDATA, sizeof(sendClockDATA));
// actually send it to second clock
esp_err_t result2 = esp_now_send(broadcastAddressClock2, (uint8_t *) &sendClockDATA, sizeof(sendClockDATA));
}
}
// 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;
//------------------------------------------------------------------------------------
@@ -199,13 +267,13 @@ void setup() {
digitalWrite(PIT_RELEASE_PIN, relayOffState);
pinMode(LIGHT_PIN, OUTPUT);
digitalWrite(LIGHT_PIN, LOW); // have it by default on, to prevent flickering, needs a better fix tho
pinMode(UNUSED_RELAY3_PIN, OUTPUT);
digitalWrite(UNUSED_RELAY3_PIN, relayOffState);
pinMode(LIGHT_PIN2, OUTPUT);
digitalWrite(LIGHT_PIN2, LOW); // have it by default on, to prevent flickering, needs a better fix tho
pinMode(UNUSED_RELAY4_PIN, OUTPUT);
digitalWrite(UNUSED_RELAY4_PIN, relayOffState);
// set status LED outputs:
pinMode(LIGHT_STATUS_LED, OUTPUT);
digitalWrite(LIGHT_PIN, HIGH); // have it by default on, to prevent flickering, needs a better fix tho
digitalWrite(LIGHT_STATUS_LED, HIGH);
pinMode(AUTOPIT_STATUS_LED, OUTPUT);
digitalWrite(AUTOPIT_STATUS_LED, LOW);
pinMode(MODE_STATUS_LED, OUTPUT);
@@ -225,17 +293,27 @@ void setup() {
// get the status of Transmitted packet
esp_now_register_send_cb(OnDataSent);
// Register clock peer
memcpy(peerInfo.peer_addr, broadcastAddressClock, 6);
// Register first clock peer
memcpy(peerInfo.peer_addr, broadcastAddressClock1, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
Serial.println("Failed to add Clock1 peer");
return;
}
esp_err_t result = esp_now_send(broadcastAddressClock, (uint8_t *) &sendClockDATA, sizeof(sendClockDATA));
// 2) Register second clock peer
memcpy(peerInfo.peer_addr, broadcastAddressClock2, 6);
// peerInfo.channel & peerInfo.encrypt already set above
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("Failed to add Clock2 peer");
return;
}
// send initial data (I think that doesn't work but meh...)
esp_err_t result1 = esp_now_send(broadcastAddressClock1, (uint8_t *) &sendClockDATA, sizeof(sendClockDATA));
esp_err_t result2 = esp_now_send(broadcastAddressClock2, (uint8_t *) &sendClockDATA, sizeof(sendClockDATA));
// Register Red Team Button peer
memcpy(peerInfo.peer_addr, broadcastAddressREDTEAMbutton, 6);
@@ -280,81 +358,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 {
// 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;
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 +448,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 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;
}
// End display
handleFightEnd();
// 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 tapout sequence and fight hasn't ended, update the display.
if (!redTapOutActive && !blueTapOutActive && !fightEnded) {
updateDisplay();
}
}

View File

@@ -25,7 +25,9 @@ const int COLONArray [] = {224,225,226,227,228,229,230,231};
// tap out
const int LitArrayTap [] = {0,1,2,3,4,5,6,7,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151};
const int LitArrayOut [] = {0,1,2,3,4,5,6,7,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159};
//const int LitArrayOut [] = {0,1,2,3,4,5,6,7,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159};
const int LitArrayOut [] = {56,57,58,59,60,61,62,63,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215};
// set digits of the timer/clock, use: setDIGIT(<digit ID>, <Number>, <red channel intensity 0-255>, <green channel intensity 0-255>, <blue channel intensity 0-255>)

View File

@@ -4,6 +4,7 @@
// 48:27:E2:50:86:84
#include <WiFi.h>
#include <esp_wifi.h> // Required for wifi_tx_info_t
#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/
@@ -58,7 +59,7 @@ void setup() {
//FastLED.addLeds<WS2811Controller800Khz, LED_DATA_PIN_TIMER, GRB>(leds_TIMER, NUM_LEDS_TIMER); // GRB ordering is typical
FastLED.addLeds<WS2812B, LED_DATA_PIN_TIMER, GRB>(leds_TIMER, NUM_LEDS_TIMER); // GRB ordering is typical
//FastLED.setMaxRefreshRate(10, true);
FastLED.setMaxPowerInVoltsAndMilliamps(5,2000); // Limit to 10W of output power
FastLED.setMaxPowerInVoltsAndMilliamps(5,4000); // Limit to 20W of output power
// set default values
// default screen, just to show it's up and running but hasn't received any data, should display 00:00 in blue

View File

@@ -13,7 +13,7 @@
#define ACTUATED_PULSE 1000 // Actuated position
// Define the reset time in milliseconds
#define RESET_TIME_MS 500
#define RESET_TIME_MS 1000
// Create two Servo objects for the two servos
Servo servo1;

View File

@@ -1,6 +1,7 @@
// ROFLS+ Referee Remote
#include <WiFi.h>
#include <esp_wifi.h> // Required for wifi_tx_info_t
#include <esp_now.h> // automatically installed for ESP32 boards, I think?
#include <Preferences.h> // automatically installed for ESP32 boards
#include <avdweb_Switch.h> // https://github.com/avdwebLibraries/avdweb_Switch
@@ -12,7 +13,12 @@
// 1 = Red Team Button
// 2 = Blue Team Button
const bool writeBoardID = false;
int boardID = 0;
int boardID = 1;
// set Arena, switches the MAC addresses out
// 1 = Arena A (Ant)
// 2 = Arena B (Beetle)
#define ARENA 1
// Hardware connections
#define START_BTN_PIN 10
@@ -45,7 +51,13 @@ bool sendDATAvar = false;
// ESP-NOW config
// REPLACE WITH YOUR RECEIVER MAC Address
uint8_t broadcastAddress[] = {0x84, 0xFC, 0xE6, 0xC7, 0x1A, 0x8C};
#if ARENA == 1
// A Arena Controller MAC Address
uint8_t broadcastAddress[] = {0x84, 0xFC, 0xE6, 0xC7, 0x1A, 0x8C};
#elif ARENA == 2
// B Arena Controller MAC Address
uint8_t broadcastAddress[] = {0xD8, 0x3B, 0xDA, 0xC9, 0x0C, 0xEE};
#endif
// Structure example to send data
// Must match the receiver structure
@@ -79,7 +91,7 @@ esp_now_peer_info_t peerInfo;
Preferences preferences;
// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
void OnDataSent(const wifi_tx_info_t *tx_info, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

80
Reference_Manual.md Normal file
View File

@@ -0,0 +1,80 @@
# ROFLS Arena Reference Manual
### Before a fight/tournament (omit step 1-6 after initial setup):
1. Check all connections
2. Power on the PSU
3. Turn on the Teambuttons and the Referee Remote
4. Wait for the 7 Segment Clock to turn on (~2s)
5. Turn on the lights (if off and needed)
6. Open the Controller Box and set the brightness with the potentiometer on the top right
7. Select the mode (explanations for the modes, see below):
8. Press and Hold Reset for >1s, can be on the Controller itself or the Referee Remote
9. Arena is ready
### Start a normal fight:
1. for good measure: Press and Hold Reset for >1s, can be on the Controller itself or the Referee Remote.
2. Team Red and Team Blue need to (short) press their respective Teambuttons, the Clock turns green to signal that both are ready.
3. Press Start, if the Teambuttons are not working, the Start button can be long pressed (>1s), to force a start.
4. To prevent the automatic pit opening, press and hold the Pit button, as soon as the pit button gets released, the pit will open.
### Start a rumble fight:
1. for good measure: Press and Hold Reset for >1s, can be on the Controller itself or the Referee Remote.
2. Press Start
3. To prevent the automatic pit opening, press and hold the Pit button, as soon as the pit button gets released, the pit will open.
### Pausing a running fight:
1. Press the Pause button
2. To resume:
- normal fight:
Both Teams need to press their respective Teambuttons to signal that they are both ready.
- Press start, after 3s, the timer resumes.
- rumble fight:
- Press start, after 3s, the stop watch resumes.
## Modes
* Normal Mode:
* Normal fight mode, 3 Minute Timer, Tapout enabled, after 90s (halftime) automatic pit opening (if enabled)
* Teambuttons are active and need to be pressed once to sign that they are ready. They light up if ready.
* Teambuttons LEDs turn of when the fight starts
* During a running fight, if one of the Teambuttons is pressed >1s, it counts as a tapout. The Timer stops and displays "TAPOUT", cycles between "TAPOUT" and the end time.
* Can be paused, resuming works the same as starting, Teams must be ready
* Rumble Mode:
* Rumble mode, stop watch counting up, Tapout disabled, after 90s automatic pit opening (if enabled)
* Match starts 3s after pressing start
* Teambuttons are inactive
* Teambutton LEDs Light up 3s before the match begins, and turn off when it starts.
* No Tapout available
* Can be paused, resuming works the same as starting, Teams must be ready
## Buttons explained
> The 4 buttons on the referee remote and the controller box have the same function and can both be used.
* Start
* Starts a fight, can be long pressed (>1s) to force start a fight
* Pause
* Pauses a fight
* Pit
* Short press (<1s) immediately opens the pit, Long press (>1s) prevents the pit from automatically open, but opens immediately after released
* Reset
* Resets the Arena Controller, needs to be long pressed (>1s)
* Lights Switch
* Turns on/off the Arena Lights, lights up if lights are on.
* Auto Pit Switch
* Turn on/off the automatic pit opening, lights up if enabled
* Mode Switch
* R = Rumble mode
* N = Normal mode
* T = Self Test (not implemented yet)
## Display Colours & Status explained
| Colour | Meaning |
|--------|---------|
| Blue with one purple segment, displaying 00:00 | not initialized (hit reset to fix) |
| Purple, displaying 03:00 | Normal Mode, Idle/Arena Ready |
| Green | Normal Mode, Both Teams Ready/Running Fight |
| Yellow | 3s Ready Countdown or Paused |
| Yellow, initally blinking 3 times | Timer run out / finish |
| Red, cycling between tap, out and time | Red Team has tapped out |
| Blue, cycling between tap, out and time | Blue Team has tapped out |
| Teal, displaying 00:00 | Rumble mode, Idle/Arena Ready |
| Teal, displaying timer | Rumble mode, active fight |