A modern, feature-rich firmware for ESP32-based e-paper photo frames (currently supporting Waveshare PhotoPainter, Seeed Studio XIAO EE02/EE04, and Seeed Studio reTerminal E1002). This firmware replaces stock firmware with a powerful RESTful API, web interface, and significantly better image quality.
- π¨ Superior Image Quality: Measured color palette with automatic calibration produces significantly better results than stock firmware
- π Smart Power Management: Deep sleep mode for weeks of battery life, or always-on for Home Assistant
- π Flexible Image Sources: SD card rotation, URL-based fetching (weather, news, random images from image server)
- π Modern Web Interface: Drag-and-drop uploads, gallery view, real-time battery status
- π± Mobile App: Companion app for WiFi provisioning, image processing, and AI generation
- πΌοΈ Image Server: Companion server with Google Photos, Synology DS Photos, and Telegram Bot support
- π Home Assistant Ready: Companion integration available
- π RESTful API: Full programmatic control (API docs)
This project has companion tools for different use cases:
| Project | Description |
|---|---|
| ha-esp32-photoframe | Home Assistant integration for control, monitoring, and automation |
| esp32-photoframe-server | Image server with text overlay, Google Photos, Synology DS Photos, and Telegram Bot integration. Can be run as a Home Assistant add-on. |
| esp32-photoframe-app | Mobile companion app for WiFi provisioning and device control (available for testing) |
| epaper-image-convert | CLI tool & npm library for e-paper image conversion with advanced dithering |
| Project | Description |
|---|---|
| puppet | HA Puppet add-on. Generate image URL from Puppet dashboard and use it as the Auto Rotate URL in PhotoFrame settings. |
π¨ Try the Interactive Demo - Drag the slider to compare algorithms in real-time with your own images!
Why Our Algorithm is Better:
- β Accurate Color Matching: Uses actual measured e-paper colors
- β Automatic Calibration: Built-in palette calibration tool adapts to your specific display
- β Better Dithering: Floyd-Steinberg algorithm with measured palette produces more natural color transitions
- β No Over-Saturation: Avoids the washed-out appearance of theoretical palette matching
The measured palette accounts for the fact that e-paper displays show darker, more muted colors than pure RGB values. By dithering with these actual colors, the firmware makes better decisions about which palette color to use for each pixel, resulting in images that look significantly better on the physical display. The automatic calibration feature allows you to measure and optimize the palette for your specific device.
π Read the technical deep-dive on measured color palettes β
Deep Sleep Enabled (Default):
- Battery life: months
- Wake via BOOT/KEY button or auto-rotate timer
- Web interface accessible only when awake
- Power: ~10ΞΌA in sleep
Deep Sleep Disabled (Always-On):
- Best for Home Assistant integration
- Web interface always accessible
- Power: ~40-80mA with auto light sleep
- Battery life: days to weeks depending on usage
Auto-Rotation: SD card (default) or URL-based (fetch from web)
Configure via web interface Settings section.
The web interface supports client-side AI image generation using OpenAI (GPT Image, DALL-E) or Google Gemini.
- Generate on Demand: Create custom artwork directly from the web interface using text prompts
- Multiple Providers: OpenAI and Google Gemini supported
- Client-Side Processing: AI generation runs in your browser, then uploads to the device
Configure your API keys in Settings > AI Generation.
| Board | Display | Storage | Board Name |
|---|---|---|---|
| Waveshare PhotoPainter | 7.3" 7-color | SD card (SDIO) | waveshare_photopainter_73 |
| Seeed Studio XIAO EE02 | 13.3" 6-color | Internal flash | seeedstudio_xiao_ee02 |
| Seeed Studio XIAO EE04 | 7.3" 6-color | Internal flash | seeedstudio_xiao_ee04 |
| Seeed Studio reTerminal E1002 | 7.3" 6-color | SD card (SPI) + Internal flash | seeedstudio_reterminal_e1002 |
The reTerminal E1002 also includes a SHT40 temperature/humidity sensor, PCF8563 RTC, and battery monitoring.
Buttons behave differently depending on whether the device is awake (web UI accessible) or in deep sleep.
When in deep sleep:
| Button | Waveshare PhotoPainter | XIAO EE02 / EE04 | reTerminal E1002 |
|---|---|---|---|
| Wake | BOOT button | Button 3 | Green button |
| Rotate | KEY button | Button 1 | Left button |
| Clear | N/A | Button 2 | Right button |
- Wake: Wakes the device and starts the web UI / HTTP server (stays awake)
- Rotate: Wakes the device, rotates to the next image, then goes back to sleep
- Clear: Wakes the device, clears the display to white, then goes back to sleep
When awake:
| Button | Function |
|---|---|
| Rotate | Rotates to the next image |
| Clear | Clears the display to white |
Boards with larger flash chips (XIAO EE02/EE04, reTerminal E1002) use internal flash as persistent storage via LittleFS. On the reTerminal, the SD card takes priority when inserted; internal flash serves as a fallback. The Waveshare board does not have internal flash storage due to its 16MB flash being fully allocated to OTA partitions.
- PhotoPainter Restarts: All existing Waveshare PhotoPainter boards on the market use the AXP2101 power management IC, which causes unexplained restarts when connected to both Type-C and a lithium battery simultaneously. Workaround: use either USB power only or battery only. Using both at the same time may cause frequent firmware restarts due to unstable power supply. Waveshare has confirmed this issue and future boards will ship with TG28 as a replacement, which will not have this problem. See waveshareteam/ESP32-S3-PhotoPainter#5 for details.
- Seeed Studio Deep Sleep & USB Power: The XIAO EE02, XIAO EE04, and reTerminal E1002 can only detect USB connections from a PC (via USB-Serial-JTAG SOF packets). Chargers and power banks will not keep the device awake β it will enter deep sleep as normal. This is a hardware limitation: these boards do not route USB VBUS to an ESP32 GPIO. The Waveshare PhotoPainter does not have this limitation as it uses the AXP2101 PMIC for USB power detection. Workaround: If you want the device to stay always accessible while powered by a charger or power bank, disable deep sleep in Settings > General.
π Flash from Browser - Chrome/Edge/Opera required
Download from Releases:
esptool.py --chip esp32s3 --port /dev/ttyUSB0 --baud 921600 write_flash 0x0 photoframe-firmware-<board>-merged.binDevice not detected? Hold BOOT button + press PWR to enter download mode.
Build from source:
We provide a build.py helper script to simplify building for different boards.
# Build for Waveshare PhotoPainter (default)
./build.py --board waveshare_photopainter_73
# Build for Seeed Studio XIAO EE02
./build.py --board seeedstudio_xiao_ee02
# Build for Seeed Studio XIAO EE04
./build.py --board seeedstudio_xiao_ee04
# Build for Seeed Studio reTerminal E1002
./build.py --board seeedstudio_reterminal_e1002
# Flash the firmware
idf.py -p /dev/ttyUSB0 flash monitorFor more details, see DEV.md
The device supports two methods for WiFi provisioning:
-
Create a file named
wifi.txton your SD card with:YourWiFiSSID YourWiFiPassword MyPhotoFrame- Line 1: WiFi SSID (network name)
- Line 2: WiFi password
- Line 3: Device name (optional, defaults to "PhotoFrame")
- Use plain text, no quotes or extra formatting
- The file can be placed at the root or in a
config/folder
-
Insert SD card and power on the device
-
Device automatically reads credentials, saves to memory, and connects
-
The
wifi.txtfile is automatically deleted after reading (to prevent issues with invalid credentials)
Note: If credentials are invalid, the device will clear them and fall back to captive portal mode.
- Device creates a unique AP on first boot (e.g.
PhotoFrame - A1B2C3, whereA1B2C3is derived from the device's MAC address) - Connect to the AP and open
http://192.168.4.1(or use captive portal) - Enter WiFi credentials (2.4GHz only)
- Device tests connection and saves if successful
Option 3: Companion App (available for testing)
- Install the ESP Frame companion app
- Tap the "+" button on the home screen
- The app scans for PhotoFrame setup hotspots, connects automatically, and guides you through WiFi configuration
Re-provision: Delete credentials with idf.py erase-flash or place new wifi.txt on SD card after clearing stored credentials
Web Interface: http://photoframe.local or device IP address
- Gallery view with drag-and-drop uploads
- Settings, battery status, display control
API: Full documentation in API.md
- WiFi issues: Ensure 2.4GHz network, check serial monitor for IP
- SD card not detected: Format as FAT32, try different card
- Upload fails: Check file is valid JPEG, monitor serial output
- Device not detected for flash: Hold BOOT + press PWR for download mode
Node.js CLI tool for batch processing and image serving:
cd process-cli && npm install
# Process to disk
node cli.js input.jpg --device-parameters -o /path/to/sdcard/images/
# Or upload directly to device
node cli.js ~/Photos/Albums --upload --device-parameters --host photoframe.localServe pre-processed images directly to your ESP32 over HTTP:
node cli.js --serve ~/Photos --serve-port 9000 --device-parameters --host photoframe.localThe ESP32 can fetch images from your computer instead of storing them on SD card. Supports EPDGZ, BMP, PNG, and JPG formats with automatic thumbnail generation.
See process-cli/README.md for details.
Building your own image server? The firmware's URL rotation fetch protocol β request method, custom X-* headers, Authorization / custom-header handling, and the ETag / 304 Not Modified caching flow β is documented in docs/API.md β URL Rotation Fetch.
If you find this project useful, consider buying me a coffee! β
This project is based on the ESP32-S3-PhotoPainter sample code. Please refer to the original project for licensing information.
- Original PhotoPainter sample: Waveshare ESP32-S3-PhotoPainter
- E-paper drivers: Waveshare
- ESP-IDF: Espressif Systems




