#include <mechButton.h>
// Custom class that handles auto-repeat and acceleration for setting time
class CounterButton : public mechButton {
public:
CounterButton(int inPin) : mechButton(inPin) {
longPressMs = 600;
startIntervalMs = 250;
maxSpeedIntervalMs = 5;
accelerationRate = 25;
currentIntervalMs = startIntervalMs;
isPressed = false;
longPressTriggered = false;
pressTime = 0;
lastRepeatTime = 0;
callbackAction = NULL;
}
void setActionCallback(void (*func)(void)) { callbackAction = func; }
void update() {
bool currentlyPressed = !trueFalse();
if (currentlyPressed && !isPressed) {
isPressed = true;
longPressTriggered = false;
currentIntervalMs = startIntervalMs;
pressTime = millis();
if (callbackAction) callbackAction();
}
else if (!currentlyPressed && isPressed) {
isPressed = false;
}
if (isPressed) {
unsigned long currentMillis = millis();
unsigned long heldDuration = currentMillis - pressTime;
if (!longPressTriggered) {
if (heldDuration >= longPressMs) {
longPressTriggered = true;
lastRepeatTime = currentMillis;
if (callbackAction) callbackAction();
}
} else {
if (currentMillis - lastRepeatTime >= currentIntervalMs) {
lastRepeatTime = currentMillis;
if (currentIntervalMs > maxSpeedIntervalMs) {
if (currentIntervalMs - accelerationRate < maxSpeedIntervalMs) {
currentIntervalMs = maxSpeedIntervalMs;
} else {
currentIntervalMs -= accelerationRate;
}
}
if (callbackAction) callbackAction();
}
}
}
}
private:
unsigned long longPressMs;
unsigned long startIntervalMs;
unsigned long currentIntervalMs;
unsigned long maxSpeedIntervalMs;
unsigned long accelerationRate;
unsigned long pressTime;
unsigned long lastRepeatTime;
bool isPressed;
bool longPressTriggered;
void (*callbackAction)(void);
};
// Hardware Assignment
const int BUZZ_PIN = 9; // Connect your piezo buzzer positive leg to Digital Pin 5
// Global Configuration
int globalCounter = 180;
const int MIN_VAL = 0;
const int MAX_VAL = 5999; // 99m 59s max
// System State
bool isRunning = false;
bool alarmActive = false; // Tracks if the buzzer alarm is currently ringing
unsigned long lastTickTime = 0;
unsigned long alarmStartTime = 0;
// Instantiate buttons
CounterButton btnUp(11);
CounterButton btnDown(10);
mechButton btnStartPause(12);
void setup() {
Serial.begin(9600);
pinMode(BUZZ_PIN, OUTPUT); // Configure the buzzer pin as an output
btnUp.setActionCallback(incrementCounter);
btnDown.setActionCallback(decrementCounter);
btnStartPause.setCallback(toggleStartPause);
Serial.println("--- Countdown Timer Ready ---");
printCounter();
Serial.println("Status: PAUSED");
}
void loop() {
idle(); // Background debouncer for all 3 buttons
// 1. If paused and alarm isn't ringing, allow user to adjust the timer setting
if (!isRunning && !alarmActive) {
btnUp.update();
btnDown.update();
}
// 2. If running, process the countdown clock
else if (isRunning) {
unsigned long currentMillis = millis();
if (currentMillis - lastTickTime >= 1000) {
lastTickTime = currentMillis;
if (globalCounter > MIN_VAL) {
globalCounter--;
printCounter();
// Play a short click tone on the buzzer every second for a "ticking" effect
if (globalCounter > 0) {
tone(BUZZ_PIN, 2000, 20);
}
if (globalCounter == MIN_VAL) {
onTimerFinished();
}
}
}
}
// 3. Non-blocking buzzer alarm control
if (alarmActive) {
unsigned long elapsedAlarm = millis() - alarmStartTime;
// Ring the alarm for a total of 3 seconds
if (elapsedAlarm < 3000) {
// Create a pulsing beep pattern (on for 250ms, off for 250ms)
if ((elapsedAlarm / 250) % 2 == 0) {
tone(BUZZ_PIN, 1000); // Ring at a clear 1000 Hz tone
} else {
noTone(BUZZ_PIN); // Pause sound
}
} else {
// 3 seconds are up, silence the alarm
noTone(BUZZ_PIN);
alarmActive = false;
Serial.println("Alarm stopped automatically.");
}
}
}
void incrementCounter() {
if (globalCounter < MAX_VAL) {
globalCounter++;
printCounter();
}
}
void decrementCounter() {
if (globalCounter > MIN_VAL) {
globalCounter--;
printCounter();
}
}
void toggleStartPause() {
if (!btnStartPause.trueFalse()) {
// If the alarm is currently ringing, clicking this button instantly silences it
if (alarmActive) {
noTone(BUZZ_PIN);
alarmActive = false;
Serial.println(">>> ALARM SILENCED BY USER <<<");
return;
}
if (!isRunning && globalCounter == MIN_VAL) {
Serial.println("Cannot start: Please add time first!");
return;
}
isRunning = !isRunning;
if (isRunning) {
Serial.println(">>> TIMER STARTED <<<");
lastTickTime = millis();
tone(BUZZ_PIN, 1500, 100); // Friendly start tone
} else {
Serial.println(">>> TIMER PAUSED <<<");
tone(BUZZ_PIN, 1200, 100); // Friendly pause tone
}
}
}
void onTimerFinished() {
isRunning = false;
alarmActive = true;
alarmStartTime = millis(); // Set time anchor for the buzzer routine
Serial.println("***********************");
Serial.println(" ALARM: TIME'S UP! ");
Serial.println("***********************");
}
void printCounter() {
Serial.print("Time Remaining: ");
Serial.print(globalCounter);
Serial.println("s");
}