#include <Arduino.h>
#include <GyverTM1637.h>
#include <EEPROM.h>
// Define the address in EEPROM to store the countdown duration
#define COUNTDOWN_DURATION_ADDR 0
// Global variable to store the countdown duration
unsigned long countdownDuration;
unsigned long initialDuration = 0;
unsigned long remainingTime;
// Constants for default countdown duration and adjust cooldown
const unsigned long DEFAULT_COUNTDOWN_DURATION = 20000; // Default countdown duration in milliseconds
const unsigned long ADJUST_COOLDOWN = 250; // Cooldown time for adjusting countdown (in milliseconds)
// Define the pins for the buttons
const int P1 = 2; // Player 1 Button
const int P2 = 3; // Player 2 Button
const int MC = 4; // MC Button
const int redPinP1 = 7; // Red LED pin for Player 1
const int greenPinP1 = 6; // Green LED pin for Player 1
const int bluePinP1 = 5; // Blue LED pin for Player 1
const int redPinP2 = 10; // Red LED pin for Player 2
const int greenPinP2 = 9; // Green LED pin for Player 2
const int bluePinP2 = 8; // Blue LED pin for Player 2
const int redPinMC = 13; // Red LED pin for MC
const int greenPinMC = 12; // Green LED pin for MC
const int bluePinMC = 11; // Blue LED pin for MC
// Define the CLK and DIO pins for the TM1637 display
#define CLK 14
#define DIO 15
// Create an instance of GyverTM1637
GyverTM1637 display(CLK, DIO);
// Define the states
enum State {
IDLE,
ARMED,
READY,
SET,
GO,
TIME,
END,
PAUSED
};
State currentState = IDLE; // Initialize the current state to IDLE
// Variables to store the button states
int buttonStateP1;
int buttonStateP2;
int buttonStateMC;
// Variables to store the previous button states
int previousButtonStateP1 = HIGH;
int previousButtonStateP2 = HIGH;
int previousButtonStateMC = HIGH;
// Variables for debounce and cooldown handling
unsigned long lastDebounceTimeP1 = 0;
unsigned long lastDebounceTimeP2 = 0;
unsigned long lastAdjustTimeP1 = 0; // Add this line
unsigned long lastAdjustTimeP2 = 0; // Add this line
const unsigned long debounceDelay = 50; // 50 milliseconds debounce delay
const unsigned long adjustCooldown = 250; // 250 milliseconds cooldown
// Flag variables
int flagP1 = 0;
int flagP2 = 0;
int flagMC = 0;
// Variable to store the time when a button is pressed
unsigned long buttonPressTime;
// Variables to store the remaining time when entering the PAUSED state and the flag to indicate whether the countdown is paused
unsigned long remainingTimeWhenPaused = 0;
bool countdownPaused = false;
// Boolean variable to track whether the initial boot print has been done
bool bootPrintDone = false;
// Constants for debounce and LED delay
const int DEBOUNCE_TIME = 100; // Adjust debounce time as needed (in milliseconds)
const int LED_delay = 100; // Adjust LED delay as needed (in milliseconds)
const int BUTTON_COOLDOWN_DELAY = 300; // Adjust button cooldown delay as needed (in milliseconds)
// Variables to store the previous state and total flags
State previousState = IDLE;
int previousTotalFlags = 0;
// Variable to track if the device has started
bool start = false;
// Countdown timer variables
unsigned long countdownStartTime;
void setup() {
// Read the countdown duration from EEPROM
EEPROM.get(COUNTDOWN_DURATION_ADDR, countdownDuration);
// If the countdown duration is not initialized in EEPROM, set it to a default value
if (countdownDuration == 0 || countdownDuration > 86400000) { // 86400000 milliseconds = 24 hours
countdownDuration = DEFAULT_COUNTDOWN_DURATION; // Set default countdown duration
EEPROM.put(COUNTDOWN_DURATION_ADDR, countdownDuration); // Store the default value in EEPROM
}
// Initialize serial communication
Serial.begin(9600);
// Set the button pins as input with internal pull-up resistors
pinMode(P1, INPUT_PULLUP);
pinMode(P2, INPUT_PULLUP);
pinMode(MC, INPUT_PULLUP);
// Set the RGB LED pins as outputs
pinMode(redPinP1, OUTPUT);
pinMode(greenPinP1, OUTPUT);
pinMode(bluePinP1, OUTPUT);
pinMode(redPinP2, OUTPUT);
pinMode(greenPinP2, OUTPUT);
pinMode(bluePinP2, OUTPUT);
pinMode(redPinMC, OUTPUT);
pinMode(greenPinMC, OUTPUT);
pinMode(bluePinMC, OUTPUT);
// Turn off the RGB LEDs initially
digitalWrite(redPinP1, LOW);
digitalWrite(greenPinP1, LOW);
digitalWrite(bluePinP1, LOW);
digitalWrite(redPinP2, LOW);
digitalWrite(greenPinP2, LOW);
digitalWrite(bluePinP2, LOW);
digitalWrite(redPinMC, LOW);
digitalWrite(greenPinMC, LOW);
digitalWrite(bluePinMC, LOW);
// Set brightness to maximum
display.brightness(7);
// Display initial countdown value
displayCountdown(countdownDuration / 60000, (countdownDuration / 1000) % 60);
}
void loop() {
// Read the current state of the buttons
buttonStateP1 = digitalRead(P1);
buttonStateP2 = digitalRead(P2);
buttonStateMC = digitalRead(MC);
// Adjust countdown timer if in the TIME state
if (currentState == TIME) {
// Adjust the countdown timer if MC is held and P1 or P2 is pressed
if (buttonStateMC == LOW && (buttonStateP1 == LOW || buttonStateP2 == LOW)) {
// Add or subtract time based on button press
adjustCountdownTimer();
}
} else {
// Handle button presses only if not in the TIME state or MC button is not pressed
handleButtonPress(P1, flagP1, previousButtonStateP1, redPinP1, greenPinP1, bluePinP1, previousTotalFlags);
handleButtonPress(P2, flagP2, previousButtonStateP2, redPinP2, greenPinP2, bluePinP2, previousTotalFlags);
handleButtonPress(MC, flagMC, previousButtonStateMC, redPinMC, greenPinMC, bluePinMC, previousTotalFlags);
}
// Determine the current state based on the total number of flags
int totalFlags = flagP1 + flagP2 + flagMC;
// Transition to different states based on conditions
switch (currentState) {
case IDLE:
displayCountdown(countdownDuration / 60000, (countdownDuration / 1000) % 60);
// Check the total number of flags to determine the next state
if (totalFlags == 1) {
currentState = ARMED;
} else if (totalFlags == 2) {
currentState = READY;
} else if (totalFlags == 3 && !start) {
currentState = SET;
} else if (buttonStateMC == LOW && millis() - buttonPressTime >= 1500) {
currentState = TIME;
}
break;
case ARMED:
if (totalFlags == 1) {
currentState = ARMED;
} else if (totalFlags == 2) {
currentState = READY;
} else {
currentState = IDLE; // Transition to IDLE if the totalFlags is not equal to 1 or 2
}
break;
case READY:
if (totalFlags == 2) {
currentState = READY;
} else if (totalFlags == 3) {
currentState = SET;
} else {
currentState = ARMED;
}
break;
case SET:
// Transition to GO state when MC button is pressed and timer is not started
if (buttonStateMC == LOW && !start) {
delay(1000);
currentState = GO;
start = true;
countdownStartTime = millis(); // Start countdown timer
} else if (totalFlags == 3) {
currentState = SET;
} else {
currentState = READY;
}
break;
case GO:
if (millis() - countdownStartTime >= countdownDuration) {
currentState = END;
countdownStartTime = millis(); // Set the start time for the END state
} else if (totalFlags == 2 && start) {
currentState = PAUSED;
remainingTimeWhenPaused = countdownDuration - (millis() - countdownStartTime);
countdownPaused = true;
}
break;
case PAUSED:
if (totalFlags == 3 && start) {
currentState = GO;
countdownStartTime = millis() - (countdownDuration - remainingTimeWhenPaused); // Resume countdown from the remaining time
countdownPaused = false;
} else if (totalFlags == 1) {
currentState = ARMED;
}
break;
case TIME:
// Adjust the countdown timer if P1 or P2 is pressed
adjustCountdownTimer();
initialDuration = countdownDuration;
// Transition to IDLE state if MC button is released
if (buttonStateMC == HIGH) {
// Reset flags to 0 when entering IDLE state
flagP1 = 0;
flagP2 = 0;
flagMC = 0;
// Transition to IDLE
currentState = IDLE;
}
break;
case END:
start = false;
// Reset countdownDuration to initial value
countdownDuration = initialDuration;
remainingTime = countdownDuration;
// Check if total flags are less than 3 to reset flags and transition to IDLE
if (flagP1 + flagP2 + flagMC < 3) {
// Reset flags to 0
flagP1 = 0;
flagP2 = 0;
flagMC = 0;
// Transition to IDLE
currentState = IDLE;
} else {
// Wait for 10 seconds before transitioning back to IDLE
if (millis() - countdownStartTime >= 10000) {
// Reset flags to 0 when entering IDLE state
flagP1 = 0;
flagP2 = 0;
flagMC = 0;
// Transition to IDLE
currentState = IDLE;
}
}
break;
}
// Display the current state and button flags only if there's a change
if (!bootPrintDone || currentState != previousState || totalFlags != previousTotalFlags) {
reportState(currentState);
printButtonFlags(flagP1, flagP2, flagMC);
// Update previous state and flags
previousState = currentState;
previousTotalFlags = totalFlags;
bootPrintDone = true;
}
// Control LED behavior based on the current state and flag counts
controlLEDs(currentState, totalFlags);
// Update countdown display if in the GO state and the countdown hasn't expired
if (currentState == GO && millis() - countdownStartTime < countdownDuration) {
// Update countdown every second
updateCountdown();
}
// Delay to debounce buttons
delay(50);
}
// Function to handle button press and LED control
void handleButtonPress(int buttonPin, int &flag, int &previousButtonState, int redPin, int greenPin, int bluePin, int totalFlags) {
// Read the current state of the button
int buttonState = digitalRead(buttonPin);
// Check if the current state is different from the previous state
if (buttonState != previousButtonState) {
// Update the previous state to the current state
previousButtonState = buttonState;
// Check if the button is pressed (LOW)
if (buttonState == LOW) {
// Record the time when the button was pressed
buttonPressTime = millis();
} else {
// Check if the button was held for 3 seconds before releasing
if (millis() - buttonPressTime >= 3000) {
Serial.println("Reset Triggered"); // Print the message
// Reset button flags
flagP1 = 0;
flagP2 = 0;
flagMC = 0;
// Wait until the button is released
while (digitalRead(buttonPin) == LOW) {
// Waiting for the button to be released
}
} else if (millis() - buttonPressTime >= 1500) {
// Check if the button was held for more than 1.5 seconds
// Transition to TIME state only if in IDLE state
if (currentState == IDLE) {
currentState = TIME;
Serial.println("Entering TIME state"); // Debugging message
}
} else {
// Check if the button was held for 0.1 seconds before releasing
if (millis() - buttonPressTime >= 100) {
// Prevent decrementing the MC button flag if it's already set (flagMC is 1) in the SET state
if (!(totalFlags == 3 && flag == 1 && buttonPin == MC)) {
if (flag == 0) {
flag = 1; // Increment flag if it's 0
} else {
flag = 0; // Decrement flag if it's 1
// Add a short delay to prevent accidental re-triggering
delay(500); // Adjust this delay as needed
}
}
// Turn on the corresponding LED for a short duration
digitalWrite(redPin, HIGH);
delay(LED_delay);
digitalWrite(redPin, LOW);
digitalWrite(greenPin, HIGH);
delay(LED_delay);
digitalWrite(greenPin, LOW);
digitalWrite(bluePin, HIGH);
delay(LED_delay);
digitalWrite(bluePin, LOW);
}
}
}
} else if (buttonState == LOW && millis() - buttonPressTime >= 3000) {
// Check if the button is still being held and it's been 3 seconds since it was pressed
Serial.println("Reset Triggered"); // Print the message
// Reset button flags
flagP1 = 0;
flagP2 = 0;
flagMC = 0;
// Wait until the button is released
while (digitalRead(buttonPin) == LOW) {
// Waiting for the button to be released
}
}
}
// Function to report the current state
void reportState(State currentState) {
switch (currentState) {
case IDLE:
Serial.println("Current State: IDLE");
break;
case ARMED:
Serial.println("Current State: ARMED");
break;
case READY:
Serial.println("Current State: READY");
break;
case SET:
Serial.println("Current State: SET");
break;
case GO:
Serial.println("Current State: GO");
break;
case TIME:
Serial.println("Current State: TIME");
break;
case END:
Serial.println("Current State: END");
break;
case PAUSED:
Serial.println("Current State: PAUSED");
break;
default:
break;
}
}
// Function to print the button flag values
void printButtonFlags(int flagP1, int flagP2, int flagMC) {
Serial.print("Button Flags: P1=");
Serial.print(flagP1);
Serial.print(" | P2=");
Serial.print(flagP2);
Serial.print(" | MC=");
Serial.println(flagMC);
}
// Function to control LED behavior based on the current state and flag counts
void controlLEDs(State currentState, int totalFlags) {
switch (currentState) {
case IDLE:
performColorFade();
break;
case ARMED:
// Player 1 LED
digitalWrite(redPinP1, flagP1 == 1 ? LOW : (millis() % 1000 < 500 ? HIGH : LOW));
digitalWrite(greenPinP1, flagP1 == 1 ? LOW : (millis() % 1000 < 500 ? HIGH : LOW));
digitalWrite(bluePinP1, HIGH);
// Player 2 LED
digitalWrite(redPinP2, flagP2 == 1 ? LOW : (millis() % 1000 < 500 ? HIGH : LOW));
digitalWrite(greenPinP2, flagP2 == 1 ? LOW : (millis() % 1000 < 500 ? HIGH : LOW));
digitalWrite(bluePinP2, HIGH);
// MC LED
digitalWrite(redPinMC, flagMC == 1 ? LOW : (millis() % 1000 < 500 ? HIGH : LOW));
digitalWrite(greenPinMC, flagMC == 1 ? LOW : (millis() % 1000 < 500 ? HIGH : LOW));
digitalWrite(bluePinMC, HIGH);
break;
case READY:
// Player 1 LED
digitalWrite(redPinP1, flagP1 == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(greenPinP1, flagP1 == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(bluePinP1, HIGH);
// Player 2 LED
digitalWrite(redPinP2, flagP2 == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(greenPinP2, flagP2 == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(bluePinP2, HIGH);
// MC LED
digitalWrite(redPinMC, flagMC == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(greenPinMC, flagMC == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(bluePinMC, HIGH);
break;
case SET:
// Blink at 0.25 sec intervals in green for MC
digitalWrite(redPinMC, HIGH); // Red OFF
digitalWrite(greenPinMC, millis() % 500 < 250 ? LOW : HIGH); // Green ON for the first 250ms
digitalWrite(bluePinMC, HIGH); // Blue OFF
// Light player LEDs solid green
digitalWrite(redPinP1, LOW);
digitalWrite(greenPinP1, LOW);
digitalWrite(bluePinP1, HIGH);
digitalWrite(redPinP2, LOW);
digitalWrite(greenPinP2, LOW);
digitalWrite(bluePinP2, HIGH);
break;
case TIME:
// MC LED solid blue
digitalWrite(redPinMC, HIGH);
digitalWrite(greenPinMC, HIGH);
digitalWrite(bluePinMC, millis() % 500 < 250 ? HIGH : LOW);
// Player 1 LED solid green
digitalWrite(redPinP1, millis() % 1000 < 500 ? LOW : HIGH);
digitalWrite(greenPinP1, HIGH);
digitalWrite(bluePinP1, HIGH);
// Player 2 LED solid red
digitalWrite(redPinP2, HIGH);
digitalWrite(greenPinP2, millis() % 1000 < 500 ? HIGH : LOW);
digitalWrite(bluePinP2, HIGH);
break;
case PAUSED:
// Player 1 LED
digitalWrite(redPinP1, flagP1 == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(greenPinP1, flagP1 == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(bluePinP1, HIGH);
// Player 2 LED
digitalWrite(redPinP2, flagP2 == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(greenPinP2, flagP2 == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(bluePinP2, HIGH);
// MC LED
digitalWrite(redPinMC, flagMC == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(greenPinMC, flagMC == 1 ? LOW : (millis() % 500 < 250 ? HIGH : LOW));
digitalWrite(bluePinMC, HIGH);
break;
case END:
// MC LED solid blue
digitalWrite(redPinMC, LOW);
digitalWrite(greenPinMC, HIGH);
digitalWrite(bluePinMC, HIGH);
// Player 1 LED solid green
digitalWrite(redPinP1, LOW);
digitalWrite(greenPinP1, HIGH);
digitalWrite(bluePinP1, HIGH);
// Player 2 LED solid red
digitalWrite(redPinP2, LOW);
digitalWrite(greenPinP2, HIGH);
digitalWrite(bluePinP2, HIGH);
break;
case GO:
unsigned long elapsedTime = millis() - countdownStartTime;
unsigned long remainingTime = countdownDuration - elapsedTime;
// Calculate remaining time percentage
float remainingPercentage = (float)remainingTime / countdownDuration * 100;
// If time is 0 or less, turn LEDs red
if (remainingTime <= 0) {
// Turn Player 1 LED red
digitalWrite(redPinP1, LOW);
digitalWrite(greenPinP1, HIGH);
digitalWrite(bluePinP1, HIGH);
// Turn Player 2 LED red
digitalWrite(redPinP2, LOW);
digitalWrite(greenPinP2, HIGH);
digitalWrite(bluePinP2, HIGH);
// Turn MC LED red
digitalWrite(redPinMC, LOW);
digitalWrite(greenPinMC, HIGH);
digitalWrite(bluePinMC, HIGH);
}
// If less than 20% time remaining and greater than 0, turn LEDs yellow
else if (remainingPercentage > 0 && remainingPercentage <= 20) {
digitalWrite(redPinP1, LOW); // Red
digitalWrite(greenPinP1, LOW); // Green
digitalWrite(bluePinP1, HIGH); // Blue
digitalWrite(redPinP2, LOW);
digitalWrite(greenPinP2, LOW);
digitalWrite(bluePinP2, HIGH);
digitalWrite(redPinMC, LOW);
digitalWrite(greenPinMC, LOW);
digitalWrite(bluePinMC, HIGH);
} else { // Otherwise, keep LEDs green
digitalWrite(redPinP1, HIGH);
digitalWrite(greenPinP1, LOW);
digitalWrite(bluePinP1, HIGH);
digitalWrite(redPinP2, HIGH);
digitalWrite(greenPinP2, LOW);
digitalWrite(bluePinP2, HIGH);
digitalWrite(redPinMC, HIGH);
digitalWrite(greenPinMC, LOW);
digitalWrite(bluePinMC, HIGH);
}
break;
}
}
// Define the HSV color components
float hue = 0; // Initial hue value (0 - 360 degrees)
float saturation = 1; // Full saturation
float value = 1; // Full brightness
// Define separate hue variables for each LED
float hueP1 = 0; // Initial hue value for Player 1 LED
float hueP2 = 120; // Initial hue value for Player 2 LED
float hueMC = 240; // Initial hue value for MC LED
// Function to perform smooth color fade for all LEDs
void performColorFade() {
// Increase hue for each LED to smoothly transition through the color spectrum
hueP1 += 2; // Adjust the speed of the fade for Player 1 LED
hueP2 += 2; // Adjust the speed of the fade for Player 2 LED
hueMC += 2; // Adjust the speed of the fade for MC LED
// Ensure hue stays within the range of 0 to 360 degrees for each LED
if (hueP1 >= 360) {
hueP1 -= 360;
}
if (hueP2 >= 360) {
hueP2 -= 360;
}
if (hueMC >= 360) {
hueMC -= 360;
}
// Convert HSV to RGB for each LED
int rP1, gP1, bP1;
int rP2, gP2, bP2;
int rMC, gMC, bMC;
hsvToRgb(hueP1, saturation, value, rP1, gP1, bP1);
hsvToRgb(hueP2, saturation, value, rP2, gP2, bP2);
hsvToRgb(hueMC, saturation, value, rMC, gMC, bMC);
// Set the LED colors for P1
analogWrite(redPinP1, rP1);
analogWrite(greenPinP1, gP1);
analogWrite(bluePinP1, bP1);
// Set the LED colors for P2
analogWrite(redPinP2, rP2);
analogWrite(greenPinP2, gP2);
analogWrite(bluePinP2, bP2);
// Set the LED colors for MC
analogWrite(redPinMC, rMC);
analogWrite(greenPinMC, gMC);
analogWrite(bluePinMC, bMC);
}
// Function to update the countdown display
void updateCountdown() {
// Calculate remaining time
unsigned long elapsedTime = millis() - countdownStartTime;
unsigned long remainingTime = countdownDuration - elapsedTime;
// Calculate minutes and seconds
int minutes = (remainingTime / 1000) / 60;
int seconds = (remainingTime / 1000) % 60;
// Display the countdown on the TM1637 display
displayCountdown(minutes, seconds);
// Delay for one second
// delay(1000);
}
// Function to display the countdown on the TM1637 display
void displayCountdown(int minutes, int seconds) {
uint8_t data[] = {minutes / 10, minutes % 10, seconds / 10, seconds % 10};
display.display(data);
}
// Function to convert HSV to RGB
void hsvToRgb(float h, float s, float v, int &r, int &g, int &b) {
int i;
float f, p, q, t;
if (s == 0) {
// Achromatic (grey)
r = g = b = round(v * 255);
return;
}
h /= 60; // sector 0 to 5
i = floor(h);
f = h - i; // factorial part of h
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch(i) {
case 0:
r = round(v * 255);
g = round(t * 255);
b = round(p * 255);
break;
case 1:
r = round(q * 255);
g = round(v * 255);
b = round(p * 255);
break;
case 2:
r = round(p * 255);
g = round(v * 255);
b = round(t * 255);
break;
case 3:
r = round(p * 255);
g = round(q * 255);
b = round(v * 255);
break;
case 4:
r = round(t * 255);
g = round(p * 255);
b = round(v * 255);
break;
default: // case 5:
r = round(v * 255);
g = round(p * 255);
b = round(q * 255);
break;
}
}
// Function to adjust the countdown timer
void adjustCountdownTimer() {
// Only allow adjustment in the TIME state
if (currentState == TIME) {
bool countdownModified = false;
unsigned long currentTime = millis();
// Add or subtract time based on button press logic with debounce and cooldown
if (buttonStateP2 == LOW && (currentTime - lastDebounceTimeP2 > debounceDelay)) {
lastDebounceTimeP2 = currentTime;
if (currentTime - lastAdjustTimeP2 > adjustCooldown) {
countdownDuration += 5000; // Add 5 seconds
lastAdjustTimeP2 = currentTime;
countdownModified = true;
}
} else if (buttonStateP1 == LOW && (currentTime - lastDebounceTimeP1 > debounceDelay)) {
lastDebounceTimeP1 = currentTime;
if (currentTime - lastAdjustTimeP1 > adjustCooldown) {
countdownDuration -= 5000; // Subtract 5 seconds
lastAdjustTimeP1 = currentTime;
countdownModified = true;
}
}
// Ensure countdown duration stays within desired limits
if (countdownDuration < 5000) {
countdownDuration = 5000; // Minimum 5 seconds
}
// Update the displayed countdown time if modified
if (countdownModified) {
displayCountdown(countdownDuration / 60000, (countdownDuration / 1000) % 60);
// Store the adjusted countdown duration in EEPROM
EEPROM.put(COUNTDOWN_DURATION_ADDR, countdownDuration);
}
}
}