Arduino-based Multifunctional Desk Assistant for Productivity, Wellness, and Environmental Awareness
DeskMate is a standalone, Arduino-powered device that integrates multiple utilities into a single compact system. Operating independently without computer or smartphone connectivity, it provides essential desk productivity tools through tactile buttons and a crisp OLED display.
| Feature | Description |
|---|---|
| π Pomodoro Timer | Classic 25/5/15 work-break cycles with session tracking and pause/resume |
| π‘οΈ Environment Monitor | Real-time temperature and humidity display via DHT11 sensor |
| π Distance Sensor | Ultrasonic proximity measurement with visual bar graph |
| π§ Breathing Exercises | Three guided patterns: Calm (4-2-4), Focus (4-4), Sleep (4-6-8) |
| πΊ Idle Screensaver | Animated cat face to prevent OLED burn-in |
| βοΈ Settings | Configurable idle timeout |
| Component | Specification | Purpose |
|---|---|---|
| Arduino Board | Uno/Nano or compatible | Main microcontroller |
| OLED Display | SSD1306 128Γ64, I2C | Visual interface |
| Temperature Sensor | DHT11 | Temperature and humidity |
| Ultrasonic Sensor | HC-SR04 | Distance measurement |
| Push Buttons | 3Γ Tactile switches | User input |
| Buzzer | Passive, 5V | Audio feedback |
| Resistors | Optional (internal pullups used) | β |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ARDUINO UNO β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β D2 βββββ BTN_UP (to GND when pressed) β
β D3 βββββ BTN_DOWN (to GND when pressed) β
β D4 βββββ BTN_SELECT (to GND when pressed) β
β D5 βββββ BUZZER_PIN (passive buzzer +) β
β D6 βββββ DHT_PIN (DHT11 data) β
β D7 βββββ SONAR_TRIG (HC-SR04 trigger) β
β D8 βββββ SONAR_ECHO (HC-SR04 echo) β
β β
β A4 βββββ SDA (OLED I2C data) β
β A5 βββββ SCL (OLED I2C clock) β
β β
β 5V βββββ VCC for all sensors β
β GND βββββ Common ground β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Pin | Connection | Notes |
|---|---|---|
| D2 | UP Button | Internal pullup, connect other leg to GND |
| D3 | DOWN Button | Internal pullup, connect other leg to GND |
| D4 | SELECT Button | Internal pullup, connect other leg to GND |
| D5 | Buzzer (+) | Negative to GND |
| D6 | DHT11 Data | VCC=5V, GND=GND |
| D7 | HC-SR04 Trig | Trigger signal output |
| D8 | HC-SR04 Echo | Echo signal input |
| A4 | OLED SDA | I2C data line |
| A5 | OLED SCL | I2C clock line |
DeskMate operates as a finite state machine where exactly one application (state) is active at any time. This design pattern was chosen for several reasons:
- Memory Efficiency: Only active app state is maintained
- Clean Transitions: Explicit init/exit handlers prevent resource leaks
- Predictable Behavior: Each state has isolated logic
- Easy Extension: New apps follow the same template
ββββββββββββββββββββββββββββββββββββ
β APP_IDLE β
β (Screensaver - Cat Face) β
βββββββββββββββββ¬βββββββββββββββββββ
β any button
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APP_MENU β
β (Navigation Hub) β
β ββββββββββββββ¬βββββββββββββ¬βββββββββββββ¬βββββββββββββ β
β β Temperatureβ Pomodoro β Sonar β Breathing β β
β ββββββββ¬ββββββ΄ββββββ¬βββββββ΄ββββββ¬βββββββ΄ββββββ¬βββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β
βΌ βΌ βΌ βΌ
ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ
β APP_TEMP β βAPP_POMO- β βAPP_SONAR β βAPP_BREA- β
β β β DORO β β β β THING β
ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ
β β β β
ββββββββββββββ΄βββββββββββββ΄βββββββββββββ
β
long press SELECT
β
βΌ
APP_MENU
Every application follows a consistent three-function pattern:
void appInit(); // Called when entering the app
void appUpdate(); // Called every loop iteration
void appDraw(); // Called every loop iteration (after update)Additionally, appExit() handles cleanup (e.g., silencing buzzer).
void loop() {
readInputs(); // 1. Process button states
handleIdle(); // 2. Check idle timeout
updateCurrentApp(); // 3. Run app logic
drawCurrentApp(); // 4. Render to buffer
display.display(); // 5. Push to screen (single call)
}Why single display.display() call?
- Prevents screen flickering
- Eliminates partial frame rendering
- Consistent 60+ FPS appearance
Purpose: Central navigation hub for accessing all features.
Features:
- Scrollable list with 5 visible items
- Inverted highlight for selected item
- Auto-idle after timeout (prevents OLED burn-in)
Controls:
| Button | Action |
|---|---|
| UP | Move selection up |
| DOWN | Move selection down |
| SELECT | Open selected app |
Purpose: Display real-time environmental conditions.
Features:
- Large centered temperature display
- Humidity percentage below
- Error handling for sensor failures
Design Decisions:
- 2-second read interval: DHT11 requires minimum 1s between reads for accuracy; 2s provides margin
- Non-blocking reads: Uses
millis()timing instead ofdelay()to maintain UI responsiveness
Controls:
| Button | Action |
|---|---|
| LONG SELECT | Return to menu |
Purpose: Implement the Pomodoro Technique for focused work sessions.
Timing:
| Phase | Duration |
|---|---|
| Work | 25 minutes |
| Short Break | 5 minutes |
| Long Break | 15 minutes (every 4th session) |
Features:
- Pause/resume functionality
- Session counter
- Audio alert on phase transitions
- Persistent elapsed time during pause
Controls:
| Button | Action |
|---|---|
| SELECT | Start / Pause / Resume |
| LONG SELECT | Exit to menu |
Implementation Notes:
// Pause stores elapsed time, resume calculates new start point
if (pomoRunning) {
pomoElapsedBeforePause = now - pomoPhaseStart;
pomoRunning = false;
} else {
pomoPhaseStart = now - pomoElapsedBeforePause;
pomoRunning = true;
}Purpose: Guided breathing for relaxation, focus, or sleep preparation.
Modes:
| Mode | Pattern | Use Case |
|---|---|---|
| Calm | 4-2-4 | General relaxation |
| Focus | 4-0-4 | Maintain alertness |
| Sleep | 4-6-8 | Pre-sleep wind-down |
Pattern notation: Inhale-Hold-Exhale in seconds
Features:
- Visual countdown timer
- Audio cue on phase transitions
- Mode selection while paused
Controls:
| Button | Action |
|---|---|
| UP/DOWN | Change breathing mode |
| SELECT | Start / Pause |
| LONG SELECT | Exit to menu |
Purpose: Measure object distance using ultrasonic sensor.
Features:
- Range: 2cm to 400cm
- Visual bar graph (closer = longer bar)
- Invalid reading detection
Design Decisions:
- 120ms read interval: Prevents echo interference from previous pulses
- 30ms pulse timeout: Limits maximum range to ~5m, prevents indefinite blocking
Controls:
| Button | Action |
|---|---|
| LONG SELECT | Return to menu |
Purpose: Animated screensaver preventing OLED burn-in.
Features:
- Cute cat face design
- Blinking eye animation (1.5s interval)
- Any button press returns to menu
Why a cat?
- Simple geometry = fast rendering
- Moving elements prevent pixel burn-in
- Adds personality and delight
| Function | Parameters | Returns | Description |
|---|---|---|---|
setup() |
β | void | Hardware initialization |
loop() |
β | void | Main application loop |
readInputs() |
β | void | Edge-detected button processing |
switchApp(next) |
AppState | void | State machine transition |
appInit(app) |
AppState | void | Routes to app init function |
updateCurrentApp() |
β | void | Routes to app update function |
drawCurrentApp() |
β | void | Routes to app draw function |
appExit(app) |
AppState | void | Cleanup handler |
handleIdle() |
β | void | Idle timeout detection |
hapticBeep() |
β | void | Button press audio feedback |
| Function | Description |
|---|---|
menuInit() |
Reset selection to first item |
menuUpdate() |
Handle navigation input |
menuDraw() |
Render scrollable list |
| Function | Description |
|---|---|
tempInit() |
Reset read timer |
tempUpdate() |
Read DHT sensor (2s intervals) |
tempDraw() |
Display temperature and humidity |
| Function | Description |
|---|---|
pomodoroInit() |
Reset timer and session count |
pomodoroUpdate() |
Handle timing and phase transitions |
pomodoroDraw() |
Render countdown and status |
| Function | Description |
|---|---|
breathingInit() |
Reset to default mode |
breathingUpdate() |
Manage phases and audio cues |
breathingDraw() |
Display phase and countdown |
loadBreathTimings() |
Load timing for selected mode |
| Function | Description |
|---|---|
sonarInit() |
Configure sensor pins |
sonarUpdate() |
Read distance (120ms intervals) |
sonarDraw() |
Display distance and bar graph |
| Function | Description |
|---|---|
idleInit() |
Reset animation state |
idleUpdate() |
Handle blink animation |
idleDraw() |
Render cat face |
| Function | Description |
|---|---|
settingsInit() |
Initialize settings state |
settingsUpdate() |
Handle option selection |
settingsDraw() |
Render settings UI |
- Simplicity: Direct hardware access without OS overhead
- Reliability: No boot time, instant-on operation
- Power Efficiency: Low power consumption for desk use
- Accessibility: Well-documented platform for makers
- Privacy: No data transmitted externally
- Reliability: No network dependencies
- Simplicity: Fewer failure points
- Focus: The device itself promotes focus, no distracting notifications
- Tactile Feedback: Satisfying click confirms input
- Eyes-Free Operation: Muscle memory for common actions
- Durability: No touchscreen to crack or calibrate
- Simplicity: Three buttons cover all interactions
// Bad: Multiple display.display() calls cause flicker
drawHeader();
display.display(); // Partial frame visible!
drawContent();
display.display(); // Another update!
// Good: Single display.display() at end of loop
display.clearDisplay();
drawEverything(); // Render to buffer
display.display(); // One atomic update- Arduino IDE (1.8.x or 2.x)
- Required Libraries (install via Library Manager):
Adafruit GFX LibraryAdafruit SSD1306DHT sensor library(by Adafruit)
- Clone or download this repository
- Open
deskmate.inoin Arduino IDE - Install required libraries if prompted
- Connect Arduino via USB
- Select correct board and port
- Click Upload
- Device starts in idle mode (cat screensaver)
- Press any button to enter menu
- Use UP/DOWN to navigate, SELECT to choose
- Long-press SELECT returns to menu from any app
To add a new application:
-
Add state to enum:
enum AppState { // ... existing states APP_MY_NEW_APP };
-
Create lifecycle functions:
void myNewAppInit() { /* setup */ } void myNewAppUpdate() { /* logic */ } void myNewAppDraw() { /* render */ }
-
Register in routers:
void appInit(AppState app) { switch (app) { // ... existing cases case APP_MY_NEW_APP: myNewAppInit(); break; } } // Same for updateCurrentApp() and drawCurrentApp()
-
Add menu entry:
const char* menuItems[] = { // ... existing items "My New App" }; // Update MENU_COUNT and menuUpdate() switch
This project is open source. Feel free to use, modify, and distribute.
- Adafruit for the excellent display and sensor libraries
- Arduino Community for extensive documentation and support
- Pomodoro Technique by Francesco Cirillo