Files
HaloKeymind/tools/bible

Offline John lookup

ESP32 and nRF52 Full Companion USB terminals (and the ESP32 TCP terminal) include the 879 verses of John from the World English Bible:

get John 3:16

Book/verb case and surrounding spaces are ignored. One chapter:verse reference is required; ranges, whole chapters and other books are not supported. ASCII verse text is printed in full, including verses longer than the ordinary 160-byte CLI response. This is a local terminal command, not a LoRa request or an extension of the framed Companion API. No WiFi, phone, API key, filesystem space or bootloader update is needed. Reduced/ordinary Companion and other roles omit the corpus. COMPANION_FEATURE_JOHN=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 John 1:1 initially, 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 John 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 John 1:1 record. Opening and closing there creates no file.

The bookmark uses /john.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 John 1:1; 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

john-web.json contains only John, 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. 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.

Regenerate or verify the firmware header:

python tools/bible/pack_john.py
python tools/bible/pack_john.py --check
python tools/bible/pack_john.py --compare
python test/test_companion_john.py

To deliberately update the source, download engwebp_vpl.zip from the recorded URL, then run python tools/bible/import_web_john.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 48,175 1,212 49,387 48.86%
2 KiB 49 43,303 588 43,891 54.55%
4 KiB 24 39,549 288 39,837 58.75%

Savings in the table are relative to uncompressed ASCII. At the chosen 2 KiB limit, ASCII typography saves 981 data bytes (2.19%) compared with the previous 44,872-byte UTF-8 DEFLATE data and index. No 6-bit packing is used.

The chosen 2 KiB limit saves 5,496 flash bytes over 1 KiB for another 1 KiB of temporary decode storage. Going to 4 KiB saves only another 4,054 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-John 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 John 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 John 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 John 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 John 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 John 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 John 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. John 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.