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",