#!/usr/bin/env python3 """Run the actual task wrapper and BLE startup failure handling.""" from pathlib import Path import subprocess import tempfile import unittest from test_t096_full_memory import method ROOT = Path(__file__).resolve().parents[1] RTOS = r''' #pragma once #include using BaseType_t = int; using UBaseType_t = unsigned; using TaskHandle_t = void*; using TaskFunction_t = void (*)(void*); using configSTACK_DEPTH_TYPE = uint16_t; constexpr BaseType_t pdPASS = 1; ''' HARNESS = r''' #include "Arduino.h" #include #include #include #include #include #include #include #include #ifndef COMPANION_FEATURE_BLE_MOTA_SOURCE #define COMPANION_FEATURE_BLE_MOTA_SOURCE 1 #endif #ifndef COMPANION_FEATURE_BLE_DFU #define COMPANION_FEATURE_BLE_DFU 1 #endif #ifndef COMPANION_BLE_PRPH_MTU #define COMPANION_BLE_PRPH_MTU 247 #endif #ifndef COMPANION_BLE_PRPH_EVENT_LENGTH #define COMPANION_BLE_PRPH_EVENT_LENGTH 100 #endif #ifndef COMPANION_BLE_PRPH_HVN_QUEUE #define COMPANION_BLE_PRPH_HVN_QUEUE 3 #endif #ifndef COMPANION_BLE_PRPH_WRCMD_QUEUE #define COMPANION_BLE_PRPH_WRCMD_QUEUE 1 #endif #define BLE_DEBUG_PRINTLN(...) do {} while (0) constexpr int BANDWIDTH_MAX = 3, NRF_SUCCESS = 0, ERROR_NONE = 0; constexpr int NRF_ERROR_INVALID_STATE = 8; constexpr int BLE_GAP_ADDR_TYPE_RANDOM_STATIC = 1, BLE_TX_POWER = 4; constexpr int BLE_MIN_CONN_INTERVAL = 12, BLE_MAX_CONN_INTERVAL = 24; constexpr int BLE_SLAVE_LATENCY = 4, BLE_CONN_SUP_TIMEOUT = 200; constexpr int SECMODE_ENC_WITH_MITM = 1, SECMODE_NO_ACCESS = 0; constexpr int CHR_PROPS_NOTIFY = 1, CHR_PROPS_WRITE = 2; constexpr int BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED = 0; constexpr int BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE = 2; constexpr int BLE_ADV_INTERVAL_MIN = 32, BLE_ADV_INTERVAL_MAX = 244, BLE_ADV_FAST_TIMEOUT = 30; struct ble_gap_addr_t { int addr_type; uint8_t addr[6]; }; struct ble_gap_conn_params_t { unsigned min_conn_interval, max_conn_interval, slave_latency, conn_sup_timeout; }; int sd_ble_gap_addr_set(ble_gap_addr_t*) { return NRF_SUCCESS; } int sd_ble_gap_addr_get(ble_gap_addr_t* a) { memset(a, 0, sizeof(*a)); return NRF_SUCCESS; } int sd_softdevice_is_enabled(uint8_t* enabled) { *enabled = 0; return NRF_SUCCESS; } namespace mesh_nrf52 { int softdeviceIsEnabled(uint8_t& enabled) { return sd_softdevice_is_enabled(&enabled); } } int sd_ble_gap_ppcp_set(ble_gap_conn_params_t*) { return NRF_SUCCESS; } namespace mesh { struct Logging { void println(const char*) {} }; Logging& usbLoggingPort() { static Logging port; return port; } } static int fail_service = 0; static bool fail_softdevice = false; static unsigned last_depth; extern "C" BaseType_t __wrap_xTaskCreate(TaskFunction_t, const char*, configSTACK_DEPTH_TYPE, void*, UBaseType_t, TaskHandle_t*); extern "C" BaseType_t __real_xTaskCreate(TaskFunction_t, const char* name, configSTACK_DEPTH_TYPE depth, void*, UBaseType_t, TaskHandle_t* out) { last_depth = depth; if (out) *out = reinterpret_cast(1); return pdPASS; } struct Settings { template void clearBonds(T...) {} template void setMITM(T...) {} template void setPIN(T...) {} template void setIOCaps(T...) {} template void setPairPasskeyCallback(T...) {} template void setPairCompleteCallback(T...) {} template void setConnectCallback(T...) {} template void setDisconnectCallback(T...) {} template void setSecuredCallback(T...) {} template void setType(T...) {} template void addFlags(T...) {} template void addTxPower(T...) {} template void addService(T...) {} template void addName(T...) {} template void setInterval(T...) {} template void setFastTimeout(T...) {} template void restartOnDisconnect(T...) {} }; struct BluefruitStub { Settings Security, Periph, Advertising, ScanResponse; int starts = 0; uint16_t mtu = 0, event_length = 0; uint8_t hvn_queue = 0, wrcmd_queue = 0; void configPrphConn(uint16_t configured_mtu, uint16_t configured_event_length, uint8_t configured_hvn_queue, uint8_t configured_wrcmd_queue) { mtu = configured_mtu; event_length = configured_event_length; hvn_queue = configured_hvn_queue; wrcmd_queue = configured_wrcmd_queue; } bool begin() { ++starts; if (fail_softdevice) return false; TaskHandle_t task; // The pinned LTO core resolves these internal calls directly, rather than // through the application's linker wrapper. __wrap_xTaskCreate(nullptr, "BLE", 1280, nullptr, 3, &task); __wrap_xTaskCreate(nullptr, "SOC", 200, nullptr, 3, &task); return true; } void setTxPower(int) {} void setName(const char*) {} template void setEventCallback(T) {} } Bluefruit; struct Service { int id; int begin() { return fail_service == id ? 1 : ERROR_NONE; } template void setPermission(T...) {} template void setRxCallback(T...) {} template void setProperties(T...) {} template void setMaxLen(T...) {} template void setUserDescriptor(T...) {} template void setWriteCallback(T...) {} }; struct MotaStream { template void setSender(T...) {} void setActive(bool) {} }; @FORMAT_DEVICE_NAME@ struct SerialBLEInterface { @STARTUP_FIELDS@ std::atomic _companionDataSeen{false}; SecuritySessionTimer _companion_start_timer; std::atomic _successfulConnectionPending{false}; std::atomic _successfulConnectionStarted{0}; std::atomic _bondedOnlyRecoveryPending{false}, _advertisingSuppressed{false}; bool _stealth_pair_once = false, _bonded_only = false, _bonded_only_configure_pending = false; mesh::companion::BluetoothPeerIdentity _pending_bonded_peer; Service bleuart{1}, _mota_service{2}, _mota_request{3}, _mota_response{4}, bledfu{5}; MotaStream _mota_stream; static void onPairingPasskey() {} static void onPairingComplete() {} static void onConnect() {} static void onDisconnect() {} static void onSecured() {} static void onBLEEvent() {} static void onBleUartRX() {} static void sendMotaRequest() {} static void onMotaResponse() {} void configureBondedOnlyAdvertising(const mesh::companion::BluetoothPeerIdentity&, bool) {} bool begin(const char*, const char*, uint32_t, const uint8_t* = nullptr, bool = false, bool = false, const mesh::companion::BluetoothPeerIdentity* = nullptr); }; static SerialBLEInterface* instance = nullptr; @BEGIN@ int main() { __wrap_xTaskCreate(nullptr, "loop", 1024, nullptr, 1, nullptr); assert(last_depth == 2048); __wrap_xTaskCreate(nullptr, "callback", 768, nullptr, 1, nullptr); assert(last_depth == 768); __wrap_xTaskCreate(nullptr, nullptr, 100, nullptr, 1, nullptr); assert(last_depth == 100); for (int fault = 0; fault <= 6; ++fault) { #if !COMPANION_FEATURE_BLE_MOTA_SOURCE if (fault >= 3 && fault <= 5) continue; #endif fail_softdevice = fault == 1; fail_service = fault >= 2 ? fault - 1 : 0; SerialBLEInterface port; const int starts = Bluefruit.starts; // UART is the required Companion transport. The mOTA and Nordic DFU // services are extensions: registration failure must leave UART BLE // available for pairing, diagnostics, and recovery. const bool expected = fault != 1 && fault != 2; assert(port.begin("MC-", "T096", 123456) == expected); assert(Bluefruit.mtu == COMPANION_BLE_PRPH_MTU); assert(Bluefruit.event_length == COMPANION_BLE_PRPH_EVENT_LENGTH); assert(Bluefruit.hvn_queue == COMPANION_BLE_PRPH_HVN_QUEUE); assert(Bluefruit.wrcmd_queue == COMPANION_BLE_PRPH_WRCMD_QUEUE); assert(Bluefruit.starts == starts + 1); assert((instance == &port) == expected); // Retrying a failed partial init must never allocate another stack/FIFO. for (int retry = 0; retry < 4; ++retry) { assert(port.begin("MC-", "T096", 123456) == expected); assert(Bluefruit.starts == starts + 1); } } } ''' class Nrf52BleStartupTest(unittest.TestCase): def test_reported_startup_failures_are_not_reinitialized(self): serial_source = (ROOT / "src/helpers/nrf52/SerialBLEInterface.cpp").read_text() formatter = method(serial_source, "static bool formatDeviceName(") begin = method(serial_source, "bool SerialBLEInterface::begin(") header = (ROOT / "src/helpers/nrf52/SerialBLEInterface.h").read_text() # Use the real in-class startup initializers, too. fields = "\n".join(line for line in header.splitlines() if "bool _begin_" in line or "bool _mota_available" in line or "char _begin_failure" in line or "char _active_name" in line) self.assertTrue(fields) source = (HARNESS.replace("@BEGIN@", begin) .replace("@STARTUP_FIELDS@", fields) .replace("@FORMAT_DEVICE_NAME@", formatter)) with tempfile.TemporaryDirectory(prefix="meshcore-ble-start-") as temp: temp = Path(temp) (temp / "Arduino.h").write_text(RTOS) for name in ("FreeRTOS.h", "task.h"): (temp / name).write_text('#include "Arduino.h"\n') cpp = temp / "startup.cpp" cpp.write_text(source) binary = temp / "startup" for mota in (0, 1): with self.subTest(mota=mota): built = subprocess.run([ "c++", "-std=c++17", "-DNRF52_PLATFORM=1", f"-DCOMPANION_FEATURE_BLE_MOTA_SOURCE={mota}", "-I" + str(temp), "-I" + str(ROOT / "src"), str(cpp), str(ROOT / "src/Nrf52LoopStack.cpp"), "-o", str(binary)], text=True, capture_output=True) self.assertEqual(built.returncode, 0, built.stderr) result = subprocess.run([str(binary)], text=True, capture_output=True) self.assertEqual(result.returncode, 0, result.stderr) if __name__ == "__main__": unittest.main()