docs(eink): the two e-ink builds in the docs and the site's sim

Docs: the E-ink section describes the landscape and portrait builds, what
the tall screens add, and the settings, with three screenshots; the e-ink
build flags; the release file names. Sim: each orientation gets its own
factory, so a page can load both, and simEinkScale 0 leaves the canvas'
CSS size to the page. CI builds both into solo-sim-wasm.zip (wasm/eink/).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-02 21:38:38 +02:00
co-authored by Claude Opus 5.5
parent e16fe2c621
commit 13d099cf55
12 changed files with 60 additions and 22 deletions
+20 -4
View File
@@ -1,9 +1,9 @@
name: Build Solo Sim (WASM)
# Builds the companion_radio browser sim (real firmware compiled to WASM via
# Emscripten, see variants/sim/) -- the OLED ui-new build, the Wio Tracker L2
# ui-lvgl build and the repeater -- and attaches it to the same draft release
# build-solo-firmwares.yml creates for this tag, as a single fixed-name
# Emscripten, see variants/sim/) -- the OLED ui-new build, the L1 E-ink's,
# the Wio Tracker L2 ui-lvgl build and the repeater -- and attaches it to the
# same draft release build-solo-firmwares.yml creates for this tag, as a single fixed-name
# asset: solo-sim-wasm.zip. Fixed name (no version in it) so
# meshcore-solo-site's server-side deploy script can always fetch it from
# the stable "latest release" download URL without knowing the tag --
@@ -75,14 +75,30 @@ jobs:
LVGL_DIR: ${{ runner.temp }}/lvgl
run: variants/sim/build_wasm.sh
# The L1 E-ink's two builds, landscape (8x13 font) and portrait (6x9);
# the site offers both beside the OLED and the L2.
- name: Build E-ink WASM (landscape)
env:
FIRMWARE_VERSION: ${{ env.BUILD_VERSION }}
SIM_EINK: landscape
run: variants/sim/build_wasm.sh
- name: Build E-ink WASM (portrait)
env:
FIRMWARE_VERSION: ${{ env.BUILD_VERSION }}
SIM_EINK: portrait
run: variants/sim/build_wasm.sh
- name: Build repeater WASM
run: variants/sim/build_wasm_repeater.sh
- name: Package
run: |
mkdir -p out/wasm/companion out/wasm/lvgl out/wasm/repeater
mkdir -p out/wasm/companion out/wasm/lvgl out/wasm/eink out/wasm/repeater
cp variants/sim/web/build/meshcore_sim.js variants/sim/web/build/meshcore_sim.wasm out/wasm/companion/
cp variants/sim/web/build/meshcore_sim_lvgl.js variants/sim/web/build/meshcore_sim_lvgl.wasm out/wasm/lvgl/
cp variants/sim/web/build/meshcore_sim_eink_landscape.js variants/sim/web/build/meshcore_sim_eink_landscape.wasm out/wasm/eink/
cp variants/sim/web/build/meshcore_sim_eink_portrait.js variants/sim/web/build/meshcore_sim_eink_portrait.wasm out/wasm/eink/
cp variants/sim/web/build/repeater/meshcore_sim_repeater.js variants/sim/web/build/repeater/meshcore_sim_repeater.wasm out/wasm/repeater/
cd out && zip -r solo-sim-wasm.zip wasm
+1 -1
View File
@@ -17,7 +17,7 @@ Discussion: [MeshCore Discord](https://discord.gg/sdhYArU2jr) — [Solo firmware
| Device | MCU | Display | Firmware file |
| ------ | --- | ------- | ------------- |
| Seeed Wio Tracker L1 (OLED) | nRF52840 | SSD1306 / SH1106 128 × 64 | `solo-<version>-WioTrackerL1.uf2` |
| Seeed Wio Tracker L1 (E-ink) | nRF52840 | GxEPD2 250 × 122 | `solo-<version>-WioTrackerL1Eink.uf2` |
| Seeed Wio Tracker L1 (E-ink) | nRF52840 | GxEPD2 250 × 122 | `solo-<version>-WioTrackerL1Eink-landscape.uf2` or `-portrait.uf2` |
| Seeed Wio Tracker L2 | ESP32-S3 | 320 × 240 touch LCD | `solo-<version>-Wio-Tracker-L2-merged.bin` |
| GAT562 30S Mesh Kit | nRF52840 | SSD1306 128 × 64 | `solo-<version>-GAT562-30S-Mesh-Kit.uf2` |
| GAT562 Mesh Watch13 *(experimental)* | nRF52840 | SSD1306 128 × 64 | `solo-<version>-GAT562-Mesh-Watch13.uf2` |
+2 -2
View File
@@ -39,9 +39,9 @@ These docs mark the differences with notes like this:
| **Keyboard** | M5Stack Cardputer ADV, T-Echo Lite + KeyShield, any board with a CardKB | Keys; arrows or their Fn combinations for the directions |
| **Touch** | Wio Tracker L2 | Taps, swipes and holds; two side buttons |
| OLED / e-ink | | Wio Tracker L2 |
| OLED | E-ink | Wio Tracker L2 |
| :---: | :---: | :---: |
| ![Clock page](./img/oled-clock.png) | ![Status page](./img/oled-status.png) | ![Home screen](./img/l2-home.png) |
| ![Clock page](./img/oled-clock.png) | ![A channel](./img/eink-channel.png) | ![Home screen](./img/l2-home.png) |
The joystick and keyboard devices share one interface, sized for small OLED
and e-ink screens. The Wio Tracker L2 has its own, built for a 320 × 240
+2
View File
@@ -84,6 +84,8 @@ existing `solo/<board>/platformio.ini` for what's already claimed).
| --- | --- |
| `OLED_MISC_FIXED_FONT=1` | Pulls in a full Latin/Greek/Cyrillic 6×9 fixed font (~14 KB flash) so typed text in those alphabets renders as itself instead of block placeholders. Worth it on any board with a keyboard; skip it on space-constrained builds without one. |
| `DISPLAY_ROTATION=<0-3>` | Rotates the panel in 90° steps, for a board mounted sideways or upside down. |
| `EINK_LARGE_FONT=1` | E-ink: writes in the 8×13 font at the panel's own resolution, with the larger icon set. The landscape e-ink build sets it; the narrow portrait screen keeps the 6×9. |
| `JOYSTICK_UPRIGHT_ROTATION=<0-3>` | The display rotation at which the joystick reads upright; the joystick then turns with the screen. Set by the e-ink builds. |
| `ENABLE_SCREENSHOT` | Lets the site's [USB tools](https://solo.marekzegarek.com/#pc-tools) (or `tools/screenshot.py`) capture the screen over USB. A colour screen (the L2) renders a fresh frame and sends it as RGB565. |
| `KEEP_DISPLAY_ON_USB` | Refreshes the auto-off deadline continuously while externally (USB) powered, so the auto-off timer only starts counting once power is actually removed. Off by default because OLED panels burn in quickly with a permanently-lit screen — only worth enabling for an LCD/e-ink target, or a display you don't mind replacing. |
| `AUTO_OFF_MILLIS=<ms>` | How long the display stays on before auto-off. Default 15000 (15s); `0` disables auto-off entirely. |
+26 -7
View File
@@ -64,16 +64,35 @@ Fn shortcuts.
## E-ink
The e-ink Wio Tracker L1 (250 × 122) has a few settings of its own in
Settings › Display:
The e-ink Wio Tracker L1 (250 × 122) comes in two builds, one per
orientation, since every screen is laid out for one shape:
- **Rotation**: landscape or portrait, applied at once; every screen reflows.
- **Joystick rotation**, independent of the display's.
- **Full refresh**: how many partial updates between full ones, against
- **Landscape** writes in a larger 8 × 13 font, with larger icons.
- **Portrait** keeps the 6 × 9 font and fits more lines.
| Landscape | Portrait | |
| :---: | :---: | :---: |
| ![A channel in landscape](./img/eink-channel.png) | ![Clock and calendar in portrait](./img/eink-clock.png) | ![The sky view in portrait](./img/eink-sky.png) |
The e-ink screens show more than the OLED, the portrait one most:
- a month calendar (portrait) under the clock and on the lock screen, the
alarm's days marked;
- whole message bubbles;
- charts of the last 8 hours on the Status screen;
- a compass rose and the path's hops on a contact's details, and direction
arrows in the node list;
- under a saved trail (portrait), its map and elevation profile.
Settings › Display has a few settings of its own:
- **Rotation** turns the screen upside down; the joystick turns with it.
- **Joystick** rotation, for a joystick mounted another way.
- **Full rfsh**: how many partial updates between full ones, against
ghosting.
Clock seconds are hidden by default, and live timers refresh coarsely, to
spare the panel.
The panel refreshes only when what it shows changes. Times are shown to the
minute (no seconds counting up), and clock seconds are hidden by default.
## Wio Tracker L2
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+4 -3
View File
@@ -200,12 +200,13 @@ fi
SIM_EINK="${SIM_EINK:-}"
if [ -n "$SIM_EINK" ]; then
case "$SIM_EINK" in
landscape) EINK_DEFS=(-DDISPLAY_ROTATION=1 -DEINK_LARGE_FONT=1) ;;
portrait) EINK_DEFS=(-DDISPLAY_ROTATION=0) ;;
landscape) EINK_DEFS=(-DDISPLAY_ROTATION=1 -DEINK_LARGE_FONT=1); EXPORT_NAME=MeshCoreSimEinkLandscape ;;
portrait) EINK_DEFS=(-DDISPLAY_ROTATION=0); EXPORT_NAME=MeshCoreSimEinkPortrait ;;
*) echo "error: SIM_EINK must be landscape or portrait" >&2; exit 1 ;;
esac
OUT_NAME="meshcore_sim_eink_$SIM_EINK"
EXPORT_NAME=MeshCoreSimEink # distinct factory: web/mesh.html?a=... loads it beside MeshCoreSim
# Distinct factories (set above): web/mesh.html?a=... loads one beside
# MeshCoreSim, and the site loads both orientations into one page.
kept=()
for d in "${DEFINES[@]}"; do
case "$d" in -DDISPLAY_CLASS=*|-DMAX_GROUP_CHANNELS=*) ;; *) kept+=("$d") ;; esac
+3 -3
View File
@@ -174,15 +174,15 @@ SimEinkDisplay::SimEinkDisplay()
}
// Sizes the page's canvas to the panel as it's turned, at Module.simEinkScale
// (default 3) CSS pixels a pixel.
// (default 3) CSS pixels a pixel; 0 leaves the CSS size to the page.
static void simEinkFitCanvas(int w, int h) {
EM_ASM({
var tag = (typeof Module !== 'undefined' && Module['simInstanceTag']) ? Module['simInstanceTag'] : '';
var c = document.getElementById(tag ? ('sim-canvas-' + tag) : 'sim-canvas');
if (!c) { console.error('[sim] e-ink canvas not found in the host page'); return; }
var k = Module.simEinkScale || 3;
var k = ('simEinkScale' in Module) ? Module.simEinkScale : 3;
c.width = $0; c.height = $1;
c.style.width = ($0 * k) + 'px'; c.style.height = ($1 * k) + 'px';
if (k) { c.style.width = ($0 * k) + 'px'; c.style.height = ($1 * k) + 'px'; }
Module.__simCtx = c.getContext('2d');
Module.__simCtx.imageSmoothingEnabled = false;
Module.__einkRefreshes = Module.__einkRefreshes || 0;
+1 -1
View File
@@ -215,7 +215,7 @@
}
}
const SimFactory = window.MeshCoreSimEink || window.MeshCoreSim;
const SimFactory = window.MeshCoreSimEinkLandscape || window.MeshCoreSimEinkPortrait || window.MeshCoreSim;
if (typeof SimFactory !== 'function') {
statusEl.textContent = 'error: build/meshcore_sim.js missing or failed to load -- run variants/sim/build_wasm.sh first.';
} else {
+1 -1
View File
@@ -238,7 +238,7 @@
</script>
<script>
const logEl = document.getElementById('log');
const FactoryA = (opts) => (A_BUILD ? MeshCoreSimEink : MeshCoreSim)(opts);
const FactoryA = (opts) => (window.MeshCoreSimEinkLandscape || window.MeshCoreSimEinkPortrait || MeshCoreSim)(opts);
function log(msg) {
const line = document.createElement('div');
line.textContent = '[' + new Date().toISOString().substr(11, 12) + '] ' + msg;