I use a dfrobot firebeetle 2 esp-c6 but you can use anything with wifi, a MAX7219 4 digit 7 segment display, a 18650 battery, a push button, a buzzer and a switch for the battery.
The battery is connected directly to the builtin li-ion charger on the board, but if your doesnt have one you can just connect it to the VIN pin and GND and charge it using a external charging board.
Pinout:
| ESP | Components |
| GPIO8 | MAX7219 DIN |
| GPIO7 | MAX7219 CLK |
| GPIO6 | MAX7219 CS |
| GPIO5 | Buzzer (+) |
| GPIO4 | Button (one side) |
| 3.3V | MAX7219 VCC |
| GND | MAX7219 GND + Buzzer (-) + Button (other side) |
#include <WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
// WiFi Settings
const char* ssid = "WIFI-SSID";
const char* password = "WIFI-PASS";
// NTP Client (UTC+1: 3600 seconds)
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 10800, 60000);
// MAX7219 Pins
#define DIN_PIN 8
#define CLK_PIN 7
#define CS_PIN 6
// Alarm Components
#define BUZZER_PIN 5
#define BUTTON_PIN 4
bool alarmTriggered = false;
bool alarmActive = false;
unsigned long lastBeep = 0;
unsigned long lastDebounce = 0;
const int debounceDelay = 50;
byte reverseSegments(byte original) {
return ((original & 0x01) << 6) | // Bit 0 → Bit 6 (A↔G)
((original & 0x02) << 4) | // Bit 1 → Bit 5 (B↔F)
((original & 0x04) << 2) | // Bit 2 → Bit 4 (C↔E)
((original & 0x08) << 0) | // Bit 3 → Bit 3 (D stays)
((original & 0x10) >> 2) | // Bit 4 → Bit 2 (E↔C)
((original & 0x20) >> 4) | // Bit 5 → Bit 1 (F↔B)
((original & 0x40) >> 6); // Bit 6 → Bit 0 (G↔A)
}
const byte SEGMENT_MAP[10] = {
reverseSegments(0x3F), // 0
reverseSegments(0x06), // 1
reverseSegments(0x5B), // 2
reverseSegments(0x4F), // 3
reverseSegments(0x66), // 4
reverseSegments(0x6D), // 5
reverseSegments(0x7D), // 6
reverseSegments(0x07), // 7
reverseSegments(0x7F), // 8
reverseSegments(0x6F) // 9
};
void sendCommand(uint8_t address, uint8_t data) {
digitalWrite(CS_PIN, LOW);
shiftOut(DIN_PIN, CLK_PIN, MSBFIRST, address);
shiftOut(DIN_PIN, CLK_PIN, MSBFIRST, data);
digitalWrite(CS_PIN, HIGH);
}
void initMAX7219() {
pinMode(DIN_PIN, OUTPUT);
pinMode(CLK_PIN, OUTPUT);
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
sendCommand(0x0C, 0x01); // Power ON
sendCommand(0x09, 0x00); // No decoding
sendCommand(0x0A, 0x0F); // Max brightness
sendCommand(0x0B, 0x03); // Scan 4 digits
for(int i = 1; i <= 4; i++) sendCommand(i, 0x00); // Clear
}
void displayTime(int hours, int minutes) {
sendCommand(4, SEGMENT_MAP[minutes % 10]); // Rightmost digit
sendCommand(3, SEGMENT_MAP[minutes / 10]);
sendCommand(2, SEGMENT_MAP[hours % 10]);
sendCommand(1, SEGMENT_MAP[hours / 10]); // Leftmost digit
}
void setup() {
Serial.begin(115200);
initMAX7219();
// Alarm components
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
digitalWrite(BUZZER_PIN, LOW);
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) delay(500);
timeClient.begin();
timeClient.update();
}
void loop() {
timeClient.update();
int hours = timeClient.getHours();
int minutes = timeClient.getMinutes();
Serial.print(hours);
Serial.println(minutes);
// Update display
displayTime(hours, minutes);
// Alarm Logic (6:00 AM)
if (!alarmTriggered && hours == 6 && minutes == 0) {
alarmActive = true;
alarmTriggered = true;
Serial.println("ALARM ACTIVATED!");
}
// Handle active alarm
if (alarmActive) {
// Non-blocking beeper (500ms on/off)
if (millis() - lastBeep >= 500) {
digitalWrite(BUZZER_PIN, !digitalRead(BUZZER_PIN));
lastBeep = millis();
}
// Button press with debounce
if (digitalRead(BUTTON_PIN) == LOW &&
(millis() - lastDebounce) > debounceDelay) {
alarmActive = false;
digitalWrite(BUZZER_PIN, LOW);
lastDebounce = millis();
Serial.println("ALARM STOPPED");
}
}
// Hourly NTP sync
static unsigned long lastUpdate = 0;
if(millis() - lastUpdate >= 3600000) {
timeClient.forceUpdate();
lastUpdate = millis();
}
delay(1000);
}If you want to change your timezone change the third parameter of the function (in my case 10800 for UTC+3) to 3600*x (your timezone is UTC+x)
NTPClient timeClient(ntpUDP, "pool.ntp.org", 10800, 60000);
The author marked this model as their own original creation.