#include <TM1637Display.h>
#include <Preferences.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Adafruit_NeoPixel.h>
#define CLK 22
#define DIO 21
#define BTN_START 18
#define BTN_RESET 19
#define BTN_LOG 23 // New button for webhook logging
#define NEOPIXEL_PIN 5 // Pin for NeoPixel
#define WIFI_SSID "WOKWI-Guest"
#define WIFI_PASSWORD ""
#define WEBHOOK_URL "https://webhook.site/646a4709-2ffc-44bc-851e-8a8cebea64e3"
TM1637Display display(CLK, DIO);
Preferences prefs;
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
bool running = false;
unsigned long lastMillis = 0;
unsigned long lastBlink = 0;
bool colonOn = true;
bool resetPending = false; // Track if waiting for reset confirmation
unsigned long resetStartMillis = 0; // Time when reset flashing started
uint8_t hours = 0;
uint8_t minutes = 0;
uint8_t seconds = 0;
const uint8_t ALL_ON[] = {0xff, 0xff, 0xff, 0xff}; // All segments ON
const uint8_t ALL_OFF[] = {0x00, 0x00, 0x00, 0x00}; // All segments OFF
// NeoPixel colors (R, G, B)
const uint32_t COLOR_BLUE = pixel.Color(0, 0, 255); // Sending request
const uint32_t COLOR_GREEN = pixel.Color(0, 255, 0); // Success
const uint32_t COLOR_RED = pixel.Color(255, 0, 0); // HTTP error
const uint32_t COLOR_ORANGE = pixel.Color(255, 165, 0); // Wi-Fi error
const uint32_t COLOR_OFF = pixel.Color(0, 0, 0); // Off
void saveTime() {
prefs.begin("timer", false);
prefs.putUChar("hours", hours);
prefs.putUChar("minutes", minutes);
prefs.putUChar("seconds", seconds);
prefs.end();
}
void loadTime() {
prefs.begin("timer", true);
hours = prefs.getUChar("hours", 0);
minutes = prefs.getUChar("minutes", 0);
seconds = prefs.getUChar("seconds", 0);
prefs.end();
if (hours > 99 || minutes > 59 || seconds > 59) {
hours = 0;
minutes = 0;
seconds = 0;
}
}
void showTime(bool colon) {
if (resetPending) {
// During reset pending, flash all segments ON/OFF
if ((millis() / 500) % 2 == 0) {
display.setSegments(ALL_ON);
} else {
display.setSegments(ALL_OFF);
}
} else {
// Normal display: show HHMM with colon state
int displayTime = hours * 100 + minutes; // HHMM
uint8_t dots = colon ? 0b01000000 : 0;
display.showNumberDecEx(displayTime, dots, true);
}
}
void setPixelColor(uint32_t color, unsigned long duration) {
pixel.setPixelColor(0, color);
pixel.show();
if (duration > 0) {
delay(duration); // Blocking delay to show color
pixel.setPixelColor(0, COLOR_OFF);
pixel.show();
}
}
bool sendWebhook() {
// Connect to Wi-Fi
setPixelColor(COLOR_BLUE, 0); // Blue: sending request
if (WiFi.status() != WL_CONNECTED) {
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
unsigned long startAttempt = millis();
while (WiFi.status() != WL_CONNECTED && (millis() - startAttempt) < 10000UL) {
delay(100); // Wait up to 10 seconds for connection
}
if (WiFi.status() != WL_CONNECTED) {
setPixelColor(COLOR_ORANGE, 2000); // Orange: Wi-Fi error
return false;
}
}
// Format time as HH:MM:SS
char timeStr[9];
snprintf(timeStr, sizeof(timeStr), "%02d:%02d:%02d", hours, minutes, seconds);
// Send POST request
HTTPClient http;
http.begin(WEBHOOK_URL);
http.addHeader("Content-Type", "application/json");
String payload = "{\"time\": \"" + String(timeStr) + "\"}";
int httpCode = http.POST(payload);
http.end();
if (httpCode == HTTP_CODE_OK) {
setPixelColor(COLOR_GREEN, 2000); // Green: success
return true;
} else {
setPixelColor(COLOR_RED, 2000); // Red: HTTP error
return false;
}
}
void setup() {
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_RESET, INPUT_PULLUP);
pinMode(BTN_LOG, INPUT_PULLUP); // Initialize log button
display.setBrightness(0x0f);
pixel.begin();
pixel.setBrightness(50); // Adjust brightness (0-255)
setPixelColor(COLOR_OFF, 0); // Ensure NeoPixel is off initially
loadTime();
showTime(true);
}
void loop() {
// --- Start/Stop button ---
if (digitalRead(BTN_START) == LOW) {
delay(50); // Simple debouncing
if (digitalRead(BTN_START) == LOW) {
running = !running;
if (running) {
lastMillis = millis(); // Reset to current time on resume (overflow-safe)
lastBlink = millis(); // Reset blink timing
colonOn = true;
} else {
saveTime();
colonOn = true; // Colon steady ON when stopped
}
resetPending = false; // Cancel any pending reset
showTime(colonOn);
while (digitalRead(BTN_START) == LOW); // Wait for button release
}
}
// --- Reset button ---
if (digitalRead(BTN_RESET) == LOW) {
delay(50); // Simple debouncing
if (digitalRead(BTN_RESET) == LOW) {
if (!resetPending) {
// First press: start flashing and reset confirmation window
resetPending = true;
resetStartMillis = millis();
lastBlink = millis(); // Sync flashing with reset
} else {
// Second press within 5 seconds: confirm reset
if ((millis() - resetStartMillis) <= 5000UL) {
hours = 0;
minutes = 0;
seconds = 0;
saveTime();
colonOn = true;
resetPending = false; // Stop flashing
showTime(true);
}
}
while (digitalRead(BTN_RESET) == LOW); // Wait for button release
}
}
// --- Log button ---
if (digitalRead(BTN_LOG) == LOW) {
delay(50); // Simple debouncing
if (digitalRead(BTN_LOG) == LOW) {
resetPending = false; // Cancel any pending reset
if (sendWebhook()) {
// On success, reset timer
hours = 0;
minutes = 0;
seconds = 0;
saveTime();
colonOn = true;
showTime(true);
}
while (digitalRead(BTN_LOG) == LOW); // Wait for button release
}
}
// --- Check if reset confirmation window expired ---
if (resetPending && (millis() - resetStartMillis) > 5000UL) {
resetPending = false; // Stop flashing after 5 seconds
showTime(colonOn); // Restore normal display
}
// --- Timer update every 1 second ---
// Note: (millis() - lastMillis) is overflow-safe due to unsigned arithmetic
if (running && !resetPending && (millis() - lastMillis) >= 1000UL) {
unsigned long elapsed = millis() - lastMillis;
unsigned long secondsToAdd = elapsed / 1000UL; // Calculate full seconds
lastMillis += secondsToAdd * 1000UL; // Update lastMillis (overflow-safe)
seconds += secondsToAdd;
while (seconds >= 60) { // Handle multiple seconds if large time elapsed
seconds -= 60;
minutes++;
if (minutes > 59) {
minutes = 0;
hours++;
if (hours > 99) hours = 0;
}
showTime(colonOn); // Update display when minutes change
}
}
// --- Colon blink every 500ms ---
// Note: (millis() - lastBlink) is overflow-safe
if ((running || resetPending) && (millis() - lastBlink) >= 500UL) {
lastBlink += (millis() - lastBlink) / 500UL * 500UL; // Update lastBlink (overflow-safe)
colonOn = !colonOn;
showTime(colonOn);
}
}