#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <WiFi.h>
#include <WiFiManager.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <time.h>
// LCD 20x4
LiquidCrystal_I2C lcd(0x27, 20, 4);
// NTP config
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 6 * 3600; // GMT+6
const int daylightOffset_sec = 0;
// API endpoints
const char* calendarURL = "onrender.com"; //change it accroding to your render link
const char* weatherURL = "https://wttr.in/Rajshahi?format=%t+%C"; //change it accroding to your city
// Data storage
String weatherInfo = "Fetching...";
String eventText = "No events yet";
// Weather scrolling
unsigned long lastScroll = 0;
int scrollIndex = 0;
// Event storage
#define MAX_EVENTS 5
String todaysEvents[MAX_EVENTS];
int eventsCount = 0;
int currentEventIndex = 0;
unsigned long lastEventChange = 0;
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
lcd.clear();
// WiFiManager portal
WiFiManager wm;
if (!wm.autoConnect("ESP32-Clock", "12345678")) {
Serial.println("Failed to connect.");
ESP.restart();
}
lcd.setCursor(0, 0);
lcd.print("WiFi Connected");
delay(1500);
lcd.clear();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
fetchWeather();
fetchGoogleEvent();
}
void loop() {
// Display time/date
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
char timeStr[12];
strftime(timeStr, sizeof(timeStr), "%I:%M:%S %p", &timeinfo);
char dateStr[16];
strftime(dateStr, sizeof(dateStr), "%d %b %Y", &timeinfo);
lcd.setCursor(4, 0);
lcd.print(timeStr);
lcd.setCursor(4, 1);
lcd.print(dateStr);
}
// Show weather (scroll if too long)
if (weatherInfo.length() <= 20) {
lcd.setCursor(0, 2);
lcd.print(weatherInfo);
for (int i = weatherInfo.length(); i < 20; i++) lcd.print(" ");
} else {
if (millis() - lastScroll > 800) {//scroll speed
lastScroll = millis();
String toShow = weatherInfo.substring(scrollIndex, scrollIndex + 20);
lcd.setCursor(0, 2);
lcd.print(toShow);
scrollIndex++;
if (scrollIndex > weatherInfo.length() - 20) scrollIndex = 0;
}
}
// Show events
lcd.setCursor(0, 3);
lcd.print(eventText);
for (int i = eventText.length(); i < 20; i++) lcd.print(" ");
// Cycle events every 5s if more than one
if (eventsCount > 1 && millis() - lastEventChange > 5000) {
lastEventChange = millis();
currentEventIndex = (currentEventIndex + 1) % eventsCount;
eventText = todaysEvents[currentEventIndex];
}
// Periodic updates
static unsigned long lastWeather = 0;
static unsigned long lastCalendar = 0;
if (millis() - lastWeather > 3600000) { // 1 hour update time
lastWeather = millis();
fetchWeather();
}
if (millis() - lastCalendar > 900000) { // 15 min update time
lastCalendar = millis();
fetchGoogleEvent();
}
delay(200);
}
void fetchWeather() {
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
Serial.println("[HTTP] Fetching weather...");
if (http.begin(client, weatherURL)) {
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
weatherInfo = http.getString();
weatherInfo.trim();
weatherInfo.replace("+", "");
weatherInfo.replace("°", " ");
for (int i = 0; i < weatherInfo.length(); i++) {
if (i == 0 || weatherInfo[i - 1] == ' ') {
weatherInfo[i] = toupper(weatherInfo[i]);
}
}
scrollIndex = 0;
} else {
weatherInfo = "API Error";
}
http.end();
}
}
void fetchGoogleEvent() {
eventsCount = 0;
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
Serial.println("[HTTP] Fetching calendar...");
if (http.begin(client, calendarURL)) {
int httpCode = http.GET();
Serial.print("[HTTP] Calendar Response: ");
Serial.println(httpCode);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println("=== Calendar Response ===");
Serial.println(payload);
DynamicJsonDocument doc(4096);
DeserializationError error = deserializeJson(doc, payload);
if (error) {
eventText = "JSON Err";
http.end();
return;
}
if (doc.size() > 0) {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
eventText = "Time Err";
http.end();
return;
}
char todayMD[6];
strftime(todayMD, sizeof(todayMD), "%m-%d", &timeinfo);
String today = String(todayMD);
for (int i = 0; i < doc.size() && eventsCount < MAX_EVENTS; i++) {
String start = doc[i]["start"].as<String>();
if (start.length() >= 10) {
String eventMD = start.substring(5, 10);
if (eventMD == today) {
todaysEvents[eventsCount++] = doc[i]["summary"].as<String>();
}
}
}
if (eventsCount == 0) {
eventText = "No Event Today";
} else {
currentEventIndex = 0;
eventText = todaysEvents[currentEventIndex];
}
} else {
eventText = "No Event";
}
} else {
eventText = "API Error";
}
http.end();
} else {
eventText = "WiFi Error";
}
}