ESP32 Speaking Alarm Clock

Last Updated on October 15, 2026

You may used different Alarm clock and Most alarm clocks only beep during the set alarm time arrives but here we made simple and easy to construct ESP32 Speaking Alarm clock that beeps and then speaks the alarm time in a clear robotic voice, for example: “The time is ten fifty P M”. The voice is repeated three times so that you cannot miss it (hopefully).

It is a kind of fun experiment and prety impressive too, there is no RTC module used in this design. Here the ESP32 connects to your WiFi once at start up after powered up or reset and then connects with internet by using the credential you provide in the code and collects the exact time from an internet time server (NTP), and then switches WiFi off. After that, the ESP32 keeps counting time on its own and checks the clock every fraction of a second.

The speech is produced by the Talkie library. Talkie copies the way the speech chip inside the old Speak & Spell toy worked, and it stores the words inside the ESP32 flash memory. So you do not need an separate SD card or storage, an MP3 module, or any extra speech IC. You only need a small amplifier and a speaker. Here we used PAM8403 amplifier module and 8Ω speaker.

The clock stays completely silent during the day. It rings only at the alarm time you set in the code, and it rings only once per day for that time. Also there is another code which says time for every one minute you can experiment both codes.




Minimalist Design

  • Gets accurate time from the internet using NTP and no RTC module needed.
  • Easy to change for any country through the code
  • WiFi is switched off after time synchronization, which saves power and avoids interference with audio
  • At alarm time ESP32 activates buzzer tone, then a spoken time announcement
  • The alarm sequence repeats 3 times (change one number to repeat more or less through code)
  • Rings only once per day, even though the loop runs many times in the same minute
  • Automatic retry every 60 seconds if the first WiFi or NTP attempt fails
  • Speaks the alarm time in 12 hour format with A M or P M
  • No SD card or external speech chip, all words are stored inside the ESP32 program memory

Circuit Diagram

Components Required

  1. ESP32 Development board (ESP32 DOIT Devkit V1)
  2. PAM8403 Audio Amplifier module
  3. 8Ω speaker
  4. Connecting wires

Computer installed with Arduino IDE and Talkie library (search for Talkie in the Library Manager and install it).

Working video

Construction & Working

After uploading the code to ESP32 with WiFi credentials and Alarm time, ESP32 connects to your router. If it cannot connect within 30 seconds then it gives up for now and tries again after 60 seconds. After connecting configTime() asks the NTP server for the current time. The time zone offset (19800 seconds) is applied so that the ESP32 clock shows local time. You can change it depends on your country. As soon as the time is received, Wi-Fi is turned off. But the ESP32 internal clock block keeps running by itself.

Inside loop() section in the code, the program reads the local time again and again (about five times per second) and compares the hour and minute with ALARM_HOUR and ALARM_MINUTE to keep tracking the time and alarm threshold. When both match, and the alarm has not already rung today, the alarm sequence starts. The program stores today’s date. Even if the loop still sees the same minute, the alarm will not start again. Tomorrow the date is different, so the alarm works again.

ESP32 Speaking Alarm Clock Code

/*
  ESP32 Talking Alarm Clock (Talkie + NTP)
  - Gets the time from the internet (NTP) once at start-up, then turns Wi-Fi off
  - Stays silent all day
  - At the set alarm time: buzzer tone + spoken alarm time, repeated 3 times
  - Fires only once per day for the set alarm time
*/

#include <WiFi.h>
#include <time.h>
#include "Talkie.h"
#include "Vocab_US_Large.h"

// ---------------- Wi-Fi credentials ----------------
const char* ssid     = "Your WiFi SSID - Name";
const char* password = "Your WiFi Password";

// ---------------- NTP and Indian Standard Time ----------------
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 19800;   // IST = UTC + 5:30
const int   daylightOffset_sec = 0;

// ---------------- Alarm settings (24-hour format) ----------------
// 22:50 means 10:50 PM
const int ALARM_HOUR   = 22;
const int ALARM_MINUTE = 50;

// How many times the alarm sequence (tone + spoken time) is repeated
const int ALARM_REPEAT_COUNT = 3;

// Silent gap between two repeats (milliseconds)
const unsigned long REPEAT_GAP_MS = 2000;

// ---------------- Pins ----------------
// Passive buzzer on GPIO 27
// Talkie uses GPIO 25 for audio output on ESP32
const int BUZZER_PIN = 27;

// ---------------- Global variables ----------------
Talkie voice;
struct tm timeinfo;

bool timeReady = false;

// Date on which the alarm already rang (year * 1000 + day of year)
long lastAlarmDateKey = -1;

// Used to retry the time sync if the first attempt fails
unsigned long lastSyncAttempt = 0;
const unsigned long SYNC_RETRY_MS = 60000;

bool syncTime();
void runAlarm();
void playAlarmTone();
void announceTime(int hour, int minute);
void speakNumber(int num);

void setup() {
  Serial.begin(115200);
  delay(1000);

  pinMode(BUZZER_PIN, OUTPUT);
  noTone(BUZZER_PIN);

  Serial.println("Talking alarm clock starting...");

  timeReady = syncTime();
  lastSyncAttempt = millis();

  if (timeReady) {
    Serial.println("Time synchronized successfully.");
    Serial.println(&timeinfo, "%A, %d-%m-%Y %H:%M:%S");
    Serial.printf("Alarm is set for %02d:%02d\n", ALARM_HOUR, ALARM_MINUTE);
    Serial.println("Clock is ready.");
  } else {
    Serial.println("Time synchronization failed. Will retry every 60 seconds.");
  }
}

void loop() {
  // If the first sync failed, keep trying until it works
  if (!timeReady) {
    if (millis() - lastSyncAttempt >= SYNC_RETRY_MS) {
      lastSyncAttempt = millis();
      Serial.println("Retrying time synchronization...");
      timeReady = syncTime();
      if (timeReady) {
        Serial.println("Time synchronized successfully.");
      }
    }
    delay(500);
    return;
  }

  if (!getLocalTime(&timeinfo, 1000)) {
    delay(500);
    return;
  }

  // One unique number for each calendar day
  long dateKey = (long)timeinfo.tm_year * 1000 + timeinfo.tm_yday;

  // Alarm condition: time matches AND it has not rung today
  if (timeinfo.tm_hour == ALARM_HOUR &&
      timeinfo.tm_min  == ALARM_MINUTE &&
      dateKey != lastAlarmDateKey) {

    lastAlarmDateKey = dateKey;   // mark as done for today
    Serial.println("ALARM TIME REACHED!");
    runAlarm();
  }

  delay(200);
}

// Connect to Wi-Fi, get the time from NTP, then switch Wi-Fi off
bool syncTime() {
  Serial.print("Connecting to Wi-Fi");

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  unsigned long startAttempt = millis();

  while (WiFi.status() != WL_CONNECTED &&
         millis() - startAttempt < 30000) {
    delay(500);
    Serial.print(".");
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("\nWi-Fi connection failed.");
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    return false;
  }

  Serial.println("\nWi-Fi connected.");

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  Serial.println("Synchronizing time...");

  bool ok = false;
  for (int i = 0; i < 30; i++) {
    if (getLocalTime(&timeinfo, 1000)) {
      ok = true;
      break;
    }
    delay(200);
  }

  // Wi-Fi is not needed any more
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  Serial.println("Wi-Fi disabled.");

  return ok;
}

// Alarm sequence: tone, then speak the alarm time. Repeated 3 times.
void runAlarm() {
  for (int i = 0; i < ALARM_REPEAT_COUNT; i++) {
    Serial.printf("Alarm cycle %d of %d\n", i + 1, ALARM_REPEAT_COUNT);

    playAlarmTone();
    announceTime(ALARM_HOUR, ALARM_MINUTE);

    if (i < ALARM_REPEAT_COUNT - 1) {
      delay(REPEAT_GAP_MS);
    }
  }

  Serial.println("Alarm finished. Waiting for the next day.");
}

// Three beeps using the passive buzzer
void playAlarmTone() {
  tone(BUZZER_PIN, 1000, 400);
  delay(450);

  tone(BUZZER_PIN, 1500, 400);
  delay(450);

  tone(BUZZER_PIN, 1000, 400);
  delay(450);

  noTone(BUZZER_PIN);
}

// Speak a time, for example "The time is ten fifty P M"
void announceTime(int hour, int minute) {
  Serial.println("Speaking time...");

  voice.say(sp3_THE);
  voice.say(sp3_TIME);
  voice.say(sp3_IS);

  // Convert 24-hour time to 12-hour time
  int hour12 = hour % 12;
  if (hour12 == 0) {
    hour12 = 12;
  }

  speakNumber(hour12);

  if (minute == 0) {
    voice.say(sp3_OCLOCK);
  } else if (minute < 10) {
    // Example: 10:05 -> ten oh five
    voice.say(sp2_O);
    speakNumber(minute);
  } else {
    speakNumber(minute);
  }

  if (hour >= 12) {
    voice.say(sp3_P_M);
  } else {
    voice.say(sp3_A_M);
  }

  Serial.println("Announcement complete.");
}

// Speak numbers from 0 to 59
void speakNumber(int num) {
  if (num < 0 || num > 59) {
    return;
  }

  switch (num) {
    case 0:  voice.say(sp3_ZERO); break;
    case 1:  voice.say(sp3_ONE); break;
    case 2:  voice.say(sp3_TWO); break;
    case 3:  voice.say(sp3_THREE); break;
    case 4:  voice.say(sp3_FOUR); break;
    case 5:  voice.say(sp3_FIVE); break;
    case 6:  voice.say(sp3_SIX); break;
    case 7:  voice.say(sp3_SEVEN); break;
    case 8:  voice.say(sp3_EIGHT); break;
    case 9:  voice.say(sp3_NINE); break;
    case 10: voice.say(sp3_TEN); break;
    case 11: voice.say(sp3_ELEVEN); break;
    case 12: voice.say(sp3_TWELVE); break;
    case 13: voice.say(sp3_THIRTEEN); break;
    case 14: voice.say(sp3_FOURTEEN); break;
    case 15: voice.say(sp3_FIFTEEN); break;
    case 16: voice.say(sp3_SIXTEEN); break;
    case 17: voice.say(sp3_SEVENTEEN); break;
    case 18: voice.say(sp3_EIGHTEEN); break;
    case 19: voice.say(sp3_NINETEEN); break;
    case 20: voice.say(sp3_TWENTY); break;

    case 30: voice.say(sp3_THIRTY); break;
    case 40: voice.say(sp3_FOURTY); break;
    case 50: voice.say(sp3_FIFTY); break;

    default:
      if (num > 20 && num < 30) {
        voice.say(sp3_TWENTY);
        speakNumber(num - 20);
      } else if (num > 30 && num < 40) {
        voice.say(sp3_THIRTY);
        speakNumber(num - 30);
      } else if (num > 40 && num < 50) {
        voice.say(sp3_FOURTY);
        speakNumber(num - 40);
      } else if (num > 50 && num < 60) {
        voice.say(sp3_FIFTY);
        speakNumber(num - 50);
      }
      break;
  }
}

Serial Monitor output

ESP32 Speaking Clock (every minute)

#include <WiFi.h>
#include <time.h>
#include "Talkie.h"
#include "Vocab_US_Large.h"

// Wi-Fi credentials: enter your credentials here
const char* ssid = "Your WiFi SSID - Name";
const char* password = "Your WiFi Password";

// NTP and Indian Standard Time
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 19800;
const int daylightOffset_sec = 0;

// Alarm time in 24-hour format
// 22:11 means 10:11 PM
const int ALARM_HOUR = 22;
const int ALARM_MINUTE = 50;

// Passive buzzer on GPIO 27
// Talkie uses GPIO 25 for audio output on ESP32
const int BUZZER_PIN = 27;

Talkie voice;
struct tm timeinfo;

// Last minute announced
int lastAnnouncedMinute = -1;
int lastAnnouncedHour = -1;
int lastAnnouncedDay = -1;

bool timeReady = false;

void announceTime(int hour, int minute);
void speakNumber(int num);
void playAlarmTone();

void setup() {
  Serial.begin(115200);
  delay(1000);

  pinMode(BUZZER_PIN, OUTPUT);
  noTone(BUZZER_PIN);

  Serial.println("Talking clock starting...");

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  Serial.print("Connecting to Wi-Fi");

  unsigned long startAttempt = millis();

  while (WiFi.status() != WL_CONNECTED &&
         millis() - startAttempt < 30000) {
    delay(500);
    Serial.print(".");
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("\nWi-Fi connection failed.");
    Serial.println("Time cannot be synchronized.");
    WiFi.mode(WIFI_OFF);
    return;
  }

  Serial.println("\nWi-Fi connected.");

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

  Serial.println("Synchronizing time...");

  for (int i = 0; i < 30; i++) {
    if (getLocalTime(&timeinfo, 1000)) {
      timeReady = true;
      break;
    }
    delay(200);
  }

  if (timeReady) {
    Serial.println("Time synchronized successfully.");
    Serial.println(&timeinfo, "%A, %d-%m-%Y %H:%M:%S");
  } else {
    Serial.println("Time synchronization failed.");
  }

  // Disable Wi-Fi after successful synchronization
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);

  Serial.println("Wi-Fi disabled.");
  Serial.println("Clock is ready.");
}

void loop() {
  if (!timeReady || !getLocalTime(&timeinfo, 1000)) {
    delay(1000);
    return;
  }

  int hour = timeinfo.tm_hour;
  int minute = timeinfo.tm_min;
  int day = timeinfo.tm_yday;

  // Announce when the minute changes
  if (minute != lastAnnouncedMinute ||
      hour != lastAnnouncedHour ||
      day != lastAnnouncedDay) {

    Serial.printf("Current time: %02d:%02d\n", hour, minute);

    // Alarm: tone first, then announce the time
    if (hour == ALARM_HOUR &&
        minute == ALARM_MINUTE) {
      Serial.println("ALARM!");

      playAlarmTone();
      announceTime(hour, minute);
    } else {
      // Normal hourly clock announcement
      announceTime(hour, minute);
    }

    // Remember this announcement
    lastAnnouncedMinute = minute;
    lastAnnouncedHour = hour;
    lastAnnouncedDay = day;
  }

  delay(200);
}

// Play two alarm tones using a passive buzzer
void playAlarmTone() {
  tone(BUZZER_PIN, 1000, 400);
  delay(450);

  tone(BUZZER_PIN, 1500, 400);
  delay(450);

  tone(BUZZER_PIN, 1000, 400);
  delay(450);

  noTone(BUZZER_PIN);
}

// Speak the current time
void announceTime(int hour, int minute) {
  Serial.println("Speaking time...");

  voice.say(sp3_THE);
  voice.say(sp3_TIME);
  voice.say(sp3_IS);

  // Convert 24-hour time to 12-hour time
  int hour12 = hour % 12;
  if (hour12 == 0) {
    hour12 = 12;
  }

  speakNumber(hour12);

  if (minute == 0) {
    voice.say(sp3_OCLOCK);
  } else if (minute < 10) {
    // Example: 10:05 -> ten oh five
    voice.say(sp2_O);
    speakNumber(minute);
  } else {
    speakNumber(minute);
  }

  if (hour >= 12) {
    voice.say(sp3_P_M);
  } else {
    voice.say(sp3_A_M);
  }

  Serial.println("Announcement complete.");
}

// Speak numbers from 0 to 59
void speakNumber(int num) {
  if (num < 0 || num > 59) {
    return;
  }

  switch (num) {
    case 0:  voice.say(sp3_ZERO); break;
    case 1:  voice.say(sp3_ONE); break;
    case 2:  voice.say(sp3_TWO); break;
    case 3:  voice.say(sp3_THREE); break;
    case 4:  voice.say(sp3_FOUR); break;
    case 5:  voice.say(sp3_FIVE); break;
    case 6:  voice.say(sp3_SIX); break;
    case 7:  voice.say(sp3_SEVEN); break;
    case 8:  voice.say(sp3_EIGHT); break;
    case 9:  voice.say(sp3_NINE); break;
    case 10: voice.say(sp3_TEN); break;
    case 11: voice.say(sp3_ELEVEN); break;
    case 12: voice.say(sp3_TWELVE); break;
    case 13: voice.say(sp3_THIRTEEN); break;
    case 14: voice.say(sp3_FOURTEEN); break;
    case 15: voice.say(sp3_FIFTEEN); break;
    case 16: voice.say(sp3_SIXTEEN); break;
    case 17: voice.say(sp3_SEVENTEEN); break;
    case 18: voice.say(sp3_EIGHTEEN); break;
    case 19: voice.say(sp3_NINETEEN); break;
    case 20: voice.say(sp3_TWENTY); break;

    case 30: voice.say(sp3_THIRTY); break;
    case 40: voice.say(sp3_FOURTY); break;
    case 50: voice.say(sp3_FIFTY); break;

    default:
      if (num > 20 && num < 30) {
        voice.say(sp3_TWENTY);
        speakNumber(num - 20);
      } else if (num > 30 && num < 40) {
        voice.say(sp3_THIRTY);
        speakNumber(num - 30);
      } else if (num > 40 && num < 50) {
        voice.say(sp3_FOURTY);
        speakNumber(num - 40);
      } else if (num > 50 && num < 60) {
        voice.say(sp3_FIFTY);
        speakNumber(num - 50);
      }
      break;
  }
}

Serial Monitor output




Leave a Reply