Desk32: The Minimalist Smart Desk Hub for Deep Work

Published  January 19, 2026   0
u uploader
Author
 Desk32 Smart Hub for Deep Work

By Ameya Angadi

In an era of endless notifications and remote work, our greatest challenge isn't a lack of tools, it’s a lack of focus. We often turn to our smartphones for a simple timer or a to-do list, only to find ourselves sucked into social media ten minutes later. This "Digital Fatigue" is a real problem, I faced similar problems while studying for my exams. I realized that using a Pomodoro timer and a clear task list was one of the only way I could manage my workload, but I needed a device that lived on my desk, not on my phone.

Desk32 was my solution to this problem. It is a standalone productivity accessory designed to act as a "Focus Anchor". Built using the ESP32-S3-BOX-3, it provides essential tools: a specialized Pomodoro timer, a Dynamic task manager, and health reminders, all of which have been integrated into a sleek, professional interface. It isn't just a table clock; it’s a dedicated device in a smart home designed to reclaim your attention.

Components Required

Component Name  Quantity Datasheet/Link
ESP32-S3-BOX-31View Datasheet
18650 Battery (2000-3000 mAh)1View Datasheet

Circuit Diagram

The core innovation of Desk32 lies in its integrated hardware architecture, which completely eliminates the "spaghetti wiring" typically found in hobby projects and prototypes. Instead of using messy jumper wires and breadboards, the ESP32-S3-BOX-3 communicates with the Sensor Dock via a high-speed, internal PCIe x1 expansion connector. This single, high-density header manages the entire interface for the LCD, the I2C bus for environmental sensors, and a robust power delivery path from the 18650 Li-ion battery. By utilizing this professional-grade interconnect, the device achieves a level of signal integrity and physical reliability impossible to reach with traditional wiring, resulting in a project that functions as a single, cohesive unit.

 

By docking the display module into the sensor base, we instantly establish a robust electrical path for both data and power without a single external cable.

Internal Architecture & Connections

Smart Desk Hub for Deep Work Circuit Diagram

The above image provides a detailed technical reference for the internal connections and component layout of the sensor dock, highlighting the integrated power path and expansion headers. (Credit: Espressif Systems GitHub)

Modular Hardware Integration

Modular Hardware Integration

The above image demonstrates the plug-and-play nature of the Desk32, where the display module is simply slotted into the sensor dock to establish a secure, wire-free hardware connection. (Credit: Espressif Systems GitHub)

Hardware Assembly

Step 1: Preparing the Sensor Dock Base

The first step involves preparing the ESP32-S3-BOX-3-SENSOR dock. This dock serves as the electrical and physical foundation for the "Focus Anchor". Familiarize yourself with the layout, specifically the high-density expansion connector at the top and the battery compartment at the bottom. This dock is what enables the device to be completely wire-free, housing all the necessary power management and environmental sensors in one unit.

Preparing the Sensor Dock Base

A technical overview of the sensor dock base, highlighting the integrated 18650 battery holder and the PCIe x1 expansion header. (Credit: Espressif Systems GitHub)

Step 2: Battery Installation for Portability

To achieve true portability, insert a high-quality 18650 Li-ion battery (2000-3000 mAh) into the holder located on the underside of the dock. This specific choice of hardware is what gives Desk32 its impressive 8-10 hour battery life. The onboard charging circuit allows you to keep the device plugged into USB-C while working, but the battery ensures that your Pomodoro cycle isn't broken if you need to move to a different room or a cafe.

CAUTION: Always double-check that the positive (+) and negative (-) terminals of your battery match the markings inside the sensor dock holder before switching the device on.

It was tested that

At 100% Brightness - 8 hours runtime

At 50% Brightness - 10 hours runtime

 

Step 3: Snap-Fit Module Integration

This is where the "magic" of the integrated architecture happens. Align the main ESP32-S3-BOX-3 display module with the expansion connector on the top of the sensor dock. Ensure the pins are centered and press down firmly but gently. This single "docking" action replaces dozens of jumper wires. This seamless connection is vital for maintaining the smooth UI experience we designed.

Snap Fit Module Integration

Step 4: Upload code using the instructions in Code explanation section
Step 5: Power-Up and Calibration

Once docked, toggle the physical power switch to the "ON" position. You should see the custom "Desk32" loading screen appear.

 

NOTE: If the device does not turn on instantly, don't worry this is a safety feature of the Power Management IC. Simply toggle the switch Off and then back On to turn on the device. (This happens when you are using a 18650 battery)

Code Explanation

1. Library Integration & System Porting

The code begins by including essential drivers for the display and touch interface. We utilize lvgl_v8_port.h to bridge the LVGL graphics library with the ESP32-S3's hardware.

You must also install the following libraries via the Library Manager with all dependencies:

LVGL (v8.3.x) (by Kisvegabor) - The core graphics engine. 
ArduinoJson (by Benoit Blanchon) - For parsing weather data. 
ESP Panel (by Espressif)- For hardware-specific display and touch drivers. 
NTP Client (by Fabrice Weinberg) - For updating time via NTP servers.


Also you need to have the following delete the lvgl, ui folders and lv_conf.h file located at Documents\Arduino\libraries\ and paste the lvgl, ui folders and lv_conf.h file from the github page (Link).

2. WiFi and Time Synchronization

The project relies on a stable internet connection to fetch real-time data. We use the WiFi.h and time.h libraries to connect to your local network and synchronize with NTP (Network Time Protocol) servers.

This ensures the Desk32 clock remains accurate to the second, which is also critical for the Pomodoro engine and the integrated calendar.

3. Real-Time Weather via JSON Parsing

To display environmental data, the device performs an HTTP GET request to the OpenWeatherMap API. We use ArduinoJson to parse the incoming data stream and extract temperature and humidity values.

The fetchWeather() function is executed every 15 minutes to prevent unnecessary network traffic and save battery power.

4. Dynamic Task Management

The Task List is highly interactive. When a user enters text, addTaskToUI() dynamically creates new LVGL objects (checkboxes and buttons) and attaches them to a scrollable container.

5. The Pomodoro Engine

The main loop monitors the pomoRunning state. If active, it decrements the timeLeft variable every 1000ms and updates the visual arc and labels.

Once the timer hits zero, the code automatically switches from "Focus Mode" to "Break Mode," cycling the totalTime between 25 and 5 minutes respectively.

Designing the UI with Squareline Studio

To bridge the gap between high-level design and embedded C code, I utilized SquareLine Studio, a professional visual drag-and-drop UI editor specifically optimized for LVGL. This tool allowed me to focus on the Design Thinking aspect of the project—ensuring the "Focus Anchor" felt intuitive and minimalist—without getting bogged down in the manual coordinate-mapping of hundreds of UI elements. Once the design was finalized, SquareLine Studio generated the highly optimized C files that drive the animations on the ESP32-S3-BOX-3. All these generated UI files, along with the main project logic, are available for review in the Desk32 GitHub Repository

Designing the UI with Squareline Studio

 

Future Improvements & Scalability

While the current version of Desk32 is a fully functional "Focus Anchor",  the ESP32-S3-BOX-3 architecture provides a massive amount of untapped potential.

The current Arduino IDE based setup is good for quick and rapid development but by using ESP-IDF more features can be implemented. The hardware is designed to scale, and I have several planned upgrades to further enhance the user experience:

  • Local Voice Control: Utilizing the integrated dual-microphone array and the ESP-SR framework, I aim to implement hands-free controls. This would allow users to start a Pomodoro session or add a task simply by speaking, keeping them in a "flow state" without needing to touch the screen.
  • SD-Card Persistence: To ensure that your goals aren't lost during a reboot, I plan to leverage the Micro-SD slot on the sensor dock. This will allow the Task List to be saved into a local CSV or JSON file, providing non-volatile storage that survives power cycles.
  • Context-Aware Environmental Logic: The onboard sensors for temperature and humidity offer a unique opportunity for indoor weather monitoring. Future firmware could automatically trigger a "Health Break" or "Ventilation Alert" if ambient air quality drops to levels that impair cognitive focus, truly making Desk32 an intelligent guardian of your workspace.

Conclusion

Building Desk32 was more than just a technical exercise; it was a personal mission to reclaim my focus. By moving away from a "distraction-first" workflow centered around smartphones and towards an "intent-first" workflow centered around this dedicated hardware anchor, I found a significant boost in my own productivity during my exam season. The physical presence of the device on my desk serves as a constant, silent reminder of my current goals.

This project is an Open Source Initiative released under the GPL v3 license. I believe that tools for self-improvement should be accessible to everyone. I invite fellow students, developers, and makers to visit the GitHub repository, and adapt Desk32 to their own unique study habits. Together, we can build better tools to navigate our increasingly distracted digital world.

 final assembled Desk32

GitHub Repository

Desk32 GitHub RepositoryDesk32 Download Zip File

Complete Project Code

/*
* Project Name: Desk32
* Designed For: ESP32 S3 BOX 3
*
*
* License: GPL3+
* This project is licensed under the GNU General Public License v3.0 or later.
* You are free to use, modify, and distribute this software under the terms
* of the GPL, as long as you preserve the original license and credit the original
* author. For more details, see <https://www.gnu.org/licenses/gpl-3.0.en.html&gt;.
*
* Copyright (C) 2026  Ameya Angadi
*
* Code Created And Maintained By: Ameya Angadi
* Last Modified On: February 10, 2026
* Version: 1.0.0
*
*/

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <time.h>
#include <esp_display_panel.hpp>
#include <lvgl.h>
#include <ui.h>
#include "lvgl_v8_port.h"
using namespace esp_panel::drivers;
using namespace esp_panel::board;
// --- USER CONFIGURATION (Update These) ---
const char* ssid = "";                             // Update: Your WiFi SSID
const char* password = "";                         // Update: Your WiFi Password
const char* apiKey = "";                           // Update: OpenWeatherMap API Key
const char* city = "";                             // Update: Your City
const char* country = "";                          // Update: Your Country Code
// Timezone Settings (India)
const long gmtOffset_sec = 19800;
const int daylightOffset_sec = 0;
// Global Timers and State
Board* board = nullptr;
unsigned long lastWeatherTime = 0;
const unsigned long weatherInterval = 900000; // 15 Minutes
// Weather Variables
String temperature = "--";
String humidity = "--";
// Pomodoro Globals
bool pomoRunning = false;
bool pomoPaused = false;
bool isBreakMode = false; 
int totalTime = 1500;     
int timeLeft = 1500;      
unsigned long lastPomoTick = 0;
const char* breakMessages[] = { "Good Job!", "Take a Walk", "Drink Water", "Meditate", "Stretch Now" };
// Keyboard and UI State Trackers
bool isKeyboardActive = false; 
int prevDay = -1; 
bool isWaterReminderEnabled = true; // Default to ON
unsigned long lastWaterAlertTime = 0;
const unsigned long waterAlertInterval = 1800000; // 30 Minutes
unsigned long lastWifiCheckTime = 0;
// Brightness Tracker (Default 100)
int currentBrightness = 100;
// --- EVENT HANDLERS ---
// Updates backlight level based on settings slider
void onBrightnessChange(lv_event_t * e) {
   lv_obj_t * slider = lv_event_get_target(e);
   int val = lv_slider_get_value(slider);
   
   // Ensure we don't go below 10 or above 100
   if (val < 10) val = 10;
   if (val > 100) val = 100;
   currentBrightness = val;
   board->getBacklight()->setBrightness(currentBrightness);
}
// Toggles the hydration alert system
void onWaterSwitchToggle(lv_event_t * e) {
   lv_obj_t * sw = lv_event_get_target(e);
   if (lv_obj_has_state(sw, LV_STATE_CHECKED)) {
       isWaterReminderEnabled = true;
       lastWaterAlertTime = millis(); 
       Serial.println("Water Reminder: ON");
   } else {
       isWaterReminderEnabled = false;
       Serial.println("Water Reminder: OFF");
   }
}
// Triggers a manual weather API fetch
void onUpdateWeatherClick(lv_event_t * e) {
   lv_obj_t * btn = lv_event_get_target(e);
   lv_obj_t * label = lv_obj_get_child(btn, 0); 
   fetchWeather(); 
}
// Forces a resync with NTP time servers
void onSyncTimeClick(lv_event_t * e) {
   lv_obj_t * btn = lv_event_get_target(e);
   lv_obj_t * label = lv_obj_get_child(btn, 0);
   configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org");
   delay(300); 
}
// Throttled UI update for WiFi signal/status
void updateSettingsScreenUI() {
   if (lv_scr_act() != ui_SettingScreen) return;
   if (millis() - lastWifiCheckTime > 2000) {
       lastWifiCheckTime = millis();
       String ssidStr = WiFi.SSID();
       if(ssidStr.isEmpty()) ssidStr = "No Network";
       lv_label_set_text(ui_LabelWifiSSID, ("SSID: " + ssidStr).c_str());
       if (WiFi.status() == WL_CONNECTED) {
           lv_label_set_text(ui_LabelWifiStatus, "Status: Connected");
           lv_obj_set_style_text_color(ui_LabelWifiStatus, lv_color_hex(0x00FF00), LV_PART_MAIN); 
       } else {
           lv_label_set_text(ui_LabelWifiStatus, "Status: Disconnected");
           lv_obj_set_style_text_color(ui_LabelWifiStatus, lv_color_hex(0xFF0000), LV_PART_MAIN); 
       }
   }
}
// Dismisses health alert and returns to Home
void onHydrateCloseClick(lv_event_t * e) {
   lv_scr_load_anim(ui_HomeScreen, LV_SCR_LOAD_ANIM_FADE_ON, 500, 0, false);
}
// Task Manager Handlers
static void textAreaFocusHandler(lv_event_t * e) {
   lv_event_code_t code = lv_event_get_code(e);
   if(code == LV_EVENT_FOCUSED || code == LV_EVENT_CLICKED) {
       isKeyboardActive = true;
       lv_obj_clear_flag(ui_Keyboard1, LV_OBJ_FLAG_HIDDEN);
   }
}
// Manages keyboard visibility
static void dismissKeyboardHandler(lv_event_t * e) {
   lv_event_code_t code = lv_event_get_code(e);
   if(code == LV_EVENT_CLICKED) {
       lv_obj_add_flag(ui_Keyboard1, LV_OBJ_FLAG_HIDDEN);
       lv_obj_clear_state(ui_TextAreaTaskInput, LV_STATE_FOCUSED);
       isKeyboardActive = false;
   }
}
// Handles task completion styling and keyboard conflict
static void taskToggleHandler(lv_event_t * e) {
   lv_event_code_t code = lv_event_get_code(e);
   lv_obj_t * cb = lv_event_get_target(e);
   if(code == LV_EVENT_VALUE_CHANGED) {
       if (isKeyboardActive) {
           lv_obj_add_flag(ui_Keyboard1, LV_OBJ_FLAG_HIDDEN);
           lv_obj_clear_state(ui_TextAreaTaskInput, LV_STATE_FOCUSED);
           isKeyboardActive = false;
           // Undo click
           if(lv_obj_has_state(cb, LV_STATE_CHECKED)) lv_obj_clear_state(cb, LV_STATE_CHECKED);
           else lv_obj_add_state(cb, LV_STATE_CHECKED);
           return; 
       }
       // Normal toggle style
       if(lv_obj_has_state(cb, LV_STATE_CHECKED)) {
            lv_obj_set_style_text_color(cb, lv_color_hex(0x4c4d52), LV_PART_MAIN);
            lv_obj_set_style_text_decor(cb, LV_TEXT_DECOR_STRIKETHROUGH, LV_PART_MAIN);
       } else {
            lv_obj_set_style_text_color(cb, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
            lv_obj_set_style_text_decor(cb, LV_TEXT_DECOR_NONE, LV_PART_MAIN);
       }
   }
}
// Deletes a task row from the list
static void deleteTaskHandler(lv_event_t * e) {
   lv_event_code_t code = lv_event_get_code(e);
   if(code == LV_EVENT_CLICKED) {
       if (isKeyboardActive) {
            lv_obj_add_flag(ui_Keyboard1, LV_OBJ_FLAG_HIDDEN);
            lv_obj_clear_state(ui_TextAreaTaskInput, LV_STATE_FOCUSED);
            isKeyboardActive = false;
       }
       lv_obj_t* row_container = (lv_obj_t*)lv_event_get_user_data(e);
       if(row_container) lv_obj_del(row_container); 
   }
}
// Dynamically creates a new task entry in the UI
void addTaskToUI(const char* text) {
   if (strlen(text) == 0) return; 
   lvgl_port_lock(-1);
   lv_obj_t* row = lv_obj_create(ui_ContainerTaskList);
   lv_obj_set_width(row, LV_PCT(100));          
   lv_obj_set_height(row, LV_SIZE_CONTENT);     
   lv_obj_set_style_pad_all(row, 2, 0);         
   lv_obj_set_style_border_width(row, 0, 0);    
   lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0); 
   lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
   lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
   lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
   lv_obj_add_event_cb(row, dismissKeyboardHandler, LV_EVENT_CLICKED, NULL);
   lv_obj_t* cb = lv_checkbox_create(row);
   lv_checkbox_set_text(cb, text);
   lv_obj_set_flex_grow(cb, 1); 
   lv_obj_set_style_text_font(cb, &ui_font_OswaldLight20, LV_PART_MAIN);
   lv_obj_set_style_width(cb, 20, LV_PART_INDICATOR); 
   lv_obj_set_style_height(cb, 20, LV_PART_INDICATOR);
   lv_obj_set_style_text_font(cb, &lv_font_montserrat_14, LV_PART_INDICATOR); 
   lv_obj_set_style_pad_column(cb, 5, LV_PART_MAIN); 
   lv_obj_set_style_pad_top(cb, 0, LV_PART_MAIN);
   lv_obj_set_style_pad_bottom(cb, 0, LV_PART_MAIN);
   lv_obj_add_event_cb(cb, taskToggleHandler, LV_EVENT_VALUE_CHANGED, NULL);
   lv_obj_t* btn_del = lv_btn_create(row);
   lv_obj_set_size(btn_del, 35, 35); 
   lv_obj_set_style_bg_opa(btn_del, LV_OPA_TRANSP, 0); 
   lv_obj_set_style_shadow_width(btn_del, 0, 0);       
   lv_obj_t* lbl = lv_label_create(btn_del);
   lv_label_set_text(lbl, "X");
   lv_obj_center(lbl);
   lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0); 
   lv_obj_set_style_text_color(lbl, lv_color_hex(0xFF0000), 0); 
   lv_obj_add_event_cb(btn_del, deleteTaskHandler, LV_EVENT_CLICKED, row);
   lvgl_port_unlock();
}
// Processes "Enter" key from the task keyboard
void onKeyboardReady(lv_event_t* e) {
   lv_obj_t* kb = lv_event_get_target(e);
   uint32_t code = lv_event_get_code(e);
   if (code == LV_EVENT_READY) { 
       const char* txt = lv_textarea_get_text(ui_TextAreaTaskInput);
       addTaskToUI(txt);
       lv_textarea_set_text(ui_TextAreaTaskInput, "");
       lv_obj_add_flag(ui_Keyboard1, LV_OBJ_FLAG_HIDDEN);
       lv_obj_clear_state(ui_TextAreaTaskInput, LV_STATE_FOCUSED);
       isKeyboardActive = false; 
   }
}
// --- POMODORO AND WEATHER CORE ---
// Updates Pomodoro countdown and progress arc
void updatePomoUI() {
   lvgl_port_lock(-1);
   char buf[10];
   sprintf(buf, "%02d:%02d", timeLeft / 60, timeLeft % 60);
   lv_label_set_text(ui_LabelPomoTime, buf);
   if (totalTime > 0) {
       int progress = (timeLeft * 100) / totalTime;
       lv_arc_set_value(ui_ArcPomo, progress); 
   }
   lvgl_port_unlock();
}
// Handles Pomodoro Start/Pause/Resume states
void startPomodoro(lv_event_t * e) {
   if (!pomoRunning) {
       pomoRunning = true;
       pomoPaused = false;
       lv_label_set_text(ui_LabelStart, "PAUSE");
       if (!isBreakMode) lv_label_set_text(ui_LabelPomoStatus, "Focus Mode");
   } else {
       pomoPaused = !pomoPaused;
       if (pomoPaused) {
           lv_label_set_text(ui_LabelStart, "RESUME");
           lv_label_set_text(ui_LabelPomoStatus, "Paused");
       } else {
           lv_label_set_text(ui_LabelStart, "PAUSE");
           lv_label_set_text(ui_LabelPomoStatus, isBreakMode ? "Relaxing..." : "Focus Mode");
       }
   }
}
// Resets Pomodoro to initial state
void stopPomodoro(lv_event_t * e) {
   pomoRunning = false;
   pomoPaused = false;
   isBreakMode = false; 
   totalTime = 1500; 
   timeLeft = 1500;
   lv_label_set_text(ui_LabelStart, "START");
   lv_label_set_text(ui_LabelPomoStatus, "Focus Mode");
   updatePomoUI();
}
// Fetches real-time JSON weather data from OpenWeatherMap
void fetchWeather() {
 if (WiFi.status() == WL_CONNECTED) {
   HTTPClient http;
   String url = "http://api.openweathermap.org/data/2.5/weather?q=" + String(city) + "," + String(country) + "&units=metric&appid=" + String(apiKey);
   http.begin(url);
   int httpResponseCode = http.GET();
   if (httpResponseCode == 200) {
     String payload = http.getString();
     DynamicJsonDocument doc(1024);
     DeserializationError error = deserializeJson(doc, payload);
     if (!error) {
       float temp_val = doc["main"]["temp"];
       int hum_val = doc["main"]["humidity"];
       char temp_buf[16];
       sprintf(temp_buf, "%.1f °C", temp_val); 
       temperature = String(temp_buf);
       humidity = String(hum_val) + " %";
       Serial.println("Weather Updated: " + temperature);
     }
   }
   http.end();
 }
}
// --- SYSTEM SETUP ---
void setup() {
 Serial.begin(115200);
 // Initialize Hardware
 board = new Board();
 board->init(); 
 assert(board->begin());
 lvgl_port_init(board->getLCD(), board->getTouch());
 lvgl_port_lock(-1);
 ui_init(); 
 
 // Dynamic Event Attachments
 lv_obj_add_event_cb(ui_Keyboard1, onKeyboardReady, LV_EVENT_READY, NULL);
 lv_obj_add_event_cb(ui_TextAreaTaskInput, textAreaFocusHandler, LV_EVENT_FOCUSED, NULL);
 lv_obj_add_event_cb(ui_TextAreaTaskInput, textAreaFocusHandler, LV_EVENT_CLICKED, NULL);
 lv_obj_add_flag(ui_ContainerTaskList, LV_OBJ_FLAG_CLICKABLE);
 lv_obj_add_event_cb(ui_ContainerTaskList, dismissKeyboardHandler, LV_EVENT_CLICKED, NULL);
 lv_obj_add_event_cb(lv_scr_act(), dismissKeyboardHandler, LV_EVENT_CLICKED, NULL);

 if (ui_SwitchWater) {
     lv_obj_add_state(ui_SwitchWater, LV_STATE_CHECKED);
     isWaterReminderEnabled = true;
 }
 if (ui_SliderBrightness) {
     lv_slider_set_range(ui_SliderBrightness, 10, 100);
     lv_slider_set_value(ui_SliderBrightness, 100, LV_ANIM_OFF);
     lv_obj_clear_flag(ui_SliderBrightness, LV_OBJ_FLAG_SCROLL_CHAIN);
     lv_obj_clear_flag(ui_SliderBrightness, LV_OBJ_FLAG_GESTURE_BUBBLE);
 }
 
 lv_task_handler(); 
 lvgl_port_unlock();
// Initialization: WiFi and Time
 WiFi.begin(ssid, password);
 Serial.print("Connecting");
 int retry = 0;
 while (WiFi.status() != WL_CONNECTED && retry < 10) { 
     delay(400); 
     Serial.print("."); 
     retry++;
 }
 Serial.println("\nConnected!");
 configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org");
 fetchWeather();
 lastWeatherTime = millis();
 lastWaterAlertTime = millis(); 
}
// --- MAIN LOOP ---
void loop() {
 struct tm timeinfo;
 // Pomodoro Tick Logic
 if (pomoRunning && !pomoPaused) {
     if (millis() - lastPomoTick > 1000) { 
         lastPomoTick = millis();
         if (timeLeft > 0) {
             timeLeft--;
             updatePomoUI();
         } else {
             if (!isBreakMode) {
                 isBreakMode = true;
                 totalTime = 300; 
                 timeLeft = 300;
                 int r = random(0, 5); 
                 lvgl_port_lock(-1);
                 lv_label_set_text(ui_LabelPomoStatus, breakMessages[r]);
                 lvgl_port_unlock();
             } else {
                 stopPomodoro(NULL); 
                 lvgl_port_lock(-1);
                 lv_label_set_text(ui_LabelPomoStatus, "Break Over!");
                 lvgl_port_unlock();
             }
         }
     }
 }
 // Background Weather and Time Updation Every 15 Minutes
 if (millis() - lastWeatherTime > weatherInterval) {
   if (WiFi.status() == WL_CONNECTED) {
      configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org");
      fetchWeather();
   }
   lastWeatherTime = millis(); 
 }
 // Water Reminder Logic
 if (isWaterReminderEnabled) {
     if (millis() - lastWaterAlertTime > waterAlertInterval) {
         lvgl_port_lock(-1);
         if (lv_scr_act() != ui_HydrateScreen) {
             lv_scr_load_anim(ui_HydrateScreen, LV_SCR_LOAD_ANIM_FADE_ON, 500, 0, false);
         }
         lvgl_port_unlock();
         lastWaterAlertTime = millis();
     }
 }
 // Settings Screen UI Updates
 if (lv_scr_act() == ui_SettingScreen) {
     lvgl_port_lock(-1);
     updateSettingsScreenUI();
     lvgl_port_unlock();
 }
 // Update Time
 if (getLocalTime(&timeinfo)) {
   char buf_time[6], buf_ampm[3], buf_day[4], buf_date[12];
   strftime(buf_time, sizeof(buf_time), "%I:%M", &timeinfo);
   strftime(buf_ampm, sizeof(buf_ampm), "%p", &timeinfo);
   strftime(buf_day, sizeof(buf_day), "%a", &timeinfo);
   strftime(buf_date, sizeof(buf_date), "%d %b %Y", &timeinfo);
   lvgl_port_lock(-1);
   lv_label_set_text(ui_LabelTime, buf_time);
   lv_label_set_text(ui_LabelDay, buf_day);
   lv_label_set_text(ui_LabelAMPM, buf_ampm);
   lv_label_set_text(ui_LabelDate, buf_date);
   
   if (timeinfo.tm_mday != prevDay) {
       prevDay = timeinfo.tm_mday; 
       lv_calendar_set_today_date(ui_Calendar1, timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday);
       lv_calendar_set_showed_date(ui_Calendar1, timeinfo.tm_year + 1900, timeinfo.tm_mon + 1);
   }
   lv_label_set_text(ui_LabelTemp, temperature.c_str());
   lv_label_set_text(ui_LabelHum, humidity.c_str());
   lvgl_port_unlock();
 }
 
 delay(200);
}
Video

Have any question related to this Article?

Add New Comment

Login to Comment Sign in with Google Log in with Facebook Sign in with GitHub