An e-paper dashboard running on ESP32. The current version includes multiple production pages (weather, timeline, world clock, focus clock, and more) and is powered by the free Open-Meteo API for weather data — no API key required.
This is just the beginning. The roadmap includes cryptocurrency prices, stock market data, local IoT device monitoring, and more — living up to the vision of a true "Dashboard for anything" on e-paper.
English | 简体中文
| Power-on splash | Waking up | Weather page |
|---|---|---|
![]() |
![]() |
![]() |
| AP config mode | Device menu | Pages menu |
|---|---|---|
![]() |
![]() |
![]() |
| Data-source menu | Focus clock menu | Focus clock page |
|---|---|---|
![]() |
![]() |
![]() |
| P0 Standard page | P1 Weather Today | P2 Today Overview |
|---|---|---|
![]() |
![]() |
![]() |
| P4 Weekly Timeline | P5 Monthly Overview | P6 Weekly Weather |
|---|---|---|
![]() |
![]() |
![]() |
| P7 World Clock | P8 Focus Clock |
|---|---|
![]() |
![]() |
- Open-Meteo weather + air quality — free, no API key, no account
- Current conditions: temperature, feels-like, wind, humidity, pressure, visibility, UV index
- 5-day daily forecast with WMO weather icons
- Hourly temperature trend line + precipitation probability bar chart (next 12 h)
- Indoor temperature & humidity from on-board AHT20 / BME280 sensor
- US AQI and PM2.5 concentration
- Deep sleep between updates (configurable interval, default 30 min)
- Bed-time / wake-time window — no display refresh during night hours
- Browser-based config portal (no app needed): WiFi, location, units, timezone, sleep interval
- Updated web config menus:
device/pages/data-source - Focus clock can be configured independently (dedicated duration setting)
- AP config mode (long-press Boot button) — ESP32 becomes a hotspot for first-time setup
- Power-on config window (configurable
PortalSec, default 30 s) — after each WiFi connection the web portal stays reachable at the device IP;0disables it for maximum power saving - SNTP time synchronisation with configurable UTC offset
- 3-color (red/black/white) accent support on compatible panels
- Multilingual UI:
en_US,zh_CN - Configurable units: °C / °F, km/h / m/s / mph / kn, hPa / inHg / mmHg, km / mi, mm / in
| Page ID | Name | Status |
|---|---|---|
P0 |
Standard page | Unchanged |
P1 |
WEATHER TODAY | Implemented |
P2 |
TODAY OVERVIEW | Implemented |
P4 |
WEEKLY TIMELINE | Implemented |
P5 |
MONTHLY OVERVIEW | Implemented |
P6 |
WEEKLY WEATHER | Implemented |
P7 |
WORLD CLOCK | Implemented |
P8 |
FOCUS CLOCK | Implemented |
P3 is currently not listed in the active release mapping.
- To sync Google Calendar (and similar providers), configure a Google Calendar ICS URL in data-source settings.
- Outlook Calendar and Apple Calendar are not fully tested yet in the current release.
| Environment | MCU | Display | Resolution | Colors | Sensor | Notes |
|---|---|---|---|---|---|---|
nm-display-420 |
ESP32-S3 | 4.2″ EPD (GDEY042Z98) | 400 × 300 | Red / Black / White | AHT20 | |
dfrobot_firebeetle2_esp32e / firebeetle32 |
ESP32 | 7.5″ EPD (GDEY075T7) | 800 × 480 | Black / White | BME280 | Planned — coming very soon, not yet tested |
| Signal | GPIO | Notes |
|---|---|---|
| EPD CS | 3 | |
| EPD DC | 4 | |
| EPD RST | 5 | |
| EPD BUSY | 6 | |
| EPD SCK | 2 | |
| EPD MOSI | 1 | |
| EPD MISO | 10 | Unused (write-only display) |
| EPD PWR | 21 | Wired to 3.3 V, not switched |
| AHT20 SDA | 39 | |
| AHT20 SCL | 38 | |
| AHT20 CTL (power) | 40 | Drive HIGH before sensor access |
| Battery ADC | A0 | 100 kΩ + 100 kΩ voltage divider |
| Boot / wake button | IO0 | External pull-up, wakes deep sleep via EXT0 |
| AP config button | IO45 | External pull-up |
The board carries an ES8311 audio codec on the same I²C bus. The firmware immediately puts it into suspend on boot to save ~3 mA.
Install PlatformIO IDE (VS Code extension) or the CLI.
git clone https://github.com/your-repo/ESP32-Dashboard.git
cd ESP32-DashboardOpen the folder in VS Code; PlatformIO will resolve all dependencies automatically.
Select the environment that matches your hardware:
# NM Display 420 (ESP32-S3, 4.2" tri-color EPD)
pio run -e nm-display-420 -t upload_allUse upload_all for normal device flashing. It builds and uploads the firmware,
generates the gzipped web assets, then uploads the LittleFS filesystem image
that contains the web portal. If the portal shows Web assets not uploaded,
flash the complete image again with:
pio run -e nm-display-420 -t upload_all --upload-port <PORT>For a release package that users can flash as one file at address 0x0, build
the merged image:
pio run -e nm-display-420 -t release_binThe output is written to release/ using the pattern
esp32-dashboard-<device>-<version>.bin, for example
release/esp32-dashboard-nm-epd-420-v1.0.0.bin. The version comes from the
root VERSION file.
Run the focused PlatformIO build-only tests before firmware changes:
pio test -e nm-display-420 -f test_display_page_state --without-uploading --without-testing
pio test -e nm-display-420 -f test_wake_coordinator --without-uploading --without-testingThe project no longer defines env:native; tests are compiled for the current
embedded target. --without-uploading --without-testing is a build-only gate:
it proves the test firmware compiles, but it does not execute Unity assertions
unless you upload/run tests on hardware.
The release build gates are:
pio test -e nm-display-420 -f test_display_page_state --without-uploading --without-testing
pio test -e nm-display-420 -f test_wake_coordinator --without-uploading --without-testing
pio test -e nm-display-420 -f test_legacy_config_migration --without-uploading --without-testing
pio test -e nm-display-420 -f test_source_runtime_cache --without-uploading --without-testing
pio run -e nm-display-420
pio run -e nm-display-420 -t buildfs
pio run -e nm-display-420 -t release_bin| Topic | English | Chinese |
|---|---|---|
| User guide | docs/user-guide.md | docs/user-guide-zh.md |
| Data sources | docs/data-sources.md | docs/data-sources-zh.md |
| Privacy and security | docs/privacy-security.md | docs/privacy-security-zh.md |
| Recovery and reflash | docs/recovery-reflash.md | docs/recovery-reflash-zh.md |
- Hold the Boot button (IO0) for ≥ 2 seconds on first power-on to enter AP config mode.
- The display shows the hotspot name (
esp_dashboard_XXXXXX), the temporary WPA2 key, and the URL192.168.4.1. - Connect your phone or PC to that hotspot, open
http://192.168.4.1. - Fill in WiFi credentials, latitude / longitude, city name, UTC offset, and preferred units; click Save.
- The device restarts, connects to your home WiFi, fetches weather, and refreshes the display.
After every wake the device keeps its WiFi connection open for a configurable window (PortalSec, default 30 s, 0 = disabled). During that window its IP address is shown at the bottom-left of the display — open http://<device-ip> from any browser on the same network to change settings. Saving in the portal offers an immediate restart so the new configuration is applied right away; otherwise it takes effect on the next wake.
| Action | Result |
|---|---|
| Short press Boot (IO0) | Next page (while the device is awake) |
| Long press Boot (IO0) ≥ 2 s | AP config mode (auto-exit after 360 s, then restart) |
All settings are stored in NVS flash and editable through the web portal:
| Setting | Default | Description |
|---|---|---|
| WiFi SSID | — | 2.4 GHz network name |
| WiFi Password | — | Network password |
| Latitude | 30.6667 |
Location for weather queries |
| Longitude | 104.0667 |
Location for weather queries |
| City name | Chengdu, Sichuan, China |
Display label only |
| UTC offset | 8 |
Hours from UTC (e.g. 8 = UTC+8) |
| Sleep interval | 30 min |
Minutes between display refreshes |
| Config window | 30 s |
Seconds the web portal stays reachable after each wake (0–600, 0 = off) |
| Bed time | 0 h |
Hour to pause refreshing (24-h clock) |
| Wake time | 6 h |
Hour to resume refreshing |
| Temperature unit | C |
C / F |
| Wind speed unit | kmh |
kmh / ms / mph / kn |
| Pressure unit | hPa |
hPa / inHg / mmHg |
| Distance unit | km |
km / mi |
| Precipitation unit | mm |
mm / in |
| Language | zh_CN |
en_US / zh_CN |
| Library | Version | Purpose |
|---|---|---|
| ArduinoJson | 7.4.3 | JSON parsing (Open-Meteo API responses) |
| GxEPD2 | 1.6.8 | E-paper display driver |
| AsyncTCP | latest | Async TCP foundation for web server |
| ESPAsyncWebServer | latest | Web config portal backend |
| Library | Version | Purpose |
|---|---|---|
| Adafruit AHTX0 | 2.0.5 | AHT20 temperature / humidity sensor |
| Adafruit BusIO | 1.17.4 | I²C / SPI abstraction |
| Adafruit Unified Sensor | 1.1.15 | Sensor abstraction layer |
src/
├── main.cpp # Entry point — calls DashboardApp::run()
├── app/
│ ├── dashboardApp.cpp/h # Top-level wake-cycle controller
│ ├── config/ # NVS config load / save, settings struct
│ ├── weather/ # Open-Meteo HTTP fetch + JSON parse
│ ├── wifi/ # WiFi connect + SNTP time sync
│ ├── web/ # ESPAsyncWebServer config portal (REST API)
│ └── locale/ # en_US / zh_CN locale strings
├── bsp/
│ ├── IBoard.h # Hardware abstraction interface
│ ├── nm_display_420/ # ESP32-S3 + 4.2" tri-color EPD BSP
│ ├── firebeetle2_esp32e/ # FireBeetle 2 ESP32-E + BME280 BSP
│ └── firebeetle32/ # FireBeetle ESP32 + BME280 BSP
├── drivers/
│ ├── sensor/ # ISensor interface + AHT20 driver
│ └── audio/ # ES8311 codec suspend helper
├── ui/
│ ├── layouts/
│ │ ├── epd_400x300/ # 4.2" tri-color page layout
│ │ └── epd_800x480/ # 7.5" BW page layout
│ └── pages/ # PageWeatherBase, PageLoading, PageError
└── assets/
├── fonts/ # FreeSans bitmap fonts (4 pt – 48 pt)
└── icons/ # Weather icons (16×16 to 96×96)
Power-on / timer wakeup
│
▼
_detectWakeup()
┌─────────────────────────────────┐
│ Long press (≥2 s) → AP mode │
│ Short press → next page │
│ Timer / cold boot → normal │
└─────────────────────────────────┘
│
▼
_initHardware() EPD init + sensor init
│
├─── AP mode ──► SoftAP + web portal → restart after 360 s
│
▼
_showLoadingPage() (cold boot / button wake only)
│
▼
_connectAndSync() WiFi connect + SNTP time sync
│ failure → retry up to 3× then show error page → sleep
▼
_fetchData() Open-Meteo weather + AQI HTTP requests
│ failure → retry up to 3× then show error page → sleep
▼
_renderWeather() EPD page-based draw loop
│
▼ (PortalSec > 0: web portal + buttons for N s)
WiFi disconnect → EPD hibernate → deep sleep (N minutes)
-
Create
src/bsp/<board_name>/config.h— defineDISP_WIDTH,DISP_HEIGHT, and allPIN_*constants. -
Create
src/bsp/<board_name>/Board.cpp— implementIBoard:Method Requirement init()Serial, power rails, peripherals epd()Return IEpdDriver&wrapping GxEPD2gfx()Return Adafruit_GFX&from GxEPD2colorAccent()/hasAccentColor()Red for 3-color panels; black / false for BW getTempSensor()Return ISensor*ornullptrreadBatteryMv()ADC reading converted to millivolts deepSleep(us)Configure wakeup, call esp_deep_sleep_start()bootButtonPin()/apButtonPin()GPIO numbers -
Add a UI layout under
src/ui/layouts/epd_NNNxNNN/if the resolution differs from existing ones. SubclassPageWeatherBaseand implement_drawCurrentConditions(),_drawForecast(), and_drawStatusBar(). -
Register a PlatformIO environment in
platformio.ini:[env:my_board] board = <pio_board_id> build_src_filter = +<*> -<bsp/> +<bsp/my_board/> -<ui/layouts/> +<ui/layouts/epd_NNNxNNN/> build_flags = ${env.build_flags} -DUI_LAYOUT_EPD_NNNxNNN lib_deps = ${env.lib_deps} <any extra sensor library>
- Open-Meteo — free, open-source weather API
- GxEPD2 — e-paper display library
- ArduinoJson — JSON library for Arduino / ESP32
- ESPAsyncWebServer — async HTTP server
- Adafruit — sensor and GFX libraries















