The time, the date, the outdoor weather and the garage temperature on an Elecrow 2.8" ESP32 display, updated over WiFi.
┌────────────────────────────────┐
│ 12:45 PM │
│ Saturday 27 September 2026 │
├───────────────┬────────────────┤
│ OUTSIDE │ GARAGE │
│ 58.2 │ 64.7 │
│ 71% RH │ 48% RH │
└────────────────────────────────┘
- Outside: Open-Meteo, every 15 minutes. Also supplies sunrise and sunset.
- Garage: an AHT10/AHT20 on the I2C connector (SDA 22, SCL 21), every 10 s, also published to MQTT. With no sensor fitted, the display reads the same MQTT topics instead.
- Time: SNTP, Pacific time. The board has no RTC, so it shows
--:--until the network has answered. - A reading that has stopped updating is dimmed.
- White text by day, red at night. The backlight is 80% by day and 5% at night.
- Hold a finger on the screen for a second to switch between °F and °C.
Elecrow "2.8inch ESP32 Solo Miner LCD Display": ESP32-WROOM-32-N4 (4 MB flash,
no PSRAM), ILI9341 320x240 on SPI, XPT2046 resistive touch, CH340C USB serial.
Every pin is in src/board_pins.h; the reasoning is in
docs/superpowers/specs/2026-09-27-garage-display-design.md.
The full hardware reference, with vendor-documentation errors, is docs/HARDWARE.md.
cp secrets.ini.example secrets.iniand fill it in.- The build fetches esp32-ota-kit v1.0.0 from GitHub; no account is needed.
- First time, over USB:
pio run -e garage_display -t upload - Serial log:
pio device monitor, or~/.platformio/penv/bin/python tools/capture_serial.py 30. - Host tests:
pio test -e native.
- Bump
custom_fw_versioninplatformio.ini. pio run -e garage_display_ota -t upload
The display shows the progress and reboots into the new version. If the new version crashes before it has been up for a while, the bootloader goes back to the previous one by itself. This was verified on this board at 1.0.1.
- Push hangs at "Waiting for device": the Mac firewall is blocking PlatformIO's Python. The device connects back to the computer, so allow incoming connections for it.
- The name does not resolve: check
ping garage-display.local. - Security: espota pushes over UDP+TCP on the LAN, and the OTA password is checked by a challenge-response, not sent. But the firmware image itself crosses the network unencrypted, and it contains the WiFi password, the OTA password and the broker address in plain text - anyone sniffing a push can read them. Keep the display on a trusted network.
- Power-cycling mid-update: cutting power within about 40 s of a push (before the new image is confirmed) reverts to the previous version.
- "esp_core_dump_flash: Core dump data check failed" at boot: harmless - the core-dump partition is just empty.
The board's factory image was saved to backup/factory-miner.bin (not in
git) before the first flash; despite the product's "miner" name, that image
is Elecrow's lv_micropython v1.19.1 build with their DHT20/LED MicroPython
demo (see docs/HARDWARE.md). To restore it:
~/.platformio/penv/bin/esptool --port /dev/cu.wchusbserial2120 write-flash 0 backup/factory-miner.bin
| Path | Contents |
|---|---|
src/main.cpp |
Startup order and the loop |
src/ui.* |
The text view |
src/board_pins.h, display_driver.*, touch.*, backlight.* |
This board |
src/source_aht10.*, source_mqtt.*, source_weather.* |
Where readings come from |
src/net.*, wallclock.*, brightness.*, update_overlay.* |
WiFi, time, backlight policy, OTA progress |
src/fonts/ |
Generated digit fonts, with the recipe |
lib/ |
Pure, host-tested logic |
test/ |
Unity tests for lib/ |
tools/capture_serial.py |
Resets the board and prints its log |
Much of src/ and lib/ comes from the CrowPanel 7" temperature display
(Crowpanel-Advance-7.0-HMI-Display, commit feee6c1).
Author: Mark Castelluccio markacastelluccio@gmail.com
Written with assistance from Claude Code (Anthropic).
MIT licence; see LICENSE.