ESP32 Based Event Calendar + Weather LCD Display Project

by MD Jahid Hassan in Circuits > Arduino

477 Views, 9 Favorites, 0 Comments

ESP32 Based Event Calendar + Weather LCD Display Project

WhatsApp Image 2025-08-13 at 7.51.38 PM.jpeg

Welcome to the ESP32 Weather Clock with Calendar project! This DIY gadget combines a stylish 20x4 LCD with an ESP32 microcontroller to create a functional clock that also fetches and displays real-time weather updates and calendar events. Perfect for hobbyists and tech enthusiasts, this project demonstrates how to integrate WiFi connectivity, NTP time synchronization, and API calls to create a personalized information hub for your workspace or home.

The clock displays the current time and date, scrolls weather information (including temperature and conditions) from a weather API, and cycles through your day's calendar events fetched via a custom API endpoint. The device is housed in a 3D-printed enclosure, adding a professional touch to its compact design. Whether you're looking to enhance your maker skills or create a practical tool, this project offers a great blend of hardware and software fun!

Supplies

Untitled.jpg
Untitlexxdd.jpg
12.jpg
b.jpg
s.jpg

Hardware Components:

  1. ESP32 Development Board
  2. 20x4 I2C LCD Display
  3. Battery
  4. TP4050
  5. Switch
  6. Wires
  7. Micro USB Cable
  8. Vero board
  9. 3D-Printed Enclosure

Software and Services (Free):

Arduino IDE: For uploading the code.

Libraries: Install via Arduino Library Manager - Wire, LiquidCrystal_I2C, WiFi, WiFiManager, HTTPClient, ArduinoJson.

API Access: Wttr. in for weather (no key needed); a custom Google Calendar endpoint (as in the code—replace with your own Render.com or similar hosted script).

NTP Server: Uses pool.ntp.org by default.

Code

#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";
}
}

3D-Printed Enclosure

Capture.JPG

This is the 3D Model of the enclosure.

GitHub Part

Upload this code to GitHub and deploy it to Render.

NB: The "render.txt" file needs to be changed in "render.yaml"

Step-by-Step Guide

This guide walks you through building your own portable ESP32-based weather clock that displays time, date, weather, and calendar events on a 20x4 LCD. The project uses a battery for portability, with charging via a TP4056 module. We'll assume basic soldering and programming skills. Total build time: 2-4 hours.