#include <WiFi.h>
#include <WebServer.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <math.h>
// =====================================================
// ESP32-C3 SAFE PINS
// =====================================================
#define TFT_CS 7
#define TFT_RST 2
#define TFT_DC 3
#define TFT_SCLK 4
#define TFT_MOSI 6
// =====================================================
// BME280 I2C
// =====================================================
#define BME_SDA 8
#define BME_SCL 9
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
Adafruit_BME280 bme;
// =====================================================
// WIFI HOTSPOT
// =====================================================
const char* AP_SSID = "ESP32-Weather";
const char* AP_PASSWORD = "12345678";
WebServer server(80);
// =====================================================
// COLORS
// =====================================================
#define BLACK 0x0000
#define WHITE 0xFFFF
#define BLUE 0x001F
#define YELLOW 0xFFE0
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define PINK 0xF81F
#define ORANGE 0xFD20
#define CLOUD 0xFFFF
#define LIGHT_PURPLE 0xB81F
// =====================================================
// DATE & TIME
// Mobile se automatically set hoga
// =====================================================
int hours = 12;
int minutes = 0;
int seconds = 0;
int day = 26;
int month = 9;
int year = 2026;
int dayOfWeek = 6;
// =====================================================
// DAY NAMES
// =====================================================
const char* dayNames[] = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
};
// =====================================================
// TIMERS
// =====================================================
unsigned long lastSecond = 0;
unsigned long lastSensorUpdate = 0;
// =====================================================
// BME VALUES
// =====================================================
float temperature = 0.0;
float humidity = 0.0;
float pressure = 0.0;
// =====================================================
// TIME SYNC STATUS
// =====================================================
bool timeSynced = false;
// =====================================================
// SETUP
// =====================================================
void setup() {
Serial.begin(115200);
// =================================================
// TFT SPI
// =================================================
SPI.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
tft.initR(INITR_MINI160x80);
tft.setRotation(3);
tft.fillScreen(BLACK);
// =================================================
// BME280
// =================================================
Wire.begin(BME_SDA, BME_SCL);
bool bmeOK = bme.begin(0x76);
if (!bmeOK) {
bmeOK = bme.begin(0x77);
}
if (!bmeOK) {
Serial.println("BME280 NOT FOUND!");
}
else {
Serial.println("BME280 OK");
}
// First BME reading
readBME280();
// =================================================
// CREATE ESP32 HOTSPOT
// =================================================
WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID, AP_PASSWORD);
IPAddress IP = WiFi.softAPIP();
Serial.println();
Serial.println("==============================");
Serial.println("ESP32 HOTSPOT STARTED");
Serial.print("SSID: ");
Serial.println(AP_SSID);
Serial.print("PASSWORD: ");
Serial.println(AP_PASSWORD);
Serial.print("OPEN: http://");
Serial.println(IP);
Serial.println("==============================");
// =================================================
// WEB SERVER
// =================================================
server.on("/", handleRoot);
server.on("/settime", handleSetTime);
server.begin();
Serial.println("Web server started");
// =================================================
// DRAW UI
// =================================================
drawUI();
lastSecond = millis();
lastSensorUpdate = millis();
}
// =====================================================
// LOOP
// =====================================================
void loop() {
// =================================================
// WEB SERVER
// =================================================
server.handleClient();
// =================================================
// CLOCK
// =================================================
if (millis() - lastSecond >= 1000) {
lastSecond += 1000;
seconds++;
if (seconds >= 60) {
seconds = 0;
minutes++;
}
if (minutes >= 60) {
minutes = 0;
hours++;
}
if (hours >= 24) {
hours = 0;
// Next day
dayOfWeek++;
if (dayOfWeek >= 7) {
dayOfWeek = 0;
}
day++;
// New month
if (day > daysInMonth(month, year)) {
day = 1;
month++;
// New year
if (month > 12) {
month = 1;
year++;
}
}
drawDate();
}
drawTime();
}
// =================================================
// BME280 UPDATE
// =================================================
if (millis() - lastSensorUpdate >= 500) {
lastSensorUpdate = millis();
readBME280();
drawSensorValues();
}
}
// =====================================================
// DAYS IN MONTH
// =====================================================
int daysInMonth(int m, int y) {
switch (m) {
case 1: return 31;
case 2:
if ((y % 400 == 0) ||
((y % 4 == 0) && (y % 100 != 0))) {
return 29;
}
return 28;
case 3: return 31;
case 4: return 30;
case 5: return 31;
case 6: return 30;
case 7: return 31;
case 8: return 31;
case 9: return 30;
case 10: return 31;
case 11: return 30;
case 12: return 31;
}
return 30;
}
// =====================================================
// DAY OF WEEK CALCULATION
// =====================================================
int calculateDayOfWeek(int d, int m, int y) {
// Zeller-like calculation
int yy = y;
int mm = m;
if (mm < 3) {
mm += 12;
yy--;
}
int k = yy % 100;
int j = yy / 100;
int h = (d +
(13 * (mm + 1)) / 5 +
k +
k / 4 +
j / 4 +
5 * j) % 7;
// Zeller:
// 0 = Saturday
// 1 = Sunday
// ...
int result = (h + 6) % 7;
return result;
}
// =====================================================
// READ BME280
// =====================================================
void readBME280() {
temperature = bme.readTemperature();
humidity = bme.readHumidity();
pressure = bme.readPressure() / 100.0F;
Serial.print("Temp: ");
Serial.print(temperature, 1);
Serial.print(" C | Hum: ");
Serial.print(humidity, 0);
Serial.print("% | Press: ");
Serial.print(pressure, 0);
Serial.println(" hPa");
}
// =====================================================
// WEB PAGE
// =====================================================
void handleRoot() {
String html = "";
html += "<!DOCTYPE html>";
html += "<html>";
html += "<head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<title>ESP32 Weather Clock</title>";
html += "<style>";
html += "body{";
html += "background:#111;";
html += "color:white;";
html += "font-family:Arial;";
html += "text-align:center;";
html += "padding-top:40px;";
html += "}";
html += "button{";
html += "background:#00c8ff;";
html += "border:0;";
html += "padding:15px 30px;";
html += "font-size:18px;";
html += "border-radius:10px;";
html += "}";
html += "</style>";
html += "</head>";
html += "<body>";
html += "<h1>ESP32 Weather Clock</h1>";
html += "<p>Mobile Date & Time</p>";
html += "<button onclick='syncTime()'>SYNC TIME NOW</button>";
html += "<p id='status'>Press button to sync</p>";
html += "<script>";
html += "function syncTime(){";
html += "let d=new Date();";
html += "let data=";
html += "d.getFullYear()+','+";
html += "(d.getMonth()+1)+','+";
html += "d.getDate()+','+";
html += "d.getHours()+','+";
html += "d.getMinutes()+','+";
html += "d.getSeconds();";
html += "fetch('/settime?t='+data)";
html += ".then(r=>r.text())";
html += ".then(x=>{";
html += "document.getElementById('status').innerHTML=x;";
html += "});";
html += "}";
// Automatically sync when page opens
html += "window.onload=function(){syncTime();};";
html += "</script>";
html += "</body>";
html += "</html>";
server.send(200, "text/html", html);
}
// =====================================================
// RECEIVE MOBILE TIME
// =====================================================
void handleSetTime() {
if (!server.hasArg("t")) {
server.send(400, "text/plain", "Time data missing");
return;
}
String data = server.arg("t");
Serial.println();
Serial.print("Mobile time received: ");
Serial.println(data);
int values[6];
int index = 0;
int start = 0;
for (int i = 0; i <= data.length(); i++) {
if (i == data.length() || data[i] == ',') {
if (index < 6) {
values[index] = data.substring(start, i).toInt();
index++;
}
start = i + 1;
}
}
if (index == 6) {
year = values[0];
month = values[1];
day = values[2];
hours = values[3];
minutes = values[4];
seconds = values[5];
// Calculate correct weekday
dayOfWeek = calculateDayOfWeek(day, month, year);
timeSynced = true;
// Reset clock timer
lastSecond = millis();
// Update TFT
drawDate();
drawTime();
Serial.println("TIME UPDATED FROM MOBILE!");
Serial.print("Date: ");
Serial.print(day);
Serial.print("/");
Serial.print(month);
Serial.print("/");
Serial.println(year);
Serial.print("Time: ");
Serial.print(hours);
Serial.print(":");
Serial.print(minutes);
Serial.print(":");
Serial.println(seconds);
server.send(
200,
"text/plain",
"Time synced successfully!"
);
}
else {
server.send(
400,
"text/plain",
"Invalid time data"
);
}
}
// =====================================================
// MAIN UI
// =====================================================
void drawUI() {
tft.fillScreen(BLACK);
// =================================================
// CITY
// =================================================
tft.setTextSize(2);
tft.setTextColor(LIGHT_PURPLE);
tft.setCursor(5, 5);
tft.print("London");
// =================================================
// DATE
// =================================================
drawDate();
// =================================================
// WEATHER
// =================================================
tft.setTextSize(1);
tft.setTextColor(ORANGE);
tft.setCursor(5, 25);
tft.print("Clouds");
// =================================================
// TIME
// =================================================
drawTime();
// =================================================
// LABELS
// =================================================
tft.setTextSize(1);
tft.setTextColor(PINK);
tft.setCursor(60, 53);
tft.print("Press");
tft.setCursor(101, 53);
tft.print("Temp");
tft.setCursor(136, 53);
tft.print("Humi");
// =================================================
// VALUES
// =================================================
drawSensorValues();
// =================================================
// WEATHER ICON
// =================================================
drawWeatherIcon();
}
// =====================================================
// DATE
// =====================================================
void drawDate() {
// Clear day
tft.fillRect(88, 0, 72, 16, BLACK);
tft.setTextSize(1);
tft.setTextColor(ORANGE);
if (dayOfWeek == 3) {
tft.setCursor(87, 5);
}
else if (dayOfWeek == 4) {
tft.setCursor(91, 5);
}
else {
tft.setCursor(98, 5);
}
tft.print(dayNames[dayOfWeek]);
// =================================================
// DATE
// =================================================
tft.fillRect(88, 17, 72, 10, BLACK);
tft.setTextColor(GREEN);
tft.setCursor(91, 19);
if (day < 10)
tft.print("0");
tft.print(day);
tft.print("/");
if (month < 10)
tft.print("0");
tft.print(month);
tft.print("/");
tft.print(year);
}
// =====================================================
// TIME
// =====================================================
void drawTime() {
tft.fillRect(60, 31, 100, 20, BLACK);
tft.setTextSize(2);
tft.setTextColor(CYAN);
tft.setCursor(62, 33);
if (hours < 10)
tft.print("0");
tft.print(hours);
tft.print(":");
if (minutes < 10)
tft.print("0");
tft.print(minutes);
tft.print(":");
if (seconds < 10)
tft.print("0");
tft.print(seconds);
}
// =====================================================
// SENSOR VALUES
// =====================================================
void drawSensorValues() {
tft.setTextSize(1);
// =================================================
// PRESSURE
// =================================================
tft.fillRect(58, 64, 45, 10, BLACK);
tft.setTextColor(YELLOW);
tft.setCursor(58, 65);
int p = (int)round(pressure);
if (p > 999)
p = 999;
if (p < 0)
p = 0;
tft.print(p);
tft.print("hPa");
// =================================================
// TEMPERATURE
// =================================================
tft.fillRect(104, 64, 35, 10, BLACK);
tft.setTextColor(BLUE);
tft.setCursor(101, 65);
tft.print(temperature, 1);
tft.print("C");
// =================================================
// HUMIDITY
// =================================================
tft.fillRect(140, 64, 20, 10, BLACK);
tft.setTextColor(RED);
tft.setCursor(136, 65);
int h = (int)round(humidity);
if (h > 99)
h = 99;
if (h < 0)
h = 0;
tft.print(h);
tft.print("%");
}
// =====================================================
// WEATHER ICON
// =====================================================
void drawWeatherIcon() {
int sunX = 23;
int sunY = 55;
// SUN
tft.fillCircle(
sunX,
sunY,
8,
CYAN
);
// SUN RAYS
for (int i = 0; i < 360; i += 45) {
float angle = i * 0.0174533;
int x1 =
sunX + cos(angle) * 11;
int y1 =
sunY + sin(angle) * 11;
int x2 =
sunX + cos(angle) * 14;
int y2 =
sunY + sin(angle) * 14;
tft.drawLine(
x1,
y1,
x2,
y2,
CYAN
);
}
// CLOUD
tft.fillCircle(
25,
65,
7,
CLOUD
);
tft.fillCircle(
34,
64,
9,
CLOUD
);
tft.fillCircle(
44,
66,
6,
CLOUD
);
tft.fillRect(
22,
65,
27,
7,
CLOUD
);
}