Restore Wireless Paper Full capacity with shared mOTA queue storage

This commit is contained in:
mikecarper committed 2026-09-08 08:09:42 -07:00
1 parent e0a229fb91
commit 1e4d1e167a
12 files changed
+379 -42

No files matched your search

+8 -1
View File
@@ -3608,8 +3608,15 @@ apply_companion_radio_full_profile() {
# measured-safe tables for FULL OTA without changing ordinary USB/BLE/WiFi
# companion builds.
case "${env_name,,}" in
heltec_wireless_paper_companion_radio_full)
# The cold mOTA workspace fits in half the offline queue. Keep the
# board's 350 contacts and all simultaneous Full transports.
append_platformio_build_unflags "-DMAX_CONTACTS=150 -DOFFLINE_QUEUE_SIZE=512 -DOFFLINE_QUEUE_SIZE=128 -DOFFLINE_QUEUE_SIZE=16"
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DMAX_CONTACTS=350 -DOFFLINE_QUEUE_SIZE=256 -DOTA_SHARED_COMPANION_QUEUE=1"
record_build_reduction \
"Wireless Paper Full: 350 contacts; 256 offline frames normally, 128 while mOTA borrows queue storage"
;;
generic_espnow_companion_radio_full|\
heltec_wireless_paper_companion_radio_full|\
heltec_wireless_tracker_companion_radio_full|\
heltec_ct62_companion_radio_full|\
heltec_v3_companion_radio_full|\
+10 -1
View File
@@ -40,6 +40,13 @@ closed. `MESH_MIN_RUNTIME_HEAP` can raise a profile's requirement; it cannot
lower the calculated requirement. Add an allocation allowance when adding a
display, transport or other substantial feature.
Wireless Paper Full keeps 350 contacts and 256 offline frames by lending the
upper 128 queue slots to its mOTA workspace during a session. An idle WiFi
mOTA listener leaves all 256 slots available. More than 128 unread frames
refuses the loan; sync messages with an app first. USB/TCP source detach or
disconnect returns all 256 slots and releases the ESP32 proof/leaf scratch
buffers. The display and simultaneous USB, Bluetooth and WiFi remain enabled.
## Release evidence and regression tests
Each newly built firmware has a matching `.memory.json` report. It records
@@ -65,7 +72,9 @@ binding and the published T096 failing budget. Shared mOTA tests exercise
complete transfers, queue wraparound, unread-message order, source ownership,
stop/disconnect and repeated reuse. Bluetooth tests inject task and service
startup failures. The manual staging buffer also has allocation-failure and
repeated release tests.
repeated release tests. ESP32 tests run the actual WiFi mOTA listener and source
framing through complete transfers, idle polling, queue-full refusal, network
loss, CLI detach and USB/TCP ownership changes under address/leak sanitizers.
For older releases without saved ELFs, an audit can compare their ESP allocator
tables against a matching pinned SDK ELF and read reservations from the
+9
View File
@@ -10,6 +10,15 @@ not require rebuilding it. USB remains the normal way to update the Companion
itself. WiFi/Bluetooth updates are optional, and Full does not install LoRa
MOTA packages on itself.
The Wireless Paper Full memory-sharing update keeps 350 contacts and 256
queued messages normally. During an mOTA session, 128 message slots are
available while the other half holds its workspace. If more than 128 unread
messages are queued, sync them with an app before starting mOTA. Disconnecting
the source or turning it off returns all 256 slots. Idle WiFi mOTA listening
does not borrow the queue. See the [1.17.1.5 release guide](releases/1.17.1.5.md)
for the corrected download; the earlier `aa20e927` Wireless Paper image has
150 contacts.
## Open the text terminal
Close the app or serial monitor using the USB port, then open a terminal at
+20 -9
View File
@@ -39,11 +39,11 @@ bool networkReady() {
void detachTcpFolder() {
if (!tcp_folder_attached) return;
OtaContext& context = ota_ctx();
if (context.folderLink() == OtaContext::FOLDER_LINK_TCP) {
context.detach_folder();
context.clear_folder_dest();
context.manager.announce();
OtaContext* context = ota_context_if_active();
if (context && context->folderLink() == OtaContext::FOLDER_LINK_TCP) {
context->detach_folder();
context->clear_folder_dest();
context->manager.announce();
}
tcp_folder_attached = false;
}
@@ -74,11 +74,15 @@ void WiFiOtaSeeder::loop() {
if (!listener_active) return;
OtaContext& context = ota_ctx();
// An idle listener must leave all 256 queue slots available. A CLI detach
// can also return the shared context before the next WiFi poll.
OtaContext* active = ota_context_if_active();
if (WiFiOtaSeederPolicy::tcpFolderWasDetached(tcp_folder_attached,
context.folder_active)) {
active && active->folder_active &&
active->folderLink() == OtaContext::FOLDER_LINK_TCP)) {
if (seeder_client) seeder_client.stop();
context.clear_folder_dest();
// A different host transport may already own the replacement context.
if (active && !active->folder_active) active->clear_folder_dest();
tcp_folder_attached = false;
}
if (seeder_client && seeder_client.connected()) return;
@@ -89,6 +93,14 @@ void WiFiOtaSeeder::loop() {
incoming.setNoDelay(true); // tiny framed requests should leave immediately
char attach_reply[120];
if (!ota_acquire_context(attach_reply, sizeof(attach_reply))) {
incoming.stop();
mesh::usbLoggingPort().printf(
"OTA seeder rejected TCP client: %s\n", attach_reply);
return;
}
OtaContext& context = ota_ctx();
if (!WiFiOtaSeederPolicy::canAttachTcpFolder(context.folder_active,
tcp_folder_attached)) {
incoming.stop();
@@ -98,7 +110,6 @@ void WiFiOtaSeeder::loop() {
}
seeder_client = incoming;
char attach_reply[120];
if (!context.attach_folder_source(&seeder_source, OtaContext::FOLDER_LINK_TCP,
"tcp", attach_reply, sizeof(attach_reply))) {
seeder_client.stop();
+3 -2
View File
@@ -637,8 +637,9 @@ void ota_begin_context(uint32_t target, OtaSend send, void* ctx,
uint8_t ota_hop_limit();
#if defined(OTA_SHARED_COMPANION_QUEUE)
#if !defined(NRF52_PLATFORM) || !defined(OTA_SEEDER_ONLY) || !defined(COMPANION_RADIO_FULL)
#error "Shared mOTA queue storage requires an nRF52 Full source-only Companion"
#if (!defined(NRF52_PLATFORM) && !defined(ESP32_PLATFORM)) || \
!defined(OTA_SEEDER_ONLY) || !defined(COMPANION_RADIO_FULL)
#error "Shared mOTA queue storage requires an nRF52 or ESP32 Full source-only Companion"
#endif
void ota_set_context_storage(void* owner, OtaContext* (*acquire)(void*),
void (*release)(void*));
+7
View File
@@ -12,6 +12,13 @@ namespace ota {
OtaManager::~OtaManager() {
free(_catalog_heap);
free(_leaves_buf);
#if defined(ESP32_PLATFORM)
// A shared Companion workspace is destroyed between mOTA sessions. Return
// the lazy source/proof buffers before its queue slots are reused.
free(_src_leaves);
free(_scratch);
#endif
}
bool OtaManager::expandCatalog() {
+4 -2
View File
@@ -1,8 +1,10 @@
#pragma once
// Full nRF52 Companions stream host-provided firmware and need no permanent
// Qualified Full Companions stream host-provided firmware and need no permanent
// mOTA workspace. Apply the same policy to build.sh and direct PlatformIO builds.
#if defined(NRF52_PLATFORM) && defined(OTA_SEEDER_ONLY) && defined(COMPANION_RADIO_FULL)
#if defined(OTA_SEEDER_ONLY) && defined(COMPANION_RADIO_FULL) && \
(defined(NRF52_PLATFORM) || \
(defined(ESP32_PLATFORM) && defined(HELTEC_WIRELESS_PAPER)))
#ifndef OTA_SHARED_COMPANION_QUEUE
#define OTA_SHARED_COMPANION_QUEUE 1
#endif
+94
View File
@@ -0,0 +1,94 @@
#pragma once
#include <Arduino.h>
#include <cstdarg>
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <vector>
enum wifi_mode_t { WIFI_OFF, WIFI_STA, WIFI_AP, WIFI_AP_STA };
constexpr int WL_CONNECTED = 3;
struct IPAddress {
uint32_t value = 0;
operator uint32_t() const { return value; }
std::string toString() const { return "192.0.2.1"; }
};
struct MockWiFi {
bool connected = false;
wifi_mode_t mode = WIFI_OFF;
IPAddress ap;
int status() const { return connected ? WL_CONNECTED : 0; }
wifi_mode_t getMode() const { return mode; }
IPAddress softAPIP() const { return ap; }
};
inline MockWiFi WiFi;
struct MockTcpState {
bool connected = true;
bool no_delay = false;
unsigned requests = 0, flushes = 0;
std::deque<uint8_t> received;
std::function<std::vector<uint8_t>(const uint8_t*, size_t)> respond;
};
class WiFiClient : public Stream {
public:
std::shared_ptr<MockTcpState> state;
WiFiClient() = default;
explicit WiFiClient(std::shared_ptr<MockTcpState> s) : state(s) {}
explicit operator bool() const { return state && state->connected; }
bool connected() const { return bool(*this); }
void stop() { if (state) state->connected = false; }
void setNoDelay(bool on) { if (state) state->no_delay = on; }
IPAddress remoteIP() const { return {1}; }
int available() override { return state ? state->received.size() : 0; }
int read() override {
if (!available()) return -1;
int byte = state->received.front();
state->received.pop_front();
return byte;
}
size_t write(const uint8_t* bytes, size_t length) override {
if (!connected()) return 0;
++state->requests;
const auto reply = state->respond(bytes, length);
state->received.insert(state->received.end(), reply.begin(), reply.end());
return length;
}
void flush() override {
++state->flushes;
state->received.clear(); // Real WiFiClient flush discards received bytes.
}
};
class WiFiServer {
public:
inline static std::deque<WiFiClient> pending;
explicit WiFiServer(uint16_t) {}
void begin() {}
void end() { pending.clear(); }
WiFiClient available() {
if (pending.empty()) return {};
WiFiClient result = pending.front();
pending.pop_front();
return result;
}
};
namespace mesh {
struct MockLog {
std::string output;
void println(const char* text) { output += text; output += '\n'; }
void printf(const char* format, ...) {
char line[256];
va_list args;
va_start(args, format);
vsnprintf(line, sizeof line, format, args);
va_end(args);
output += line;
}
};
inline MockLog& usbLoggingPort() { static MockLog log; return log; }
}
+36 -22
View File
@@ -61,6 +61,7 @@ public:
// The actual BLE source controller below sees a ready/not-ready link and a
// source with real container bytes. Hardware GATT/serial framing is separate.
#if !defined(ESP32_PLATFORM)
struct Bluetooth {
bool ready = false, active = false;
int motaStream() { return 0; }
@@ -78,6 +79,7 @@ public:
};
} }
#include "ble_control_under_test.h"
#endif
struct Packet { bool to_source; std::vector<uint8_t> bytes; };
static std::deque<Packet> packets;
@@ -86,6 +88,35 @@ static bool send(void* to_source, const uint8_t* bytes, uint16_t length, bool) {
return true;
}
static void transfer(OtaContext& context, void* reply_route) {
OtaManager receiver;
OtaStoreRam<8192> destination;
receiver.begin(EXP_TARGET_ID, send, reply_route);
receiver.set_fetch_store(&destination);
assert(receiver.pull(EXP_MERKLE_ROOT, EXP_TARGET_ID) == OtaManager::PULL_STARTED);
for (uint32_t time = 100; time < 300000 && receiver.fetchState() != OtaManager::COMPLETE;
time += 100) {
context.manager.set_clock(time);
receiver.set_clock(time);
context.manager.serviceEgress();
receiver.serviceEgress();
if (time % 1000 == 0) { context.manager.loop(); receiver.loop(); }
while (!packets.empty()) {
Packet packet = packets.front();
packets.pop_front();
(packet.to_source ? context.manager : receiver).on_message(
packet.bytes.data(), packet.bytes.size());
}
}
assert(receiver.fetchState() == OtaManager::COMPLETE);
assert(destination.staged_size() == MOTA_VEC_LEN);
assert(memcmp(destination.data(), MOTA_VEC, MOTA_VEC_LEN) == 0);
}
#if defined(ESP32_PLATFORM)
#include "test_wifi_shared_queue.h"
#endif
int main() {
Queue q;
for (int i = 0; i < q.count; ++i) {
@@ -145,28 +176,7 @@ int main() {
"wrong owner", reply, sizeof reply));
assert(context.folderLink() == link);
OtaManager receiver;
OtaStoreRam<8192> destination;
receiver.begin(EXP_TARGET_ID, send, &q);
receiver.set_fetch_store(&destination);
assert(receiver.pull(EXP_MERKLE_ROOT, EXP_TARGET_ID) == OtaManager::PULL_STARTED);
for (uint32_t time = 100; time < 300000 && receiver.fetchState() != OtaManager::COMPLETE;
time += 100) {
context.manager.set_clock(time);
receiver.set_clock(time);
context.manager.serviceEgress();
receiver.serviceEgress();
if (time % 1000 == 0) { context.manager.loop(); receiver.loop(); }
while (!packets.empty()) {
Packet packet = packets.front();
packets.pop_front();
(packet.to_source ? context.manager : receiver).on_message(
packet.bytes.data(), packet.bytes.size());
}
}
assert(receiver.fetchState() == OtaManager::COMPLETE);
assert(destination.staged_size() == MOTA_VEC_LEN);
assert(memcmp(destination.data(), MOTA_VEC, MOTA_VEC_LEN) == 0);
transfer(context, &q);
check_messages();
context.detach_folder(); // same cleanup for USB/BLE stop and disconnect
context.manager.announce(); // callers may still use the context until loop boundary
@@ -195,6 +205,9 @@ int main() {
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
check_messages();
#if defined(ESP32_PLATFORM)
test_wifi_shared_queue(q, check_messages);
#else
Nrf52BleMotaSourceControl ble;
assert(!ble.start(reply, sizeof reply)); // no subscription, no storage loan
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
@@ -235,4 +248,5 @@ int main() {
ota_release_context_if_idle(true);
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
check_messages();
#endif
}
+159
View File
@@ -0,0 +1,159 @@
#pragma once
#include <WiFi.h>
#include <helpers/esp32/WiFiOtaSeeder.h>
#include <helpers/ota/FolderMotaStore.h>
#include <helpers/ota/MotaSeederProto.h>
#include <helpers/ota/OtaByteIO.h>
// Simulate only the TCP endpoint/host. The listener, stream framing, context,
// queue, mOTA manager, source reader and complete transferred bytes are real.
static std::shared_ptr<MockTcpState> connect_host() {
auto state = std::make_shared<MockTcpState>();
state->respond = [](const uint8_t* request, size_t length) {
assert(length >= 4 && request[0] == 'M' && request[1] == 'S');
uint8_t checksum = 0;
for (size_t i = 2; i < length; ++i) checksum ^= request[i];
assert(checksum == 0);
std::vector<uint8_t> payload;
const uint8_t op = request[2];
if (op == MS_OP_COUNT) {
payload.push_back(1);
} else if (op == MS_OP_DESCRIBE) {
assert(request[3] == 0);
MotaDesc desc;
Folder folder;
assert(folder.describe(0, desc));
payload.resize(MOTA_DESC_WIRE);
memcpy(payload.data(), desc.mid, 4);
wr_u32le(payload.data() + 4, desc.target_id);
wr_u32le(payload.data() + 8, desc.fw_version);
payload[12] = desc.codec_id;
payload[13] = desc.flags;
wr_u32le(payload.data() + 14, desc.total_size);
wr_u32le(payload.data() + 18, desc.leaves_off);
wr_u32le(payload.data() + 22, desc.block_count);
wr_u32le(payload.data() + 26, desc.payload_off);
wr_u32le(payload.data() + 30, desc.payload_size);
payload[34] = desc.block_size_log2;
} else {
assert(op == MS_OP_READ && length == 11 && request[3] == 0);
uint32_t offset = rd_u32le(request + 4);
uint16_t count = request[8] | (uint16_t(request[9]) << 8);
assert(uint64_t(offset) + count <= MOTA_VEC_LEN);
payload.assign(MOTA_VEC + offset, MOTA_VEC + offset + count);
}
std::vector<uint8_t> reply = {'m', 's', op, MS_STATUS_OK};
reply.insert(reply.end(), payload.begin(), payload.end());
checksum = 0;
for (uint8_t byte : reply) checksum ^= byte;
reply.push_back(checksum);
return reply;
};
WiFiServer::pending.emplace_back(state);
return state;
}
template<class CheckMessages>
static void test_wifi_shared_queue(Queue& q, CheckMessages check_messages) {
WiFiOtaSeeder::stop(); // stopping before startup never allocates
WiFiOtaSeeder::loop();
assert(!WiFiOtaSeeder::isListening() && !ota_context_if_active());
WiFi.connected = true;
for (int i = 0; i < 100; ++i) WiFiOtaSeeder::loop();
assert(WiFiOtaSeeder::isListening() && !ota_context_if_active());
assert(q.buffer.capacity() == 256);
q.count = 129;
Frame& extra = q.buffer.at((q.head + 128) % 256);
extra.len = 176;
memset(extra.buf, 128, sizeof extra.buf);
auto refused = connect_host();
WiFiOtaSeeder::loop();
assert(!refused->connected && refused->requests == 0);
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
assert(mesh::usbLoggingPort().output.find("sync unread messages") != std::string::npos);
check_messages();
--q.count;
for (int cycle = 0; cycle < 12; ++cycle) {
WiFi.connected = true;
auto host = connect_host();
WiFiOtaSeeder::loop();
assert(host->connected && host->no_delay && WiFiOtaSeeder::isAttached());
assert(q.buffer.capacity() == 128 && ota_ctx().folder_dest);
assert(ota_ctx().folderLink() == OtaContext::FOLDER_LINK_TCP);
assert(ota_ctx().manager.servedCount() == 1);
transfer(ota_ctx(), &q);
assert(host->requests > 2 && host->flushes == 0);
ota_release_context_if_idle(false);
assert(ota_context_if_active()); // a connected host owns the loan
check_messages();
switch (cycle % 4) {
case 0: // TCP disconnect while listener remains available
host->connected = false;
WiFiOtaSeeder::loop();
break;
case 1: // WiFi loss
WiFi.connected = false;
WiFiOtaSeeder::loop();
assert(!WiFiOtaSeeder::isListening());
break;
case 2: // CLI off can return the context before the listener sees it
ota_ctx().detach_folder();
ota_ctx().clear_folder_dest();
ota_release_context_if_idle(true);
assert(!ota_context_if_active());
WiFiOtaSeeder::loop();
break;
case 3: WiFiOtaSeeder::stop(); break;
}
ota_release_context_if_idle(true);
assert(!host->connected && !WiFiOtaSeeder::isAttached());
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
check_messages();
packets.clear();
}
// An incoming TCP client and listener stop must not detach a USB source.
char reply[160];
assert(ota_acquire_context(reply, sizeof reply));
Folder usb;
assert(ota_ctx().attach_folder_source(&usb, OtaContext::FOLDER_LINK_SERIAL,
"USB", reply, sizeof reply));
WiFi.connected = true;
auto conflict = connect_host();
WiFiOtaSeeder::loop();
assert(!conflict->connected && !WiFiOtaSeeder::isAttached());
WiFiOtaSeeder::stop();
ota_release_context_if_idle(false);
assert(ota_ctx().folderLink() == OtaContext::FOLDER_LINK_SERIAL);
ota_ctx().detach_folder();
ota_release_context_if_idle(true);
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
// TCP's stale attached flag must not clear a new USB capture destination.
auto old_host = connect_host();
WiFiOtaSeeder::loop();
assert(WiFiOtaSeeder::isAttached());
ota_ctx().detach_folder();
ota_ctx().clear_folder_dest();
ota_release_context_if_idle(true);
assert(ota_acquire_context(reply, sizeof reply));
assert(ota_ctx().attach_folder_source(&usb, OtaContext::FOLDER_LINK_SERIAL,
"USB", reply, sizeof reply));
WiFiClient usb_stream;
FolderMotaStore usb_dest(usb_stream, MotaStreamWritePolicy::NoFlush);
ota_ctx().set_folder_dest(&usb_dest, "USB");
WiFiOtaSeeder::loop();
assert(!old_host->connected && !WiFiOtaSeeder::isAttached());
assert(ota_ctx().folder_dest == &usb_dest);
assert(ota_ctx().folderLink() == OtaContext::FOLDER_LINK_SERIAL);
ota_ctx().detach_folder();
ota_ctx().clear_folder_dest();
ota_release_context_if_idle(true);
WiFiOtaSeeder::stop();
assert(!ota_context_if_active() && q.buffer.capacity() == 256);
check_messages();
packets.clear();
}
+15 -4
View File
@@ -218,13 +218,16 @@ int main() {
with self.assertRaises(ValueError):
ram.requirements(platform, definitions, "companion")
def test_nrf52_queue_sharing_applies_to_direct_full_builds_only(self):
def test_queue_sharing_applies_to_qualified_direct_full_builds_only(self):
source = '#include "src/helpers/ota/OtaMemoryPolicy.h"\n#ifdef OTA_SHARED_COMPANION_QUEUE\nSHARING_ENABLED\n#endif\n'
for flags, expected in (
(["NRF52_PLATFORM", "COMPANION_RADIO_FULL", "OTA_SEEDER_ONLY"], True),
(["NRF52_PLATFORM", "OTA_SEEDER_ONLY"], False),
(["NRF52_PLATFORM", "COMPANION_RADIO_FULL"], False),
(["ESP32_PLATFORM", "COMPANION_RADIO_FULL", "OTA_SEEDER_ONLY"], False),
(["ESP32_PLATFORM", "HELTEC_WIRELESS_PAPER", "COMPANION_RADIO_FULL", "OTA_SEEDER_ONLY"], True),
(["ESP32_PLATFORM", "HELTEC_WIRELESS_PAPER", "OTA_SEEDER_ONLY"], False),
(["ESP32_PLATFORM", "HELTEC_WIRELESS_PAPER", "COMPANION_RADIO_FULL"], False),
):
result = subprocess.run(["c++", "-x", "c++", "-E", "-I", str(ROOT),
*("-D" + flag for flag in flags), "-"], input=source,
@@ -233,7 +236,8 @@ int main() {
def test_affected_esp32_full_overlay_keeps_queue_and_both_transports(self):
for target in ("Heltec_v3_companion_radio_full", "Xiao_C3_companion_radio_full",
"heltec_tracker_v2_companion_radio_full_femon"):
"heltec_tracker_v2_companion_radio_full_femon",
"Heltec_Wireless_Paper_companion_radio_full"):
result = subprocess.run(["bash", "-c", '''
source build.sh
PIO_ENV_PLATFORM_BY_NAME["$1"]=ESP32_PLATFORM
@@ -242,8 +246,15 @@ requires_esp32_companion_full_ota_fallback() { return 1; }
apply_companion_radio_full_profile "$1" "$1"
printf '%s\\n' "$PLATFORMIO_BUILD_FLAGS"
''', "test", target], cwd=ROOT, text=True, capture_output=True, check=True)
self.assertIn("-DMAX_CONTACTS=150", result.stdout)
self.assertNotIn("-DOFFLINE_QUEUE_SIZE=", result.stdout)
if target == "Heltec_Wireless_Paper_companion_radio_full":
self.assertIn("-DMAX_CONTACTS=350", result.stdout)
self.assertNotIn("-DMAX_CONTACTS=150", result.stdout)
self.assertIn("-DOFFLINE_QUEUE_SIZE=256", result.stdout)
self.assertIn("-DOTA_SHARED_COMPANION_QUEUE=1", result.stdout)
else:
self.assertIn("-DMAX_CONTACTS=150", result.stdout)
self.assertNotIn("-DOFFLINE_QUEUE_SIZE=", result.stdout)
self.assertNotIn("-DOTA_SHARED_COMPANION_QUEUE", result.stdout)
self.assertIn("-DWIFI_OTA_SEEDER=1", result.stdout)
self.assertIn("-DBLE_PIN_CODE=123456", result.stdout)
+14 -1
View File
@@ -12,6 +12,12 @@ ROOT = Path(__file__).resolve().parents[1]
class SharedMotaQueueTest(unittest.TestCase):
def test_context_lifecycle_and_real_transfers_preserve_unread_messages(self):
self.run_transfer_test("NRF52_PLATFORM")
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):
with tempfile.TemporaryDirectory(prefix="meshcore-mota-queue-") as temp:
binary = Path(temp) / "transfer"
tinf = Path(temp) / "tinf.o"
@@ -29,11 +35,18 @@ class SharedMotaQueueTest(unittest.TestCase):
"src/helpers/ota/MerkleTree.cpp", "src/helpers/ota/OtaDeflate.cpp",
"src/Utils.cpp",
]
flags = ["-D" + platform + "=1"]
if platform == "ESP32_PLATFORM":
flags += ["-DHELTEC_WIRELESS_PAPER=1", "-DWIFI_OTA_SEEDER=1"]
sources += ["src/helpers/esp32/WiFiOtaSeeder.cpp",
"src/helpers/ota/MotaSourceSerial.cpp",
"src/helpers/ota/FolderMotaStore.cpp"]
built = subprocess.run([
"c++", "-std=c++17", "-fsanitize=address,undefined", "-g",
"-DNRF52_PLATFORM=1", "-DOTA_SEEDER_ONLY=1",
*flags, "-DOTA_SEEDER_ONLY=1",
"-DCOMPANION_RADIO_FULL=1", "-DOTA_SHARED_COMPANION_QUEUE=1",
"-DENABLE_OTA=1", "-I", str(ROOT / "src"),
"-I", str(ROOT / "test/fixtures/shared_mota_queue/mocks"),
"-I", str(ROOT / "test/mocks"), "-I", temp,
*[str(ROOT / source) for source in sources], str(tinf),
"-o", str(binary),