Skip to content

tahfimism/deskmate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

DeskMate

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.


✨ Features

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

πŸ”§ Hardware Requirements

Components

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) β€”

Wiring Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   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

πŸ—οΈ System Architecture

State Machine Design

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:

  1. Memory Efficiency: Only active app state is maintained
  2. Clean Transitions: Explicit init/exit handlers prevent resource leaks
  3. Predictable Behavior: Each state has isolated logic
  4. 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

Application Lifecycle

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).

Main Loop Structure

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

πŸ“¦ Application Modules

Menu Application (APP_MENU)

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

Temperature Application (APP_TEMP)

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 of delay() to maintain UI responsiveness

Controls:

Button Action
LONG SELECT Return to menu

Pomodoro Timer (APP_POMODORO)

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;
}

Breathing Exercise (APP_BREATHING)

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

Sonar Distance (APP_SONAR)

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

Idle Screensaver (APP_IDLE)

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 Reference

Core System Functions

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

Menu Functions

Function Description
menuInit() Reset selection to first item
menuUpdate() Handle navigation input
menuDraw() Render scrollable list

Temperature Functions

Function Description
tempInit() Reset read timer
tempUpdate() Read DHT sensor (2s intervals)
tempDraw() Display temperature and humidity

Pomodoro Functions

Function Description
pomodoroInit() Reset timer and session count
pomodoroUpdate() Handle timing and phase transitions
pomodoroDraw() Render countdown and status

Breathing Functions

Function Description
breathingInit() Reset to default mode
breathingUpdate() Manage phases and audio cues
breathingDraw() Display phase and countdown
loadBreathTimings() Load timing for selected mode

Sonar Functions

Function Description
sonarInit() Configure sensor pins
sonarUpdate() Read distance (120ms intervals)
sonarDraw() Display distance and bar graph

Idle Functions

Function Description
idleInit() Reset animation state
idleUpdate() Handle blink animation
idleDraw() Render cat face

Settings Functions

Function Description
settingsInit() Initialize settings state
settingsUpdate() Handle option selection
settingsDraw() Render settings UI

🎯 Design Rationale

Why Arduino?

  • 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

Why Standalone (No WiFi/Bluetooth)?

  • Privacy: No data transmitted externally
  • Reliability: No network dependencies
  • Simplicity: Fewer failure points
  • Focus: The device itself promotes focus, no distracting notifications

Why Physical Buttons?

  • 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

Why Single-Frame Rendering?

// 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

πŸš€ Getting Started

Prerequisites

  1. Arduino IDE (1.8.x or 2.x)
  2. Required Libraries (install via Library Manager):
    • Adafruit GFX Library
    • Adafruit SSD1306
    • DHT sensor library (by Adafruit)

Installation

  1. Clone or download this repository
  2. Open deskmate.ino in Arduino IDE
  3. Install required libraries if prompted
  4. Connect Arduino via USB
  5. Select correct board and port
  6. Click Upload

First Run

  1. Device starts in idle mode (cat screensaver)
  2. Press any button to enter menu
  3. Use UP/DOWN to navigate, SELECT to choose
  4. Long-press SELECT returns to menu from any app

πŸ“ Extending DeskMate

To add a new application:

  1. Add state to enum:

    enum AppState {
      // ... existing states
      APP_MY_NEW_APP
    };
  2. Create lifecycle functions:

    void myNewAppInit() { /* setup */ }
    void myNewAppUpdate() { /* logic */ }
    void myNewAppDraw() { /* render */ }
  3. Register in routers:

    void appInit(AppState app) {
      switch (app) {
        // ... existing cases
        case APP_MY_NEW_APP: myNewAppInit(); break;
      }
    }
    // Same for updateCurrentApp() and drawCurrentApp()
  4. Add menu entry:

    const char* menuItems[] = {
      // ... existing items
      "My New App"
    };
    // Update MENU_COUNT and menuUpdate() switch

πŸ“„ License

This project is open source. Feel free to use, modify, and distribute.


πŸ™ Acknowledgments

  • Adafruit for the excellent display and sensor libraries
  • Arduino Community for extensive documentation and support
  • Pomodoro Technique by Francesco Cirillo

About

Arduino desk utility sysyem

Resources

Stars

Watchers

Forks

Releases

No releases published