CodeHub
HomeUploadContact
Back to Hub

Smart Desk Buddy

April 5, 2026 Watch Tutorial

Wiring Schematic

Smart Desk Buddy wiring diagram

Hardware Required

  • Esp32
    Buy on Amazon

Source Code

Arduino / C++
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}
li-ion battery
Buy on Amazon
  • Toggle switch
    Buy on Amazon
  • Touch Sensor
    Buy on Amazon
  • SSD1306 display module
    Buy on Amazon