#include <WiFi.h>
#include <WebServer.h>
#include <time.h>
#include <WitAITTS.h>
#include <ESPmDNS.h>
// -------- OLED ADDITIONS --------
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ================= CREDENTIALS =================
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* WIT_TOKEN = "YOUR_API_KEY";
// ================= I2S (XIAO ESP32-S3) =================
#define I2S_BCLK 7
#define I2S_LRC 8
#define I2S_DIN 9
#define BTN_STOP 2 // Active LOW
WitAITTS tts(I2S_BCLK, I2S_LRC, I2S_DIN);
WebServer server(80);
// ================= TIME =================
uint32_t baseEpochUTC = 0;
uint32_t baseMillis = 0;
const uint32_t IST_OFFSET = 19800;
uint32_t nowEpochUTC() {
return baseEpochUTC + (millis() - baseMillis) / 1000;
}
// ================= ALARMS =================
#define MAX_ALARMS 10
struct Alarm {
uint8_t hour;
uint8_t minute;
String message;
bool played;
};
Alarm alarms[MAX_ALARMS];
uint8_t alarmCount = 0;
bool alarmKilled = false;
// ================= OLED DEFINITIONS =================
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
/* HTML Web Page String omitted for brevity.
Please refer to the GitHub repository for the full HTML string.
*/
const char PAGE[] PROGMEM = R"rawliteral(
)rawliteral";
// ================= HANDLERS =================
void handleRoot(){ server.send_P(200,"text/html",PAGE); }
void handleEpoch(){ server.send(200,"text/plain",String(nowEpochUTC())); }
void handleSet(){
if(alarmCount>=MAX_ALARMS){ server.send(400,"text/plain","Limit"); return; }
alarms[alarmCount++] = {
(uint8_t)server.arg("h").toInt(),
(uint8_t)server.arg("m").toInt(),
server.arg("msg"),
false
};
server.send(200,"text/plain","OK");
}
// Additional handlers (Edit, Delete, GetAlarms) are available in the full code.
// ================= OLED UPDATE =================
void updateOLED(){
display.clearDisplay();
uint32_t tIST = nowEpochUTC() + IST_OFFSET;
uint8_t h24 = (tIST / 3600) % 24;
uint8_t m = (tIST / 60) % 60;
uint8_t h12 = h24 % 12;
if(h12 == 0) h12 = 12;
String ap = (h24 >= 12) ? "PM" : "AM";
display.setTextSize(2);
int timeX = (SCREEN_WIDTH - 60) / 2;
display.setCursor(timeX, 0);
display.printf("%02d:%02d", h12, m);
display.setTextSize(1);
display.setCursor(timeX + 64, 6);
display.print(ap);
display.drawLine(0, 26, SCREEN_WIDTH, 26, SSD1306_WHITE);
display.setCursor(0, 30);
display.print("NEXT ALARM");
display.drawLine(0, 40, 60, 40, SSD1306_WHITE);
bool found=false;
for(int i=0;i=12)?"PM":"AM";
display.setCursor(0,44);
display.printf("%02d:%02d %s", ah12, alarms[i].minute, aap.c_str());
found=true;
break;
}
}
if(!found){
display.setCursor(0,44);
display.print("None");
}
display.display();
}
// ================= SETUP =================
void setup(){
Serial.begin(115200);
pinMode(BTN_STOP, INPUT_PULLUP);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(300);
if (!MDNS.begin("alarm")) {
Serial.println("mDNS failed");
} else {
Serial.println("mDNS started: http://alarm.local");
}
configTime(0, 0, "pool.ntp.org");
struct tm ti;
while (!getLocalTime(&ti) || ti.tm_year < 123) delay(500);
baseEpochUTC = mktime(&ti);
baseMillis = millis();
tts.begin(WIFI_SSID, WIFI_PASSWORD, WIT_TOKEN);
server.on("/", handleRoot);
server.on("/epoch", handleEpoch);
server.on("/set", handleSet);
server.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED init failed");
} else {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.println("Speaking Alarm");
display.println("Clock Ready");
display.display();
}
}
// ================= LOOP =================
void loop(){
if(!alarmKilled) tts.loop();
server.handleClient();
uint32_t tIST = nowEpochUTC() + IST_OFFSET;
uint8_t h24 = (tIST / 3600) % 24;
uint8_t m = (tIST / 60) % 60;
for(int i=0;i=12)?"PM":"AM";
tts.speak("The time is "+String(h12)+" "+String(m)+" "+ap+". "+alarms[i].message);
}
}
if(digitalRead(BTN_STOP)==LOW && !alarmKilled){
alarmKilled=true;
tts.stop();
}
updateOLED();
delay(10);
}