Custom firmware overlay for TRMNL e-ink displays that replaces the cloud API with GitHub Pages as the image source, using AES-256-CBC encryption.
Instead of fetching images from the TRMNL server, the device downloads encrypted images from a static GitHub Pages site that you control.
- Device wakes from deep sleep, connects to WiFi
- Downloads an encrypted manifest from your GitHub Pages URL
- Decrypts manifest with a pre-shared AES key stored in NVS
- Picks the next screen (round-robin) and downloads the encrypted BMP
- Decrypts in PSRAM, renders on the e-paper display
- Goes back to deep sleep for
refresh_rateseconds
This repo contains only the overlay files — custom code that gets layered on top of the official TRMNL firmware. The upstream firmware lives in the upstream branch (clean snapshots of official releases).
main branch (overlay):
src/ — Custom firmware source (crypto, GitHub client, manifest parser)
include/ — Headers for overlay modules
test/ — Unit tests for crypto
tools/ — Python utilities (key generation, image encryption, manifest builder)
content-template/ — Template for your content repository
platformio.ini — Extended with github_pages build environment
build.sh — Build script that merges upstream + overlay
upstream branch:
(complete TRMNL firmware snapshot, e.g. v1.7.5)
- Python 3 (venv recommended)
pip install platformio esptool
Copy the example secrets file and fill in your values:
cp src/secrets.h.example src/secrets.hThen edit src/secrets.h:
| Define | Description |
|---|---|
GITHUB_PAGES_MANIFEST_URL |
URL to your encrypted manifest file (built with ./tools/update_manifest.py) |
GITHUB_PAGES_IMAGES_BASE |
Base URL for encrypted images (created by ./tools/encrypt_image.py) |
GITHUB_PAGES_AES_KEY_HEX |
64-character hex string of your 256-bit AES key (generate with ./tools/generate_key.py) |
src/secrets.h is listed in .gitignore — never commit real keys or private URLs. The values in secrets.h serve as compile-time defaults; they can be overridden at runtime via NVS (see Device configuration below).
The build script extracts the upstream firmware and layers your overlay files on top in a .build directory:
MacOS/Linux
./build.sh
cd .build
pio run -e github_pages # compile
pio run -e github_pages -t upload # flash to deviceWindows
.\build.bat
cd .build
pio run -e github_pages # compile
pio run -e github_pages -t upload # flash to deviceTo run crypto unit tests:
cd .build
pio test -e native-cryptoClean up with rm -rf .build.
On first boot the device starts a WiFi captive portal for network setup. The following NVS preferences must be set:
| Key | Description |
|---|---|
manifest_url |
Full URL to your manifest.enc on GitHub Pages |
aes_key_hex |
64-character hex string of your 256-bit AES key |
images_base |
Base URL for image downloads (e.g. https://user.github.io/content/images/) |
See content-template/README.md for instructions on setting up your content repository with GitHub Actions for automated image generation and encryption.
Quick start:
# Generate an AES key
python tools/generate_key.py
# Encrypt a test image
python tools/encrypt_image.py encrypt input.bmp output.enc --key <hex-key>
# Build the manifest
python tools/update_manifest.py images/ manifest.enc --key <hex-key>When a new TRMNL firmware version is released:
- Download or checkout the new release
- Replace the contents of the
upstreambranch:git checkout upstream # remove old files, copy new release files git add -A git commit -m "trmnl-firmware vX.Y.Z" git checkout main
- Rebuild with
./build.shand test
Targets Seeed Studio XIAO ESP32-S3 PLUS (8MB Flash, 8MB PSRAM). PSRAM is required for image buffering.