Keep Full sensors, UART2, adaptive OTA storage and the 8 KiB loop stack. Arbitrate shared SPI and OTA workspace ownership, move network waits into a worker, and use bounded queued TCP operations. Add reproducible test configs, RAM checks and regression coverage. Validated the Full build, UF2 layout and 29 sanitizer runtime scenarios.
509 lines
21 KiB
Python
509 lines
21 KiB
Python
"""Exercise combined RAK4631 SPI ownership and the actual adaptive store."""
|
|
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def run_cpp(source, extra_sources=()):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory)
|
|
sources = []
|
|
for index, text in enumerate((source, *extra_sources)):
|
|
cpp = path / f"test{index}.cpp"
|
|
cpp.write_text(text)
|
|
sources.append(str(cpp))
|
|
exe = path / "test"
|
|
subprocess.run([
|
|
"c++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
"-DRAK4631_COMBINED_ETHERNET", "-DOTA_RAK_AUTO_STORE",
|
|
"-DOTA_INTERNAL_BOOTLOADER_UPDATE", "-DMESHCORE_OTA_DEVICE_DEFLATE=1",
|
|
"-I", str(ROOT),
|
|
*sources, "-o", str(exe),
|
|
], check=True)
|
|
subprocess.run([str(exe)], check=True)
|
|
|
|
|
|
class CombinedRakOtaTest(unittest.TestCase):
|
|
def test_self_serve_and_manual_buffer_cannot_allocate_during_ethernet(self):
|
|
context = (ROOT / "src/helpers/ota/OtaContext.h").read_text()
|
|
start = context.index(" bool ensureServeBuffer() {")
|
|
end = context.index(" void releaseServeBuffer()", start)
|
|
buffer_method = context[start:end]
|
|
self_source = (ROOT / "src/helpers/ota/OtaSelf.cpp").read_text()
|
|
start = self_source.index("bool ota_serve_self(OtaContext& c, uint32_t fw_version) {")
|
|
end = self_source.index(" SelfFwInfo fi;", start)
|
|
self_prologue = self_source[start:end]
|
|
source = r'''
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include "src/helpers/nrf52/Rak4631SharedSpi.h"
|
|
using namespace mesh::ota;
|
|
namespace mesh { namespace ota {
|
|
static unsigned allocations = 0, self_body_calls = 0;
|
|
constexpr unsigned OTA_SERVE_BUF_SIZE = 4096;
|
|
void* tracked_malloc(size_t bytes) {
|
|
assert(!rak4631_ethernet_owns_spi()); ++allocations;
|
|
return std::malloc(bytes);
|
|
}
|
|
struct OtaContext {
|
|
uint8_t* serve_buf = nullptr;
|
|
#define malloc tracked_malloc
|
|
''' + buffer_method + r'''
|
|
#undef malloc
|
|
};
|
|
''' + self_prologue + r'''
|
|
(void)c; (void)fw_version;
|
|
++self_body_calls;
|
|
return true;
|
|
}
|
|
} }
|
|
int main() {
|
|
OtaContext context;
|
|
rak4631_set_ethernet_spi_owner(true);
|
|
assert(!context.ensureServeBuffer() && !ota_serve_self(context, 0));
|
|
assert(!context.serve_buf && allocations == 0 && self_body_calls == 0);
|
|
rak4631_set_ethernet_spi_owner(false);
|
|
assert(context.ensureServeBuffer() && ota_serve_self(context, 0));
|
|
assert(context.serve_buf && allocations == 1 && self_body_calls == 1);
|
|
free(context.serve_buf);
|
|
}
|
|
'''
|
|
run_cpp(source)
|
|
|
|
def test_context_handoff_frees_idle_self_serve_and_preserves_busy_ota(self):
|
|
header = (ROOT / "src/helpers/ota/OtaContext.h").read_text()
|
|
start = header.index(" // Only the main thread may change ownership.")
|
|
end = header.index("#endif\n\n#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)", start)
|
|
body = header[start:end]
|
|
source = r'''
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include "src/helpers/ota/OtaBlInfo.h"
|
|
#include "src/helpers/ota/OtaRakStoragePolicy.h"
|
|
#include "src/helpers/nrf52/Rak4631SharedSpi.h"
|
|
using namespace mesh::ota;
|
|
namespace mesh { namespace ota {
|
|
static unsigned frees = 0, probes = 0;
|
|
static uint8_t detection = 2;
|
|
static uint32_t rak_id = 0;
|
|
static OtaBlCaps app_caps, boot_caps;
|
|
struct OtaBootloaderIdentity {
|
|
bool present = true, crc_ok = true;
|
|
const char* device_name = "4631_DFU";
|
|
};
|
|
static OtaBootloaderIdentity identity;
|
|
void ota_transport_deflate() {}
|
|
bool ota_self_serve_supported(bool external) { return external; }
|
|
constexpr unsigned CODEC_DETOOLS_INPLACE = 2;
|
|
class OtaStoreQspiNrf52 {
|
|
public:
|
|
static uint8_t autoDetect() { assert(!rak4631_ethernet_owns_spi()); ++probes; return detection; }
|
|
static void autoProbeIds(uint32_t& rak, uint32_t& w25) { rak = rak_id; w25 = 0xef4015; }
|
|
};
|
|
struct OtaManager {
|
|
enum FetchState { IDLE, WANT_MANIFEST, WANT_LEAVES, VERIFYING_STAGED, FETCHING,
|
|
COMPLETE, FAILED, PAUSED };
|
|
unsigned state = IDLE, serve_jobs = 0, manifest_jobs = 0, served = 0;
|
|
bool own = false, accept_full = true, accept_boot = false, archive = true;
|
|
void (*encoder)() = ota_transport_deflate;
|
|
bool servingPrimaryManifest(const uint8_t*) const { return own; }
|
|
unsigned fetchState() const { return state; }
|
|
unsigned pendingServeJobs() const { return serve_jobs; }
|
|
unsigned pendingManifestJobs() const { return manifest_jobs; }
|
|
unsigned servedCount() const { return served; }
|
|
void set_accept_full(bool value) { accept_full = value; }
|
|
void set_accept_bootloader(bool value) { accept_boot = value; }
|
|
void set_apply_codec(unsigned) {}
|
|
void set_fetch_store(void*) {}
|
|
void set_archive_interest(bool value) { archive = value; }
|
|
void set_transport_deflate_encoder(void (*value)()) { encoder = value; }
|
|
void clear_primary() { own = false; served = 0; }
|
|
};
|
|
struct Store {
|
|
unsigned size = 0, resets = 0;
|
|
bool allow_reset = true, allow_internal = true;
|
|
unsigned staged_size() const { return size; }
|
|
bool resetSelection() { if (size || !allow_reset) return false; ++resets; return true; }
|
|
bool usesExternal() const { return !rak4631_ethernet_owns_spi() && detection == 2; }
|
|
bool usesInternal() const { return allow_internal && rak4631_ethernet_owns_spi(); }
|
|
};
|
|
void tracked_free(void* pointer) {
|
|
if (pointer) { assert(!rak4631_ethernet_owns_spi()); ++frees; }
|
|
std::free(pointer);
|
|
}
|
|
struct Context {
|
|
OtaManager manager;
|
|
Store fetch_store;
|
|
uint8_t serve_self_manifest[1] = {};
|
|
bool apply_pending = false, bootloader_apply_pending = false, serving = false;
|
|
bool folder_active = false, fetch_to_folder = false, self_serve_supported = true;
|
|
unsigned serve_expected = 0;
|
|
void* _folder_source = nullptr;
|
|
void* folder_dest = nullptr;
|
|
void* disconnected_folder_dest = nullptr;
|
|
uint8_t *serve_self_leaves = nullptr, *serve_self_proof = nullptr, *serve_buf = nullptr;
|
|
OtaBlCaps& bootloaderAppCaps() { return app_caps; }
|
|
OtaBlCaps& bootloaderUpdateCaps() { return boot_caps; }
|
|
OtaBootloaderIdentity& bootloaderIdentity() { return identity; }
|
|
void releaseServeBuffer() { tracked_free(serve_buf); serve_buf = nullptr; }
|
|
#define free tracked_free
|
|
''' + body + r'''
|
|
#undef free
|
|
};
|
|
} }
|
|
int main() {
|
|
app_caps.present = boot_caps.present = true;
|
|
app_caps.apply_abi = boot_caps.apply_abi = 3;
|
|
app_caps.codec_mask = boot_caps.codec_mask = 5;
|
|
app_caps.storage_flags = 0x1e; app_caps.optional_app_storage = 0x14;
|
|
boot_caps.storage_flags = 0x0a;
|
|
Context c;
|
|
char error[160];
|
|
for (unsigned state = 1; state <= 7; ++state) {
|
|
c.manager.state = state;
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)));
|
|
assert(!c.canReleaseRakEthernet() && !rak4631_ethernet_owns_spi());
|
|
assert(probes == 0 && c.fetch_store.resets == 0);
|
|
}
|
|
c.manager.state = OtaManager::IDLE;
|
|
c.fetch_store.size = 100;
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)) && probes == 0);
|
|
c.fetch_store.size = 0;
|
|
for (bool* flag : {&c.apply_pending, &c.bootloader_apply_pending, &c.serving,
|
|
&c.folder_active, &c.fetch_to_folder}) {
|
|
*flag = true;
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)) && probes == 0);
|
|
*flag = false;
|
|
}
|
|
for (unsigned* count : {&c.manager.serve_jobs, &c.manager.manifest_jobs,
|
|
&c.manager.served, &c.serve_expected}) {
|
|
*count = 1;
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)) && probes == 0);
|
|
*count = 0;
|
|
}
|
|
for (void** pointer : {&c._folder_source, &c.folder_dest, &c.disconnected_folder_dest}) {
|
|
*pointer = &c;
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)) && probes == 0);
|
|
*pointer = nullptr;
|
|
}
|
|
detection = 1; rak_id = 0xc84015;
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)));
|
|
assert(strstr(error, "RAK15001") && !rak4631_ethernet_owns_spi());
|
|
detection = 3;
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)));
|
|
detection = 2; rak_id = 0x123456;
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)));
|
|
assert(strstr(error, "unknown SPI"));
|
|
rak_id = 0;
|
|
app_caps.optional_app_storage = 0;
|
|
identity.device_name = "4631_W25Q16_DFU";
|
|
assert(!c.prepareRakEthernet(error, sizeof(error)));
|
|
assert(strstr(error, "bootloader cannot") && c.fetch_store.resets == 0);
|
|
app_caps.optional_app_storage = 0x14; identity.device_name = "4631_DFU";
|
|
c.manager.own = true; c.manager.served = 1; c.serving = true;
|
|
c.serve_self_leaves = static_cast<uint8_t*>(malloc(64));
|
|
c.serve_self_proof = static_cast<uint8_t*>(malloc(64));
|
|
c.serve_buf = static_cast<uint8_t*>(malloc(64));
|
|
assert(c.prepareRakEthernet(error, sizeof(error)));
|
|
assert(rak4631_ethernet_owns_spi() && c.fetch_store.resets == 1);
|
|
assert(frees == 3 && !c.serve_self_leaves && !c.serve_self_proof && !c.serve_buf);
|
|
assert(!c.serving && c.manager.served == 0 && !c.manager.own);
|
|
assert(!c.manager.accept_full && c.manager.accept_boot && !c.manager.archive);
|
|
assert(!c.self_serve_supported && !c.manager.encoder);
|
|
c.manager.state = OtaManager::FETCHING;
|
|
assert(!c.releaseRakEthernet() && rak4631_ethernet_owns_spi());
|
|
assert(c.fetch_store.resets == 1 && !c.self_serve_supported);
|
|
c.manager.state = OtaManager::IDLE; c.fetch_store.size = 100;
|
|
assert(!c.releaseRakEthernet() && rak4631_ethernet_owns_spi());
|
|
c.fetch_store.size = 0;
|
|
assert(c.releaseRakEthernet() && !rak4631_ethernet_owns_spi());
|
|
assert(c.fetch_store.resets == 2 && c.manager.accept_full);
|
|
assert(c.self_serve_supported && c.manager.encoder == ota_transport_deflate);
|
|
}
|
|
'''
|
|
run_cpp("#include <initializer_list>\n" + source)
|
|
|
|
def test_real_encoder_cannot_allocate_while_ethernet_owns_spi(self):
|
|
source = r'''
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include "src/helpers/ota/OtaDeflate.h"
|
|
#include "src/helpers/nrf52/Rak4631SharedSpi.h"
|
|
using namespace mesh::ota;
|
|
static unsigned allocations = 0;
|
|
extern "C" void* __real_calloc(size_t, size_t);
|
|
extern "C" void* __wrap_calloc(size_t n, size_t size) {
|
|
assert(!rak4631_ethernet_owns_spi()); ++allocations;
|
|
return __real_calloc(n, size);
|
|
}
|
|
int main() {
|
|
uint8_t input[256], encoded[256], output[256];
|
|
memset(input, 'A', sizeof(input));
|
|
uint16_t length = 123;
|
|
rak4631_set_ethernet_spi_owner(true);
|
|
assert(!ota_transport_deflate(nullptr, input, sizeof(input), encoded, sizeof(encoded), &length));
|
|
assert(length == 0 && allocations == 0);
|
|
rak4631_set_ethernet_spi_owner(false);
|
|
assert(ota_transport_deflate(nullptr, input, sizeof(input), encoded, sizeof(encoded), &length));
|
|
assert(allocations == 1 && length < sizeof(input));
|
|
rak4631_set_ethernet_spi_owner(true);
|
|
uint16_t decoded = 0;
|
|
assert(ota_transport_inflate(nullptr, encoded, length, output, sizeof(output), &decoded));
|
|
assert(decoded == sizeof(input) && memcmp(input, output, sizeof(input)) == 0);
|
|
assert(allocations == 1); // ordinary OTA reception stays available
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory)
|
|
objects = []
|
|
for relative in ("OtaTinf.c",):
|
|
obj = path / (Path(relative).stem + ".o")
|
|
subprocess.run([
|
|
"cc", "-std=c99", "-DENABLE_OTA", "-DMESHCORE_OTA_DEVICE_DEFLATE=1",
|
|
"-c", str(ROOT / "src/helpers/ota" / relative), "-o", str(obj),
|
|
], check=True)
|
|
objects.append(str(obj))
|
|
cpp = path / "test.cpp"
|
|
cpp.write_text(source)
|
|
exe = path / "test"
|
|
subprocess.run([
|
|
"c++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
"-DRAK4631_COMBINED_ETHERNET", "-DENABLE_OTA",
|
|
"-DMESHCORE_OTA_DEVICE_DEFLATE=1", "-I", str(ROOT),
|
|
str(cpp), str(ROOT / "src/helpers/ota/OtaDeflate.cpp"),
|
|
*objects, "-Wl,--wrap=calloc", "-o", str(exe),
|
|
], check=True)
|
|
subprocess.run([str(exe)], check=True)
|
|
|
|
def test_only_explicit_combined_build_can_relax_static_bus_exclusion(self):
|
|
base = ["NRF52_PLATFORM", "OTA_QSPI_STORE", "OTA_QSPI_SHARED_WISBLOCK_SPI"]
|
|
cases = [
|
|
(base, True),
|
|
(base + ["ETHERNET_ENABLED"], False),
|
|
(base + ["ETHERNET_ENABLED", "RAK4631_COMBINED_ETHERNET"], False),
|
|
(base + ["ETHERNET_ENABLED", "RAK4631_COMBINED_ETHERNET",
|
|
"OTA_RAK_AUTO_STORE", "RAK_4631"], True),
|
|
]
|
|
for defines, accepted in cases:
|
|
with self.subTest(defines=defines):
|
|
result = subprocess.run([
|
|
"c++", "-std=c++17", "-E", "-x", "c++", "-",
|
|
"-I", str(ROOT), *("-D" + value for value in defines),
|
|
], input='#include "src/helpers/ota/OtaStoreQspiNrf52.h"\n',
|
|
capture_output=True, text=True)
|
|
self.assertEqual(result.returncode == 0, accepted, result.stderr)
|
|
|
|
def test_spi_owner_is_shared_between_translation_units(self):
|
|
run_cpp(r'''
|
|
#include <cassert>
|
|
#include "src/helpers/nrf52/Rak4631SharedSpi.h"
|
|
void reserve_elsewhere(bool);
|
|
int main() {
|
|
assert(!mesh::ota::rak4631_ethernet_owns_spi());
|
|
reserve_elsewhere(true);
|
|
assert(mesh::ota::rak4631_ethernet_owns_spi());
|
|
reserve_elsewhere(false);
|
|
assert(!mesh::ota::rak4631_ethernet_owns_spi());
|
|
}
|
|
''', (r'''
|
|
#include "src/helpers/nrf52/Rak4631SharedSpi.h"
|
|
void reserve_elsewhere(bool owner) {
|
|
mesh::ota::rak4631_set_ethernet_spi_owner(owner);
|
|
}
|
|
''',))
|
|
|
|
def test_actual_adaptive_store_respects_owner_and_bootloader(self):
|
|
header = (ROOT / "src/helpers/ota/OtaStoreAdaptiveNrf52.h").read_text()
|
|
body = header[header.index("class OtaStoreAdaptiveNrf52"):
|
|
header.index("} // namespace ota")]
|
|
source = r'''
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <new>
|
|
#include "src/helpers/ota/OtaStore.h"
|
|
#include "src/helpers/ota/OtaBlInfo.h"
|
|
#include "src/helpers/ota/OtaRakStoragePolicy.h"
|
|
#include "src/helpers/nrf52/Rak4631SharedSpi.h"
|
|
using namespace mesh::ota;
|
|
namespace mesh { namespace ota {
|
|
static unsigned detection = 2, probes = 0, internal_live = 0, external_live = 0;
|
|
static uint8_t storage_flags = 0x1e, optional_storage = 0x14;
|
|
static bool valid_identity = true;
|
|
static const char* device_name = "4631_DFU";
|
|
struct OtaBootloaderIdentity { bool crc_ok = true; const char* device_name; };
|
|
bool ota_installed_bootloader_identity(OtaBootloaderIdentity& identity) {
|
|
identity.device_name = device_name; return valid_identity;
|
|
}
|
|
struct MotaManifest { bool is_bootloader() const { return false; } };
|
|
bool mota_parse_manifest(const uint8_t*, size_t, MotaManifest&) { return false; }
|
|
OtaBlCaps simulatedAppCaps() {
|
|
OtaBlCaps c; c.present = true; c.apply_abi = 3; c.codec_mask = 5;
|
|
c.storage_flags = storage_flags; c.optional_app_storage = optional_storage;
|
|
return c;
|
|
}
|
|
OtaBlCaps simulatedBootCaps() {
|
|
OtaBlCaps c = simulatedAppCaps(); c.storage_flags = 0x0a; return c;
|
|
}
|
|
class Store : public OtaStore {
|
|
public:
|
|
uint32_t size = 0;
|
|
bool begin(uint32_t n) override { size = n; return true; }
|
|
bool write(uint32_t, const uint8_t*, uint32_t) override { return true; }
|
|
bool read(uint32_t, uint8_t*, uint32_t) const override { return true; }
|
|
uint32_t capacity() const override { return 65536; }
|
|
uint32_t staged_size() const override { return size; }
|
|
void clear() override { size = 0; }
|
|
};
|
|
class OtaStoreFlashNrf52 : public Store {
|
|
public:
|
|
OtaStoreFlashNrf52() { ++internal_live; assert(external_live == 0); }
|
|
~OtaStoreFlashNrf52() { --internal_live; }
|
|
};
|
|
class OtaStoreQspiNrf52 : public Store {
|
|
public:
|
|
OtaStoreQspiNrf52() { ++external_live; assert(internal_live == 0); }
|
|
~OtaStoreQspiNrf52() { --external_live; }
|
|
static uint8_t autoDetect() { ++probes; return detection; }
|
|
static bool headerW25Detected() { return false; }
|
|
uint32_t jedec_id() const { return 0xef4015; }
|
|
uint8_t status1() const { return 0; }
|
|
const char* last_stage() const { return "idle"; }
|
|
const char* last_error() const { return ""; }
|
|
};
|
|
#define ota_bootloader_app_caps simulatedAppCaps
|
|
#define ota_bootloader_update_caps simulatedBootCaps
|
|
''' + body + r'''
|
|
} }
|
|
int main() {
|
|
// Ethernet may not discover NOR, and selects the internal path only when
|
|
// the installed bootloader supports that actual source.
|
|
rak4631_set_ethernet_spi_owner(true);
|
|
{
|
|
OtaStoreAdaptiveNrf52 store;
|
|
assert(store.usesInternal() && !store.usesExternal());
|
|
assert(probes == 0);
|
|
assert(strstr(store.selectionReason(), "Ethernet active"));
|
|
assert(store.plan_layout(true, 40960, 365, 40960, true));
|
|
assert(store.usesInternal());
|
|
assert(store.begin(41330));
|
|
assert(!store.resetSelection()); // privileged staging is pinned too
|
|
}
|
|
assert(internal_live == 0 && external_live == 0);
|
|
storage_flags = 0x06; optional_storage = 0;
|
|
device_name = "4631_W25Q16_DFU";
|
|
{
|
|
OtaStoreAdaptiveNrf52 store;
|
|
assert(!store.usesInternal() && !store.usesExternal());
|
|
assert(store.capacity() == 0 && !store.begin(100));
|
|
assert(strstr(store.selectionReason(), "bootloader lacks internal OTA"));
|
|
assert(probes == 0);
|
|
}
|
|
storage_flags = 0x1e; optional_storage = 0x14;
|
|
device_name = "4631_DFU"; valid_identity = false;
|
|
{
|
|
OtaStoreAdaptiveNrf52 store;
|
|
assert(!store.usesInternal()); // an unverified optional marker is insufficient
|
|
assert(!store.begin(100));
|
|
assert(probes == 0);
|
|
}
|
|
valid_identity = true;
|
|
rak4631_set_ethernet_spi_owner(false);
|
|
{
|
|
OtaStoreAdaptiveNrf52 store;
|
|
assert(store.usesExternal() && probes == 1);
|
|
assert(store.begin(100));
|
|
assert(!store.resetSelection()); // neither receive nor ready data is dropped
|
|
rak4631_set_ethernet_spi_owner(true); // defensive gate against a bad caller
|
|
uint8_t byte = 0;
|
|
assert(!store.begin(200) && !store.write(0, &byte, 1));
|
|
assert(!store.read(0, &byte, 1) && store.capacity() == 0);
|
|
assert(!store.resetSelection()); // existing data remains pinned
|
|
rak4631_set_ethernet_spi_owner(false);
|
|
store.clear();
|
|
assert(store.resetSelection());
|
|
assert(internal_live == 0 && external_live == 0);
|
|
rak4631_set_ethernet_spi_owner(true);
|
|
assert(store.usesInternal() && probes == 1);
|
|
assert(store.resetSelection());
|
|
rak4631_set_ethernet_spi_owner(false);
|
|
assert(store.usesExternal() && probes == 2);
|
|
}
|
|
assert(internal_live == 0 && external_live == 0);
|
|
}
|
|
'''
|
|
run_cpp(source)
|
|
|
|
def test_actual_discovery_does_no_gpio_io_while_ethernet_owns_spi(self):
|
|
cpp = (ROOT / "src/helpers/ota/OtaStoreQspiNrf52.cpp").read_text()
|
|
body = cpp[cpp.index("uint8_t OtaStoreQspiNrf52::autoDetect()"):
|
|
cpp.index("OtaStoreQspiNrf52::OtaStoreQspiNrf52()")]
|
|
source = r'''
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include "src/helpers/nrf52/Rak4631SharedSpi.h"
|
|
using namespace mesh::ota;
|
|
namespace mesh { namespace ota {
|
|
static uint8_t auto_cs_pin = 0xff, auto_detection = 0xff;
|
|
static uint32_t auto_jedec_id = 0, auto_rak_probe_id = 0, auto_w25_probe_id = 0;
|
|
static bool auto_header_w25 = false;
|
|
static constexpr uint8_t HEADER_SCK_PIN = 16, HEADER_IO0_PIN = 17, HEADER_IO1_PIN = 15;
|
|
static unsigned gpio_calls = 0, nor_calls = 0;
|
|
static uint32_t rak_id = 0, w25_id = 0xef4015, header_id = 0;
|
|
uint8_t arduino_to_physical(uint32_t pin) { return pin; }
|
|
void nrf_gpio_pin_set(uint32_t) { ++gpio_calls; }
|
|
void nrf_gpio_cfg_output(uint32_t) { ++gpio_calls; }
|
|
void nrf_gpio_cfg_default(uint32_t) { ++gpio_calls; }
|
|
uint32_t probe_nor(uint8_t cs) {
|
|
++nor_calls;
|
|
return cs == 26 ? rak_id : auto_header_w25 ? header_id : w25_id;
|
|
}
|
|
class OtaStoreQspiNrf52 {
|
|
public:
|
|
static uint8_t autoDetect();
|
|
static bool headerW25Detected();
|
|
static void autoProbeIds(uint32_t&, uint32_t&);
|
|
};
|
|
#if defined(OTA_RAK_AUTO_STORE)
|
|
''' + body + r'''
|
|
} }
|
|
int main() {
|
|
uint32_t rak, w25;
|
|
rak4631_set_ethernet_spi_owner(true);
|
|
assert(OtaStoreQspiNrf52::autoDetect() == 0);
|
|
OtaStoreQspiNrf52::autoProbeIds(rak, w25);
|
|
assert(!OtaStoreQspiNrf52::headerW25Detected());
|
|
assert(gpio_calls == 0 && nor_calls == 0 && auto_detection == 0xff);
|
|
rak4631_set_ethernet_spi_owner(false);
|
|
assert(OtaStoreQspiNrf52::autoDetect() == 2);
|
|
assert(nor_calls == 2 && auto_cs_pin == 31 && auto_jedec_id == 0xef4015);
|
|
const unsigned gpio_after_discovery = gpio_calls;
|
|
rak4631_set_ethernet_spi_owner(true);
|
|
assert(OtaStoreQspiNrf52::autoDetect() == 0);
|
|
OtaStoreQspiNrf52::autoProbeIds(rak, w25);
|
|
assert(rak == 0 && w25 == 0xef4015); // diagnostics retain physical discovery
|
|
assert(gpio_calls == gpio_after_discovery && nor_calls == 2);
|
|
rak4631_set_ethernet_spi_owner(false);
|
|
assert(OtaStoreQspiNrf52::autoDetect() == 2 && nor_calls == 2);
|
|
auto_detection = 0xff; w25_id = 0; header_id = 0xef4015;
|
|
assert(OtaStoreQspiNrf52::autoDetect() == 2);
|
|
assert(OtaStoreQspiNrf52::headerW25Detected());
|
|
const unsigned after_header_probe = nor_calls;
|
|
rak4631_set_ethernet_spi_owner(true);
|
|
assert(OtaStoreQspiNrf52::headerW25Detected()); // GPS still reserves header UART1
|
|
assert(nor_calls == after_header_probe);
|
|
}
|
|
'''
|
|
run_cpp(source)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|