Consolidate companion components

This commit is contained in:
mikecarper committed 2026-09-17 13:41:39 -07:00
1 parent aab8f5038a
commit ebee99af81
32 files changed
+286 -287

No files matched your search

@@ -1,15 +1,15 @@
#include "CompanionJohn.h"
#include "CompanionReader.h"
#if COMPANION_FEATURE_JOHN
#if COMPANION_FEATURE_READER
#include <Arduino.h>
#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<unsigned>(ref.chapter),
static_cast<unsigned>(ref.verse), bible::generated::johnCorpus.translation);
output.printf(" Reader %u:%u (%s)\r\n", static_cast<unsigned>(ref.chapter),
static_cast<unsigned>(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 <chapter>:<verse> (example: get John 3:16)\r\n");
output.print(" ERROR: use get reader <chapter>:<verse> (example: get reader 3:16)\r\n");
} else {
printVerse(ref, output);
}
@@ -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
@@ -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
@@ -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<uint8_t>(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;
@@ -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 <typename Files>
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 <typename Files>
@@ -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;
}
@@ -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
};
}}}
@@ -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;
+2 -2
View File
@@ -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. */
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
*/