From ebee99af813aacce6ded8967a3318faf04effeb4 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Thu, 17 Sep 2026 13:41:39 -0700 Subject: [PATCH] Consolidate companion components --- docs/terminal_chat_cli.md | 6 +- docs/v4_pixel5_font_trial.md | 2 +- examples/companion_radio/CompanionFeatures.h | 2 +- examples/companion_radio/DataStore.cpp | 18 ++-- examples/companion_radio/DataStore.h | 12 +-- examples/companion_radio/MyMesh.cpp | 10 +- examples/companion_radio/MyMesh.h | 6 +- .../{JohnReaderScreen.h => ReaderScreen.h} | 22 ++-- examples/companion_radio/ui-new/UITask.cpp | 68 ++++++------ examples/companion_radio/ui-new/UITask.h | 14 +-- ...{CompanionJohn.cpp => CompanionReader.cpp} | 26 ++--- .../{CompanionJohn.h => CompanionReader.h} | 10 +- ...onJohnConfig.h => CompanionReaderConfig.h} | 10 +- src/helpers/bible/{JohnReader.h => Reader.h} | 8 +- ...nBookmarkFiles.h => ReaderBookmarkFiles.h} | 24 ++--- ...ata.generated.h => ReaderData.generated.h} | 20 ++-- .../bible/{JohnLookup.h => ReaderLookup.h} | 2 +- src/helpers/ota/OtaTinf.c | 4 +- src/helpers/ota/tinf/README.meshcore.txt | 2 +- src/helpers/ota/tinf/tinflate.c | 2 +- .../Arduino.h | 0 .../test_reader_lookup.cpp} | 28 ++--- .../test_reader_screen.cpp} | 100 +++++++++--------- test/test_companion_inbox.py | 10 +- test/test_companion_reader.py | 54 +++++----- test/test_indicator_messages_profile.py | 4 +- test/test_message_navigation.py | 12 +-- test/test_reader_touch_coordinates.py | 2 +- tools/bible/README.md | 51 +++++---- ...mport_web_john.py => import_web_reader.py} | 10 +- tools/bible/{pack_john.py => pack_reader.py} | 34 +++--- .../bible/{john-web.json => reader-web.json} | 0 32 files changed, 286 insertions(+), 287 deletions(-) rename examples/companion_radio/ui-new/{JohnReaderScreen.h => ReaderScreen.h} (91%) rename src/helpers/{CompanionJohn.cpp => CompanionReader.cpp} (59%) rename src/helpers/{CompanionJohn.h => CompanionReader.h} (54%) rename src/helpers/{CompanionJohnConfig.h => CompanionReaderConfig.h} (58%) rename src/helpers/bible/{JohnReader.h => Reader.h} (95%) rename src/helpers/bible/{JohnBookmarkFiles.h => ReaderBookmarkFiles.h} (60%) rename src/helpers/bible/{JohnData.generated.h => ReaderData.generated.h} (99%) rename src/helpers/bible/{JohnLookup.h => ReaderLookup.h} (98%) rename test/fixtures/{companion_john => companion_reader}/Arduino.h (100%) rename test/fixtures/{companion_john/test_john.cpp => companion_reader/test_reader_lookup.cpp} (79%) rename test/fixtures/{companion_john/test_reader.cpp => companion_reader/test_reader_screen.cpp} (85%) rename tools/bible/{import_web_john.py => import_web_reader.py} (85%) rename tools/bible/{pack_john.py => pack_reader.py} (81%) rename tools/bible/{john-web.json => reader-web.json} (100%) diff --git a/docs/terminal_chat_cli.md b/docs/terminal_chat_cli.md index 2298ffead..0c3af9455 100644 --- a/docs/terminal_chat_cli.md +++ b/docs/terminal_chat_cli.md @@ -179,12 +179,12 @@ for the state machine and limitations. ESP32 and nRF52 Full Companions include an offline World English Bible lookup: ```text -get John 3:16 +get reader 3:16 ``` -This returns a single complete verse from John, using plain ASCII punctuation +This returns a single complete verse from the embedded corpus, using plain ASCII punctuation with unchanged wording and capitalization, through the local USB/TCP terminal. -See [John lookup and compression](https://github.com/mikecarper/MeshCore/blob/keymindCascade/tools/bible/README.md) for the +See [Reader lookup and compression](https://github.com/mikecarper/MeshCore/blob/keymindCascade/tools/bible/README.md) for the source, supported build profiles and memory costs. ``` diff --git a/docs/v4_pixel5_font_trial.md b/docs/v4_pixel5_font_trial.md index 0b11d4b37..c2f6919e0 100644 --- a/docs/v4_pixel5_font_trial.md +++ b/docs/v4_pixel5_font_trial.md @@ -51,7 +51,7 @@ CLI rescue instead; wait for that startup window to finish before opening the inbox. Exiting a message preview and the WiFi setup page's hold action remain available immediately. -The hidden [John reader](https://github.com/mikecarper/MeshCore/blob/keymindCascade/tools/bible/README.md#on-device-reader), opened +The hidden [Reader](https://github.com/mikecarper/MeshCore/blob/keymindCascade/tools/bible/README.md#on-device-reader), opened by a long press on the radio page, uses the same button hints. Three taps jump to the first verse of the next chapter; four taps jump to the first verse of the previous chapter. Chapter navigation stops at the beginning and end of diff --git a/examples/companion_radio/CompanionFeatures.h b/examples/companion_radio/CompanionFeatures.h index 224d3656b..45d98ec51 100644 --- a/examples/companion_radio/CompanionFeatures.h +++ b/examples/companion_radio/CompanionFeatures.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include // The role terminal is also used by WebConfig on WiFi-only Companions. Its diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 3a38a4baf..16e9b9c42 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -11,8 +11,8 @@ #include #include #endif -#if COMPANION_FEATURE_JOHN -#include +#if COMPANION_FEATURE_READER +#include #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) @@ -446,12 +446,12 @@ File DataStore::openRead(const char* filename) { return openRead(_fs, filename); } -#if COMPANION_FEATURE_JOHN +#if COMPANION_FEATURE_READER namespace { -class JohnBookmarkFiles { +class ReaderBookmarkFiles { FILESYSTEM* _fs; public: - explicit JohnBookmarkFiles(FILESYSTEM* fs) : _fs(fs) {} + explicit ReaderBookmarkFiles(FILESYSTEM* fs) : _fs(fs) {} bool exists(const char* path) { #if defined(NRF52_PLATFORM) bool present = false; @@ -496,20 +496,20 @@ public: }; } // namespace -bool DataStore::loadJohnBookmark(mesh::bible::Position& pos) { +bool DataStore::loadReaderBookmark(mesh::bible::Position& pos) { pos = mesh::bible::Position{}; #if defined(NRF52_PLATFORM) if (_primary_storage_unavailable) return false; #endif - JohnBookmarkFiles files(_fs); + ReaderBookmarkFiles files(_fs); return mesh::bible::loadReaderBookmark(files, pos); } -bool DataStore::saveJohnBookmark(mesh::bible::Position pos) { +bool DataStore::saveReaderBookmark(mesh::bible::Position pos) { #if defined(NRF52_PLATFORM) if (_primary_storage_unavailable) return false; #endif - JohnBookmarkFiles files(_fs); + ReaderBookmarkFiles files(_fs); return mesh::bible::saveReaderBookmark(files, pos); } #endif diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h index 2c33269d4..edea3ec11 100644 --- a/examples/companion_radio/DataStore.h +++ b/examples/companion_radio/DataStore.h @@ -8,9 +8,9 @@ #if MESH_CONTACT_CACHE #include #endif -#include -#if COMPANION_FEATURE_JOHN -#include +#include +#if COMPANION_FEATURE_READER +#include #endif class DataStoreHost { @@ -116,9 +116,9 @@ public: bool loadPrefs(CompanionNodePrefs& prefs, double& node_lat, double& node_lon); bool savePrefs(const CompanionNodePrefs& prefs, double node_lat, double node_lon); -#if COMPANION_FEATURE_JOHN - bool loadJohnBookmark(mesh::bible::Position& pos); - bool saveJohnBookmark(mesh::bible::Position pos); +#if COMPANION_FEATURE_READER + bool loadReaderBookmark(mesh::bible::Position& pos); + bool saveReaderBookmark(mesh::bible::Position pos); #endif void loadContacts(DataStoreHost* host); bool saveContacts(DataStoreHost* host, bool (*filter)(const ContactInfo& c) = NULL); diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 4d8e1b58f..19571ee7a 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -7881,8 +7881,8 @@ void MyMesh::handleTerminalCommand(char* command) { if (*command == 0) return; mesh::cli::normalizeCommandVerb(command); -#if COMPANION_FEATURE_JOHN - if (mesh::handleJohnCommand(command, terminalOutput())) return; +#if COMPANION_FEATURE_READER + if (mesh::handleReaderCommand(command, terminalOutput())) return; #endif char local_reply[160]; @@ -8362,8 +8362,8 @@ void MyMesh::handleTerminalCommand(char* command) { terminalOutput().print(" board\r\n"); terminalOutput().print(" version\r\n"); terminalOutput().print(" get storage.layout\r\n"); -#if COMPANION_FEATURE_JOHN - terminalOutput().print(" get John : (World English Bible, offline)\r\n"); +#if COMPANION_FEATURE_READER + terminalOutput().print(" get reader : (World English Bible, offline)\r\n"); #endif terminalOutput().print(" get pwrmgt.bootreason\r\n"); #if COMPANION_FEATURE_MEMORY_DIAGNOSTICS diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 2bbb947ba..8392a0a81 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -360,9 +360,9 @@ public: if (saved) _prefs.clearDirty(); return saved; } -#if COMPANION_FEATURE_JOHN - bool loadJohnBookmark(mesh::bible::Position& pos) { return _store->loadJohnBookmark(pos); } - bool saveJohnBookmark(mesh::bible::Position pos) { return _store->saveJohnBookmark(pos); } +#if COMPANION_FEATURE_READER + bool loadReaderBookmark(mesh::bible::Position& pos) { return _store->loadReaderBookmark(pos); } + bool saveReaderBookmark(mesh::bible::Position pos) { return _store->saveReaderBookmark(pos); } #endif #if ENV_INCLUDE_GPS == 1 diff --git a/examples/companion_radio/ui-new/JohnReaderScreen.h b/examples/companion_radio/ui-new/ReaderScreen.h similarity index 91% rename from examples/companion_radio/ui-new/JohnReaderScreen.h rename to examples/companion_radio/ui-new/ReaderScreen.h index 6f7039631..36e94d9ea 100644 --- a/examples/companion_radio/ui-new/JohnReaderScreen.h +++ b/examples/companion_radio/ui-new/ReaderScreen.h @@ -1,6 +1,6 @@ #pragma once -#include -#include +#include +#include #include #if UI_SMALL_MESSAGE_FONT || UI_BUTTON_READER_HINT #include @@ -8,7 +8,7 @@ // Included by UITask.cpp after the task and MyMesh declarations. Only a small // cursor/checkpoint object persists; the 2 KiB decode buffer is on demand. -class JohnReaderScreen : public UIScreen { +class ReaderScreen : public UIScreen { UITask* _task; DisplayDriver* _display; mesh::bible::ReaderBookmark _bookmark; @@ -79,12 +79,12 @@ class JohnReaderScreen : public UIScreen { // At most two lookups: the current verse, then its adjacent verse when // navigation crosses a boundary. No recursive decode or second buffer. for (int pass = 0; pass < 2; ++pass) { - const LookupResult result = mesh::readJohnVerse( + const LookupResult result = mesh::readReaderVerse( referenceAt(pos.verse), scratch, sizeof(scratch), text); if (result != LookupResult::Found) { if (draw) { body.setColor(UIColor::warning_txt); - body.drawTextEllipsized(0, top, width, "John unavailable"); + body.drawTextEllipsized(0, top, width, "Reader unavailable"); #if UI_BUTTON_READER_HINT #if UI_READER_TOUCH_BAR mesh::ui::drawReaderTouchBar(hint_text, _touch_bar); @@ -92,7 +92,7 @@ class JohnReaderScreen : public UIScreen { mesh::ui::drawButtonReaderHint(hint_text, hint); #endif #endif - } else _task->showAlert("John data error", 1500); + } else _task->showAlert("Reader data error", 1500); return; } memmove(scratch, text, strlen(text) + 1); @@ -114,7 +114,7 @@ class JohnReaderScreen : public UIScreen { if (!draw) return; char label[32], progress[16]; const Reference ref = referenceAt(pos.verse); - snprintf(label, sizeof(label), "John %u:%u", ref.chapter, ref.verse); + snprintf(label, sizeof(label), "Reader %u:%u", ref.chapter, ref.verse); snprintf(progress, sizeof(progress), "%u/%u", page.part, page.parts); const int progress_width = _display->getTextWidth(progress); if (stacked_header || _display->getTextWidth(label) > width - progress_width - 4) @@ -147,18 +147,18 @@ public: #if UI_READER_TOUCH_BAR const mesh::ui::TouchNavigationBar* readerTouchBar() const { return &_touch_bar; } #endif - JohnReaderScreen(UITask* task, DisplayDriver* display) : _task(task), _display(display) {} + ReaderScreen(UITask* task, DisplayDriver* display) : _task(task), _display(display) {} void open() { if (!_loaded) { mesh::bible::Position pos; - the_mesh.loadJohnBookmark(pos); + the_mesh.loadReaderBookmark(pos); _bookmark.restore(pos); _loaded = true; } } bool flush() { if (!_bookmark.dirty()) return true; - if (!the_mesh.saveJohnBookmark(_bookmark.position())) { + if (!the_mesh.saveReaderBookmark(_bookmark.position())) { _retry_at = millis() + 30000; return false; } @@ -186,7 +186,7 @@ public: } return true; } - if (key == KEY_ENTER || key == KEY_CANCEL) { _task->closeJohnReader(); return true; } + if (key == KEY_ENTER || key == KEY_CANCEL) { _task->closeReader(); return true; } return false; } }; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ebd6ccdb0..a6c1ec095 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -90,8 +90,8 @@ static uint64_t companionMessageElapsedMillis(uint64_t heard_millis) { #endif #endif -#if COMPANION_FEATURE_JOHN -#include "JohnReaderScreen.h" +#if COMPANION_FEATURE_READER +#include "ReaderScreen.h" #include #endif @@ -575,7 +575,7 @@ public: } void showFirstPage() { _page = HomePage::FIRST; } -#if COMPANION_FEATURE_JOHN +#if COMPANION_FEATURE_READER bool isRadioPage() const { return _page == HomePage::RADIO; } #endif @@ -1169,9 +1169,9 @@ public: _page = (_page + HomePage::Count - 1) % HomePage::Count; return true; } -#if COMPANION_FEATURE_JOHN +#if COMPANION_FEATURE_READER if (key == KEY_ENTER && _page == HomePage::RADIO) { - _task->showJohnReader(); + _task->showReader(); return true; } #endif @@ -1823,17 +1823,17 @@ void UITask::showMessages() { setCurrScreen(msg_preview); } -#if COMPANION_FEATURE_JOHN -void UITask::showJohnReader() { +#if COMPANION_FEATURE_READER +void UITask::showReader() { if (!_display || isPairingScreenActive()) return; if (!_display->isOn()) _display->wake(mesh::ui::DisplayWake::Button); - if (!john_reader) john_reader = new (std::nothrow) JohnReaderScreen(this, _display); - if (!john_reader) { showAlert("Reader: no memory", 1500); return; } - static_cast(john_reader)->open(); - setCurrScreen(john_reader); + if (!reader) reader = new (std::nothrow) ReaderScreen(this, _display); + if (!reader) { showAlert("Reader: no memory", 1500); return; } + static_cast(reader)->open(); + setCurrScreen(reader); } -void UITask::closeJohnReader() { +void UITask::closeReader() { // Home retains the selected radio page while the reader is open. setCurrScreen(home); } @@ -1911,8 +1911,8 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, // pairing completes or the pairing display window expires. _deferred_msg_preview = true; } -#if COMPANION_FEATURE_JOHN - else if (isJohnReaderActive()) { +#if COMPANION_FEATURE_READER + else if (isReaderActive()) { // Queue incoming messages without stealing the reader's page. _deferred_msg_preview = true; } @@ -1951,9 +1951,9 @@ void UITask::userLedHandler() { } void UITask::setCurrScreen(UIScreen* c) { -#if COMPANION_FEATURE_JOHN - if (isJohnReaderActive() && c != john_reader - && !static_cast(john_reader)->flush()) { +#if COMPANION_FEATURE_READER + if (isReaderActive() && c != reader + && !static_cast(reader)->flush()) { showAlert("Bookmark save failed", 1500); } #endif @@ -2033,9 +2033,9 @@ void UITask::servicePairingState() { _board->isExternalPowered() || _board->isUsbHostConnected(), hasConnection(), isPairingScreenActive())) { _next_refresh = 0; -#if COMPANION_FEATURE_JOHN - if (!_display->isOn() && isJohnReaderActive()) - static_cast(john_reader)->flush(); +#if COMPANION_FEATURE_READER + if (!_display->isOn() && isReaderActive()) + static_cast(reader)->flush(); #endif } } @@ -2045,8 +2045,8 @@ void UITask::servicePairingState() { */ void UITask::shutdown(bool restart){ if (!prepareForShutdown()) return; -#if COMPANION_FEATURE_JOHN - if (john_reader) static_cast(john_reader)->flush(); +#if COMPANION_FEATURE_READER + if (reader) static_cast(reader)->flush(); #endif #ifdef PIN_BUZZER @@ -2126,8 +2126,8 @@ void UITask::loop() { if (ev == BUTTON_EVENT_CLICK) { c = checkDisplayOn(KEY_ENTER); } else if (ev == BUTTON_EVENT_LONG_PRESS) { -#if COMPANION_FEATURE_JOHN - if (isJohnReaderActive() || (curr == home && static_cast(home)->isRadioPage())) +#if COMPANION_FEATURE_READER + if (isReaderActive() || (curr == home && static_cast(home)->isRadioPage())) c = handleLongPress(KEY_ENTER); else #endif @@ -2277,8 +2277,8 @@ void UITask::loop() { #endif if (curr) curr->poll(); -#if COMPANION_FEATURE_JOHN - if (john_reader && curr != john_reader) john_reader->poll(); +#if COMPANION_FEATURE_READER + if (reader && curr != reader) reader->poll(); #endif if (_display != NULL && _display->isOn()) { @@ -2372,9 +2372,9 @@ void UITask::getTouchControls(mesh::ui::TouchSplitSelector& transport_touch_sele #if UI_READER_TOUCH_BAR if (curr == msg_preview) reader_touch_bar = static_cast(msg_preview)->readerTouchBar(); -#if COMPANION_FEATURE_JOHN - else if (isJohnReaderActive()) - reader_touch_bar = static_cast(john_reader)->readerTouchBar(); +#if COMPANION_FEATURE_READER + else if (isReaderActive()) + reader_touch_bar = static_cast(reader)->readerTouchBar(); #endif #endif } @@ -2396,12 +2396,12 @@ char UITask::handleLongPress(char c) { // Exit an incoming message preview even during the startup rescue window. // The on-screen reader hint must not unexpectedly enter CLI rescue. if (curr == msg_preview) return c; -#if COMPANION_FEATURE_JOHN +#if COMPANION_FEATURE_READER // The radio page's reader gesture, including exit, takes precedence over // the global early-boot rescue gesture just like the WiFi setup page. - if (isJohnReaderActive()) { closeJohnReader(); return 0; } + if (isReaderActive()) { closeReader(); return 0; } if (curr == home && static_cast(home)->isRadioPage()) { - showJohnReader(); + showReader(); return 0; } #endif @@ -2428,8 +2428,8 @@ char UITask::handleDoubleClick(char c) { char UITask::handleMultiClick(char c, bool backwards) { MESH_DEBUG_PRINTLN("UITask: %s-click triggered", backwards ? "quadruple" : "triple"); if (curr == msg_preview -#if COMPANION_FEATURE_JOHN - || isJohnReaderActive() +#if COMPANION_FEATURE_READER + || isReaderActive() #endif ) { return checkDisplayOn(backwards ? KEY_UP : KEY_DOWN); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 815808733..327b56021 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -24,7 +24,7 @@ #include "../AbstractUITask.h" #include "../NodePrefs.h" -#include +#include class UITask : public AbstractUITask { DisplayDriver* _display; @@ -94,8 +94,8 @@ class UITask : public AbstractUITask { UIScreen* home; UIScreen* msg_preview = nullptr; UIScreen* curr; -#if COMPANION_FEATURE_JOHN - UIScreen* john_reader = nullptr; +#if COMPANION_FEATURE_READER + UIScreen* reader = nullptr; #endif void userLedHandler(); @@ -132,10 +132,10 @@ public: void gotoHomeScreen() { setCurrScreen(home); } void showMessages(); -#if COMPANION_FEATURE_JOHN - bool isJohnReaderActive() const { return john_reader && curr == john_reader; } - void showJohnReader(); - void closeJohnReader(); +#if COMPANION_FEATURE_READER + bool isReaderActive() const { return reader && curr == reader; } + void showReader(); + void closeReader(); #endif void showAlert(const char* text, int duration_millis); int getMsgCount() const { return _msgcount; } diff --git a/src/helpers/CompanionJohn.cpp b/src/helpers/CompanionReader.cpp similarity index 59% rename from src/helpers/CompanionJohn.cpp rename to src/helpers/CompanionReader.cpp index 4dc63da59..a3afd1629 100644 --- a/src/helpers/CompanionJohn.cpp +++ b/src/helpers/CompanionReader.cpp @@ -1,15 +1,15 @@ -#include "CompanionJohn.h" +#include "CompanionReader.h" -#if COMPANION_FEATURE_JOHN +#if COMPANION_FEATURE_READER #include -#include "bible/JohnLookup.h" +#include "bible/ReaderLookup.h" -#include "bible/JohnData.generated.h" +#include "bible/ReaderData.generated.h" namespace mesh { -bible::LookupResult readJohnVerse(bible::Reference ref, char* scratch, +bible::LookupResult readReaderVerse(bible::Reference ref, char* scratch, size_t capacity, const char*& text) { - return bible::lookup(bible::generated::johnCorpus, ref, scratch, capacity, text); + return bible::lookup(bible::generated::readerCorpus, ref, scratch, capacity, text); } namespace { @@ -19,30 +19,30 @@ namespace { __attribute__((noinline)) void printVerse(bible::Reference ref, Stream& output) { char scratch[bible::kBlockSize]; const char* text = nullptr; - const bible::LookupResult result = readJohnVerse(ref, scratch, sizeof(scratch), text); + const bible::LookupResult result = readReaderVerse(ref, scratch, sizeof(scratch), text); if (result == bible::LookupResult::Found) { - output.printf(" John %u:%u (%s)\r\n", static_cast(ref.chapter), - static_cast(ref.verse), bible::generated::johnCorpus.translation); + output.printf(" Reader %u:%u (%s)\r\n", static_cast(ref.chapter), + static_cast(ref.verse), bible::generated::readerCorpus.translation); // Do not feed long text through Adafruit Print::printf's 256-byte scratch. output.print(text); output.print("\r\n"); - output.print(bible::generated::johnCorpus.attribution); + output.print(bible::generated::readerCorpus.attribution); output.print("\r\n"); } else if (result == bible::LookupResult::Missing) { output.print(" This verse is not present in the supplied translation.\r\n"); } else { - output.print(" ERROR: invalid compressed John data\r\n"); + output.print(" ERROR: invalid compressed Reader data\r\n"); } } } // namespace -bool handleJohnCommand(const char* command, Stream& output) { +bool handleReaderCommand(const char* command, Stream& output) { bible::Reference ref = {}; const bible::ParseResult parsed = bible::parse(command, ref); if (parsed == bible::ParseResult::NoMatch) return false; if (parsed == bible::ParseResult::Invalid) { - output.print(" ERROR: use get John : (example: get John 3:16)\r\n"); + output.print(" ERROR: use get reader : (example: get reader 3:16)\r\n"); } else { printVerse(ref, output); } diff --git a/src/helpers/CompanionJohn.h b/src/helpers/CompanionReader.h similarity index 54% rename from src/helpers/CompanionJohn.h rename to src/helpers/CompanionReader.h index b77050976..ff3d9cc01 100644 --- a/src/helpers/CompanionJohn.h +++ b/src/helpers/CompanionReader.h @@ -1,14 +1,14 @@ #pragma once -#include "CompanionJohnConfig.h" +#include "CompanionReaderConfig.h" -#if COMPANION_FEATURE_JOHN -#include "bible/JohnLookup.h" +#if COMPANION_FEATURE_READER +#include "bible/ReaderLookup.h" class Stream; namespace mesh { // Caller-owned scratch lets the terminal and screen share one flash corpus. -bible::LookupResult readJohnVerse(bible::Reference ref, char* scratch, +bible::LookupResult readReaderVerse(bible::Reference ref, char* scratch, size_t capacity, const char*& text); // Terminal-only: verses can exceed the framed/remote CLI's 160-byte reply. -bool handleJohnCommand(const char* command, Stream& output); +bool handleReaderCommand(const char* command, Stream& output); } #endif diff --git a/src/helpers/CompanionJohnConfig.h b/src/helpers/CompanionReaderConfig.h similarity index 58% rename from src/helpers/CompanionJohnConfig.h rename to src/helpers/CompanionReaderConfig.h index cf9816b92..04e53db9c 100644 --- a/src/helpers/CompanionJohnConfig.h +++ b/src/helpers/CompanionReaderConfig.h @@ -1,16 +1,16 @@ #pragma once -#ifndef COMPANION_FEATURE_JOHN +#ifndef COMPANION_FEATURE_READER #if defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE) \ && (defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM)) - #define COMPANION_FEATURE_JOHN 1 + #define COMPANION_FEATURE_READER 1 #else - #define COMPANION_FEATURE_JOHN 0 + #define COMPANION_FEATURE_READER 0 #endif #endif -#if COMPANION_FEATURE_JOHN \ +#if COMPANION_FEATURE_READER \ && !(defined(COMPANION_RADIO_FULL) && defined(ENABLE_USB_INTERFACE) \ && (defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM))) - #error "John lookup requires an ESP32 or nRF52 Full Companion USB terminal" + #error "Reader lookup requires an ESP32 or nRF52 Full Companion USB terminal" #endif diff --git a/src/helpers/bible/JohnReader.h b/src/helpers/bible/Reader.h similarity index 95% rename from src/helpers/bible/JohnReader.h rename to src/helpers/bible/Reader.h index c7bc537c7..9d80c9582 100644 --- a/src/helpers/bible/JohnReader.h +++ b/src/helpers/bible/Reader.h @@ -1,5 +1,5 @@ #pragma once -#include "JohnLookup.h" +#include "ReaderLookup.h" namespace mesh { namespace bible { @@ -92,7 +92,7 @@ inline uint32_t bookmarkChecksum(const uint8_t* data) { return hash; } inline void encodeBookmark(Position pos, uint8_t (&out)[kBookmarkBytes]) { - out[0] = 'J'; out[1] = 'N'; out[2] = 1; out[3] = 0; + out[0] = 'R'; out[1] = 'D'; out[2] = 1; out[3] = 0; out[4] = pos.verse; out[5] = pos.verse >> 8; out[6] = pos.offset; out[7] = pos.offset >> 8; const uint32_t hash = bookmarkChecksum(out); @@ -100,7 +100,7 @@ inline void encodeBookmark(Position pos, uint8_t (&out)[kBookmarkBytes]) { } inline bool decodeBookmark(const uint8_t (&in)[kBookmarkBytes], Position& pos) { pos = Position{}; - if (in[0] != 'J' || in[1] != 'N' || in[2] != 1 || in[3] != 0) return false; + if (in[0] != 'R' || in[1] != 'D' || in[2] != 1 || in[3] != 0) return false; const uint32_t hash = bookmarkChecksum(in); for (unsigned i = 0; i < 4; ++i) if (in[8 + i] != static_cast(hash >> (8 * i))) return false; @@ -112,7 +112,7 @@ inline bool decodeBookmark(const uint8_t (&in)[kBookmarkBytes], Position& pos) { return true; } -// Debounced checkpointing: no writes just for opening John 1:1, no rewrite +// Debounced checkpointing: no writes just for opening reader 1:1, no rewrite // of unchanged positions, and a failed save remains pending for retry. class ReaderBookmark { Position _position, _saved; diff --git a/src/helpers/bible/JohnBookmarkFiles.h b/src/helpers/bible/ReaderBookmarkFiles.h similarity index 60% rename from src/helpers/bible/JohnBookmarkFiles.h rename to src/helpers/bible/ReaderBookmarkFiles.h index cd61a4e36..2cfc80458 100644 --- a/src/helpers/bible/JohnBookmarkFiles.h +++ b/src/helpers/bible/ReaderBookmarkFiles.h @@ -1,5 +1,5 @@ #pragma once -#include "JohnReader.h" +#include "Reader.h" namespace mesh { namespace bible { // A verified replacement plus one previous record also works on SPIFFS, @@ -9,8 +9,8 @@ template bool loadReaderBookmark(Files& files, Position& pos) { uint8_t data[kBookmarkBytes]; pos = Position{}; - if (files.read("/john.pos", data) && decodeBookmark(data, pos)) return true; - return files.read("/john.pos.bak", data) && decodeBookmark(data, pos); + if (files.read("/reader.pos", data) && decodeBookmark(data, pos)) return true; + return files.read("/reader.pos.bak", data) && decodeBookmark(data, pos); } template @@ -19,26 +19,26 @@ bool saveReaderBookmark(Files& files, Position pos) { if (pos.atStart()) { // Remove the fallback before the live record so a completed clear cannot // resurrect an earlier place on reboot. Do not create a start marker. - return files.remove("/john.pos.tmp") && files.remove("/john.pos.bak") - && files.remove("/john.pos"); + return files.remove("/reader.pos.tmp") && files.remove("/reader.pos.bak") + && files.remove("/reader.pos"); } uint8_t data[kBookmarkBytes], verify[kBookmarkBytes]; encodeBookmark(pos, data); - if (!files.remove("/john.pos.tmp") || !files.write("/john.pos.tmp", data)) return false; - if (!files.read("/john.pos.tmp", verify) || memcmp(data, verify, sizeof(data)) != 0) + if (!files.remove("/reader.pos.tmp") || !files.write("/reader.pos.tmp", data)) return false; + if (!files.read("/reader.pos.tmp", verify) || memcmp(data, verify, sizeof(data)) != 0) return false; - if (files.exists("/john.pos")) { + if (files.exists("/reader.pos")) { // Keep a valid fallback if the live record was interrupted/corrupt. Position old; // A failed read is not proof of corruption. Preserve the live record on // transient I/O failure rather than deleting it before publication. - if (!files.read("/john.pos", verify)) return false; + if (!files.read("/reader.pos", verify)) return false; if (decodeBookmark(verify, old)) { - if (!files.remove("/john.pos.bak") || !files.rename("/john.pos", "/john.pos.bak")) + if (!files.remove("/reader.pos.bak") || !files.rename("/reader.pos", "/reader.pos.bak")) return false; - } else if (!files.remove("/john.pos")) return false; + } else if (!files.remove("/reader.pos")) return false; } - if (!files.rename("/john.pos.tmp", "/john.pos")) return false; + if (!files.rename("/reader.pos.tmp", "/reader.pos")) return false; // Retain the last valid fallback until the next successful replacement. return true; } diff --git a/src/helpers/bible/JohnData.generated.h b/src/helpers/bible/ReaderData.generated.h similarity index 99% rename from src/helpers/bible/JohnData.generated.h rename to src/helpers/bible/ReaderData.generated.h index db955723c..4caebb2a9 100644 --- a/src/helpers/bible/JohnData.generated.h +++ b/src/helpers/bible/ReaderData.generated.h @@ -1,13 +1,13 @@ -// Generated by tools/bible/pack_john.py; do not edit. -// World English Bible (engwebp), John. Public domain; see tools/bible/README.md. +// Generated by tools/bible/pack_reader.py; do not edit. +// Offline Reader corpus. Public domain; see tools/bible/README.md. // Firmware typography: ASCII quotes, apostrophes, dashes and spaces; wording unchanged. // Canonical source SHA-256: 916bf9d1feae0ad410d69f61e6c5f328ac6acba10651d751b5bb29719732c49e #pragma once -#include "JohnLookup.h" +#include "ReaderLookup.h" namespace mesh { namespace bible { namespace generated { -static_assert(kBlockSize == 2048, "Regenerate John data after changing block size"); -static_assert(sizeof(BlockIndex) == 12, "John flash-size accounting expects a 12-byte index"); -static const uint8_t johnData[] = { +static_assert(kBlockSize == 2048, "Regenerate Reader data after changing block size"); +static_assert(sizeof(BlockIndex) == 12, "Reader flash-size accounting expects a 12-byte index"); +static const uint8_t readerData[] = { 0x8d,0x54,0xc1,0x6e,0xdb,0x30,0x0c,0xcd,0xa7,0x10,0xbe,0xf4,0x12,0xf8,0x03,0x76,0x29,0xb6,0x61,0xed, 0x5a,0x6c,0xbb,0xac,0x40,0xcf,0x72,0x4c,0x5b,0x5a,0x64,0x29,0x90,0xec,0x06,0xd9,0xd7,0xef,0x91,0x92, 0xdd,0xb4,0xdb,0x61,0x97,0xc4,0xa2,0x28,0xf2,0xf1,0xf1,0x91,0x0f,0x81,0x66,0xcb,0xd4,0xf1,0xe8,0x42, @@ -2175,7 +2175,7 @@ static const uint8_t johnData[] = { 0xc6,0x51,0x3c,0x7b,0xbd,0x92,0xc0,0xdb,0xfa,0x70,0xd9,0x94,0x44,0xf6,0xab,0x34,0x0b,0xc1,0x09,0x7c, 0x7b,0xf3,0x17, }; -static const BlockIndex johnBlocks[] = { +static const BlockIndex readerBlocks[] = { {0, 841, 1930, 0}, {841, 910, 1946, 22}, {1751, 884, 1881, 41}, @@ -2226,10 +2226,10 @@ static const BlockIndex johnBlocks[] = { {41668, 923, 2027, 854}, {42591, 712, 1669, 869}, }; -static const Corpus johnCorpus = { +static const Corpus readerCorpus = { "WEB", "World English Bible - public domain (eBible.org).", - johnData, sizeof(johnData), johnBlocks, - sizeof(johnBlocks) / sizeof(johnBlocks[0]), kVerseCount + readerData, sizeof(readerData), readerBlocks, + sizeof(readerBlocks) / sizeof(readerBlocks[0]), kVerseCount }; }}} diff --git a/src/helpers/bible/JohnLookup.h b/src/helpers/bible/ReaderLookup.h similarity index 98% rename from src/helpers/bible/JohnLookup.h rename to src/helpers/bible/ReaderLookup.h index 0d5dc37fb..0ff300a97 100644 --- a/src/helpers/bible/JohnLookup.h +++ b/src/helpers/bible/ReaderLookup.h @@ -71,7 +71,7 @@ inline ParseResult parse(const char* command, Reference& ref) { skipSpace(command); if (!token(command, "get")) return ParseResult::NoMatch; skipSpace(command); - if (!token(command, "john")) return ParseResult::NoMatch; + if (!token(command, "reader")) return ParseResult::NoMatch; skipSpace(command); if (!number(command, ref.chapter) || *command != ':') return ParseResult::Invalid; ++command; diff --git a/src/helpers/ota/OtaTinf.c b/src/helpers/ota/OtaTinf.c index 8e9a46a51..772997830 100644 --- a/src/helpers/ota/OtaTinf.c +++ b/src/helpers/ota/OtaTinf.c @@ -2,8 +2,8 @@ * Compile the vendored tinf core as C. Building the same source as C++ costs * several extra kilobytes with the embedded toolchains used by MeshCore. */ -#include "../CompanionJohnConfig.h" -#if defined(ENABLE_OTA) || defined(OTA_TRANSPORT_DEFLATE_TEST) || COMPANION_FEATURE_JOHN +#include "../CompanionReaderConfig.h" +#if defined(ENABLE_OTA) || defined(OTA_TRANSPORT_DEFLATE_TEST) || COMPANION_FEATURE_READER #if defined(__GNUC__) && !defined(__clang__) /* nRF52's Arduino recipe defaults to -Ofast, which more than doubles this * decoder. Keep the size policy local to tinf instead of changing MeshCore. */ diff --git a/src/helpers/ota/tinf/README.meshcore.txt b/src/helpers/ota/tinf/README.meshcore.txt index 1363233dc..4866796bf 100644 --- a/src/helpers/ota/tinf/README.meshcore.txt +++ b/src/helpers/ota/tinf/README.meshcore.txt @@ -13,7 +13,7 @@ MeshCore changes are marked in tinf.h and tinflate.c. They add tinf_uncompress_exact(), which accepts legal padding bits in the byte containing the final end-of-block code but rejects any trailing whole input byte. The C implementation is included by ../OtaTinf.c for ENABLE_OTA application builds, -COMPANION_FEATURE_JOHN offline lookup, and the native OTA test profile. +COMPANION_FEATURE_READER offline lookup, and the native OTA test profile. ../OtaDeflate.cpp is the small C++ callback adapter. Keeping the decoder in a true C translation unit avoids the several-kilobyte penalty produced by the embedded C++ toolchains. Firmware diff --git a/src/helpers/ota/tinf/tinflate.c b/src/helpers/ota/tinf/tinflate.c index 19370b913..98bc4b000 100644 --- a/src/helpers/ota/tinf/tinflate.c +++ b/src/helpers/ota/tinf/tinflate.c @@ -27,7 +27,7 @@ * MeshCore modifications (2026): * - expose tinf_uncompress_exact() to reject trailing whole input bytes; * - compile this vendored implementation through OtaTinf.c only, keeping - * it out of builds which need neither OTA nor offline John lookup. + * it out of builds which need neither OTA nor offline reader lookup. * See README.meshcore.txt for the upstream revision and integration details. */ diff --git a/test/fixtures/companion_john/Arduino.h b/test/fixtures/companion_reader/Arduino.h similarity index 100% rename from test/fixtures/companion_john/Arduino.h rename to test/fixtures/companion_reader/Arduino.h diff --git a/test/fixtures/companion_john/test_john.cpp b/test/fixtures/companion_reader/test_reader_lookup.cpp similarity index 79% rename from test/fixtures/companion_john/test_john.cpp rename to test/fixtures/companion_reader/test_reader_lookup.cpp index 8384d8177..7536888f2 100644 --- a/test/fixtures/companion_john/test_john.cpp +++ b/test/fixtures/companion_reader/test_reader_lookup.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #include #include #include @@ -10,23 +10,23 @@ using namespace mesh::bible; int main(int argc, char**) { Reference ref = {}; - for (const char* cmd : {"get John 3:16", " GET\tJoHn 3:16 \t", "get john 03:16"}) { + for (const char* cmd : {"get reader 3:16", " GET\treader 3:16 \t", "get reader 03:16"}) { assert(parse(cmd, ref) == ParseResult::Valid && ref.chapter == 3 && ref.verse == 16); } - for (const char* cmd : {"get John", "get John 0:1", "get John 22:1", - "get John 1:52", "get John 3:0", "get John -3:16", "get John 3:16-18", - "get John 3:16 junk", "get John 9999999999:1", "get John 1:9999999999", - "get John 3:", "get John 3:16\x1b", "get John 3 :16", "get John :16"}) { + for (const char* cmd : {"get reader", "get reader 0:1", "get reader 22:1", + "get reader 1:52", "get reader 3:0", "get reader -3:16", "get reader 3:16-18", + "get reader 3:16 junk", "get reader 9999999999:1", "get reader 1:9999999999", + "get reader 3:", "get reader 3:16\x1b", "get reader 3 :16", "get reader :16"}) { assert(parse(cmd, ref) == ParseResult::Invalid); } - for (const char* cmd : {"get name", "forget John 3:16", "get johnny 3:16", - "get 1 John 3:16", "getJohn 3:16", "", "get J", "get", "g"}) { + for (const char* cmd : {"get name", "forget reader 3:16", "get readerny 3:16", + "get 1 reader 3:16", "getreader 3:16", "", "get J", "get", "g"}) { assert(parse(cmd, ref) == ParseResult::NoMatch); Stream output; - assert(!mesh::handleJohnCommand(cmd, output) && output.text.empty()); + assert(!mesh::handleReaderCommand(cmd, output) && output.text.empty()); } assert(parse(nullptr, ref) == ParseResult::NoMatch); - const Corpus& corpus = generated::johnCorpus; + const Corpus& corpus = generated::readerCorpus; struct Guarded { uint32_t pre; char scratch[kBlockSize]; uint32_t post; } buf = {}; buf.pre = 0x11223344; buf.post = 0xaabbccdd; unsigned count = 0; @@ -36,9 +36,9 @@ int main(int argc, char**) { assert(lookup(corpus, {chapter, verse}, buf.scratch, kBlockSize, text) == LookupResult::Found); assert(buf.pre == 0x11223344 && buf.post == 0xaabbccdd); char cmd[32]; - snprintf(cmd, sizeof(cmd), "get John %u:%u", chapter, verse); + snprintf(cmd, sizeof(cmd), "get reader %u:%u", chapter, verse); Stream output; - assert(mesh::handleJohnCommand(cmd, output)); + assert(mesh::handleReaderCommand(cmd, output)); const std::string expected = "\r\n" + std::string(text) + "\r\n"; assert(output.text.find(expected) != std::string::npos); for (const unsigned char* c = reinterpret_cast(text); *c; ++c) @@ -88,7 +88,7 @@ int main(int argc, char**) { assert(text == nullptr && buf.pre == 0x11223344 && buf.post == 0xaabbccdd); } Stream invalid; - assert(mesh::handleJohnCommand("get John 3:99", invalid)); + assert(mesh::handleReaderCommand("get reader 3:99", invalid)); assert(invalid.text.find("ERROR") != std::string::npos); if (argc == 1) std::puts("All 879 verses, terminal output, parser and corruption checks passed"); } diff --git a/test/fixtures/companion_john/test_reader.cpp b/test/fixtures/companion_reader/test_reader_screen.cpp similarity index 85% rename from test/fixtures/companion_john/test_reader.cpp rename to test/fixtures/companion_reader/test_reader_screen.cpp index d85989941..a647b344c 100644 --- a/test/fixtures/companion_john/test_reader.cpp +++ b/test/fixtures/companion_reader/test_reader_screen.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include #include @@ -41,18 +41,18 @@ struct Files { struct FakeMesh { Files files; int saves = 0; - bool loadJohnBookmark(Position& p) { return loadReaderBookmark(files, p); } - bool saveJohnBookmark(Position p) { ++saves; return saveReaderBookmark(files, p); } + bool loadReaderBookmark(Position& p) { return mesh::bible::loadReaderBookmark(files, p); } + bool saveReaderBookmark(Position p) { ++saves; return mesh::bible::saveReaderBookmark(files, p); } } the_mesh; class UITask { public: bool closed = false; std::string alert; - void closeJohnReader() { closed = true; } + void closeReader() { closed = true; } void showAlert(const char* text, int) { alert = text; } }; -#include "../../../examples/companion_radio/ui-new/JohnReaderScreen.h" +#include "../../../examples/companion_radio/ui-new/ReaderScreen.h" class Display : public DisplayDriver { int x = 0, y = 0; @@ -141,25 +141,25 @@ int main(int argc, char** argv) { if (argc > 2) assert(std::freopen(argv[2], "w", stdout)); Display display(128, 64); UITask task; - JohnReaderScreen first(&task, &display); + ReaderScreen first(&task, &display); first.open(); first.render(display); - std::puts("FRAME 128 64 John 1:1"); + std::puts("FRAME 128 64 reader 1:1"); display.dump(); - assert(the_mesh.saveJohnBookmark(pos(91))); // John 3:16 - JohnReaderScreen verse(&task, &display); + assert(the_mesh.saveReaderBookmark(pos(91))); // reader 3:16 + ReaderScreen verse(&task, &display); verse.open(); for (int part = 0; part < (UI_SMALL_MESSAGE_FONT ? 1 : 2); ++part) { display.clear(); verse.render(display); - std::printf("FRAME 128 64 John 3:16 part %d\n", part + 1); + std::printf("FRAME 128 64 reader 3:16 part %d\n", part + 1); display.dump(); verse.handleInput(KEY_NEXT); } #if UI_SMALL_MESSAGE_FONT Display tiny(72, 40); - assert(the_mesh.saveJohnBookmark(pos(91))); - JohnReaderScreen tiny_verse(&task, &tiny); + assert(the_mesh.saveReaderBookmark(pos(91))); + ReaderScreen tiny_verse(&task, &tiny); tiny_verse.open(); tiny_verse.render(tiny); - std::puts("FRAME 72 40 John 3:16 on a tiny screen (5px)"); + std::puts("FRAME 72 40 reader 3:16 on a tiny screen (5px)"); tiny.dump(); tiny.resize(40, 72); tiny_verse.render(tiny); @@ -199,9 +199,9 @@ int main(int argc, char** argv) { auto measure = [&expected_text](const char* line) { return expected_text.getTextWidth(line); }; UITask task; the_mesh = FakeMesh{}; - JohnReaderScreen screen(&task, &display); + ReaderScreen screen(&task, &display); screen.open(); - assert(screen.flush() && the_mesh.saves == 0); // don't create John 1:1 + assert(screen.flush() && the_mesh.saves == 0); // don't create reader 1:1 #if UI_BUTTON_READER_HINT display.clear(); screen.render(display); Display footer(display.width(), display.height()); @@ -237,7 +237,7 @@ int main(int argc, char** argv) { char scratch[kBlockSize]; for (uint16_t verse = 0; verse < kVerseCount; ++verse) { const char* text; - assert(mesh::readJohnVerse(referenceAt(verse), scratch, sizeof(scratch), text) == LookupResult::Found); + assert(mesh::readReaderVerse(referenceAt(verse), scratch, sizeof(scratch), text) == LookupResult::Found); memmove(scratch, text, strlen(text) + 1); std::string rebuilt; uint16_t offset = 0; @@ -304,27 +304,27 @@ int main(int argc, char** argv) { touch_gesture(x, screen.readerTouchBar()->top, x, screen.readerTouchBar()->top); }; tap(2); assert(screen.flush()); - Position saved; assert(the_mesh.loadJohnBookmark(saved) && saved.verse == 1); + Position saved; assert(the_mesh.loadReaderBookmark(saved) && saved.verse == 1); tap(1); assert(screen.flush()); - assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart()); + assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart()); tap(3); assert(screen.flush()); - assert(the_mesh.loadJohnBookmark(saved) && referenceAt(saved.verse).chapter == 2); + assert(the_mesh.loadReaderBookmark(saved) && referenceAt(saved.verse).chapter == 2); tap(0); assert(screen.flush()); - assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart()); + assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart()); tap(4); assert(task.closed); task.closed = false; touch_gesture(120,70,120,70); assert(screen.flush()); - assert(the_mesh.loadJohnBookmark(saved) && saved.verse == 1); + assert(the_mesh.loadReaderBookmark(saved) && saved.verse == 1); touch_gesture(40,70,40,70); assert(screen.flush()); - assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart()); + assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart()); touch_gesture(120,70,40,70); assert(screen.flush()); - assert(the_mesh.loadJohnBookmark(saved) && saved.verse == 1); + assert(the_mesh.loadReaderBookmark(saved) && saved.verse == 1); touch_gesture(40,70,120,70); assert(screen.flush()); - assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart()); + assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart()); touch_gesture(80,100,80,50); assert(screen.flush()); - assert(the_mesh.loadJohnBookmark(saved) && referenceAt(saved.verse).chapter == 2); + assert(the_mesh.loadReaderBookmark(saved) && referenceAt(saved.verse).chapter == 2); touch_gesture(80,50,80,100); assert(screen.flush()); - assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart()); + assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart()); touch_gesture(80,0,80,0); assert(task.closed); #endif } @@ -337,7 +337,7 @@ int main(int argc, char** argv) { the_mesh = FakeMesh{}; Display chapters_display(128, 64); UITask chapters_task; - JohnReaderScreen chapters(&chapters_task, &chapters_display); + ReaderScreen chapters(&chapters_task, &chapters_display); chapters.open(); assert(chapters.handleInput(KEY_UP)); assert(chapters.flush() && the_mesh.saves == 0); @@ -345,7 +345,7 @@ int main(int argc, char** argv) { assert(chapters.handleInput(KEY_DOWN)); assert(chapters.flush()); Position saved; - assert(the_mesh.loadJohnBookmark(saved)); + assert(the_mesh.loadReaderBookmark(saved)); assert(referenceAt(saved.verse).chapter == chapter && saved.offset == 0); assert(referenceAt(saved.verse).verse == 1); } @@ -353,66 +353,66 @@ int main(int argc, char** argv) { assert(chapters.handleInput(KEY_DOWN)); assert(chapters.flush() && the_mesh.saves == writes); // Moving backwards from the middle of a chapter reaches the preceding one. - assert(the_mesh.saveJohnBookmark(pos(91, 12))); // 3:16, with a stale page offset - JohnReaderScreen middle(&chapters_task, &chapters_display); + assert(the_mesh.saveReaderBookmark(pos(91, 12))); // 3:16, with a stale page offset + ReaderScreen middle(&chapters_task, &chapters_display); middle.open(); assert(middle.handleInput(KEY_UP) && middle.flush()); Position chapter_saved; - assert(the_mesh.loadJohnBookmark(chapter_saved)); + assert(the_mesh.loadReaderBookmark(chapter_saved)); assert(referenceAt(chapter_saved.verse).chapter == 2); assert(referenceAt(chapter_saved.verse).verse == 1 && chapter_saved.offset == 0); assert(middle.handleInput(KEY_UP) && middle.flush()); - assert(!the_mesh.loadJohnBookmark(chapter_saved) && chapter_saved.atStart()); + assert(!the_mesh.loadReaderBookmark(chapter_saved) && chapter_saved.atStart()); // Resume within a split verse; independent screen and store lifecycle. the_mesh = FakeMesh{}; Display display(64,32); UITask task; - JohnReaderScreen first(&task, &display); + ReaderScreen first(&task, &display); first.open(); first.handleInput(KEY_NEXT); display.clear(); first.render(display); const auto body = display.body(); first.poll(); assert(the_mesh.saves == 0); test_now += 2000; first.poll(); assert(the_mesh.saves == 1); first.poll(); assert(the_mesh.saves == 1); Position saved; - assert(the_mesh.loadJohnBookmark(saved) && saved.verse == 0 && saved.offset > 0); - JohnReaderScreen rebooted(&task, &display); + assert(the_mesh.loadReaderBookmark(saved) && saved.verse == 0 && saved.offset > 0); + ReaderScreen rebooted(&task, &display); rebooted.open(); display.clear(); rebooted.render(display); assert(display.body() == body); rebooted.handleInput(KEY_PREV); assert(rebooted.flush()); - assert(!the_mesh.files.exists("/john.pos") && !the_mesh.files.exists("/john.pos.bak")); - assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart()); + assert(!the_mesh.files.exists("/reader.pos") && !the_mesh.files.exists("/reader.pos.bak")); + assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart()); #if UI_SMALL_MESSAGE_FONT // A bookmark from the old 6-pixel-wide font resumes on the new page that // contains those words; it does not advance to another verse or lose them. char scratch[kBlockSize]; const char* text; - assert(mesh::readJohnVerse({3, 16}, scratch, sizeof(scratch), text) == LookupResult::Found); + assert(mesh::readReaderVerse({3, 16}, scratch, sizeof(scratch), text) == LookupResult::Found); const auto legacy_measure = [](const char* line) { return strlen(line) * 6; }; const auto legacy = readerPage(text, 0, 128, 5, legacy_measure); assert(text[legacy.next]); the_mesh = FakeMesh{}; - assert(the_mesh.saveJohnBookmark(pos(91, legacy.next))); + assert(the_mesh.saveReaderBookmark(pos(91, legacy.next))); Display migrated_display(128,64); mesh::ui::SmallMessageText compact(migrated_display); const auto compact_measure = [&compact](const char* line) { return compact.getTextWidth(line); }; const auto compact_page = readerPage(text, legacy.next, 128, compact.lineCount(12, migrated_display.bodyBottom()), compact_measure); assert(compact_page.start <= legacy.next && compact_page.next > legacy.next); - JohnReaderScreen migrated(&task, &migrated_display); + ReaderScreen migrated(&task, &migrated_display); migrated.open(); migrated.render(migrated_display); assert(migrated.flush()); - assert(the_mesh.loadJohnBookmark(saved) && saved == pos(91, compact_page.start)); - assert(compact_page.parts == 1); // John 3:16 now fits in one V4 screen + assert(the_mesh.loadReaderBookmark(saved) && saved == pos(91, compact_page.start)); + assert(compact_page.parts == 1); // reader 3:16 now fits in one V4 screen // Reflow an already-open reader when panel geometry crosses the 5px/6px // threshold, and when a tiny panel rotates into the stacked-header layout. for (const auto& dimensions : {std::make_pair(72,40), {128,64}, {40,72}, {64,128}, {32,128}, {160,80}, {80,160}, {159,80}}) { - assert(the_mesh.saveJohnBookmark(pos(91, legacy.next))); + assert(the_mesh.saveReaderBookmark(pos(91, legacy.next))); Display changed(72,40); - JohnReaderScreen active(&task, &changed); + ReaderScreen active(&task, &changed); active.open(); active.render(changed); assert(active.flush()); - Position before; assert(the_mesh.loadJohnBookmark(before)); + Position before; assert(the_mesh.loadReaderBookmark(before)); changed.resize(dimensions.first, dimensions.second); mesh::ui::SmallMessageText small_font(changed); const bool small = changed.useSmallMessageFont(); @@ -424,10 +424,10 @@ int main(int argc, char** argv) { new_measure); assert(new_page.start <= before.offset && new_page.next > before.offset); active.render(changed); assert(active.flush()); - Position after; assert(the_mesh.loadJohnBookmark(after)); + Position after; assert(the_mesh.loadReaderBookmark(after)); assert(after == pos(91, new_page.start)); const auto rendered = changed.body(); - JohnReaderScreen reopened(&task, &changed); + ReaderScreen reopened(&task, &changed); reopened.open(); changed.clear(); reopened.render(changed); assert(changed.body() == rendered); } @@ -459,12 +459,12 @@ int main(int argc, char** argv) { } // A reset between renames recovers the previous record, not the .tmp. Files interrupted = original; - assert(interrupted.rename("/john.pos", "/john.pos.bak")); + assert(interrupted.rename("/reader.pos", "/reader.pos.bak")); assert(loadReaderBookmark(interrupted, saved) && saved == pos(4, 12)); assert(saveReaderBookmark(interrupted, pos(20))); assert(saveReaderBookmark(interrupted, pos(0)) && interrupted.data.empty()); Files damaged; - damaged.data["/john.pos"] = {1, 2, 3}; + damaged.data["/reader.pos"] = {1, 2, 3}; assert(!loadReaderBookmark(damaged, saved) && saved.atStart()); assert(saveReaderBookmark(damaged, pos(10))); assert(loadReaderBookmark(damaged, saved) && saved == pos(10)); diff --git a/test/test_companion_inbox.py b/test/test_companion_inbox.py index 0274a40fc..826ab8cda 100644 --- a/test/test_companion_inbox.py +++ b/test/test_companion_inbox.py @@ -159,20 +159,20 @@ int main() { displayPowerPrefs().inbox = DisplayInboxMode::Pending; assert(!messages.hasMessages()); -#if COMPANION_FEATURE_JOHN +#if COMPANION_FEATURE_READER // Receiving and downloading messages must leave an open reader alone. Screen reader; - task.john_reader = task.curr = &reader; + task.reader = task.curr = &reader; for (bool connected : {false, true}) { task.connected = connected; for (int i = 0; i < 2; ++i) { mesh.receive("while reading"); - assert(task.isJohnReaderActive()); + assert(task.isReaderActive()); } for (int remaining : {1, 0}) { mesh.download(); assert(task.getMsgCount() == remaining); - assert(task.isJohnReaderActive()); + assert(task.isReaderActive()); } } #endif @@ -284,7 +284,7 @@ int main() { with tempfile.TemporaryDirectory(prefix="mesh-inbox-test-") as directory: executable = Path(directory) / "inbox.exe" result = subprocess.run([ - compiler, "-std=c++17", f"-DCOMPANION_FEATURE_JOHN={reader_enabled}", + compiler, "-std=c++17", f"-DCOMPANION_FEATURE_READER={reader_enabled}", "-I" + str(ROOT / "src"), "-I" + str(ROOT / "test/mocks"), "-x", "c++", "-", str(ROOT / "src/helpers/ui/MomentaryButton.cpp"), str(ROOT / "src/helpers/ui/DisplayDriver.cpp"), "-o", str(executable), diff --git a/test/test_companion_reader.py b/test/test_companion_reader.py index 56063c84f..49361a3e1 100644 --- a/test/test_companion_reader.py +++ b/test/test_companion_reader.py @@ -14,8 +14,8 @@ from test_message_navigation import PREAMBLE from test_replay_reset_integration import extract_braced ROOT = Path(__file__).resolve().parents[1] -FIXTURE = ROOT / "test/fixtures/companion_john" -spec = importlib.util.spec_from_file_location("pack_john", ROOT / "tools/bible/pack_john.py") +FIXTURE = ROOT / "test/fixtures/companion_reader" +spec = importlib.util.spec_from_file_location("pack_reader", ROOT / "tools/bible/pack_reader.py") packer = importlib.util.module_from_spec(spec) spec.loader.exec_module(packer) @@ -48,10 +48,10 @@ class CompanionReaderTest(unittest.TestCase): def test_ascii_typography_preserves_words_case_and_pinned_source(self): original = copy.deepcopy(self.document) - sample = '\u201cJohn\u2019s\u00a0Word\u201d\u2014\u2018Test\u2019\u2013Yes!' - self.assertEqual(packer.ascii_verse(sample, 64), b'"John\'s Word"-\'Test\'-Yes!') - self.assertEqual(packer.ascii_verse('Already ASCII: John 3:16.', 64), - b'Already ASCII: John 3:16.') + sample = '\u201creader\u2019s\u00a0Word\u201d\u2014\u2018Test\u2019\u2013Yes!' + self.assertEqual(packer.ascii_verse(sample, 64), b'"reader\'s Word"-\'Test\'-Yes!') + self.assertEqual(packer.ascii_verse('Already ASCII: reader 3:16.', 64), + b'Already ASCII: reader 3:16.') # Bounds apply after conversion, not to the original multibyte form. self.assertEqual(packer.ascii_verse('\u201cHi\u201d', 4), b'"Hi"') with self.assertRaises(ValueError): @@ -92,8 +92,8 @@ class CompanionReaderTest(unittest.TestCase): str(ROOT / "src/helpers/ota/OtaTinf.c"), "-o", str(obj)]) self.run_checked([cxx, "-std=c++17", "-Os", "-Wall", "-Wextra", "-Werror", *flags, "-I" + str(ROOT / "src"), "-I" + str(FIXTURE), - str(FIXTURE / "test_john.cpp"), - str(ROOT / "src/helpers/CompanionJohn.cpp"), str(obj), "-o", str(binary)]) + str(FIXTURE / "test_reader_lookup.cpp"), + str(ROOT / "src/helpers/CompanionReader.cpp"), str(obj), "-o", str(binary)]) result = self.run_checked([str(binary), "--dump"]) actual = dict(line.split("\t", 1) for line in result.splitlines()) self.assertEqual(actual, self.expected_verses) @@ -105,28 +105,28 @@ class CompanionReaderTest(unittest.TestCase): for flags in ([], ["-DESP32_PLATFORM=1"], ["-DNRF52_PLATFORM=1"], ["-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1"], ["-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", - "-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_JOHN=0"]): + "-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_READER=0"]): result = self.run_checked([cxx, "-E", "-P", *flags, - str(ROOT / "src/helpers/CompanionJohn.cpp")]) - self.assertNotIn("johnData", result) - self.assertNotIn("handleJohnCommand", result) + str(ROOT / "src/helpers/CompanionReader.cpp")]) + self.assertNotIn("readerData", result) + self.assertNotIn("handleReaderCommand", result) def test_terminal_dispatch_is_separate_from_bounded_api_replies(self): source = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text(encoding="utf-8") start = source.index("void MyMesh::handleTerminalCommand(") end = source.index("\nvoid MyMesh::enterCLIRescue()", start) terminal = source[start:end] - self.assertEqual(source.count("mesh::handleJohnCommand("), 1) - self.assertLess(terminal.index("mesh::handleJohnCommand("), + self.assertEqual(source.count("mesh::handleReaderCommand("), 1) + self.assertLess(terminal.index("mesh::handleReaderCommand("), terminal.index("char local_reply[160]")) - self.assertIn("get John : (World English Bible, offline)", terminal) - self.assertEqual(terminal.count("#if COMPANION_FEATURE_JOHN"), 2) + self.assertIn("get reader : (World English Bible, offline)", terminal) + self.assertEqual(terminal.count("#if COMPANION_FEATURE_READER"), 2) def test_reader_navigation_and_bookmark_recovery(self): cc, cxx = shutil.which("gcc"), shutil.which("g++") if not cc or not cxx: self.skipTest("host GCC and G++ required") - with tempfile.TemporaryDirectory(prefix="meshcore-john-reader-") as directory: + with tempfile.TemporaryDirectory(prefix="meshcore-reader-reader-") as directory: directory = Path(directory) flags = ["-DNRF52_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"] obj, binary = directory / "tinf.o", directory / "reader.exe" @@ -139,21 +139,21 @@ class CompanionReaderTest(unittest.TestCase): f"-DUI_BUTTON_READER_HINT={button_hint}", f"-DUI_READER_TOUCH_BAR={touch_bar}", "-I" + str(ROOT / "src"), "-I" + str(FIXTURE), - str(FIXTURE / "test_reader.cpp"), str(ROOT / "src/helpers/CompanionJohn.cpp"), + str(FIXTURE / "test_reader_screen.cpp"), str(ROOT / "src/helpers/CompanionReader.cpp"), str(obj), "-o", str(binary)]) self.run_checked([str(binary)]) def test_reader_button_routing_and_profile_gate(self): ui = (ROOT / "examples/companion_radio/ui-new/UITask.cpp").read_text(encoding="utf-8") long_press = ui[ui.index("char UITask::handleLongPress("):] - self.assertLess(long_press.index("isJohnReaderActive()"), long_press.index("enterCLIRescue()")) + self.assertLess(long_press.index("isReaderActive()"), long_press.index("enterCLIRescue()")) self.assertLess(long_press.index("isRadioPage()"), long_press.index("enterCLIRescue()")) - self.assertIn("#if COMPANION_FEATURE_JOHN\n#include \"JohnReaderScreen.h\"", ui) + self.assertIn("#if COMPANION_FEATURE_READER\n#include \"ReaderScreen.h\"", ui) self.assertLess(ui.index("#define UI_BUTTON_READER_HINT 1"), - ui.index('#include "JohnReaderScreen.h"')) + ui.index('#include "ReaderScreen.h"')) # Queue downloads preserve the current screen unconditionally now; # test_companion_inbox exercises this with the reader enabled. - self.assertIn("else if (isJohnReaderActive())", ui) + self.assertIn("else if (isReaderActive())", ui) self.assertIn("c = handleLongPress(KEY_ENTER);", ui) self.assertIn("handleDoubleClick(KEY_PREV)", ui) @@ -166,7 +166,7 @@ struct Board { bool isUsbHostConnected() const { return host; } }; struct Interfaces { bool takePairingRequest() { return false; } }; -struct JohnReaderScreen : Screen { int saves = 0; void flush() { ++saves; } }; +struct ReaderScreen : Screen { int saves = 0; void flush() { ++saves; } }; ''' members = r''' Board* _board; @@ -189,12 +189,12 @@ int main() { displayPowerPrefs().usb = {DisplayMode::On, 5}; Display display; Screen home; - JohnReaderScreen reader; + ReaderScreen reader; Board board; Interfaces interfaces; UITask task(display); task._board = &board; task._interfaceManager = &interfaces; - task.curr = task.home = &home; task.john_reader = &reader; + task.curr = task.home = &home; task.reader = &reader; task.servicePairingState(); assert(!display.isOn() && reader.saves == 0); task.curr = &reader; @@ -204,7 +204,7 @@ int main() { assert(display.isOn() && reader.saves == 0); g_mock_millis = 5000; task.servicePairingState(); assert(!display.isOn()); - const int expected_saves = COMPANION_FEATURE_JOHN ? 1 : 0; + const int expected_saves = COMPANION_FEATURE_READER ? 1 : 0; assert(reader.saves == expected_saves); task.servicePairingState(); assert(reader.saves == expected_saves); // No repeated writes while dark. @@ -233,7 +233,7 @@ int main() { for enabled in (0, 1): with self.subTest(reader_enabled=enabled): binary = directory / "power.exe" - self.run_checked([compiler, "-std=c++17", f"-DCOMPANION_FEATURE_JOHN={enabled}", + self.run_checked([compiler, "-std=c++17", f"-DCOMPANION_FEATURE_READER={enabled}", "-I" + str(ROOT / "src"), "-I" + str(ROOT / "test/mocks"), str(directory / "power.cpp"), str(ROOT / "src/helpers/ui/MomentaryButton.cpp"), diff --git a/test/test_indicator_messages_profile.py b/test/test_indicator_messages_profile.py index 6ed7cc959..1830ad856 100644 --- a/test/test_indicator_messages_profile.py +++ b/test/test_indicator_messages_profile.py @@ -22,14 +22,14 @@ class IndicatorMessagesProfileTest(unittest.TestCase): # Compile the real gate: HAS_TOUCH must not overwrite the explicit # physical-button footer setting with zero. start = ui.index("#ifndef UI_BUTTON_READER_HINT") - gate = ui[start:ui.index("#if COMPANION_FEATURE_JOHN", start)] + gate = ui[start:ui.index("#if COMPANION_FEATURE_READER", start)] result = subprocess.run(["c++", "-E", "-P", "-x", "c++", "-"], input="#define HAS_TOUCH 1\n#define UI_BUTTON_READER_HINT 1\n" + gate + "\n#if UI_BUTTON_READER_HINT != 1\n#error reader hint disabled\n#endif\n", text=True, capture_output=True) self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("static_cast(msg_preview)->readerTouchBar()", ui) - self.assertIn("static_cast(john_reader)->readerTouchBar()", ui) + self.assertIn("static_cast(reader)->readerTouchBar()", ui) def test_indicator_enables_messages_home_page(self): profile = PROFILE.read_text() diff --git a/test/test_message_navigation.py b/test/test_message_navigation.py index f434703eb..884db6989 100644 --- a/test/test_message_navigation.py +++ b/test/test_message_navigation.py @@ -117,15 +117,15 @@ public: UIScreen* curr=nullptr; UIScreen* msg_preview=nullptr; UIScreen* home=nullptr; - UIScreen* john_reader=nullptr; + UIScreen* reader=nullptr; uint32_t _auto_off=0,_next_refresh=0; int buzzer_changes=0; int getMsgCount() const { return 0; } explicit UITask(DisplayDriver& display) : _display(&display) {} - bool isJohnReaderActive() const { return john_reader && curr==john_reader; } + bool isReaderActive() const { return reader && curr==reader; } void gotoHomeScreen() { curr=home; } void toggleBuzzer() { ++buzzer_changes; } - void showJohnReader() { curr=john_reader; } + void showReader() { curr=reader; } void showAlert(const char*,int) {} char handleLongPress(char c) { return c; } char handleMultiClick(char,bool); @@ -159,7 +159,7 @@ int main() { HomeScreen home(&task); Screen group; MsgPreviewScreen messages(&task); - task.home=&home; task.msg_preview=task.curr=&messages; task.john_reader=&group; + task.home=&home; task.msg_preview=task.curr=&messages; task.reader=&group; messages.addPreview(1,"Alice","public old",0,"Public"); messages.addPreview(1,"Bob","public new",0,"Public"); messages.addPreview(1,"Carol","second",2,"Second"); @@ -289,7 +289,7 @@ int main() { g_mock_millis+=25; task.pollButton(); g_mock_millis+=MOMENTARY_BUTTON_MULTI_CLICK_MS; task.pollButton(); assert(home.key==0); -#if COMPANION_FEATURE_JOHN +#if COMPANION_FEATURE_READER task.curr=&group; gesture(task,1); assert(group.key==KEY_NEXT && group.events==1); gesture(task,2); assert(group.key==KEY_PREV && group.events==2); @@ -391,7 +391,7 @@ class MessageNavigationTest(unittest.TestCase): binary = Path(temp) / "navigation" result = subprocess.run([ "c++", "-std=c++17", signedness, - f"-DCOMPANION_FEATURE_JOHN={enabled}", + f"-DCOMPANION_FEATURE_READER={enabled}", *flags, "-I" + str(ROOT / "src"), "-I" + str(ROOT / "test/mocks"), "-fsanitize=address,undefined", "-fno-pie", "-no-pie", diff --git a/test/test_reader_touch_coordinates.py b/test/test_reader_touch_coordinates.py index 372fb7844..3a13a9d5b 100644 --- a/test/test_reader_touch_coordinates.py +++ b/test/test_reader_touch_coordinates.py @@ -223,7 +223,7 @@ class ReaderTouchCoordinatesTest(unittest.TestCase): source = (ROOT / "examples/companion_radio/ui-new/UITask.cpp").read_text() self.assertIn("layout.header_divider_y + 1", source) self.assertIn("if (!_display->isOn()) reader_touch_bar = nullptr;", source) - reader = (ROOT / "examples/companion_radio/ui-new/JohnReaderScreen.h").read_text() + reader = (ROOT / "examples/companion_radio/ui-new/ReaderScreen.h").read_text() self.assertIn("makeReaderTouchBar(hint_text, bottom, top - 1)", reader) diff --git a/tools/bible/README.md b/tools/bible/README.md index bb3b8b7f9..472b1e069 100644 --- a/tools/bible/README.md +++ b/tools/bible/README.md @@ -1,10 +1,10 @@ -# Offline John lookup +# Offline Reader lookup ESP32 and nRF52 **Full Companion** USB terminals (and the ESP32 TCP terminal) -include the 879 verses of John from the World English Bible: +include an embedded 879-verse corpus from the World English Bible: ```text -get John 3:16 +get reader 3:16 ``` Book/verb case and surrounding spaces are ignored. One chapter:verse reference @@ -13,14 +13,14 @@ 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. +roles omit the corpus. `COMPANION_FEATURE_READER=0` excludes it from a Full build. -## On-device reader +## On-device Reader Full Companions using the `ui-new` display (including the V4) also offer a -reader from the radio-settings page: +Reader from the radio-settings page: -- Long press opens John 1:1 initially, or resumes the saved position. +- 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. @@ -50,8 +50,7 @@ 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 +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 @@ -70,18 +69,18 @@ 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. +instead of saving a first-reference record. Opening and closing there creates no file. -The bookmark uses `/john.pos` on the primary filesystem, with a verified `.tmp` +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 John 1:1; no filesystem is formatted. +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 -`john-web.json` contains only John, copied from eBible.org's verse-per-line +`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 @@ -102,14 +101,14 @@ moving upstream text. See the [official download inventory](https://ebible.org/f Regenerate or verify the firmware header: ```sh -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 +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_john.py /path/to/engwebp_vpl.zip`. +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. @@ -155,7 +154,7 @@ 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 +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 @@ -185,7 +184,7 @@ 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 +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 | @@ -205,7 +204,7 @@ 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 +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 @@ -214,7 +213,7 @@ 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 +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 @@ -225,7 +224,7 @@ 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, +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. @@ -235,7 +234,7 @@ 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. +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. @@ -254,7 +253,7 @@ 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), +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. @@ -263,7 +262,7 @@ 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 +`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 diff --git a/tools/bible/import_web_john.py b/tools/bible/import_web_reader.py similarity index 85% rename from tools/bible/import_web_john.py rename to tools/bible/import_web_reader.py index 2124f7d65..973d755ba 100644 --- a/tools/bible/import_web_john.py +++ b/tools/bible/import_web_reader.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Import only John from eBible.org's public-domain engwebp VPL download.""" +"""Import the source for the offline Reader from eBible.org's engwebp VPL download.""" import argparse import hashlib import json @@ -7,7 +7,7 @@ from pathlib import Path import re import zipfile -from pack_john import SOURCE, pack, references +from pack_reader import SOURCE, pack, references SOURCE_URL = "https://ebible.org/Scriptures/engwebp_vpl.zip" MEMBER = "engwebp_vpl.txt" @@ -24,10 +24,10 @@ def import_archive(path): continue match = re.fullmatch(r"JOH ([0-9]+:[0-9]+) (.+)", line) if not match or match[1] in verses: - raise ValueError("invalid or duplicate John verse in source") + raise ValueError("invalid or duplicate Reader verse in source") verses[match[1]] = match[2] if set(verses) != set(references()): - raise ValueError("the source must contain exactly the 879 verses of John") + raise ValueError("the source must contain exactly the 879 Reader verses") document = { "translation": "WEB", "edition": "World English Bible, American English, Protestant Edition (engwebp)", @@ -52,7 +52,7 @@ def main(): document = import_archive(args.archive) args.output.write_text(json.dumps(document, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n") - print(f"Imported {len(document['verses'])} verses of John; source {document['source_date']}") + print(f"Imported {len(document['verses'])} Reader verses; source {document['source_date']}") if __name__ == "__main__": diff --git a/tools/bible/pack_john.py b/tools/bible/pack_reader.py similarity index 81% rename from tools/bible/pack_john.py rename to tools/bible/pack_reader.py index b288f7b83..5165a9064 100644 --- a/tools/bible/pack_john.py +++ b/tools/bible/pack_reader.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Pack the pinned public-domain WEB John source into independent DEFLATE blocks.""" +"""Pack the pinned public-domain source for the offline Reader into DEFLATE blocks.""" import argparse import hashlib import json @@ -8,8 +8,8 @@ import unicodedata import zlib ROOT = Path(__file__).resolve().parents[2] -SOURCE = Path(__file__).with_name("john-web.json") -HEADER = ROOT / "src/helpers/bible/JohnData.generated.h" +SOURCE = Path(__file__).with_name("reader-web.json") +HEADER = ROOT / "src/helpers/bible/ReaderData.generated.h" BLOCK_SIZE = 2048 ASCII_TYPOGRAPHY = str.maketrans({ "\u00a0": " ", "\u2013": "-", "\u2014": "-", @@ -42,7 +42,7 @@ def ascii_verse(value, limit): try: encoded = value.translate(ASCII_TYPOGRAPHY).encode("ascii") except UnicodeEncodeError as error: - raise ValueError("unsupported non-ASCII character in John source") from error + raise ValueError("unsupported non-ASCII character in Reader source") from error if len(encoded) > limit: raise ValueError(f"text exceeds {limit} ASCII bytes") return encoded @@ -55,7 +55,7 @@ def pack(document, block_size=BLOCK_SIZE): checked_text(document["attribution"], 256) verses = document["verses"] if not isinstance(verses, dict) or set(verses) != set(references()): - raise ValueError("source must explicitly include all 879 John references") + raise ValueError("source must explicitly include all 879 Reader references") blocks, starts, current = [], [0], bytearray() for index, reference in enumerate(references()): verse = verses[reference] @@ -82,24 +82,24 @@ def render(document): packed, blocks = pack(document) digest = hashlib.sha256(json.dumps(document, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()).hexdigest() - lines = ["// Generated by tools/bible/pack_john.py; do not edit.", - "// World English Bible (engwebp), John. Public domain; see tools/bible/README.md.", + lines = ["// Generated by tools/bible/pack_reader.py; do not edit.", + "// Offline Reader corpus. Public domain; see tools/bible/README.md.", "// Firmware typography: ASCII quotes, apostrophes, dashes and spaces; wording unchanged.", f"// Canonical source SHA-256: {digest}", - "#pragma once", '#include "JohnLookup.h"', + "#pragma once", '#include "ReaderLookup.h"', "namespace mesh { namespace bible { namespace generated {", - f'static_assert(kBlockSize == {BLOCK_SIZE}, "Regenerate John data after changing block size");', - 'static_assert(sizeof(BlockIndex) == 12, "John flash-size accounting expects a 12-byte index");', - "static const uint8_t johnData[] = {"] + f'static_assert(kBlockSize == {BLOCK_SIZE}, "Regenerate Reader data after changing block size");', + 'static_assert(sizeof(BlockIndex) == 12, "Reader flash-size accounting expects a 12-byte index");', + "static const uint8_t readerData[] = {"] for start in range(0, len(packed), 20): lines.append(" " + ",".join(f"0x{v:02x}" for v in packed[start:start + 20]) + ",") - lines += ["};", "static const BlockIndex johnBlocks[] = {"] + lines += ["};", "static const BlockIndex readerBlocks[] = {"] lines += [" {%d, %d, %d, %d}," % block for block in blocks] - lines += ["};", "static const Corpus johnCorpus = {", + lines += ["};", "static const Corpus readerCorpus = {", f" {json.dumps(document['translation'], ensure_ascii=False)},", f" {json.dumps(document['attribution'], ensure_ascii=False)},", - " johnData, sizeof(johnData), johnBlocks,", - " sizeof(johnBlocks) / sizeof(johnBlocks[0]), kVerseCount", "};", "}}}", ""] + " readerData, sizeof(readerData), readerBlocks,", + " sizeof(readerBlocks) / sizeof(readerBlocks[0]), kVerseCount", "};", "}}}", ""] return "\n".join(lines) @@ -124,14 +124,14 @@ def main(): header = render(document) if args.check: if args.output.read_text(encoding="utf-8") != header: - raise SystemExit("John header differs; regenerate with tools/bible/pack_john.py") + raise SystemExit("Reader header differs; regenerate with tools/bible/pack_reader.py") else: args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(header, encoding="utf-8", newline="\n") packed, blocks = pack(document) plain = sum(block[2] for block in blocks) tables = len(blocks) * 12 - print(f"John: {len(references())} verses, {len(blocks)} blocks; " + print(f"Reader: {len(references())} verses, {len(blocks)} blocks; " f"{plain} plain bytes -> {len(packed)} DEFLATE + {tables} index bytes " f"({100 * (1 - (len(packed) + tables) / plain):.1f}% smaller incl. indexes)") diff --git a/tools/bible/john-web.json b/tools/bible/reader-web.json similarity index 100% rename from tools/bible/john-web.json rename to tools/bible/reader-web.json