From cfcfc832b136855a13cd82857b96b16f677f8126 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Tue, 27 Oct 2026 08:17:23 -0700 Subject: [PATCH] Keep Full C3 Companion queues within the DRAM budget Place 32 existing retained frames in RTC FAST without changing the 224 normal or 128 active-OTA queue capacities. Keep the default backing layout unchanged for other boards and cover ownership, wraparound and OTA transfers. --- build_legacy.sh | 5 + examples/companion_radio/MyMesh.h | 30 +++-- src/helpers/BorrowableFrameBuffer.h | 19 ++- src/helpers/CompanionRtcQueue.h | 8 +- test/test_companion_rtc_queue.py | 18 ++- test/test_companion_rtc_terminal_input.py | 5 +- test/test_companion_shared_ota_capacity.py | 131 +++++++++++++++++++-- test/test_shared_mota_queue.py | 29 ++++- 8 files changed, 213 insertions(+), 32 deletions(-) diff --git a/build_legacy.sh b/build_legacy.sh index bb34e3e03..ff3d69999 100755 --- a/build_legacy.sh +++ b/build_legacy.sh @@ -4587,6 +4587,11 @@ apply_companion_radio_full_profile() { # Reuse idle queue storage during explicit OTA; retain all 224 normal slots. if [[ "${env_name,,}" != generic_espnow_companion_radio_full ]]; then export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_SHARED_COMPANION_QUEUE=1 -DONE_KEY_DM_COMPACT_HELD=1" + if [[ "${env_name,,}" == xiao_c3_companion_radio_full ]]; then + # Move 32 of the existing 128 retained slots into byte-accessible + # RTC FAST. Both the 224 normal and 128 active-OTA limits stay intact. + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCOMPANION_RTC_FAST_OFFLINE_FRAMES=32" + fi else # Keep all 224 frames during OTA too; RTC FAST holds 32 existing # slots without borrowing the queue or shrinking held-DM storage. diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 68371f067..a15321283 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -1003,18 +1003,7 @@ private: int offline_queue_head; // Local onboarding only: RAM-backed like the receive queue, not RF traffic. uint8_t pending_emergency_notices[(MAX_GROUP_CHANNELS + 7) / 8] = {}; -#if defined(OTA_SHARED_COMPANION_QUEUE) - static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES = 128; - static_assert(OFFLINE_QUEUE_SIZE > OTA_RETAINED_OFFLINE_FRAMES, - "Shared mOTA queue must retain 128 frames and provide workspace storage"); - // The buffer also asserts that the complete context fits in the remaining - // frames. Constrained ESP32 recipes keep 224 normal slots; nRF52 and Paper keep 256. - mesh::BorrowableFrameBuffer offline_queue; - static mesh::ota::OtaContext* acquireOfflineQueueForOta(void* owner); - static uint16_t drainOfflineQueueForOta(void* owner); - static void releaseOfflineQueueFromOta(void* owner); -#elif COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES +#if COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES // Concrete definitions in MyMesh.cpp keep GCC from moving template-local // statics back into ordinary .bss despite the requested RTC attribute. #if COMPANION_RTC_FAST_OFFLINE_FRAMES @@ -1037,6 +1026,23 @@ private: return nullptr; #endif } +#endif +#if defined(OTA_SHARED_COMPANION_QUEUE) + static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES = 128; + static_assert(OFFLINE_QUEUE_SIZE > OTA_RETAINED_OFFLINE_FRAMES, + "Shared mOTA queue must retain 128 frames and provide workspace storage"); + // The buffer also asserts that the complete context fits in the remaining + // frames. Constrained ESP32 recipes keep 224 normal slots; nRF52 and Paper keep 256. + mesh::BorrowableFrameBuffer +#endif + > offline_queue; + static mesh::ota::OtaContext* acquireOfflineQueueForOta(void* owner); + static uint16_t drainOfflineQueueForOta(void* owner); + static void releaseOfflineQueueFromOta(void* owner); +#elif COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES mesh::CompanionRtcQueue offline_queue; #elif defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM) enum { diff --git a/src/helpers/BorrowableFrameBuffer.h b/src/helpers/BorrowableFrameBuffer.h index 16cdc1a8f..32fda417d 100644 --- a/src/helpers/BorrowableFrameBuffer.h +++ b/src/helpers/BorrowableFrameBuffer.h @@ -6,16 +6,28 @@ namespace mesh { +template class InlineRetainedFrames { + Frame frames[Count]; +public: + static constexpr size_t frame_count = Count; + Frame& at(size_t index) { return frames[index]; } +}; + // One owner at a time: an offline FIFO borrows the workspace's storage while // idle. Switching owners compacts the ring in place, without allocating RAM or // dropping unread frames. The caller owns the FIFO's length and head index. -template +// Qualified boards can supply a physically separate retained-frame bank. It +// replaces only existing retained slots, never the extra frames or workspace. +template> class BorrowableFrameBuffer { static_assert(Reserved > 0 && Reserved < Capacity, "Invalid shared queue capacities"); + static_assert(RetainedFrames::frame_count == Reserved, + "The retained backing must preserve every active-OTA slot"); struct ExtraFrames { Frame frames[Capacity - Reserved]; }; static_assert(sizeof(Workspace) <= sizeof(ExtraFrames), "mOTA exceeds borrowed queue storage"); - Frame _retained[Reserved]; + RetainedFrames _retained; union Extra { ExtraFrames queue; Workspace workspace; @@ -45,6 +57,7 @@ class BorrowableFrameBuffer { public: BorrowableFrameBuffer() = default; + BorrowableFrameBuffer(Frame* fast, Frame* slow) : _retained(fast, slow) {} BorrowableFrameBuffer(const BorrowableFrameBuffer&) = delete; BorrowableFrameBuffer& operator=(const BorrowableFrameBuffer&) = delete; ~BorrowableFrameBuffer() { @@ -53,7 +66,7 @@ public: size_t capacity() const { return _borrowed ? Reserved : Capacity; } Frame& at(size_t physical_index) { - return physical_index < Reserved ? _retained[physical_index] + return physical_index < Reserved ? _retained.at(physical_index) : _extra.queue.frames[physical_index - Reserved]; } diff --git a/src/helpers/CompanionRtcQueue.h b/src/helpers/CompanionRtcQueue.h index 4ac374675..692fe75ce 100644 --- a/src/helpers/CompanionRtcQueue.h +++ b/src/helpers/CompanionRtcQueue.h @@ -16,8 +16,7 @@ #if !defined(ESP32_PLATFORM) || !defined(COMPANION_RADIO_FULL) #error "RTC offline frames require a qualified ESP32 Full Companion recipe" #endif -#if defined(BOARD_HAS_PSRAM) || defined(OTA_SHARED_COMPANION_QUEUE) \ - || ONE_KEY_DM_SHARED_OFFLINE_QUEUE +#if defined(BOARD_HAS_PSRAM) || ONE_KEY_DM_SHARED_OFFLINE_QUEUE #error "RTC offline frames require the independent fixed-capacity queue" #endif #include @@ -36,6 +35,10 @@ #if COMPANION_RTC_SLOW_OFFLINE_FRAMES && !defined(CONFIG_IDF_TARGET_ESP32S3) #error "The separate RTC SLOW offline-frame bank requires ESP32-S3" #endif +#if defined(OTA_SHARED_COMPANION_QUEUE) \ + && (!defined(CONFIG_IDF_TARGET_ESP32C3) || COMPANION_RTC_SLOW_OFFLINE_FRAMES) +#error "RTC retained shared-OTA frames are qualified only on ESP32-C3 FAST memory" +#endif namespace mesh { namespace companion_rtc { @@ -75,6 +78,7 @@ template class CompanionRtcQueue { } public: + static constexpr size_t frame_count = Count; CompanionRtcQueue(Frame* fast, Frame* slow) : fast_frames(fast), slow_frames(slow) { if ((FastCount && (!fast || reinterpret_cast(fast) % 4)) || (SlowCount && (!slow || reinterpret_cast(slow) % 4))) abort(); diff --git a/test/test_companion_rtc_queue.py b/test/test_companion_rtc_queue.py index 100918bf0..bec0754cf 100644 --- a/test/test_companion_rtc_queue.py +++ b/test/test_companion_rtc_queue.py @@ -196,7 +196,10 @@ class CompanionRtcQueueTests(unittest.TestCase): source = (ROOT / 'examples/companion_radio/MyMesh.cpp').read_text() start = header.index('#elif COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES', header.index('static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES')) end = header.index('#elif defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)', start) - storage = header[start:].split('\n', 1)[1][:end - start - len(header[start:].split('\n', 1)[0]) - 1] + common_start = header.index(' // Concrete definitions in MyMesh.cpp') + common_end = header.index('\n#endif\n#if defined(OTA_SHARED_COMPANION_QUEUE)', common_start) + storage = (header[common_start:common_end] + '\n' + + header[start:].split('\n', 1)[1][:end - start - len(header[start:].split('\n', 1)[0]) - 1]) banks_start = source.index('#if COMPANION_RTC_FAST_OFFLINE_FRAMES\n') banks_end = source.index('bool MyMesh::Frame::isChannelMsg()', banks_start) methods = '\n'.join(extract_braced(source, name) for name in ( @@ -287,7 +290,8 @@ class CompanionRtcQueueTests(unittest.TestCase): base = ['ESP32_PLATFORM=1', 'COMPANION_RADIO_FULL=1', 'CONFIG_IDF_TARGET_ESP32C3=1', 'CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=1', 'COMPANION_RTC_FAST_OFFLINE_FRAMES=32'] cases = ([value for value in base if not value.startswith('CONFIG_IDF_TARGET_')], - base + ['BOARD_HAS_PSRAM=1'], base + ['OTA_SHARED_COMPANION_QUEUE=1'], + base + ['BOARD_HAS_PSRAM=1'], [value for value in base if not value.startswith('CONFIG_IDF_TARGET_')] + + ['OTA_SHARED_COMPANION_QUEUE=1', 'CONFIG_IDF_TARGET_ESP32S3=1'], base + ['ONE_KEY_DM_SHARED_OFFLINE_QUEUE=1'], base + ['COMPANION_RTC_SLOW_OFFLINE_FRAMES=32'], [value for value in base if not value.startswith('COMPANION_RADIO_FULL')], @@ -299,7 +303,7 @@ class CompanionRtcQueueTests(unittest.TestCase): capture_output=True, text=True, timeout=15) self.assertNotEqual(result.returncode, 0) - def test_only_five_named_full_policies_select_banks_without_capacity_reductions(self): + def test_only_named_full_policies_select_banks_without_capacity_reductions(self): source = (ROOT / 'build_legacy.sh').read_text() start = source.index(' # BLE + WiFi exhaust internal DRAM') start = source.index(' case "${env_name,,}" in', start) @@ -310,6 +314,8 @@ class CompanionRtcQueueTests(unittest.TestCase): self.assertNotIn('COMPANION_RTC_SLOW_OFFLINE_FRAMES', prefix) for target, fast, slow, contacts, queue in ( ('Generic_ESPNOW_companion_radio_full', 32, 0, 100, 224), + ('Xiao_C3_companion_radio_full', 32, 0, 100, 224), + ('Heltec_CT62_companion_radio_full', 0, 0, 100, 224), ('Heltec_Wireless_Tracker_companion_radio_full', 32, 32, 150, 256), ('MKE_s3_companion_radio_full', 32, 32, 350, 256), ('ThinkNode_M2_companion_radio_full', 32, 32, 350, 256), @@ -328,7 +334,11 @@ class CompanionRtcQueueTests(unittest.TestCase): self.assertEqual(int(flags.get('COMPANION_RTC_SLOW_OFFLINE_FRAMES', 0)), slow) self.assertEqual(int(flags.get('MAX_CONTACTS', 350)), contacts) self.assertEqual(int(flags.get('OFFLINE_QUEUE_SIZE', 256)), queue) - self.assertNotIn('OTA_SHARED_COMPANION_QUEUE', flags) + if target in ('Xiao_C3_companion_radio_full', 'Heltec_CT62_companion_radio_full'): + self.assertEqual(flags.get('OTA_SHARED_COMPANION_QUEUE'), '1') + self.assertEqual(flags.get('ONE_KEY_DM_COMPACT_HELD'), '1') + else: + self.assertNotIn('OTA_SHARED_COMPANION_QUEUE', flags) if __name__ == '__main__': diff --git a/test/test_companion_rtc_terminal_input.py b/test/test_companion_rtc_terminal_input.py index 8f596ae07..96593c055 100644 --- a/test/test_companion_rtc_terminal_input.py +++ b/test/test_companion_rtc_terminal_input.py @@ -209,7 +209,10 @@ int main() { self.assertEqual(flags.get('COMPANION_RTC_TERMINAL_INPUT')=='1', selected) self.assertEqual(int(flags['MAX_CONTACTS']), contacts) self.assertEqual(int(flags.get('OFFLINE_QUEUE_SIZE', 256)), queue) - self.assertNotIn('COMPANION_RTC_FAST_OFFLINE_FRAMES', flags) + if target == 'Xiao_C3_companion_radio_full': + self.assertEqual(flags.get('COMPANION_RTC_FAST_OFFLINE_FRAMES'), '32') + else: + self.assertNotIn('COMPANION_RTC_FAST_OFFLINE_FRAMES', flags) self.assertNotIn('COMPANION_RTC_SLOW_OFFLINE_FRAMES', flags) diff --git a/test/test_companion_shared_ota_capacity.py b/test/test_companion_shared_ota_capacity.py index 788393cb7..9d247ed65 100644 --- a/test/test_companion_shared_ota_capacity.py +++ b/test/test_companion_shared_ota_capacity.py @@ -3,6 +3,9 @@ from pathlib import Path import re +import resource +import signal +import shutil import subprocess import tempfile import unittest @@ -22,24 +25,41 @@ def method(text, signature): class SharedCompanionOtaCapacityTest(unittest.TestCase): - def compile(self, text, *, run=False): + def compile(self, text, *, run=False, rtc=False, args=(), expected_returncode=0): with tempfile.TemporaryDirectory(prefix="meshcore-shared-c3-") as temp: binary = Path(temp) / "queue" + (Path(temp) / "sdkconfig.h").write_text("#pragma once\n") + (Path(temp) / "esp_attr.h").write_text( + '#pragma once\n#define RTC_FAST_ATTR __attribute__((section(".rtc.force_fast.test")))\n') command = ["c++", "-std=c++17", "-Wall", "-Wextra", "-Werror", - "-I", str(ROOT / "src"), "-x", "c++", "-", + "-I", str(ROOT / "src"), "-I", temp, "-x", "c++", "-", "-o", str(binary)] + if rtc: + command[1:1] = ["-DNDEBUG", "-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", + "-DOTA_SHARED_COMPANION_QUEUE=1", "-DOFFLINE_QUEUE_SIZE=224", "-DCONFIG_IDF_TARGET_ESP32C3=1", + "-DCONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=1", + "-DCOMPANION_RTC_FAST_OFFLINE_FRAMES=32"] if run: command[1:1] = ["-fsanitize=address,undefined", "-g"] result = subprocess.run(command, input=text, text=True, capture_output=True) if run: self.assertEqual(result.returncode, 0, result.stderr) - result = subprocess.run([str(binary)], text=True, - capture_output=True) - self.assertEqual(result.returncode, 0, result.stderr) + if rtc: + objdump = shutil.which('objdump') + self.assertIsNotNone(objdump, 'objdump is required for the physical RTC bank check') + sections = subprocess.check_output([objdump, '-h', str(binary)], text=True) + match = re.search(r'\.rtc\.force_fast\.test\s+([0-9a-fA-F]+)', sections) + self.assertIsNotNone(match, sections) + self.assertEqual(int(match[1], 16), 32 * 177, sections) + def no_core_dump(): + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + result = subprocess.run([str(binary), *args], text=True, + capture_output=True, preexec_fn=no_core_dump) + self.assertEqual(result.returncode, expected_returncode, result.stderr) return result - def test_224_normal_slots_and_128_retained_slots_preserve_fifo_and_ownership(self): + def queue_fixture(self, rtc=False): source = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text() declaration = (ROOT / "examples/companion_radio/MyMesh.h").read_text() retained = re.search( @@ -52,10 +72,14 @@ class SharedCompanionOtaCapacityTest(unittest.TestCase): )) harness = r''' #include +#include +#include #include #include #include #include +#undef assert +#define assert(value) do { if (!(value)) std::abort(); } while (0) constexpr unsigned MAX_FRAME_SIZE = 176; namespace mesh { namespace ota { struct OtaContext { @@ -76,8 +100,7 @@ unsigned OtaContext::live = 0, OtaContext::constructions = 0, struct MyMesh { struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; }; static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES = @RETAINED@; - mesh::BorrowableFrameBuffer offline_queue; + @STORAGE@ int offline_queue_len = 0, offline_queue_head = 0; unsigned next = 0, notifications = 0; std::deque expected; @@ -112,6 +135,7 @@ struct MyMesh { } void pop() { uint8_t bytes[MAX_FRAME_SIZE]; assert(getFromOfflineQueue(bytes)); } }; +@BANKS@ @METHODS@ int main() { using mesh::ota::OtaContext; @@ -148,7 +172,96 @@ int main() { } } '''.replace("@RETAINED@", retained).replace("@METHODS@", methods) - self.compile(harness, run=True) + if rtc: + start = declaration.index(' // Concrete definitions in MyMesh.cpp') + end = declaration.index('\n#endif\n#if defined(OTA_SHARED_COMPANION_QUEUE)', start) + shared_start = declaration.index(' static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES') + queue_start = declaration.index(' mesh::BorrowableFrameBuffer offline_queue;') + banks = '' + return harness.replace('@STORAGE@', storage).replace('@BANKS@', banks) + + def test_224_normal_slots_and_128_retained_slots_preserve_fifo_and_ownership(self): + self.compile(self.queue_fixture(), run=True) + + def test_rtc_backed_224_and_128_fifo_all_heads_use_actual_mesh_storage_and_callbacks(self): + self.compile(self.queue_fixture(rtc=True), run=True, rtc=True) + + def test_rtc_owner_reset_geometry_and_release_build_safeguards(self): + harness = self.queue_fixture(rtc=True).split('int main() {', 1)[0] + r''' +using Frame = MyMesh::Frame; +using Workspace = mesh::ota::OtaContext; +using Inline = mesh::BorrowableFrameBuffer; +using Retained = mesh::CompanionRtcQueue; +using Split = mesh::BorrowableFrameBuffer; +struct Legacy { + Frame retained[128]; + union Extra { Frame frames[96]; Workspace workspace; Extra() {} ~Extra() {} } extra; + bool borrowed; +}; +static_assert(sizeof(Inline) == sizeof(Legacy), "Ordinary targets keep the exact BSS layout"); +static_assert(alignof(Inline) == alignof(Legacy), "Ordinary targets keep their alignment"); +static_assert(sizeof(Inline) - sizeof(Split) >= 32 * sizeof(Frame) - 2 * sizeof(void*) - alignof(Split), + "The complete 32-slot bank must leave internal DRAM"); +static_assert(Retained::frame_count == 128, "All active-OTA slots remain independent of workspace"); +static_assert(!std::is_copy_constructible::value && !std::is_move_constructible::value, + "A bank lease must never be duplicated"); +int main(int argc, char** argv) { + alignas(4) Frame bank[32]; + memset(bank, 0xA5, sizeof bank); + if (argc > 1 && !strcmp(argv[1], "null")) { Split bad(nullptr, nullptr); return 1; } + if (argc > 1 && !strcmp(argv[1], "misaligned")) { + Split bad(reinterpret_cast(reinterpret_cast(bank) + 1), nullptr); return 1; + } + if (argc > 1 && !strcmp(argv[1], "range")) { + Retained owner(bank, nullptr); owner.at(128); return 1; + } + { + Split owner(bank, nullptr); + for (const auto& frame : bank) { + assert(frame.len == 0); + for (uint8_t byte : frame.buf) assert(byte == 0); + } + for (unsigned i = 0; i < 128; ++i) { + auto& frame = owner.at(i); assert(frame.len == 0); + for (uint8_t byte : frame.buf) assert(byte == 0); + } + if (argc > 1 && !strcmp(argv[1], "second")) { Split second(bank, nullptr); return 1; } + if (argc > 1 && !strcmp(argv[1], "other-count")) { + mesh::CompanionRtcQueue second(bank, nullptr); return 1; + } + if (argc > 1 && !strcmp(argv[1], "foreign")) { + // A stale or foreign lease must be rejected, even with NDEBUG. + mesh::companion_rtc::Lease::owner() = nullptr; + owner.at(0); return 1; + } + for (unsigned i = 0; i < 224; ++i) memset(&owner.at(i), 0xBB, sizeof(Frame)); + int head = 217; + assert(owner.acquire(128, head) && head == 0 && owner.capacity() == 128); + // Destruction must end an active workspace exactly once before releasing its bank. + } + assert(Workspace::live == 0 && Workspace::constructions == Workspace::destructions); + Split next(bank, nullptr); + for (unsigned i = 0; i < 224; ++i) { + const auto& frame = next.at(i); assert(frame.len == 0); + for (uint8_t byte : frame.buf) assert(byte == 0); + } +} +''' + harness = harness.replace('#include ', '#include \n#include ') + self.compile(harness, run=True, rtc=True) + for argument in ('null', 'misaligned', 'second', 'other-count', 'foreign', 'range'): + with self.subTest(argument=argument): + self.compile(harness, run=True, rtc=True, args=(argument,), + expected_returncode=-signal.SIGABRT) def test_insufficient_workspace_or_no_retained_region_cannot_compile(self): harness = r''' diff --git a/test/test_shared_mota_queue.py b/test/test_shared_mota_queue.py index 9be466d99..3f8a0188b 100644 --- a/test/test_shared_mota_queue.py +++ b/test/test_shared_mota_queue.py @@ -17,7 +17,10 @@ class SharedMotaQueueTest(unittest.TestCase): def test_wireless_paper_wifi_listener_and_transfers_share_only_when_needed(self): self.run_transfer_test("ESP32_PLATFORM") - def run_transfer_test(self, platform): + def test_c3_rtc_retained_queue_preserves_real_ota_and_wifi_transfers(self): + self.run_transfer_test("ESP32_PLATFORM", rtc=True) + + def run_transfer_test(self, platform, rtc=False): with tempfile.TemporaryDirectory(prefix="meshcore-mota-queue-") as temp: binary = Path(temp) / "transfer" tinf = Path(temp) / "tinf.o" @@ -44,6 +47,30 @@ class SharedMotaQueueTest(unittest.TestCase): sources += ["src/helpers/esp32/WiFiOtaSeeder.cpp", "src/helpers/ota/MotaSourceSerial.cpp", "src/helpers/ota/FolderMotaStore.cpp"] + if rtc: + fixture = ROOT / "test/fixtures/shared_mota_queue" + source = (fixture / "test_shared_mota_queue.cpp").read_text() + source = source[:source.index(" Queue full;")] + "}\n" + source = source.replace("256", "224") + source = source.replace("struct Queue {", ( + "#include \n" + "alignas(4) RTC_FAST_ATTR Frame rtc_frames[32];\nstruct Queue {")) + source = source.replace( + "mesh::BorrowableFrameBuffer buffer;", + "mesh::BorrowableFrameBuffer> buffer;\n" + " Queue() : buffer(rtc_frames, nullptr) {}") + (Path(temp) / "test_shared_rtc.cpp").write_text(source) + (Path(temp) / "test_wifi_shared_queue.h").write_text( + (fixture / "test_wifi_shared_queue.h").read_text().replace("256", "224")) + (Path(temp) / "sdkconfig.h").write_text("#pragma once\n") + (Path(temp) / "esp_attr.h").write_text( + '#pragma once\n#define RTC_FAST_ATTR __attribute__((section(".rtc.force_fast.test")))\n') + sources[0] = str(Path(temp) / "test_shared_rtc.cpp") + flags += ["-DCOMPANION_RTC_FAST_OFFLINE_FRAMES=32", + "-DCONFIG_IDF_TARGET_ESP32C3=1", + "-DCONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=1", + "-I", str(fixture)] built = subprocess.run([ "c++", "-std=c++17", "-fsanitize=address,undefined", "-g", *flags, "-DOTA_SEEDER_ONLY=1",