#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
// Internally 24-hour format
// TFT par 12-hour format show 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 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 sensor reading
readBME280();
// =================================================
// 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 UPDATE
// =================================================
if (millis() - lastSecond >= 1000) {
lastSecond += 1000;
seconds++;
// Seconds
if (seconds >= 60) {
seconds = 0;
minutes++;
}
// Minutes
if (minutes >= 60) {
minutes = 0;
hours++;
}
// New day
if (hours >= 24) {
hours = 0;
// Next weekday
dayOfWeek++;
if (dayOfWeek >= 7) {
dayOfWeek = 0;
}
// Next date
day++;
// New month
if (day > daysInMonth(month, year)) {
day = 1;
month++;
// New year
if (month > 12) {
month = 1;
year++;
}
}
drawDate();
}
// Update time
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;
}
// =====================================================
// CALCULATE DAY OF WEEK
// =====================================================
int calculateDayOfWeek(
int d,
int m,
int y
) {
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{"
"background:#111;"
"color:white;"
"font-family:Arial;"
"text-align:center;"
"padding-top:40px;"
"}";
html +=
"button{"
"background:#00c8ff;"
"color:#000;"
"border:0;"
"padding:15px 30px;"
"font-size:18px;"
"border-radius:10px;"
"}";
html += "</style>";
html += "</head>";
html += "<body>";
html +=
"<h1>ESP32 Weather Clock</h1>";
html +=
"<p>Mobile Date & Time Sync</p>";
html +=
"<button onclick='syncTime()'>"
"SYNC TIME NOW"
"</button>";
html +=
"<p id='status'>"
"Syncing..."
"</p>";
html += "<script>";
// =================================================
// SEND MOBILE DATE/TIME
// =================================================
html +=
"function syncTime(){";
html +=
"let d=new Date();";
html +=
"let data="
"d.getFullYear()+','+"
"(d.getMonth()+1)+','+"
"d.getDate()+','+"
"d.getHours()+','+"
"d.getMinutes()+','+"
"d.getSeconds();";
html +=
"fetch('/settime?t='+data)"
".then(r=>r.text())"
".then(x=>{"
"document.getElementById('status')"
".innerHTML=x;"
"});";
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;
}
}
// =================================================
// CHECK DATA
// =================================================
if (index == 6) {
year =
values[0];
month =
values[1];
day =
values[2];
hours =
values[3];
minutes =
values[4];
seconds =
values[5];
// Calculate weekday
dayOfWeek =
calculateDayOfWeek(
day,
month,
year
);
timeSynced = true;
// Reset one-second timer
lastSecond =
millis();
// Update display
drawDate();
drawTime();
// Serial
Serial.println(
"TIME UPDATED FROM MOBILE!"
);
Serial.print(
"Date: "
);
Serial.print(day);
Serial.print("/");
Serial.print(month);
Serial.print("/");
Serial.println(year);
Serial.print(
"24H 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 + DAY
// =====================================================
void drawDate() {
// Clear day
tft.fillRect(
88,
0,
72,
16,
BLACK
);
tft.setTextSize(1);
tft.setTextColor(
ORANGE
);
// Wednesday
if (dayOfWeek == 3) {
tft.setCursor(
87,
5
);
}
// Thursday
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 - 12 HOUR FORMAT
// =====================================================
void drawTime() {
// Clear complete time area
tft.fillRect(
55,
30,
105,
22,
BLACK
);
// =================================================
// 12 HOUR CONVERSION
// =================================================
int displayHour =
hours;
if (displayHour == 0) {
displayHour = 12;
}
else if (displayHour > 12) {
displayHour -= 12;
}
// =================================================
// MAIN TIME
// =================================================
tft.setTextSize(2);
tft.setTextColor(
CYAN
);
tft.setCursor(
56,
32
);
// Hour
if (displayHour < 10)
tft.print("0");
tft.print(
displayHour
);
tft.print(":");
// Minute
if (minutes < 10)
tft.print("0");
tft.print(
minutes
);
tft.print(":");
// Second
if (seconds < 10)
tft.print("0");
tft.print(
seconds
);
// =================================================
// AM / PM
// =================================================
tft.setTextSize(1);
tft.setTextColor(
WHITE
);
tft.setCursor(
139,
34
);
if (hours < 12) {
tft.print(
"AM"
);
}
else {
tft.print(
"PM"
);
}
}
// =====================================================
// BME280 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
);
}