Enable contact caching for high-capacity nRF52 companions
This commit is contained in:
1 parent
8a5f735bba
commit
50428f0fb7
4 files changed
+69
-8
No files matched your search
@@ -4,8 +4,8 @@
|
||||
|
||||
# Companion contact caches
|
||||
|
||||
Full Companions on ESP32 and nRF52 without PSRAM, plus the high-contact RAK4631
|
||||
USB/BLE profiles, keep 16 outgoing paths
|
||||
Full Companions on ESP32 and nRF52 without PSRAM, plus nRF52 USB/BLE
|
||||
profiles with more than 300 contacts, keep 16 outgoing paths
|
||||
and 16 shared secrets in RAM. Every contact remains in the contact table;
|
||||
selecting a different contact loads its saved path as needed. This recovers
|
||||
about 25.4 KiB of internal RAM in the 350-contact ESP32 qualification builds.
|
||||
@@ -14,12 +14,20 @@ See the [qualification results](../test-results/companion_contact_cache_results.
|
||||
six-board RAM comparison, physical tests, and shared-secret timings.
|
||||
|
||||
PSRAM boards retain their existing complete contact table in external RAM.
|
||||
Most legacy USB/BLE/WiFi Companion profiles retain inline paths and secrets.
|
||||
The high-contact RAK4631 USB/BLE profiles also enable the same cache,
|
||||
Lower-contact legacy profiles and ordinary ESP32 USB/BLE/WiFi Companion
|
||||
profiles retain inline paths and secrets. High-contact nRF52 USB/BLE profiles
|
||||
(including Wio Tracker L1) enable the same cache,
|
||||
keeping all 350 contacts while making room for the shared 256-frame offline/DM
|
||||
pool. The build flag `MESH_CONTACT_CACHE=0` or `1` overrides this policy for
|
||||
qualification; it is not a runtime feature switch.
|
||||
|
||||
The ordinary Wio Tracker L1 BLE build reproduced a runtime RAM failure with
|
||||
48,964 bytes available against 59,968 required. Enabling the existing cache
|
||||
for high-contact nRF52 profiles recovered 30,200 bytes: the linked build has
|
||||
79,164 bytes available and a 19,196-byte margin against the unchanged budget.
|
||||
Its 350 contacts, 40 channels and configured 256-frame offline/held-DM pool
|
||||
are retained. This is a linked-capacity result, not a physical load/soak test.
|
||||
|
||||
## Paths and persistence
|
||||
|
||||
The 16 resident paths use least-recently-used replacement. Contact records
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
// PSRAM boards already keep the complete contact table outside internal RAM.
|
||||
// High-contact nRF52 USB/BLE recipes need the same cache as Full Companion:
|
||||
// inline paths/secrets can otherwise leave too little heap for startup.
|
||||
// An explicit 0/1 override is useful for matched qualification builds.
|
||||
#ifndef MESH_CONTACT_CACHE
|
||||
#if defined(COMPANION_RADIO_FULL) && !defined(BOARD_HAS_PSRAM) && \
|
||||
(defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM))
|
||||
#if !defined(BOARD_HAS_PSRAM) && \
|
||||
((defined(COMPANION_RADIO_FULL) && \
|
||||
(defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM))) || \
|
||||
(defined(NRF52_PLATFORM) && defined(MAX_CONTACTS) && MAX_CONTACTS > 300))
|
||||
#define MESH_CONTACT_CACHE 1
|
||||
#else
|
||||
#define MESH_CONTACT_CACHE 0
|
||||
|
||||
@@ -22,6 +22,30 @@ def crypto_objects(temp):
|
||||
|
||||
|
||||
class ContactCacheTest(unittest.TestCase):
|
||||
def test_default_policy_covers_high_contact_nrf52_without_full(self):
|
||||
cases = (
|
||||
({"NRF52_PLATFORM": 1, "MAX_CONTACTS": 350}, 1),
|
||||
({"NRF52_PLATFORM": 1, "MAX_CONTACTS": 301}, 1),
|
||||
({"NRF52_PLATFORM": 1, "MAX_CONTACTS": 300}, 0),
|
||||
({"NRF52_PLATFORM": 1}, 0),
|
||||
({"ESP32_PLATFORM": 1, "MAX_CONTACTS": 350}, 0),
|
||||
({"NRF52_PLATFORM": 1, "COMPANION_RADIO_FULL": 1}, 1),
|
||||
({"ESP32_PLATFORM": 1, "COMPANION_RADIO_FULL": 1}, 1),
|
||||
({"NRF52_PLATFORM": 1, "MAX_CONTACTS": 350, "BOARD_HAS_PSRAM": 1}, 0),
|
||||
({"ESP32_PLATFORM": 1, "COMPANION_RADIO_FULL": 1, "BOARD_HAS_PSRAM": 1}, 0),
|
||||
({"NRF52_PLATFORM": 1, "MAX_CONTACTS": 350, "MESH_CONTACT_CACHE": 0}, 0),
|
||||
({"NRF52_PLATFORM": 1, "MAX_CONTACTS": 100, "MESH_CONTACT_CACHE": 1}, 1),
|
||||
)
|
||||
for defines, expected in cases:
|
||||
with self.subTest(defines=defines):
|
||||
result = subprocess.run([
|
||||
"c++", "-E", "-P", "-x", "c++", "-",
|
||||
"-I", str(ROOT / "src"),
|
||||
*("-D%s=%s" % item for item in defines.items()),
|
||||
], input='#include <helpers/ContactCachePolicy.h>\nMESH_CONTACT_CACHE\n',
|
||||
text=True, capture_output=True, check=True)
|
||||
self.assertEqual(result.stdout.strip(), str(expected))
|
||||
|
||||
def test_esp32_misses_recalculate_without_flash_io(self):
|
||||
with tempfile.TemporaryDirectory(prefix="mesh-secret-power-") as temp:
|
||||
temp = Path(temp)
|
||||
@@ -44,6 +68,11 @@ class ContactCacheTest(unittest.TestCase):
|
||||
def test_nrf52_paged_store_migration_and_snapshots(self):
|
||||
self.run_cache_test("NRF52_PLATFORM")
|
||||
|
||||
def test_standard_nrf52_policy_preserves_migration_and_snapshots(self):
|
||||
# No Full or explicit cache flag: exercise the ordinary 350-contact
|
||||
# recipe's default through the same real cache/persistence harness.
|
||||
self.run_cache_test("NRF52_PLATFORM", standard_nrf52=True)
|
||||
|
||||
def test_esp32_default_crc_cadence_is_detected(self):
|
||||
self.run_cache_test("ESP32_PLATFORM", disable_crc_batch=True)
|
||||
|
||||
@@ -66,7 +95,8 @@ class ContactCacheTest(unittest.TestCase):
|
||||
disable_raw_recovery=False,
|
||||
unsafe_legacy_discovery=False, unsafe_page_discovery=False,
|
||||
fixture_transform=None, expected_failure=None,
|
||||
expected_marker='PASS: 114 shared request/login payload and routing traces'):
|
||||
expected_marker='PASS: 114 shared request/login payload and routing traces',
|
||||
standard_nrf52=False):
|
||||
with tempfile.TemporaryDirectory(prefix="mesh-contact-cache-") as temp:
|
||||
temp = Path(temp)
|
||||
store = (ROOT / "examples/companion_radio/DataStore.cpp").read_text()
|
||||
@@ -188,7 +218,7 @@ class ContactCacheTest(unittest.TestCase):
|
||||
result = subprocess.run([
|
||||
"c++", "-std=c++17", "-O1", "-g", "-Wall", "-Wextra",
|
||||
"-fsanitize=address,undefined", "-fno-omit-frame-pointer",
|
||||
"-DMESH_CONTACT_CACHE=1", "-DCOMPANION_RADIO_FULL=1",
|
||||
*([] if standard_nrf52 else ["-DMESH_CONTACT_CACHE=1", "-DCOMPANION_RADIO_FULL=1"]),
|
||||
"-DMESH_CONTACT_SECRET_FLASH_CACHE=1",
|
||||
"-D" + platform + "=1", "-DMAX_CONTACTS=350",
|
||||
"-I", str(ROOT / "test/fixtures/contact_cache/mocks"),
|
||||
|
||||
@@ -22,6 +22,25 @@ def method(source: str, signature: str) -> str:
|
||||
|
||||
|
||||
class OneKeyDMSharedQueueTest(unittest.TestCase):
|
||||
def test_standard_high_contact_nrf52_selects_shared_pool_by_default(self):
|
||||
header = (ROOT / "examples/companion_radio/MyMesh.h").read_text()
|
||||
start = header.index("#if defined(NRF52_PLATFORM) && MAX_CONTACTS > 300")
|
||||
end = header.index("#ifndef ROOM_MESSAGE_TIMESTAMP_CACHE_SIZE", start)
|
||||
policy = '#include <helpers/ContactCachePolicy.h>\n' + header[start:end]
|
||||
policy += '\nstatic_assert(MESH_CONTACT_CACHE == 1);\n'
|
||||
policy += 'static_assert(ONE_KEY_DM_SHARED_OFFLINE_QUEUE == 1);\n'
|
||||
policy += 'static_assert(OFFLINE_QUEUE_SIZE == 256);\nint main() {}\n'
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-dm-policy-") as temp:
|
||||
binary = Path(temp) / "policy.exe"
|
||||
for capacity in (301, 350):
|
||||
with self.subTest(capacity=capacity):
|
||||
result = subprocess.run([
|
||||
"c++", "-std=c++17", "-x", "c++", "-",
|
||||
"-DNRF52_PLATFORM=1", f"-DMAX_CONTACTS={capacity}",
|
||||
"-I", str(ROOT / "src"), "-o", str(binary),
|
||||
], input=policy, text=True, capture_output=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
def compile_and_run(self, source: str):
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-shared-dm-") as temp:
|
||||
path = Path(temp) / "shared_dm.cpp"
|
||||
|
||||
Reference in new issue
Block a user