A smart desk display system powered by ESP32 and a 128x64 OLED display, controlled via a modern web dashboard. Display time, weather (with AQI), custom text, images, animated GIFs, and scrolling marquees on your desk companion.
- Time Display β Real-time clock with configurable timezone
- Spotify Integration β Display currently playing song and artist with album art support
- Pomodoro Timer β Productivity timer with work/break intervals
- QR Codes β Generate and display QR codes for any text/URL
- Advanced Clocks β Binary (BCD), Analog, and Word Clock faces
- Moon Phase β Real-time moon phase tracking
- Weather Widget β Live weather data from Open-Meteo API with Air Quality Index (AQI), PM2.5, and PM10 readings
- Uptime Tracker β Server uptime monitoring
- Custom Text β Display custom messages (normal, centered, or framed styles)
- Marquee/Scrolling Text β Animated scrolling text with configurable speed, direction, and local ESP32 playback
- Image Upload β Upload PNG, JPG, or GIF files (auto-converted to 1-bit for OLED)
- GIF Animations β Full animated GIF support with local ESP32 playback (no network lag)
- Display Cycle β Customizable rotation of widgets with drag-and-drop ordering
- RGB LED Beacon β Satellite-style status indicator with configurable brightness
- Password Protection β Optional dashboard authentication with rate limiting
- Responsive UI β Modern tabbed interface optimized for desktop and mobile
The system follows a "Backend as Brain, ESP32 as GPU" philosophy β the ESP32 never decides what to show, only how to render the instructions it receives.
βββββββββββββββββββββββ HTTP/JSON βββββββββββββββββββββββ
β Web Dashboard β βββββββββββββββββββΊ β Go Backend β
β (Browser) β β (main.go) β
βββββββββββββββββββββββ ββββββββββββ¬βββββββββββ
β
β HTTP API
β β’ Polling mode (frame/next)
β β’ Local playback mode (gif/full)
βΌ
βββββββββββββββββββββββ
β ESP32 + OLED β
β (main.ino) β
β + RGB LED Beacon β
βββββββββββββββββββββββ
| Mode | Description | Use Case |
|---|---|---|
| Polling Mode | ESP32 fetches /frame/next repeatedly |
Time, weather, uptime, static content |
| Local Playback Mode | ESP32 downloads all frames via /api/gif/full and plays locally |
GIFs, marquees β eliminates network lag |
The ESP32 automatically switches between modes based on server hints (isGifMode field).
esp_desk/
βββ main.go # Go backend (API, image processing, frame generation)
βββ main.ino # ESP32 Arduino firmware (display, WiFi, local playback)
βββ spotify.go # Spotify integration
βββ pomodoro.go # Pomodoro timer logic
βββ qrcode.go # QR code generation
βββ bcd.go # Binary Clock Display logic
βββ analog.go # Analog clock logic
βββ wordclock.go # Word Clock display logic
βββ moonphase.go # Moon phase calculation
βββ weather.go # Weather API handling
βββ background.go # Background tasks and polling
βββ config.json # Persisted settings (auto-generated)
βββ static/
β βββ index.html # Web dashboard UI (tabbed layout)
β βββ css/
β β βββ style.css # Dashboard styling (modern minimal theme)
β βββ js/
β βββ app.js # Main app initialization
β βββ api.js # Backend API communication
β βββ auth.js # Authentication handling
β βββ autoplay.js # Auto-play cycle control
β βββ controls.js # UI control handlers
β βββ cycle.js # Display cycle management
β βββ render.js # OLED preview rendering
β βββ texthelper.js # Text input utilities
β βββ upload.js # Image/GIF upload handling
β βββ utils.js # Shared utilities
β βββ weather.js # Weather display logic
βββ .env.example # Environment configuration template
βββ render.yaml # Render.com deployment configuration
βββ rules.md # Development guidelines
βββ go.mod # Go module definition
- Go 1.21+ β Backend server
- ESP32 with SSD1306 OLED (128x64) and optional RGB LED
- Arduino IDE with ESP32, ArduinoJson, and Adafruit SSD1306 libraries
# Clone the repository
git clone https://github.com/boredom1234/esp_desk.git
cd esp_desk
# Configure environment
cp .env.example .env
# Edit .env and set DASHBOARD_PASSWORD
# Run the server
go run main.goThe dashboard will be available at http://localhost:3000
- Open
main.inoin Arduino IDE - Update the WiFi credentials and backend URL:
const char* ssid = "Your-WiFi-SSID"; const char* password = "Your-WiFi-Password"; const char* FRAME_CURRENT_URL = "https://your-server.com/frame/current"; const char* FRAME_NEXT_URL = "https://your-server.com/frame/next"; const char* GIF_FULL_URL = "https://your-server.com/api/gif/full";
- Upload to your ESP32
ESP32 SSD1306 OLED (128x64)
βββββββββββ βββββββββββββββββββ
β 3V3 βββββββββββββββββββ€ VCC β
β GND βββββββββββββββββββ€ GND β
β GPIO 21 βββββββββββββββββββ€ SDA (Data) β
β GPIO 22 βββββββββββββββββββ€ SCL (Clock) β
βββββββββββ βββββββββββββββββββ
ESP32 RGB LED (Common Cathode)
βββββββββββ βββββββββββββββββββ
β GPIO 25 βββ[220Ξ©]ββββββββββ€ Red β
β GPIO 26 βββ[220Ξ©]ββββββββββ€ Green β
β GPIO 27 βββ[220Ξ©]ββββββββββ€ Blue β
β GND βββββββββββββββββββ€ Common GND β
βββββββββββ βββββββββββββββββββ
| ESP32 Pin | Component | Description |
|---|---|---|
| 3V3 | OLED VCC | Power (3.3V) |
| GND | OLED GND, LED GND | Ground |
| GPIO 21 | OLED SDA | I2C Data |
| GPIO 22 | OLED SCL | I2C Clock |
| GPIO 2 | Built-in LED | Status indicator (blinks during fetch) |
| GPIO 25 | RGB Red | RGB beacon (through 220Ξ© resistor) |
| GPIO 26 | RGB Green | RGB beacon (through 220Ξ© resistor) |
| GPIO 27 | RGB Blue | RGB beacon (through 220Ξ© resistor) |
Note: Default I2C address is
0x3C. UpdateOLED_ADDRESSinmain.inoif different.
| Color | State |
|---|---|
| Blue | Idle/standby |
| Orange | Fetching data |
| Green | Data loaded successfully |
| Red | Error |
| Purple | Animation playing |
| Cyan | WiFi connecting |
| Endpoint | Method | Description |
|---|---|---|
/frame/current |
GET | Get current display frame (initial boot) |
/frame/next |
GET | Advance to next frame in cycle (polling mode) |
/api/gif/full |
GET | Download all GIF/marquee frames (local playback mode) |
| Endpoint | Method | Description |
|---|---|---|
/api/settings |
GET/POST | Read/update display settings, cycle items, LED beacon |
/api/text |
POST | Display styled text (normal/centered/framed) |
/api/marquee |
POST | Start scrolling text animation (local playback) |
/api/custom |
POST | Display custom bitmap or text |
/api/upload |
POST | Upload image/GIF (auto-converts to 1-bit) |
/api/weather |
GET/POST | Get weather data / change city |
/api/timezone |
POST | Set display timezone |
/api/reset |
POST | Reset all settings to defaults |
| Endpoint | Method | Description |
|---|---|---|
/api/auth/status |
GET | Check if authentication is required |
/api/auth/login |
POST | Authenticate with dashboard password |
/api/auth/logout |
POST | Invalidate session token |
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
DASHBOARD_PASSWORD |
β | Dashboard access password (optional) |
SPOTIFY_CLIENT_ID |
β | Spotify Client ID (for music integration) |
SPOTIFY_CLIENT_SECRET |
β | Spotify Client Secret |
The following settings are automatically saved and restored:
- Display cycle items and order
- Auto-play state and frame duration
- ESP32 refresh interval
- Weather city and coordinates
- Timezone
- Display rotation
- LED beacon settings (brightness, enabled)
- Header visibility
Deploy to Render.com using the included render.yaml:
services:
- type: web
name: esp-desk
env: go
buildCommand: go build -tags netgo -ldflags '-s -w' -o app .
startCommand: ./appBackend (Go):
- Standard library only (no external dependencies)
github.com/skip2/go-qrcode
ESP32 (Arduino):
WiFi.h/WiFiClientSecure.hHTTPClient.hArduinoJson.hAdafruit_GFX.hAdafruit_SSD1306.h
See rules.md for the complete development philosophy. Key principles:
- Backend = Brain, ESP32 = GPU β ESP32 never decides what to show
- Stateless ESP32 β Can reboot anytime, backend stays in control
- Stable JSON Contract β Add fields, never rename/remove
- Safe Defaults β ESP32 provides fallbacks for all values
- One Feature at a Time β Backend β cURL test β ESP32 β UI
MIT License β Feel free to use, modify, and distribute.