Files
MeshCore/tools/bible/README.md
mikecarper 8c698dc2d1 Reduce nRF52 Companion flash and stream Reader chapters
Use a 960-byte constant-time Ed25519 base table, compact hash/scalar code and unused-library trims while retaining Reader, BLE P-256 and the 8 KiB stack. RAK4631 Full saves 57,496 flash bytes and 3,152 static RAM bytes versus preview3.

Support complete chapters and verse ranges with bounded app continuation replies and cooperative terminal output. Use nonblocking TCP writes under reader backpressure. Add scalar/signature comparisons and a linked ARM crypto/stack CI gate.

Validated clean RAK4631, T1000-E, T096 and V4 Full builds, host regressions, and physical RAK chapter/reconnect and signing comparisons. Physical signing averages 35.9 ms versus 14.4 ms with the original table.
2026-10-13 01:02:30 -07:00

284 lines
16 KiB
Markdown

# Offline Reader lookup
ESP32 and nRF52 **Full Companion** USB terminals (and the ESP32 TCP terminal)
include an embedded 879-verse corpus from the World English Bible:
```text
get reader 3:16
get reader 2
get reader 2:2-12
```
Verb case and surrounding spaces are ignored. A chapter number selects the
whole chapter; `chapter:first-last` selects an inclusive range within it.
Other books and ranges crossing chapter boundaries are not supported.
USB/TCP/browser text terminals print each selected verse in full. The output
advances by at most 96 bytes per mesh loop and resumes after short or zero-byte
writes, so a chapter larger than the USB queue does not get truncated. A new
terminal command or a session disconnect cancels unfinished output.
The app's local USB/BLE CLI uses the existing bounded reply frames. Long verses
and selections include a `Next: get reader ...` continuation, preserving the
last verse of a range. An optional page number refers to the first verse in
that command: `get reader 21:17 2`. No radio request can access the reader.
No WiFi, phone, API key, filesystem
space or bootloader update is needed. Reduced/ordinary Companion and other
roles omit the corpus. `COMPANION_FEATURE_READER=0` excludes it from a Full build.
## On-device Reader
Full Companions using the `ui-new` display (including the V4) also offer a
Reader from the radio-settings page:
- Long press opens at the first reference, or resumes the saved position.
- Single click advances one screenful, then to the next verse.
- Double click goes back one screenful; at a verse boundary it shows the
previous verse's last screenful.
- Three taps jump to verse one of the next chapter; four taps jump to verse
one of the previous chapter. Navigation stops at the first and last chapters.
- Long press exits to the radio page. It does not invoke early-boot CLI rescue
while opening or closing the reader.
The header shows the reference and part count. Single-button builds alternate
`<- 2 tap 1 tap -> long press: exit` and `<<- 4 tap 3 tap ->>` in the footer
every three seconds, matching the message controls (which change channels).
These are taps of the user button. Narrower displays use `hold: X` for exit
or split the hint across lines. Both hint views reserve the same height; the
V4 keeps its existing single footer row. Touch/joystick builds keep their own
controls. The entry gesture stays hidden on the radio page. The footer has
reserved space in the pagination calculation, so text continues on another
page when necessary.
Text wraps at word boundaries without truncation. Both the screen and CLI use
the same text with plain ASCII quotes, apostrophes, dashes and spaces; wording
and capitalization are unchanged. Incoming messages are retained without
replacing the reader; Bluetooth pairing retains its existing priority. Existing
screen-sleep/wake behavior is retained: the first gesture while asleep wakes
the display.
Sub-160px displays use the same adaptive font selector as received messages:
Squeezed Regular 6 on panels at least 128x64 (or 64x128 when rotated), and
5px Picopixel on smaller panels. The reference and part counter retain their
normal header font, stacking on narrow rotated screens to avoid overlap. On
the V4's 128x64 display, five complete 8-pixel-spaced body rows fit above the
navigation hint, and reference 3:16 fits on one screen. Wrapping and pagination use the selected font's
actual character widths and line height. Any panel with either dimension at
least 160px uses its normal font, including rotated 80x160 panels. Larger display classes, and builds with
`UI_SMALL_MESSAGE_FONT=0`, retain the regular body font. Existing bookmarks
resume at the page containing the same text offset after a font/layout change.
The T096 and other ST7735 boards use native 160x80 coordinates and the normal
body font (five 10-pixel-spaced rows between the header and button hint). The existing color
framebuffer is reused, with no framebuffer RAM increase. ST7789, ST7789LCD,
NV3001B and GxEPD displays likewise expose their actual panel dimensions and
render pixels without coordinate scaling; portrait orientations are retained.
SenseCAP Indicator scaling is unchanged. Integer heading sizes remain explicit
font choices, not whole-screen coordinate scaling.
A 12-byte, versioned/checksummed bookmark stores the verse and text offset.
It is checkpointed after two seconds without navigation and flushed on exit,
screen sleep, or UI shutdown. Writes occur only for changed positions; failures
remain pending for retry. Abrupt power loss during the two-second debounce may
lose that last movement. Returning to the very first screen clears the bookmark
instead of saving a first-reference record. Opening and closing there creates no file.
The bookmark uses `/reader.pos` on the primary filesystem, with a verified `.tmp`
replacement and `.bak` fallback for interrupted saves. It does not change the
radio-preference layout, contacts, channels, or keys. A malformed bookmark
falls back to the previous valid record or the first reference; no filesystem is formatted.
The reader allocates only a small cursor object on first use, reusing the
same flash corpus and on-demand 2 KiB decode scratch as the terminal.
## Source and reproduction
`reader-web.json` contains the embedded corpus, copied from eBible.org's verse-per-line
**engwebp** source (World English Bible, American English Protestant edition,
a subset of the World English Bible Updated). The JSON records the archive URL,
source date and SHA-256 hashes. The verse-per-line export omits footnotes and
headings; punctuation and words are preserved as UTF-8 in this source file.
The packer converts curly quotes/apostrophes to straight ones, en/em dashes to
hyphens, and nonbreaking spaces to ordinary spaces before compression. No
letters or words are changed. Any other non-ASCII character is rejected for
review, never silently dropped or transliterated. This conversion happens on
the computer, with no firmware conversion buffer or extra decoding step.
No NLT or ERV text is included.
The text is [public domain](https://ebible.org/engwebp/copyright.htm).
World English Bible is a trademark of eBible.org; the source must not be
rewritten while continuing to label it WEB. The source and generated header
are versioned together so ordinary builds work offline and never fetch a
moving upstream text. See the [official download inventory](https://ebible.org/find/details.php?all=1&id=engwebp).
Regenerate or verify the firmware header:
```sh
python -m pip install -r requirements-build.txt
python tools/bible/pack_reader.py
python tools/bible/pack_reader.py --check
python tools/bible/pack_reader.py --compare
python test/test_companion_reader.py
```
To deliberately update the source, download `engwebp_vpl.zip` from the recorded
URL, then run `python tools/bible/import_web_reader.py /path/to/engwebp_vpl.zip`.
Review the plaintext changes and regenerate the header. This importer does
not download content automatically or add other books to the firmware.
## Compression and memory
Measured after ASCII typography conversion (96,568 bytes including verse
terminators, versus 98,911 bytes for the pinned UTF-8 source):
| Block limit | Blocks | DEFLATE bytes | Index bytes | Total flash data | Savings |
| --- | ---: | ---: | ---: | ---: | ---: |
| 1 KiB | 101 | 47,133 | 1,212 | 48,345 | 49.94% |
| **2 KiB** | **49** | **42,187** | **588** | **42,775** | **55.70%** |
| 4 KiB | 24 | 38,366 | 288 | 38,654 | 59.97% |
Savings in the table are relative to uncompressed ASCII. Google Zopfli 0.4.3
uses 15 optimization iterations to write standards-compliant raw DEFLATE, so
the existing firmware decoder and RAM use do not change. At the chosen 2 KiB
limit, the Zopfli asset is **2,097 bytes (4.67%)** smaller than the prior
44,872-byte UTF-8 level-9 DEFLATE data and index; that result includes the
existing ASCII typography conversion. No 6-bit packing is used.
The chosen 2 KiB limit saves 5,570 flash bytes over 1 KiB for another 1 KiB
of temporary decode storage. Going to 4 KiB saves only another 4,121 flash
bytes and doubles that buffer. The 2 KiB choice leaves more stack headroom
for nRF52's existing BLE/USB/radio calls. These figures include block-index
padding but exclude the small command code, attribution strings and decoder.
Compression runs on the computer. Firmware reuses the existing bounds-checked
C `tinf` decoder and expands one independent raw-DEFLATE block per lookup.
The block index stores only each block's first verse; scanning NUL separators
inside the decoded block avoids a large per-verse index. Text/indexes remain
in memory-mapped flash on ESP32 and nRF52. There is no permanent text buffer,
cache, or additional compression library. The terminal needs no heap allocation;
the screen reader's small cursor object is described above. The temporary
2 KiB output buffer is **in addition to** the decoder's trees and call stack.
A Cortex-M4 GCC 14.2 `-Os -fstack-usage` build of the RAK3401 Full Companion
reports a 2,088-byte frame for verse output, a 16-byte command dispatcher,
and about 1,720 bytes for the nested decoder calls. Including the terminal
and main-loop frames, the decode path is approximately **4.3 KiB**, before
framework/task and interrupt overhead; the nRF52 loop task has an 8 KiB stack.
Before the navigation-hint addition, the small-font reader's decode/draw
function had a 2,480-byte frame on RAK3401
and a 2,496-byte frame on V4. Its nested decode path is approximately 4.3 KiB
on RAK3401 and 4.7 KiB on V4, including the main-loop/UI frames but before
framework/task and interrupt overhead. The ESP32-S3 V4 terminal's corresponding
decode path is approximately 4.6 KiB.
This is a compiler-derived estimate, not a hardware stack high-water reading.
Ordinary non-reader commands never enter the decode-buffer function. Its object
file has zero `.data`/`.bss`; the screen reader adds only its small UI state.
Tests compare all 879 decoded verses and terminal responses with an independently
ASCII-normalized copy of the pinned plaintext source, check references across
every block boundary, exercise malformed references and corrupted block
bounds, and verify that unrelated build profiles exclude the data.
Reader tests additionally traverse every part of all 879 verses in both
directions, with and without the button hint, using regular and adaptive
5px/6px fonts at 128x64, 64x128, 160x80,
240x135, 72x40, 40x72, 128x32, 32x128, 64x48 and 48x64. They compare rendered
body pixels/text separately from the hint, check active-reader resizing across
font thresholds, and cover resume
within a verse, book/chapter boundaries, no writes at the start, clearing stale
bookmarks,
corruption, I/O failure at each replacement step, timer rollover and bookmark
migration from the older font. The small-font renderer leaves the underlying
driver/header font unchanged.
`preview_reader.py` can render the fixture's `--preview` output using an
installed Adafruit GFX `glcdfont.c` and Pillow for pixel-level V4 layout checks.
Adaptive-font body pixels come directly from the real shared renderer, not a
substitute desktop font.
Earlier verification on 2026-09-08: all ten host tests pass on Windows and Linux after
the ASCII conversion and integration with the smaller font at `03e42830`.
The small-font C++11 reader fixture also passes AddressSanitizer and
UndefinedBehaviorSanitizer, the 22 existing nRF52 ExtraFS contract tests pass,
and the separate shared-font check matches all 95 printable ASCII glyphs
against Adafruit Picopixel. All 13 memory-policy tests pass in WSL. The following
USA Cascade Full builds include the local ASCII Reader changes on top of
`03e42830`; both pass firmware-size, runtime-RAM and capability checks:
| Build | Flash bytes | Static RAM bytes | Runtime RAM available / required |
| --- | ---: | ---: | ---: |
| RAK3401 Full | 574,028 | 135,620 | 99,892 / 53,792 |
| V4 Full | 2,220,529 | 112,824 | 248,824 / 175,872 |
These totals also include the pulled contact-cache/message-font changes, so
they are not an isolated reader-size comparison with earlier builds. The
separate upstream contact-cache host suite could not run in this WSL image
because its OpenSSL development headers are absent; firmware compilation and
the nRF52 ExtraFS contract checks succeeded.
Firmware build/stack measurements are compiler checks, not hardware high-water
readings; those earlier builds were not flashed to hardware.
### Adaptive 6px/5px update, 2026-09-08
The reader now shares `SmallMessageText` with messages on base `8f5614c9`.
All ten reader host tests pass on both Windows and Linux. The actual reader
also passes AddressSanitizer and UndefinedBehaviorSanitizer across all ten
screen geometries above, including resizing and bookmark migration. The
separate shared-font test checks all 95 glyphs of both fonts against their
upstream fixtures; the four T096 footer/profile checks also pass.
The T096 Full FEM-on USA Cascade build uses 573,384 flash bytes and 135,452
static RAM bytes, with 100,060 runtime bytes available against 75,298 required.
Its ten capability markers pass. The application-only UF2 was installed on
the T096, then the full running version and references 1:1, 3:16 and 21:25 were
verified over USB. The API snapshots confirm unchanged identity, contacts,
channels, self settings, auto-add, tuning and custom variables. No bootloader
or filesystem was erased. Physical on-screen readability and button behavior
remain a user check, separate from these host-rendering and USB tests.
The V4 Full NimBLE build uses 1,826,513 flash bytes and 97,836 static RAM bytes,
with 263,936 internal runtime bytes available against 175,872 required. All 13
capability markers pass. The nonmerged application was installed in both
existing V4 app slots; their hashes were verified, and the bootloader, partition
table, NVS, OTA selector, SPIFFS and coredump regions compare byte-for-byte with
the pre-update full-flash backup. The full running version and references 1:1, 3:16,
21:17 (the longest verse) and 21:25 were verified over USB. Pre/post API snapshots
match for identity, contacts, channels, self settings, auto-add, tuning, custom
variables, Bluetooth name and default flood scope.
The subsequent T096 native-resolution build uses 573,288 flash bytes with the
same 135,452 static RAM bytes and runtime budget as the scaled build. The actual
driver header/drawing-method fixture passes in native and legacy modes, with
both constructor variants; it checks complete pixel coverage and all 95 native
6px glyphs. Linux additionally runs that fixture with address/undefined-behavior
sanitizers. The reader tests include 160x80 navigation and bookmark migration.
The adaptive reader's compiler-reported process frame is 2,504 bytes on T096
and 2,512 bytes on V4; these are not total stack high-water measurements.
### Native display profiles and normal-font cutoff, 2026-09-08
All five formerly scaled non-Indicator driver families now use native panel
coordinates: ST7735, ST7789, ST7789LCD, NV3001B and GxEPD. This includes both
portrait and landscape TFTs and the repeater observer dashboard. Controller
RAM offsets, panel wake/reset wiring and existing framebuffer allocations are
retained. ST7789 flush tests also cover widths above 255 and final partial
8-row bands, in both single- and double-buffer modes.
The small-font policy is runtime geometry-based: either dimension >=160px
selects the normal driver font. It does not change message-buffer capacity or
the compact-panel footer policy. Normal-font line heights drive message and
radio spacing; Indicator render profiles keep their existing geometry.
Validation: 35 Python-host tests (including actual-driver recording fixtures,
all 879 corpus verses across 17 geometries, shared fonts, wake and pairing),
30 native dashboard tests, and nine message-history/layout tests pass. Linux
pixel/transfer/font fixtures use address/undefined-behavior sanitizers. The
T096 normal-font routing check was rerun after its final assertion was added.
The T096 Full FEM-on USA Cascade build on `0058f721` plus these local changes
uses 573,480 flash bytes and 135,452 static RAM bytes. Runtime capacity is
100,060 bytes against 75,298 required; all ten capability checks pass. Its
application-only UF2 was installed, and USB reports
`v1.17.1.5-native-normal-0058f721`. References 1:1, 3:16 and 21:25 work. API snapshots
match for identity/private key, contacts, channels, self settings, auto-add,
tuning and custom variables. The bootloader and storage were not flashed.
Other panel families were host-tested, not physically flashed or visually
qualified in this step. Physical readability remains a user check.