1// ==================================================
2// ADITYA INVENTS - DESK BUDDY (AUTONOMOUS EDITION)
3// HIGHLY OPTIMIZED FOR ESP32 & SSD1306 OLED
4// ==================================================
5
6#include <Wire.h>
7#include <Adafruit_GFX.h>
8#include <Adafruit_SSD1306.h>
9#include <math.h>
10#include <Fonts/FreeSansBold18pt7b.h>
11#include <Fonts/FreeSansBold9pt7b.h>
12#include <Fonts/FreeSans9pt7b.h>
13
14// ==================================================
15// 1. ASSETS & CONFIG
16// ==================================================
17#define SCREEN_WIDTH 128
18#define SCREEN_HEIGHT 64
19
20// ESP32 NATIVE TOUCH PIN (GPIO 4 is Touch0)
21#define TOUCH_PIN 4
22// Threshold for touch detection (Lower value = touched. Adjust if needed)
23#define TOUCH_THRESHOLD 30
24
25Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
26
27// --- WEATHER ICONS ---
28const unsigned char bmp_clear[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0xc0, 0x80, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x06, 0xff, 0xff, 0x60, 0x06, 0xff, 0xff, 0x60, 0x06, 0xff, 0xff, 0x60, 0x00, 0xff, 0xff, 0x00, 0x3e, 0xff, 0xff, 0x7c, 0x3e, 0xff, 0xff, 0x7c, 0x3e, 0xff, 0xff, 0x7c, 0x00, 0xff, 0xff, 0x00, 0x06, 0xff, 0xff, 0x60, 0x06, 0xff, 0xff, 0x60, 0x06, 0xff, 0xff, 0x60, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x01, 0x0f, 0xf0, 0x80, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
29const unsigned char bmp_clouds[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x00, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0x80, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
30const unsigned char bmp_rain[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x00, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0x80, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x0c, 0x00, 0x00, 0x60, 0x0c, 0x00, 0x00, 0xe0, 0x1c, 0x00, 0x00, 0xc0, 0x18, 0x00, 0x03, 0x80, 0x70, 0x00, 0x03, 0x80, 0x70, 0x00, 0x03, 0x00, 0x60, 0x00, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
31const unsigned char mini_sun[] PROGMEM = { 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x10, 0x08, 0x04, 0x20, 0x03, 0xc0, 0x27, 0xe4, 0x07, 0xe0, 0x07, 0xe0, 0x27, 0xe4, 0x03, 0xc0, 0x04, 0x20, 0x10, 0x08, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00 };
32const unsigned char mini_cloud[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xfe, 0x3f, 0xfe, 0x1f, 0xfc, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
33const unsigned char mini_rain[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xfe, 0x3f, 0xfe, 0x1f, 0xfc, 0x00, 0x00, 0x44, 0x44, 0x22, 0x22, 0x11, 0x11 };
34const unsigned char bmp_tiny_drop[] PROGMEM = { 0x10, 0x38, 0x7c, 0xfe, 0xfe, 0x7c, 0x38, 0x00 };
35
36// --- EMOTION PARTICLES (16x16) ---
37const unsigned char bmp_heart[] PROGMEM = { 0x00, 0x00, 0x0c, 0x60, 0x1e, 0xf0, 0x3f, 0xf8, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f, 0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
38const unsigned char bmp_zzz[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x3c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00 };
39const unsigned char bmp_anger[] PROGMEM = { 0x00, 0x00, 0x11, 0x10, 0x2a, 0x90, 0x44, 0x40, 0x80, 0x20, 0x80, 0x20, 0x44, 0x40, 0x2a, 0x90, 0x11, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
40
41// --- GLOBALS ---
42int currentPage = 0; // Starts at 0 (The Eyes) - INFINITE SCREENSAVER
43bool highBrightness = true;
44int tapCounter = 0;
45unsigned long lastTapTime = 0;
46bool lastPinState = false;
47unsigned long pressStartTime = 0;
48const unsigned long DOUBLE_TAP_DELAY = 300;
49
50// MOODS
51#define MOOD_NORMAL 0
52#define MOOD_HAPPY 1
53#define MOOD_SURPRISED 2
54#define MOOD_SLEEPY 3
55#define MOOD_ANGRY 4
56#define MOOD_SAD 5
57#define MOOD_EXCITED 6
58#define MOOD_LOVE 7
59#define MOOD_SUSPICIOUS 8
60
61int currentMood = MOOD_NORMAL;
62unsigned long lastMoodChange = 0;
63unsigned long moodInterval = 5000;
64
65// MOCK WEATHER DATA
66String city = "DARBHANGA";
67float temperature = 35.0;
68float feelsLike = 37.0;
69int humidity = 40;
70String weatherMain = "Clear";
71String weatherDesc = "Sunny";
72
73struct ForecastDay {
74 String dayName;
75 int temp;
76 String iconType;
77};
78
79ForecastDay fcast[3] = {
80 {"Fri", 36, "Clear"},
81 {"Sat", 34, "Clear"},
82 {"Sun", 33, "Clouds"}
83};
84
85// ==================================================
86// 2. SMOOTH PHYSICS ENGINE (TWO EYES)
87// ==================================================
88
89struct Eye {
90 float x, y;
91 float w, h;
92 float targetX, targetY, targetW, targetH;
93 float pupilX, pupilY;
94 float targetPupilX, targetPupilY;
95 float velX, velY, velW, velH;
96 float pVelX, pVelY;
97
98 // ESP32 SMOOTH PHYSICS CONSTANTS
99 float k = 0.04; // Lower spring constant (softer movement)
100 float d = 0.85; // Higher damping (glides beautifully)
101 float pk = 0.05; // Pupil spring constant
102 float pd = 0.80; // Pupil damping
103
104 bool blinking;
105 unsigned long lastBlink;
106 unsigned long nextBlinkTime;
107
108 void init(float _x, float _y, float _w, float _h) {
109 x = targetX = _x;
110 y = targetY = _y;
111 w = targetW = _w;
112 h = targetH = _h;
113 pupilX = targetPupilX = 0;
114 pupilY = targetPupilY = 0;
115 nextBlinkTime = millis() + random(1000, 4000);
116 }
117
118 void update() {
119 float ax = (targetX - x) * k;
120 float ay = (targetY - y) * k;
121 float aw = (targetW - w) * k;
122 float ah = (targetH - h) * k;
123
124 velX = (velX + ax) * d;
125 velY = (velY + ay) * d;
126 velW = (velW + aw) * d;
127 velH = (velH + ah) * d;
128
129 x += velX;
130 y += velY;
131 w += velW;
132 h += velH;
133
134 float pax = (targetPupilX - pupilX) * pk;
135 float pay = (targetPupilY - pupilY) * pk;
136 pVelX = (pVelX + pax) * pd;
137 pVelY = (pVelY + pay) * pd;
138 pupilX += pVelX;
139 pupilY += pVelY;
140 }
141};
142
143Eye leftEye, rightEye;
144unsigned long lastSaccade = 0;
145unsigned long saccadeInterval = 3000;
146float breathVal = 0;
147
148// ==================================================
149// 3. LOGIC & ESP32 TOUCH SENSOR
150// ==================================================
151const unsigned char* getBigIcon(String w) {
152 if (w == "Clear") return bmp_clear;
153 if (w == "Clouds") return bmp_clouds;
154 if (w == "Rain" || w == "Drizzle") return bmp_rain;
155 return bmp_clouds;
156}
157const unsigned char* getMiniIcon(String w) {
158 if (w == "Clear") return mini_sun;
159 if (w == "Rain" || w == "Drizzle" || w == "Thunderstorm") return mini_rain;
160 return mini_cloud;
161}
162
163void handleTouch() {
164 // ESP32 Native Touch Read: Returns a lower number when touched
165 int touchValue = touchRead(TOUCH_PIN);
166 bool currentPinState = (touchValue < TOUCH_THRESHOLD);
167 unsigned long now = millis();
168
169 if (currentPinState && !lastPinState) {
170 pressStartTime = now;
171 }
172 else if (!currentPinState && lastPinState) {
173 if (now - pressStartTime < 800) {
174 tapCounter++;
175 lastTapTime = now;
176 }
177 }
178
179 lastPinState = currentPinState;
180
181 if (tapCounter > 0) {
182 if (now - lastTapTime > DOUBLE_TAP_DELAY) {
183 if (tapCounter >= 2) {
184 highBrightness = !highBrightness;
185 display.dim(!highBrightness);
186 } else if (tapCounter == 1) {
187 currentPage++;
188 if (currentPage > 4) currentPage = 0;
189 }
190 tapCounter = 0;
191 }
192 }
193}
194
195// ==================================================
196// 4. DRAWING & ANIMATION
197// ==================================================
198
199void drawEyelidMask(float x, float y, float w, float h, int mood, bool isLeft) {
200 int ix = (int)x;
201 int iy = (int)y;
202 int iw = (int)w;
203 int ih = (int)h;
204 display.setTextColor(SSD1306_BLACK);
205
206 // Upward half mask for happy/excited
207 if (mood == MOOD_HAPPY || mood == MOOD_LOVE || mood == MOOD_EXCITED) {
208 display.fillRect(ix, iy + ih - 12, iw, 14, SSD1306_BLACK);
209 display.fillCircle(ix + iw / 2, iy + ih + 6, iw / 1.3, SSD1306_BLACK);
210 }
211 else if (mood == MOOD_ANGRY) {
212 // Subtle slanted line
213 if (isLeft)
214 for (int i = 0; i < 4; i++) display.drawLine(ix, iy + i, ix + iw, iy - 4 + i, SSD1306_BLACK);
215 else
216 for (int i = 0; i < 4; i++) display.drawLine(ix, iy - 4 + i, ix + iw, iy + i, SSD1306_BLACK);
217 }
218 else if (mood == MOOD_SAD) {
219 if (isLeft)
220 for (int i = 0; i < 4; i++) display.drawLine(ix, iy - 4 + i, ix + iw, iy + i, SSD1306_BLACK);
221 else
222 for (int i = 0; i < 4; i++) display.drawLine(ix, iy + i, ix + iw, iy - 4 + i, SSD1306_BLACK);
223 }
224}
225
226void drawUltraProEye(Eye& e, bool isLeft) {
227 int ix = (int)e.x;
228 int iy = (int)e.y;
229 int iw = (int)e.w;
230 int ih = (int)e.h;
231
232 int r = 8;
233 if (iw < 20) r = 3;
234 display.fillRoundRect(ix, iy, iw, ih, r, SSD1306_WHITE);
235
236 int cx = ix + iw / 2;
237 int cy = iy + ih / 2;
238
239 int pw = iw / 2.2;
240 int ph = ih / 2.2;
241
242 int px = cx + (int)e.pupilX - (pw / 2);
243 int py = cy + (int)e.pupilY - (ph / 2);
244
245 if (px < ix) px = ix;
246 if (px + pw > ix + iw) px = ix + iw - pw;
247 if (py < iy) py = iy;
248 if (py + ph > iy + ih) py = iy + ih - ph;
249
250 display.fillRoundRect(px, py, pw, ph, r / 2, SSD1306_BLACK);
251
252 if (iw > 15 && ih > 15) {
253 display.fillCircle(px + pw - 4, py + 4, 2, SSD1306_WHITE);
254 }
255
256 drawEyelidMask(e.x, e.y, e.w, e.h, currentMood, isLeft);
257}
258
259void updatePhysicsAndMood() {
260 unsigned long now = millis();
261 breathVal = sin(now / 1200.0) * 2.0;
262
263 if (now - lastMoodChange > moodInterval) {
264 lastMoodChange = now;
265 moodInterval = random(4000, 9000);
266 currentMood = random(0, 9);
267 }
268
269 // --- INDEPENDENT BLINKING ---
270 if (now > leftEye.nextBlinkTime) {
271 leftEye.blinking = true;
272 leftEye.lastBlink = now;
273 rightEye.blinking = true;
274 leftEye.nextBlinkTime = now + random(2000, 6000);
275 }
276 if (leftEye.blinking) {
277 leftEye.targetH = 2;
278 rightEye.targetH = 2;
279 if (now - leftEye.lastBlink > 150) {
280 leftEye.blinking = false;
281 rightEye.blinking = false;
282 }
283 }
284
285 // --- INDEPENDENT EYE MOVEMENT ---
286 if (!leftEye.blinking && now - lastSaccade > saccadeInterval) {
287 lastSaccade = now;
288 saccadeInterval = random(500, 2500);
289
290 int dir = random(0, 15);
291 float lx = 0, ly = 0;
292 float rx = 0, ry = 0;
293
294 if (dir < 3) { lx = 0; ly = 0; rx = 0; ry = 0; }
295 else if (dir == 3) { lx = -10; ly = -6; rx = -10; ry = -6; }
296 else if (dir == 4) { lx = 10; ly = -6; rx = 10; ry = -6; }
297 else if (dir == 5) { lx = -10; ly = 6; rx = -10; ry = 6; }
298 else if (dir == 6) { lx = 10; ly = 6; rx = 10; ry = 6; }
299 else if (dir >= 7 && dir <= 10) { lx = 14; ly = 0; rx = 14; ry = 0; }
300 else if (dir >= 11 && dir <= 13) { lx = -14; ly = 0; rx = -14; ry = 0;}
301 else if (dir == 14) { lx = 10; ly = 0; rx = -10; ry = 0; }
302
303 leftEye.targetPupilX = lx;
304 leftEye.targetPupilY = ly;
305 rightEye.targetPupilX = rx;
306 rightEye.targetPupilY = ry;
307
308 leftEye.targetX = 18 + (lx * 0.3);
309 leftEye.targetY = 14 + (ly * 0.3);
310 rightEye.targetX = 74 + (lx * 0.3);
311 rightEye.targetY = 14 + (ly * 0.3);
312 }
313
314 // --- FULL EYES SHAPE LOGIC ---
315 if (!leftEye.blinking) {
316 float baseW = 36;
317 float baseH = 36;
318 baseH += breathVal;
319
320 switch (currentMood) {
321 case MOOD_NORMAL:
322 leftEye.targetW = baseW; leftEye.targetH = baseH;
323 rightEye.targetW = baseW; rightEye.targetH = baseH;
324 break;
325 case MOOD_HAPPY:
326 case MOOD_LOVE:
327 leftEye.targetW = 40; leftEye.targetH = 32;
328 rightEye.targetW = 40; rightEye.targetH = 32;
329 break;
330 case MOOD_SURPRISED:
331 case MOOD_EXCITED:
332 leftEye.targetW = 34; leftEye.targetH = 45;
333 rightEye.targetW = 34; rightEye.targetH = 45;
334 leftEye.targetPupilX += random(-2, 3);
335 rightEye.targetPupilX += random(-2, 3);
336 break;
337 case MOOD_SLEEPY:
338 leftEye.targetW = 38; leftEye.targetH = 26;
339 rightEye.targetW = 38; rightEye.targetH = 26;
340 break;
341 case MOOD_ANGRY:
342 leftEye.targetW = 34; leftEye.targetH = 32;
343 rightEye.targetW = 34; rightEye.targetH = 32;
344 break;
345 case MOOD_SAD:
346 leftEye.targetW = 34; leftEye.targetH = 40;
347 rightEye.targetW = 34; rightEye.targetH = 40;
348 break;
349 case MOOD_SUSPICIOUS:
350 leftEye.targetW = 36; leftEye.targetH = 22;
351 rightEye.targetW = 36; rightEye.targetH = 22;
352 break;
353 }
354 }
355
356 leftEye.update();
357 rightEye.update();
358}
359
360void drawEmoPage() {
361 updatePhysicsAndMood();
362
363 if (currentMood == MOOD_LOVE) {
364 display.drawBitmap(56, 0, bmp_heart, 16, 16, SSD1306_WHITE);
365 } else if (currentMood == MOOD_SLEEPY) {
366 display.drawBitmap(110, 0, bmp_zzz, 16, 16, SSD1306_WHITE);
367 } else if (currentMood == MOOD_ANGRY) {
368 display.drawBitmap(56, 0, bmp_anger, 16, 16, SSD1306_WHITE);
369 }
370
371 drawUltraProEye(leftEye, true);
372 drawUltraProEye(rightEye, false);
373}
374
375// --- STANDARD PAGES ---
376void drawClock() {
377 unsigned long elapsedSecs = millis() / 1000;
378 unsigned long currentMockTime = 57720 + elapsedSecs; // Starts at 4:02 PM
379
380 int h24 = (currentMockTime / 3600) % 24;
381 int m = (currentMockTime / 60) % 60;
382
383 int h12 = h24 % 12;
384 if (h12 == 0) h12 = 12;
385
386 String ampm = (h24 >= 12) ? "PM" : "AM";
387
388 display.setTextColor(SSD1306_WHITE);
389 display.setFont(NULL);
390 display.setTextSize(1);
391 display.setCursor(114, 0);
392 display.print(ampm);
393 display.setFont(&FreeSansBold18pt7b);
394 char timeStr[6];
395 sprintf(timeStr, "%02d:%02d", h12, m);
396 int16_t x1, y1;
397 uint16_t w, h;
398 display.getTextBounds(timeStr, 0, 0, &x1, &y1, &w, &h);
399 display.setCursor((SCREEN_WIDTH - w) / 2, 42);
400 display.print(timeStr);
401 display.setFont(&FreeSans9pt7b);
402
403 char dateStr[20] = "Thursday";
404 display.getTextBounds(dateStr, 0, 0, &x1, &y1, &w, &h);
405 display.setCursor((SCREEN_WIDTH - w) / 2, 62);
406 display.print(dateStr);
407}
408
409void drawWeatherCard() {
410 display.drawBitmap(96, 0, getBigIcon(weatherMain), 32, 32, SSD1306_WHITE);
411
412 display.setFont(&FreeSans9pt7b);
413 display.setCursor(0, 14);
414
415 String displayCity = city;
416 if (displayCity.length() > 7) {
417 displayCity = displayCity.substring(0, 5) + "..";
418 }
419 display.print(displayCity);
420
421 display.setFont(&FreeSansBold18pt7b);
422 int tempInt = (int)temperature;
423 display.setCursor(0, 48);
424 display.print(tempInt);
425 int16_t x1, y1;
426 uint16_t w, h;
427 display.getTextBounds(String(tempInt).c_str(), 0, 48, &x1, &y1, &w, &h);
428 display.fillCircle(x1 + w + 5, 26, 4, SSD1306_WHITE);
429 display.setFont(NULL);
430 display.drawBitmap(88, 32, bmp_tiny_drop, 8, 8, SSD1306_WHITE);
431 display.setCursor(100, 32);
432 display.print(humidity);
433 display.print("%");
434 display.setCursor(88, 45);
435 display.print("~");
436 display.print((int)feelsLike);
437 display.drawLine(0, 52, 128, 52, SSD1306_WHITE);
438 display.setCursor(0, 55);
439 display.print(weatherDesc);
440}
441
442void drawWorldClock() {
443 unsigned long elapsedSecs = millis() / 1000;
444 unsigned long currentMockTime = 57720 + elapsedSecs;
445
446 int i_h = (currentMockTime / 3600) % 24;
447 int i_m = (currentMockTime / 60) % 60;
448
449 int s_m = i_m + 30;
450 int s_h = i_h + 5;
451
452 if (s_m >= 60) {
453 s_m -= 60;
454 s_h += 1;
455 }
456 s_h = s_h % 24;
457
458 display.fillRect(0, 0, 128, 16, SSD1306_WHITE);
459 display.setFont(NULL);
460 display.setTextColor(SSD1306_BLACK);
461 display.setCursor(32, 4);
462 display.print("WORLD CLOCK");
463 display.setTextColor(SSD1306_WHITE);
464 display.drawLine(64, 18, 64, 54, SSD1306_WHITE);
465 display.setFont(NULL);
466 display.setCursor(16, 22);
467 display.print("INDIA");
468 display.setFont(&FreeSansBold9pt7b);
469 char iStr[10];
470 sprintf(iStr, "%02d:%02d", i_h, i_m);
471 display.setCursor(5, 46);
472 display.print(iStr);
473 display.setFont(NULL);
474 display.setCursor(78, 22);
475 display.print("SYDNEY");
476 display.setFont(&FreeSansBold9pt7b);
477 char sStr[10];
478 sprintf(sStr, "%02d:%02d", s_h, s_m);
479 display.setCursor(69, 46);
480 display.print(sStr);
481 display.setFont(NULL);
482 display.setCursor(35, 56);
483 display.print("Tap to Exit");
484}
485
486void drawForecastPage() {
487 display.fillRect(0, 0, 128, 16, SSD1306_WHITE);
488 display.setFont(NULL);
489 display.setTextColor(SSD1306_BLACK);
490 display.setCursor(20, 4);
491 display.print("3-DAY FORECAST");
492 display.setTextColor(SSD1306_WHITE);
493 display.drawLine(42, 16, 42, 64, SSD1306_WHITE);
494 display.drawLine(85, 16, 85, 64, SSD1306_WHITE);
495 for (int i = 0; i < 3; i++) {
496 int xStart = i * 43;
497 int centerX = xStart + 21;
498 display.setFont(NULL);
499 String d = fcast[i].dayName;
500 display.setCursor(centerX - (d.length() * 3), 20);
501 display.print(d);
502 display.drawBitmap(centerX - 8, 28, getMiniIcon(fcast[i].iconType), 16, 16, SSD1306_WHITE);
503 display.setFont(&FreeSansBold9pt7b);
504 int16_t x1, y1;
505 uint16_t w, h;
506 display.getTextBounds(String(fcast[i].temp).c_str(), 0, 0, &x1, &y1, &w, &h);
507 display.setCursor(centerX - (w / 2) - 2, 60);
508 display.print(fcast[i].temp);
509 display.fillCircle(centerX + (w / 2) + 1, 52, 2, SSD1306_WHITE);
510 }
511}
512
513// ==================================================
514// 5. BOOT & MAIN
515// ==================================================
516
517void playBootAnimation() {
518 display.setTextColor(SSD1306_WHITE);
519 int cx = 64;
520 int cy = 32;
521
522 for (int r = 0; r < 80; r += 4) {
523 display.clearDisplay();
524 display.fillCircle(cx, cy, r, SSD1306_WHITE);
525 display.display();
526 delay(5);
527 }
528 for (int r = 0; r < 80; r += 4) {
529 display.clearDisplay();
530 display.fillCircle(cx, cy, 80, SSD1306_WHITE);
531 display.fillCircle(cx, cy, r, SSD1306_BLACK);
532 display.display();
533 delay(5);
534 }
535
536 display.setFont(&FreeSansBold9pt7b);
537 String line1 = "Aditya";
538 String line2 = "Invents";
539
540 int16_t x1, y1;
541 uint16_t w1, h1, w2, h2;
542 display.getTextBounds(line1, 0, 0, &x1, &y1, &w1, &h1);
543 display.getTextBounds(line2, 0, 0, &x1, &y1, &w2, &h2);
544
545 display.clearDisplay();
546 display.setCursor((SCREEN_WIDTH - w1) / 2, 26);
547 display.print(line1);
548 display.setCursor((SCREEN_WIDTH - w2) / 2, 48);
549 display.print(line2);
550 display.display();
551 delay(2500);
552}
553
554void setup() {
555 // ESP32 Default I2C Pins: SDA = 21, SCL = 22
556 Wire.begin();
557
558 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
559 for(;;);
560 }
561
562 Wire.setClock(400000);
563 display.setTextColor(SSD1306_WHITE);
564
565 leftEye.init(18, 14, 36, 36);
566 rightEye.init(74, 14, 36, 36);
567
568 playBootAnimation();
569}
570
571void loop() {
572 handleTouch();
573
574 display.clearDisplay();
575
576 switch (currentPage) {
577 case 0: drawEmoPage(); break;
578 case 1: drawClock(); break;
579 case 2: drawWeatherCard(); break;
580 case 3: drawWorldClock(); break;
581 case 4: drawForecastPage(); break;
582 }
583 display.display();
584}