diff --git a/.github/workflows/build-solo-sim.yml b/.github/workflows/build-solo-sim.yml index 81e19d04..4ab135c9 100644 --- a/.github/workflows/build-solo-sim.yml +++ b/.github/workflows/build-solo-sim.yml @@ -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 diff --git a/README.md b/README.md index 997923ae..0f8dbf4c 100644 --- a/README.md +++ b/README.md @@ -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--WioTrackerL1.uf2` | -| Seeed Wio Tracker L1 (E-ink) | nRF52840 | GxEPD2 250 × 122 | `solo--WioTrackerL1Eink.uf2` | +| Seeed Wio Tracker L1 (E-ink) | nRF52840 | GxEPD2 250 × 122 | `solo--WioTrackerL1Eink-landscape.uf2` or `-portrait.uf2` | | Seeed Wio Tracker L2 | ESP32-S3 | 320 × 240 touch LCD | `solo--Wio-Tracker-L2-merged.bin` | | GAT562 30S Mesh Kit | nRF52840 | SSD1306 128 × 64 | `solo--GAT562-30S-Mesh-Kit.uf2` | | GAT562 Mesh Watch13 *(experimental)* | nRF52840 | SSD1306 128 × 64 | `solo--GAT562-Mesh-Watch13.uf2` | diff --git a/docs/solo/README.md b/docs/solo/README.md index 42faf766..fe4b9119 100644 --- a/docs/solo/README.md +++ b/docs/solo/README.md @@ -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 diff --git a/docs/solo/developer/build-flags.md b/docs/solo/developer/build-flags.md index 3c40c7db..22ca99e4 100644 --- a/docs/solo/developer/build-flags.md +++ b/docs/solo/developer/build-flags.md @@ -84,6 +84,8 @@ existing `solo//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=` | How long the display stays on before auto-off. Default 15000 (15s); `0` disables auto-off entirely. | diff --git a/docs/solo/hardware.md b/docs/solo/hardware.md index 0c029608..fdfe5118 100644 --- a/docs/solo/hardware.md +++ b/docs/solo/hardware.md @@ -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 diff --git a/docs/solo/img/eink-channel.png b/docs/solo/img/eink-channel.png new file mode 100644 index 00000000..c9d32f9c Binary files /dev/null and b/docs/solo/img/eink-channel.png differ diff --git a/docs/solo/img/eink-clock.png b/docs/solo/img/eink-clock.png new file mode 100644 index 00000000..4976d445 Binary files /dev/null and b/docs/solo/img/eink-clock.png differ diff --git a/docs/solo/img/eink-sky.png b/docs/solo/img/eink-sky.png new file mode 100644 index 00000000..ef553f04 Binary files /dev/null and b/docs/solo/img/eink-sky.png differ diff --git a/variants/sim/build_wasm.sh b/variants/sim/build_wasm.sh index 182864a0..1698b793 100755 --- a/variants/sim/build_wasm.sh +++ b/variants/sim/build_wasm.sh @@ -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 diff --git a/variants/sim/target.cpp b/variants/sim/target.cpp index 89b8a73b..8d66ad57 100644 --- a/variants/sim/target.cpp +++ b/variants/sim/target.cpp @@ -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; diff --git a/variants/sim/web/index.html b/variants/sim/web/index.html index 94685161..1b58922a 100644 --- a/variants/sim/web/index.html +++ b/variants/sim/web/index.html @@ -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 { diff --git a/variants/sim/web/mesh.html b/variants/sim/web/mesh.html index dae3de40..ac42166c 100644 --- a/variants/sim/web/mesh.html +++ b/variants/sim/web/mesh.html @@ -238,7 +238,7 @@