A battery-powered ESP32 e-ink display that fetches images from a local Raspberry Pi server with intelligent power management and remote control.
๐ Try the Live Demo - Interactive web interface preview with mock data
๐ Ultra-Low Power - Months of battery life with deep sleep cycles ๐จ 6-Color Display - Beautiful Spectra 6 e-paper technology ๐ก OTA Updates - Wireless firmware updates with automatic rollback protection ๐ค AI Art Generation - GPT-4o powered artwork optimized for e-ink displays ๐ Semantic Visual Search - CLIP-powered search by vibe, not keywords โจ Visual Similarity - "More like this" finds artworks with 0.73-0.84 similarity ๐ฏ Personalized Recommendations - Taste profile learns from your interactions ๐ผ๏ธ Personal Gallery - Unified collection of generated, uploaded, and saved artworks ๐ฑ Minimal Web Interface - Clean, calm design for creating and exploring art ๐ Local First - Runs on your Raspberry Pi, no cloud dependencies ๐ณ Docker Ready - Easy server deployment with published Docker images
โโโโโโโโโโโโโโโโโโโ WiFi โโโโโโโโโโโโโโโโโโโ
โ โโโโโโโโโโโโโโบโ โ
โ ESP32 โ โ Raspberry Pi โ
โ (Display) โ HTTP โ (Server) โ
โ โ โ โ
โโโโโโโโโโโฌโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ SPI โ
โผ โ Port 3000
โโโโโโโโโโโโโโโโโโโ โผ
โ Waveshare โ โโโโโโโโโโโโโโโโโโโ
โ 13.3" E-Ink โ โ Web Dashboard โ
โ Spectra 6 โ โ โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
How it works:
- ESP32 wakes from deep sleep every 1-6 hours
- Connects to WiFi and fetches current image from Raspberry Pi server
- Updates the e-ink display with new content
- Reports battery status and device health
- Enters deep sleep until next scheduled wake-up
| Component | Model | Purpose |
|---|---|---|
| Controller | Good Display ESP32-133C02 | ESP32-S3 with QSPI interface |
| Display | Waveshare 13.3" Spectra 6 | 1200ร1600 6-color e-paper |
| Server | Raspberry Pi 3/4/Zero 2W | Local server hosting |
| Battery | LiPo 12,000mAh (PiJuice) | Months of battery life |
| Power Mgmt | LiPo Amigo Pro + MiniBoost | Charging + 5V boost |
| GPIO | Function | Notes |
|---|---|---|
| 18 | SPI_CS0 | Chip select 0 |
| 17 | SPI_CS1 | Chip select 1 |
| 9 | SPI_CLK | SPI clock |
| 41 | SPI_Data0 | QSPI data line 0 |
| 40 | SPI_Data1 | QSPI data line 1 |
| 39 | SPI_Data2 | QSPI data line 2 |
| 38 | SPI_Data3 | QSPI data line 3 |
| 7 | EPD_BUSY | Display busy signal (input) |
| 6 | EPD_RST | Display reset (output) |
| 45 | LOAD_SW | Load switch control |
| 2 | BATTERY | Battery ADC via voltage divider |
Option A: One-command deploy
./deploy-to-pi.sh serverpi.local your-dockerhub-usernameOption B: Manual Docker deploy
# On your Raspberry Pi
docker run -d \
--name glance-server \
-p 3000:3000 \
-v $(pwd)/data:/app/data \
--restart unless-stopped \
your-username/glance-server:latestAccess the dashboard at: http://your-pi-ip:3000
New to Glance? Try the interactive demo to explore the interface with mock data.
Three Simple Modes:
- Create - Generate AI artwork or upload images
- Explore - AI-powered search across 8 museum sources with 2+ million artworks
- My Collection - View all your generated, uploaded, and saved artworks
Museum Sources: Met Museum, Art Institute of Chicago, Cleveland Museum, Rijksmuseum, Wikimedia Commons, Victoria & Albert Museum, Harvard Art Museums*, Smithsonian* (*API key required)
The interface follows calm design principles - minimalistic and distraction-free.
# Clone repository
git clone https://github.com/your-username/Glance.git
cd Glance/esp32-client/
# Set WiFi credentials
export WIFI_SSID="YourWiFiNetwork"
export WIFI_PASSWORD="YourWiFiPassword"
export DEVICE_ID="esp32-001" # Optional: unique device ID
# Build and upload firmware
./build.sh- Connect LiPo battery to ESP32 BAT pin
- Verify display shows test pattern or fetched image
- Check web dashboard shows device as "online"
- System now runs autonomously!
graph LR
A[๐ด Deep Sleep<br/>~10ฮผA] --> B[โฐ Wake Up<br/>RTC Timer]
B --> C[๐ถ WiFi Connect<br/>2-3 seconds]
C --> D[๐ฅ Check Commands<br/>Remote control]
D --> E[๐ท Fetch Image<br/>From server API]
E --> F[๐ผ๏ธ Update Display<br/>30-45 seconds]
F --> G[๐ Report Status<br/>Battery, signal]
G --> H[๐ค Calculate Sleep<br/>Server controlled]
H --> A
The ESP32 communicates with these server endpoints:
GET /api/current.json- Fetch current image and sleep durationPOST /api/device-status- Report device health (battery, signal, firmware version)GET /api/firmware/version- Check for available firmware updatesGET /api/firmware/download- Download firmware binary for OTA updatePOST /api/logs- Send device logs to serverGET /api/commands/:deviceId- Check for pending remote commands
Send commands to sleeping devices via web dashboard:
- ๐ฑ Stay Awake - Keep device active for 5 minutes for debugging
- ๐ Force Update - Trigger immediate display refresh
- ๐ View Logs - Real-time ESP32 serial output
- ๐ Battery Monitor - Track voltage, charging status, and battery history
- ๐ก OTA Updates - Deploy firmware updates wirelessly with automatic rollback
- ๐ Device Stats - Monitor wake cycles, display updates, brownouts, and firmware version
Glance/
โโโ README.md # This file
โโโ CLAUDE.md # Claude Code instructions
โโโ docker-compose.yml # Local development
โโโ deploy-to-pi.sh # Raspberry Pi deployment script
โ
โโโ docs/ # ๐ Documentation
โ โโโ DESIGN.md # Design principles & philosophy
โ โโโ SECURITY.md # Security guidelines
โ โโโ DEPLOYMENT.md # Deployment instructions
โ โโโ HARDWARE.md # Hardware documentation
โ โโโ PROJECT_GOALS.md # Project goals & roadmap
โ
โโโ esp32-client/ # ๐ง ESP32 Firmware
โ โโโ gooddisplay-clean/ # Production firmware (Good Display board)
โ โ โโโ src/
โ โ โ โโโ main.c # Main application with battery monitoring
โ โ โ โโโ ota.c/h # OTA firmware update system
โ โ โ โโโ server_config.h # Shared server configuration
โ โ โ โโโ GDEP133C02.c/h # E-ink display driver
โ โ โโโ platformio.ini # Build config with firmware version
โ โโโ lib/epd/ # E-ink display drivers
โ โโโ build.sh # Build & upload script
โ
โโโ server/ # ๐ฅ๏ธ Node.js Server
โ โโโ server.js # Express.js entry point
โ โโโ routes/ # API route handlers (9 modules)
โ โโโ services/ # Business logic (7 modules)
โ โโโ utils/ # Shared utilities (3 modules)
โ โโโ public/ # Web interface
โ โ โโโ index.html # Main template
โ โ โโโ css/ # Stylesheets
โ โ โโโ js/ # Modular JavaScript
โ โโโ __tests__/ # Test suite (188 tests)
โ โโโ Dockerfile # Container build
โ
โโโ hardware/ # ๐ PCB Design (KiCad)
โ โโโ Glance.kicad_* # Circuit board files
โ
โโโ scripts/ # ๐ Automation
โโโ build-and-push.sh # Docker Hub publishing
โโโ update-glance.sh # Update helper
- ๐ 6-Color Optimization - Automatic conversion to Spectra 6 palette (black, white, yellow, red, blue, green)
- โจ Floyd-Steinberg Dithering - Professional quality color mapping for art reproduction
- ๐ฆ Server-Side Processing - Reduces ESP32 memory usage
- โ๏ธ Auto-Crop - Removes whitespace margins from AI-generated images
- ๐ Contrast Enhancement - Optimizes images for e-ink characteristics
- ๐ Rotation Support - 0ยฐ, 90ยฐ, 180ยฐ, or 270ยฐ rotation
- ๐บ Full Resolution - 1150ร1550 pixels (93% screen coverage)
- ๐ค GPT-4o Integration - Generate custom artwork via OpenAI image models
- ๐ Smart Search - Natural language queries interpreted by AI ("peaceful blue paintings")
- ๐๏ธ 8 Museum Sources - Met, ARTIC, Cleveland, Rijksmuseum, Wikimedia, V&A, Harvard*, Smithsonian*
- ๐ 2M+ Artworks - Search across millions of public domain artworks simultaneously
- ๐ Prompt History - View the original prompt used to generate any artwork
- ๐จ E-Ink Optimized - AI prompts emphasize full-bleed, high-contrast compositions
- ๐พ Personal Collection - Unified view of generated, uploaded, and saved artworks
- ๐ฏ Search by Vibe - "peaceful blue water" finds Japanese winter landscapes, not keyword matching
- โจ Visual Similarity - "More like this" finds artworks with 0.73-0.84 similarity accuracy
- ๐ง Personalized Recommendations - Taste profile learns from your interactions (likes weighted 2x)
- โก Local & Fast - CLIP ViT-B/32 runs locally (~600MB cached), ~200ms searches, no API costs
- ๐ Scalable Collection - Start with 105 artworks, easily scale to 1000-5000
Scale Up Your Collection:
cd server
# Requires Qdrant running: docker run -d -p 6333:6333 qdrant/qdrant
node scripts/populate-from-museums.js 100 # Add 200 artworks (100/museum)
node scripts/populate-from-museums.js 500 # Scale to 1000 artworks (500/museum)| Metric | Value | Notes |
|---|---|---|
| Display Resolution | 1150ร1550px | 93% screen area |
| Refresh Time | 30-45 seconds | Full 6-color update |
| Deep Sleep Current | ~10ฮผA | Months of battery life |
| Active Current (WiFi On) | ~460mA @ 5V | Peak during display refresh |
| Active Current (WiFi Off) | ~250-300mA @ 5V | Display-only operation |
| Wake-up Time | 2-3 seconds | WiFi connection ready |
| Battery Life | 3-6 months | 3000mAh LiPo, 6-hour cycle |
The firmware includes intelligent power management and monitoring:
- WiFi Auto-Shutdown: Automatically disables WiFi during display refresh to save ~160-210mA
- Row-by-Row Updates: 1ms delays between display rows for stable power draw
- Progressive Delays: Strategic pauses between driver ICs to prevent voltage sag
- Battery Monitoring: Real-time voltage tracking via GPIO 2 (calibrated 4.7:1 voltage divider)
- Charging Detection: Automatic detection when battery voltage >= 4.0V
- Brownout Protection: Recovery mode after 3 brownouts to prevent boot loops
- Smart OTA: Only performs firmware updates when battery >= 3.6V or charging
- Median Filtering: 20-sample ADC readings reject electrical noise outliers
- Verified with KCX-017: Tested at 460mA peak with WiFi on, 250-300mA with WiFi off
- Works on Battery: Successfully operates on PowerBoost 1000C + LiPo battery
Power consumption varies by image complexity. Test images are included in server/data/:
test-1-checkerboard-worst-case.png- Maximum power draw (all pixels change)test-2-solid-blue.pngโtest-3-solid-yellow.png- Full color shift testtest-4-color-stripes.png- All 6 colors displayedtest-5-fine-lines-extreme.png- Highest frequency pattern
If your battery setup works with test-1-checkerboard-worst-case.png, all normal images will work reliably.
cd server/
npm install
npm run dev # Auto-reload development
npm test # Run test suite
npm run coverage # Test coverage reportcd esp32-client/
./build.sh compile # Build only
./build.sh upload # Build + upload + monitor
./build.sh monitor # Serial monitor only
./build.sh clean # Clean build filesGlance uses a multi-layered storage architecture for reliable data persistence:
SQLite Database (data/embeddings.db)
- Artwork metadata and embeddings
- User interaction history (likes, displays, skips)
- Personalized taste profiles
- 768-dimensional SigLIP embeddings for recommendations
Qdrant Vector Database (Port 6333)
- High-performance vector similarity search
- 512-dimensional CLIP embeddings
- Semantic search by natural language
- Visual similarity matching
JSON Files (data/)
current.json- Current display image (7.4 MB with base64 RGB data)playlist.json- Automated playlist configurationmy-collection.json- User's saved artwork collectioncollections.json- Collection metadatauser-interactions.json- Recent interaction history
File Storage (uploads/)
- User-uploaded images
- Processed artwork files
The docker-compose.yml includes persistent volumes for all data:
services:
glance-server:
volumes:
- glance-data:/app/data # SQLite + JSON files
- glance-uploads:/app/uploads # User uploads
- huggingface-cache:/root/.cache/huggingface # ML models (~600MB)
environment:
- QDRANT_URL=http://qdrant:6333
- HF_TOKEN=${HF_TOKEN:-} # Optional: Hugging Face API
depends_on:
- qdrant
qdrant:
image: qdrant/qdrant:latest
ports:
- "6333:6333" # REST API
- "6334:6334" # gRPC (optional)
volumes:
- qdrant-storage:/qdrant/storage # Vector database
volumes:
glance-data:
glance-uploads:
qdrant-storage:
huggingface-cache:Verify and initialize storage:
cd server/
npm run init:storageThis command will:
- Create necessary data directories
- Initialize SQLite database with proper schema
- Verify Qdrant connection
- Report storage statistics and file sizes
Output example:
โ Directory exists: /app/data
โ SQLite database initialized
- Total artworks: 1,247
- With embeddings: 1,247
- Coverage: 100%
โ Qdrant connection successful
- Total artworks indexed: 1,247
- Vector size: 512
๐พ Storage Status Report
Data Directory: 14.2 MB
Uploads Directory: 42.8 MB (15 files)
Local Development:
# Data stored in ./server/data/ and ./server/uploads/
npm startDocker Deployment:
# Named volumes persist across container restarts
docker-compose up -d
# View volume locations
docker volume inspect glance_glance-data
docker volume inspect glance_qdrant-storageBackup Strategy:
# Backup SQLite database
docker cp glance-server:/app/data/embeddings.db ./backup/
# Backup all data
docker run --rm \
-v glance_glance-data:/data \
-v $(pwd)/backup:/backup \
alpine tar czf /backup/glance-data.tar.gz -C /data .
# Backup Qdrant
docker run --rm \
-v glance_qdrant-storage:/qdrant \
-v $(pwd)/backup:/backup \
alpine tar czf /backup/qdrant-storage.tar.gz -C /qdrant .Required:
OPENAI_API_KEY- For AI art generation (optional if not using AI features)
Optional:
HF_TOKEN- Hugging Face API token for SigLIP embeddings (falls back to local CLIP)QDRANT_URL- Qdrant server URL (default:http://localhost:6333)PORT- Server port (default: 3000)NODE_ENV- Environment mode (production/development)
Index artworks from various sources:
cd server/
# Index from JSON collection
node scripts/populate-qdrant.js path/to/artworks.json
# Index WikiArt dataset
node scripts/populate-qdrant-wikiart.js
# Fetch and index from museums
node scripts/populate-from-museums.jsCheck database stats via API:
# SQLite statistics
curl http://localhost:3000/api/semantic/stats
# Qdrant statistics
curl http://localhost:6333/collections/artworksMonitor disk usage:
# Local development
du -sh server/data server/uploads
# Docker volumes
docker system df -v | grep glancePushing to main branch automatically:
- Runs full test suite
- Builds multi-architecture Docker image
- Deploys to Raspberry Pi via Tailscale SSH
- Updates running container with zero downtime
# Deploy specific version
IMAGE_VERSION=abc1234 ./deploy-to-pi.sh serverpi.local your-username
# Check running version
docker exec glance-server env | grep IMAGE_VERSION- WiFi fails: Check SSID/password, ensure 2.4GHz network
- Display blank: Verify SPI connections, check power supply
- Won't sleep: Check for active serial monitor connection
- Battery drain: Verify deep sleep current with multimeter
- Can't access dashboard: Check port 3000, firewall settings
- Images not processing: Verify Sharp dependencies installed
- Container won't start: Check disk space, Docker daemon
- ESP32 can't reach server: Check
serverpi.localresolution - Slow image downloads: Verify network bandwidth, WiFi signal
- Commands not working: Ensure device recently active (within 5 min)
- ESP32 firmware with ultra-low power management
- Node.js server with modular architecture
- AI art generation with GPT-4o
- AI-powered museum art search
- Semantic visual search with CLIP - Find art by vibe, not keywords
- "More like this" - Visual similarity search (0.73-0.84 accuracy)
- Personalized recommendations - Taste profile learns from interactions
- Personal art collection management
- Clean, minimalistic web interface
- Automated deployment pipeline
- Comprehensive test coverage
- Scheduling art for different times of day
- Battery usage analytics and optimization
- Multi-device synchronization
- Collaborative collections
Detailed documentation is available in the docs/ directory:
- DESIGN.md - Design principles, UI guidelines, calm technology philosophy
- SECURITY.md - Security best practices, credential management
- DEPLOYMENT.md - Complete deployment guide (GitHub Actions, Docker, Tailscale)
- HARDWARE.md - Hardware specifications and wiring diagrams
- PROJECT_GOALS.md - Project goals and vision
Ready to build your own smart e-ink display? ๐
Start with the Quick Start guide above!