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.
This commit is contained in:
mikecarper committed 2026-10-27 08:17:23 -07:00
1 parent d1a2fd74ba
commit cfcfc832b1
8 files changed
+213 -32

No files matched your search

+5
View File
@@ -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.
+18 -12
View File
@@ -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<Frame, OFFLINE_QUEUE_SIZE,
OTA_RETAINED_OFFLINE_FRAMES, mesh::ota::OtaContext> 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<Frame, OFFLINE_QUEUE_SIZE,
OTA_RETAINED_OFFLINE_FRAMES, mesh::ota::OtaContext
#if COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES
, mesh::CompanionRtcQueue<Frame, OTA_RETAINED_OFFLINE_FRAMES>
#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<Frame, OFFLINE_QUEUE_SIZE> offline_queue;
#elif defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
enum {
+16 -3
View File
@@ -6,16 +6,28 @@
namespace mesh {
template<class Frame, size_t Count> 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<class Frame, size_t Capacity, size_t Reserved, class Workspace>
// Qualified boards can supply a physically separate retained-frame bank. It
// replaces only existing retained slots, never the extra frames or workspace.
template<class Frame, size_t Capacity, size_t Reserved, class Workspace,
class RetainedFrames = InlineRetainedFrames<Frame, Reserved>>
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];
}
+6 -2
View File
@@ -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 <sdkconfig.h>
@@ -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<typename Frame, size_t Count> 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<uintptr_t>(fast) % 4))
|| (SlowCount && (!slow || reinterpret_cast<uintptr_t>(slow) % 4))) abort();
+14 -4
View File
@@ -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__':
+4 -1
View File
@@ -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)
+122 -9
View File
@@ -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 <helpers/BorrowableFrameBuffer.h>
#include <helpers/CompanionRtcQueue.h>
#include <cstdlib>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <deque>
#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<Frame, 224, OTA_RETAINED_OFFLINE_FRAMES,
mesh::ota::OtaContext> offline_queue;
@STORAGE@
int offline_queue_len = 0, offline_queue_head = 0;
unsigned next = 0, notifications = 0;
std::deque<unsigned> 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<Frame', shared_start)
queue_end = declaration.index(' static mesh::ota::OtaContext* acquireOfflineQueueForOta', queue_start)
storage = (declaration[start:end] + '\n' + declaration[queue_start:queue_end]
+ '\n MyMesh() : offline_queue(rtcFastQueueBank(), rtcSlowQueueBank()) {}\n')
bank_start = source.index('#if COMPANION_RTC_FAST_OFFLINE_FRAMES\n')
bank_end = source.index('bool MyMesh::Frame::isChannelMsg()', bank_start)
banks = source[bank_start:bank_end]
else:
storage = ('mesh::BorrowableFrameBuffer<Frame, 224, OTA_RETAINED_OFFLINE_FRAMES, '
'mesh::ota::OtaContext> 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<Frame, 224, 128, Workspace>;
using Retained = mesh::CompanionRtcQueue<Frame, 128>;
using Split = mesh::BorrowableFrameBuffer<Frame, 224, 128, Workspace, Retained>;
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<Split>::value && !std::is_move_constructible<Split>::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<Frame*>(reinterpret_cast<uint8_t*>(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<Frame, 224> 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<Frame>::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 <deque>', '#include <deque>\n#include <type_traits>')
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'''
+28 -1
View File
@@ -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 <helpers/CompanionRtcQueue.h>\n"
"alignas(4) RTC_FAST_ATTR Frame rtc_frames[32];\nstruct Queue {"))
source = source.replace(
"mesh::BorrowableFrameBuffer<Frame, 224, 128, OtaContext> buffer;",
"mesh::BorrowableFrameBuffer<Frame, 224, 128, OtaContext, "
"mesh::CompanionRtcQueue<Frame, 128>> 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",