fix(nrf52): stabilize full companion bluetooth

This commit is contained in:
mikecarper committed 2026-09-22 05:54:46 -07:00
1 parent 21f877befc
commit 3282b7898e
10 files changed
+283 -59

No files matched your search

+33 -5
View File
@@ -2736,7 +2736,11 @@ requires_full_usb_packet_logging() {
# Full Companion and the combined USB+WiFi infrastructure artifact always
# carry the logger behind runtime controls. Other Full profiles retain the
# ordinary explicit logging-off/disable-debug behavior.
is_companion_radio_full_target "$env_name" && return 0
# T1000-E Full has a single, stability-critical USB CDC endpoint. It keeps
# that endpoint for Companion data and the terminal, not optional packet
# logging, so its packaged contract must not promise a logger.
[ "$env_name" != "t1000e_companion_radio_full" ] \
&& is_companion_radio_full_target "$env_name" && return 0
[ "$ESP32_FULL_BUILD" = "1" ] || return 1
is_companion_build "$env_name" && return 1
[ "${PACKET_LOGGING_OVERRIDE,,}" = "on" ] && return 0
@@ -3287,11 +3291,14 @@ declare_build_capability_contract() {
record_build_expectation "companion.usb" "+++MESHCORE-TERM-START"
record_build_expectation "companion.bluetooth" \
"Companion: starting Bluetooth"
record_build_expectation "companion.usb_logging" "get usb.logging"
if [ "$env_name" != "t1000e_companion_radio_full" ]; then
record_build_expectation "companion.usb_logging" "get usb.logging"
fi
record_build_expectation "companion.usb_mota_source" "ota folder on"
record_build_expectation "companion.mota_sender" \
"_ZN4mesh3ota16SerialMotaSource4read"
if is_nrf52_companion_radio_full_target "$env_name"; then
if is_nrf52_companion_radio_full_target "$env_name" \
&& [ "$env_name" != "t1000e_companion_radio_full" ]; then
record_build_expectation "companion.dedicated_usb_logging" \
"get usb.logging"
fi
@@ -3845,9 +3852,30 @@ apply_companion_radio_full_profile() {
# an exclusive host-folder mode with its existing `ota folder on` preamble.
# CDC 1 is a write-only plaintext packet/debug logging stream; BLE remains
# an independent Companion link. ESP32 intentionally does not use this
# dual-CDC capability.
# dual-CDC capability. The T1000-E's nRF52840 USB hardware resets when
# its persisted logging preference asks TinyUSB to activate CDC 1, so it
# intentionally keeps logging on the primary USB interface instead.
append_platformio_build_unflags "-UOTA_FOLDER_SERIAL"
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_FOLDER_SERIAL=1 -DCOMPANION_FEATURE_USB_MOTA_SOURCE=1 -DCOMPANION_FEATURE_BLE_MOTA_SOURCE=1 -DCOMPANION_FEATURE_DEDICATED_USB_LOGGING=1 -DCFG_TUD_CDC=2 -DMESH_DUAL_CDC_LOGGING=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1"
# The nRF52 core's production-safe Bluefruit bootstrap must retain its
# early SoftDevice diagnostics. Without CFG_DEBUG, a few nRF52840 USB
# layouts can finish the radio setup but leave the BLE stack unavailable.
# Apply the proven bootstrap to every nRF52 Full Companion rather than
# making a board-specific Bluetooth reliability promise.
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_FOLDER_SERIAL=1 -DCOMPANION_FEATURE_USB_MOTA_SOURCE=1 -DCOMPANION_FEATURE_BLE_MOTA_SOURCE=1 -DMESH_DEBUG=1 -DMESH_PACKET_LOGGING=1 -DCFG_DEBUG=1 -DRECOVERABLE_EXTERNAL_RADIO=1"
if [ "$env_name" = "t1000e_companion_radio_full" ]; then
# The T1000-E's primary CDC transport is the only USB interface. Its
# optional packet/debug logger can make the device reset while a host is
# enumerating, so keep Full Companion USB strictly data/terminal-only.
# This also ignores a stale persisted `usb.logging on` preference from
# an older dual-CDC image, allowing the device to recover automatically.
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DMESH_USB_LOGGING_DISABLED=1"
# MESH_USB_LOGGING_DISABLED still silences MeshCore's optional packet
# logger on the shared CDC endpoint. The shared nRF52 Full profile above
# retains the validated BLE bootstrap and radio recovery behavior.
append_platformio_build_unflags "-UMESH_DEBUG -UMESH_PACKET_LOGGING"
else
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCOMPANION_FEATURE_DEDICATED_USB_LOGGING=1 -DCFG_TUD_CDC=2 -DMESH_DUAL_CDC_LOGGING=1"
fi
# Every nRF52 Full Companion lends the upper half of its offline queue
# to the cold mOTA context. Keep 256 slots for everyday use and reserve
+41 -3
View File
@@ -2095,8 +2095,16 @@ void halt() {
#if defined(BLE_PIN_CODE)
static bool companion_bluetooth_initialized = false;
static char companion_bluetooth_start_failure[96] =
"Bluetooth has not started";
static uint32_t companion_bluetooth_start_at = 0;
static constexpr uint32_t COMPANION_BLUETOOTH_RETRY_MS = 5000UL;
#ifndef COMPANION_BLUETOOTH_BOOT_DELAY_MS
// Most boards can start Bluefruit as soon as the Companion store and radio
// are ready. A board profile may defer only its first start while a shared
// USB/radio power domain settles; retries continue to use the normal delay.
#define COMPANION_BLUETOOTH_BOOT_DELAY_MS 0UL
#endif
static uint8_t companion_bluetooth_session_address[
mesh::companion::BLUETOOTH_MAC_BYTES] = {};
static bool companion_bluetooth_session_address_ready = false;
@@ -2192,6 +2200,11 @@ void halt() {
mesh::usbLoggingPort().println("Companion: starting Bluetooth");
CompanionNodePrefs* prefs = the_mesh.getNodePrefs();
if (!prepareCompanionBluetoothIdentity(prefs)) {
strncpy(companion_bluetooth_start_failure,
"Bluetooth identity preparation could not save",
sizeof(companion_bluetooth_start_failure) - 1);
companion_bluetooth_start_failure[
sizeof(companion_bluetooth_start_failure) - 1] = 0;
mesh::usbLoggingPort().println(
"Companion: Bluetooth identity preparation failed; retrying");
scheduleCompanionBluetoothRetry();
@@ -2199,6 +2212,11 @@ void halt() {
}
if (!interface_manager.addInterface(InterfaceType::Bluetooth,
&bluetooth_interface)) {
strncpy(companion_bluetooth_start_failure,
"no free Companion interface slot",
sizeof(companion_bluetooth_start_failure) - 1);
companion_bluetooth_start_failure[
sizeof(companion_bluetooth_start_failure) - 1] = 0;
mesh::usbLoggingPort().println(
"Companion: no interface slot available for Bluetooth; retrying");
scheduleCompanionBluetoothRetry();
@@ -2231,6 +2249,11 @@ void halt() {
the_mesh.getBLEPin(), bluetooth_address,
clear_bonds, stealth_pair_once,
bonded_only_peer_ptr)) {
strncpy(companion_bluetooth_start_failure,
bluetooth_interface.beginFailure(),
sizeof(companion_bluetooth_start_failure) - 1);
companion_bluetooth_start_failure[
sizeof(companion_bluetooth_start_failure) - 1] = 0;
interface_manager.removeInterface(&bluetooth_interface);
#if defined(NRF52_PLATFORM)
// A partly initialized Bluefruit stack cannot be started again safely.
@@ -2250,6 +2273,7 @@ void halt() {
the_mesh.getNodePrefs()->powersaving_enabled != 0, false);
#endif
companion_bluetooth_initialized = true;
companion_bluetooth_start_failure[0] = 0;
if (interface_manager.isEnabled()) bluetooth_interface.enable();
}
@@ -2447,7 +2471,9 @@ bool handleCompanionBluetoothCommand(const char* command, char* reply,
}
#if defined(BLE_PIN_CODE)
if (!companion_bluetooth_initialized) {
snprintf(reply, reply_size, "Error: Bluetooth unavailable in this boot; check transport mode/startup");
snprintf(reply, reply_size,
"Error: Bluetooth unavailable in this boot: %s",
companion_bluetooth_start_failure);
return true;
}
if (action == CompanionBluetoothCommand::Get) {
@@ -2658,9 +2684,13 @@ bool handleCompanionWirelessCommand(const char* command, char* reply, size_t siz
}
void setup() {
// On nRF52 TinyUSB, bring the primary CDC transport up before any board or
// wireless preparation. Bluefruit preserves USB power state while it
// enables the SoftDevice; a late first Serial.begin() can leave that enable
// path in an invalid state on native-USB boards such as the T1000-E.
Serial.begin(115200);
mesh::wireless::control().begin(companion_wireless);
mesh::prepareUsbLoggingPort();
Serial.begin(115200);
#if MESH_PACKET_LOGGING
mesh::serialLogBegin();
#endif
@@ -2962,7 +2992,15 @@ void setup() {
// not be fragmented by WiFi. Other BLE companions start here.
#if defined(BLE_PIN_CODE)
#if !(defined(ESP32) && defined(WIFI_SSID))
startCompanionBluetooth();
#if COMPANION_BLUETOOTH_BOOT_DELAY_MS > 0
companion_bluetooth_start_at =
millis() + COMPANION_BLUETOOTH_BOOT_DELAY_MS;
if (companion_bluetooth_start_at == 0) {
companion_bluetooth_start_at = 1;
}
#else
startCompanionBluetooth();
#endif
#endif
#endif
+1 -1
View File
@@ -3,7 +3,7 @@
// Canonical USB-capable images compile at least one of these two diagnostics.
// One runtime gate covers every diagnostic category that writes to the USB
// Serial stream, so a separate logging firmware is unnecessary.
#if defined(ARDUINO) && \
#if !defined(MESH_USB_LOGGING_DISABLED) && defined(ARDUINO) && \
((defined(MESH_DEBUG) && MESH_DEBUG) || \
(defined(MESH_PACKET_LOGGING) && MESH_PACKET_LOGGING))
#define MESH_USB_LOGGING_AVAILABLE 1
+76 -43
View File
@@ -407,6 +407,7 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
// or configuration and a reboot.
if (_begin_attempted) return _begin_ready;
_begin_attempted = true;
_begin_failure[0] = 0;
instance = this;
_successfulConnectionPending.store(false, std::memory_order_release);
_successfulConnectionStarted.store(0, std::memory_order_relaxed);
@@ -422,16 +423,34 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
// If we want to control BLE LED ourselves, uncomment this:
// Bluefruit.autoConnLed(false);
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
// Configure individual connection values rather than the all-or-nothing
// Bluefruit preset. Defaults are identical to BANDWIDTH_MAX; constrained
// board profiles can lower event/buffer reservations without shrinking the
// Companion's negotiated 247-byte MTU.
Bluefruit.configPrphConn(COMPANION_BLE_PRPH_MTU,
COMPANION_BLE_PRPH_EVENT_LENGTH,
COMPANION_BLE_PRPH_HVN_QUEUE,
COMPANION_BLE_PRPH_WRCMD_QUEUE);
// The pinned nRF52 core is built with LTO. Its internal xTaskCreate() calls
// are resolved before the application's linker wrappers, so a wrapper-based
// task observation incorrectly reports that the BLE/SOC workers never
// started. Bluefruit.begin() is the reliable startup result available to
// this application.
if (!Bluefruit.begin()) {
uint8_t softdevice_enabled = 0;
const uint32_t softdevice_status =
sd_softdevice_is_enabled(&softdevice_enabled);
ble_gap_addr_t address = {};
const uint32_t gap_status = softdevice_enabled
? sd_ble_gap_addr_get(&address) : NRF_ERROR_INVALID_STATE;
snprintf(_begin_failure, sizeof(_begin_failure),
"Bluefruit begin failed (SD %lu/%u, GAP %lu)",
(unsigned long)softdevice_status,
(unsigned)softdevice_enabled,
(unsigned long)gap_status);
instance = nullptr;
mesh::usbLoggingPort().println(
"Bluetooth startup failed; reboot required");
_begin_failure);
return false;
}
@@ -439,6 +458,9 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
if (custom_address != nullptr) {
if (!mesh::companion::isValidBluetoothMac(custom_address)) {
strncpy(_begin_failure, "custom Bluetooth MAC is invalid",
sizeof(_begin_failure) - 1);
_begin_failure[sizeof(_begin_failure) - 1] = 0;
instance = nullptr;
BLE_DEBUG_PRINTLN("Custom Bluetooth MAC is invalid");
return false;
@@ -452,6 +474,9 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
}
const uint32_t address_error = sd_ble_gap_addr_set(&address);
if (address_error != NRF_SUCCESS) {
snprintf(_begin_failure, sizeof(_begin_failure),
"custom Bluetooth MAC rejected (%lu)",
(unsigned long)address_error);
instance = nullptr;
BLE_DEBUG_PRINTLN("Custom Bluetooth MAC failed: %lu", address_error);
return false;
@@ -474,6 +499,9 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
const int dev_name_len = snprintf(dev_name, sizeof(dev_name), "%s%s",
prefix, suffix);
if (dev_name_len < 0 || dev_name_len >= (int)sizeof(dev_name)) {
strncpy(_begin_failure, "Bluetooth device name is too long",
sizeof(_begin_failure) - 1);
_begin_failure[sizeof(_begin_failure) - 1] = 0;
instance = nullptr;
BLE_DEBUG_PRINTLN("Bluetooth device name is too long");
return false;
@@ -514,57 +542,61 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
if (bleuart.begin() != ERROR_NONE) {
strncpy(_begin_failure, "Bluetooth UART service registration failed",
sizeof(_begin_failure) - 1);
_begin_failure[sizeof(_begin_failure) - 1] = 0;
instance = nullptr;
BLE_DEBUG_PRINTLN("Bluetooth UART service begin failed");
return false;
}
bleuart.setRxCallback(onBleUartRX);
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
_mota_stream.setSender(sendMotaRequest, this);
_mota_stream.setActive(false);
_mota_service.setPermission(SECMODE_ENC_WITH_MITM,
SECMODE_ENC_WITH_MITM);
if (_mota_service.begin() != ERROR_NONE) {
instance = nullptr;
BLE_DEBUG_PRINTLN("Bluetooth mOTA service begin failed");
return false;
}
_mota_request.setProperties(CHR_PROPS_NOTIFY);
_mota_request.setPermission(SECMODE_ENC_WITH_MITM,
SECMODE_NO_ACCESS);
_mota_request.setMaxLen(mesh::ota::BLE_MOTA_REQUEST_MAX);
_mota_request.setUserDescriptor("mOTA device request");
if (_mota_request.begin() != ERROR_NONE) {
instance = nullptr;
BLE_DEBUG_PRINTLN("Bluetooth mOTA request characteristic begin failed");
return false;
}
_mota_response.setProperties(CHR_PROPS_WRITE);
_mota_response.setPermission(SECMODE_NO_ACCESS,
SECMODE_ENC_WITH_MITM);
_mota_response.setMaxLen(mesh::ota::BLE_MOTA_RESPONSE_MAX);
_mota_response.setUserDescriptor("mOTA host response");
_mota_response.setWriteCallback(onMotaResponse);
if (_mota_response.begin() != ERROR_NONE) {
instance = nullptr;
BLE_DEBUG_PRINTLN("Bluetooth mOTA response characteristic begin failed");
return false;
// Register the legacy DFU service before the optional mOTA service. A
// fielded SoftDevice has a fixed GATT table: reserve the ordinary Companion
// and firmware-recovery transports first, then use whatever remains for
// the larger mOTA extension. Do not attempt a new service after an mOTA
// registration failure because Bluefruit cannot remove attributes that did
// fit from its active table.
#if COMPANION_FEATURE_BLE_DFU
bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
if (bledfu.begin() != ERROR_NONE) {
mesh::usbLoggingPort().println(
"Bluetooth DFU service unavailable; normal Companion Bluetooth continues");
}
#endif
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
_mota_stream.setSender(sendMotaRequest, this);
_mota_stream.setActive(false);
_mota_available = false;
// Register DFU on the main BLE stack so paired clients can discover it
// without switching the device into a separate OTA-only BLE mode first.
bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
if (bledfu.begin() != ERROR_NONE) {
instance = nullptr;
BLE_DEBUG_PRINTLN("Bluetooth DFU service begin failed");
return false;
_mota_service.setPermission(SECMODE_ENC_WITH_MITM,
SECMODE_ENC_WITH_MITM);
if (_mota_service.begin() == ERROR_NONE) {
_mota_request.setProperties(CHR_PROPS_NOTIFY);
_mota_request.setPermission(SECMODE_ENC_WITH_MITM,
SECMODE_NO_ACCESS);
_mota_request.setMaxLen(mesh::ota::BLE_MOTA_REQUEST_MAX);
_mota_request.setUserDescriptor("mOTA device request");
if (_mota_request.begin() == ERROR_NONE) {
_mota_response.setProperties(CHR_PROPS_WRITE);
_mota_response.setPermission(SECMODE_NO_ACCESS,
SECMODE_ENC_WITH_MITM);
_mota_response.setMaxLen(mesh::ota::BLE_MOTA_RESPONSE_MAX);
_mota_response.setUserDescriptor("mOTA host response");
_mota_response.setWriteCallback(onMotaResponse);
_mota_available = _mota_response.begin() == ERROR_NONE;
}
}
if (!_mota_available) {
// GATT attributes cannot be removed after Bluefruit starts, so do not
// retry here. Continuing with the already-registered UART service keeps
// the phone and USB recovery paths available; a corrected image can add
// the optional mOTA service on the next reboot.
mesh::usbLoggingPort().println(
"Bluetooth mOTA unavailable; normal Companion Bluetooth continues");
}
#endif
Bluefruit.Advertising.setType(
BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED);
@@ -985,7 +1017,8 @@ bool SerialBLEInterface::isConnected() const {
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
bool SerialBLEInterface::isMotaChannelReady() {
return isConnected() && _conn_handle != BLE_CONN_HANDLE_INVALID
return _mota_available && isConnected()
&& _conn_handle != BLE_CONN_HANDLE_INVALID
&& _mota_request.notifyEnabled(_conn_handle);
}
#endif
+38
View File
@@ -16,8 +16,38 @@
#define BLE_TX_POWER 4
#endif
// Bluefruit's BANDWIDTH_MAX preset reserves a 100-unit connection event and
// three notification buffers. That is useful on boards with roomy RAM, but
// it exceeds the usable SoftDevice reservation of some bootloader layouts.
// Keep the existing preset as the default while allowing constrained boards
// to retain the 247-byte MTU with a smaller event/buffer reservation.
#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
// Nordic UART is the required Companion service. The legacy Nordic DFU
// service is an optional extension that some constrained field combinations
// cannot register alongside the Companion GATT table.
#ifndef COMPANION_FEATURE_BLE_DFU
#define COMPANION_FEATURE_BLE_DFU 1
#endif
class SerialBLEInterface : public BaseSerialInterface {
#if COMPANION_FEATURE_BLE_DFU
BLEDfu bledfu;
#endif
BLEUart bleuart;
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
BLEService _mota_service = BLEService(mesh::ota::BLE_MOTA_SERVICE_UUID);
@@ -26,10 +56,15 @@ class SerialBLEInterface : public BaseSerialInterface {
BLECharacteristic _mota_response =
BLECharacteristic(mesh::ota::BLE_MOTA_RESPONSE_UUID);
mesh::ota::BleMotaStream _mota_stream;
// The normal Nordic UART service is the Companion transport. mOTA is an
// optional Full-Companion extension and must never take that transport down
// if a fielded SoftDevice rejects one of its extra GATT attributes.
bool _mota_available = false;
#endif
bool _isEnabled;
bool _begin_attempted = false;
bool _begin_ready = false;
char _begin_failure[96] = {};
bool _isDeviceConnected;
uint16_t _conn_handle;
unsigned long _last_health_check;
@@ -129,6 +164,9 @@ public:
bool clear_bonds = false, bool stealth_pair_once = false,
const mesh::companion::BluetoothPeerIdentity*
bonded_only_peer = nullptr);
// Kept after a failed begin so a normal Companion terminal can report the
// cause even when early USB logging was not available.
const char* beginFailure() const { return _begin_failure; }
void disconnect();
void enable() override;
+36 -2
View File
@@ -581,8 +581,41 @@ for full_env in "${SUPPORTED_PIO_ENVS[@]}"; do
|| fail "$full_env Full Companion omitted LoRa OTA seeding"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"OTA_FOLDER_SERIAL=1"* ]] \
|| fail "$full_env Full Companion omitted USB folder seeding"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"COMPANION_FEATURE_BLE_MOTA_SOURCE=1"* ]] \
|| fail "$full_env Full Companion omitted BLE folder seeding"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"CFG_DEBUG=1"* ]] \
|| fail "$full_env Full Companion omitted the validated nRF52 BLE bootstrap"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"RECOVERABLE_EXTERNAL_RADIO=1"* ]] \
|| fail "$full_env Full Companion must keep Companion transports available while radio recovery runs"
if [ "$full_env" = "t1000e_companion_radio_full" ]; then
[[ "$PLATFORMIO_BUILD_FLAGS" == *"COMPANION_FEATURE_BLE_MOTA_SOURCE=1"* ]] \
|| fail "$full_env Full Companion omitted BLE folder seeding"
[[ "$PLATFORMIO_BUILD_FLAGS" != *"COMPANION_FEATURE_BLE_MOTA_SOURCE=0"* ]] \
|| fail "$full_env must not disable its BLE mOTA extension"
[[ "$PLATFORMIO_BUILD_FLAGS" != *"COMPANION_FEATURE_BLE_DFU=0"* ]] \
|| fail "$full_env must not disable its BLE DFU extension"
[[ "$PLATFORMIO_BUILD_FLAGS" != *"COMPANION_FEATURE_DEDICATED_USB_LOGGING=1"* ]] \
|| fail "$full_env must not enable its unstable second USB CDC interface"
[[ "$PLATFORMIO_BUILD_FLAGS" != *"CFG_TUD_CDC=2"* ]] \
|| fail "$full_env must keep a single USB CDC interface"
[[ "$PLATFORMIO_BUILD_FLAGS" != *"MESH_DUAL_CDC_LOGGING=1"* ]] \
|| fail "$full_env must keep USB logging on its primary interface"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"MESH_USB_LOGGING_DISABLED=1"* ]] \
|| fail "$full_env must disable its unstable primary USB logger"
[[ "$PLATFORMIO_BUILD_FLAGS" != *"COMPANION_BLE_PRPH_MTU="* ]] \
|| fail "$full_env must retain the normal Full-Companion BLE bandwidth"
[[ "$PLATFORMIO_BUILD_FLAGS" != *"COMPANION_BLUETOOTH_BOOT_DELAY_MS="* ]] \
|| fail "$full_env must start Bluetooth promptly"
else
[[ "$PLATFORMIO_BUILD_FLAGS" == *"COMPANION_FEATURE_BLE_MOTA_SOURCE=1"* ]] \
|| fail "$full_env Full Companion omitted BLE folder seeding"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"COMPANION_FEATURE_DEDICATED_USB_LOGGING=1"* ]] \
|| fail "$full_env Full Companion omitted dedicated USB logging"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"CFG_TUD_CDC=2"* ]] \
|| fail "$full_env Full Companion omitted its second USB CDC interface"
[[ "$PLATFORMIO_BUILD_FLAGS" == *"MESH_DUAL_CDC_LOGGING=1"* ]] \
|| fail "$full_env Full Companion omitted dedicated USB logging routing"
[[ "$PLATFORMIO_BUILD_FLAGS" != *"MESH_USB_LOGGING_DISABLED=1"* ]] \
|| fail "$full_env unexpectedly disabled USB logging"
fi
if ! pio_env_option_contains "$pio_env" build_src_filter "helpers/ota/*.cpp" \
&& [[ "$PLATFORMIO_BUILD_SRC_FILTER" != *"helpers/ota/*.cpp"* ]]; then
fail "$full_env Full Companion omitted the OTA C++ implementation"
@@ -761,6 +794,7 @@ done
for logging_target in Station_G2_companion_radio_full RAK_4631_companion_radio_full; do
verify_full_logging_contract "$logging_target" 0 off 1 yes no
done
verify_full_logging_contract t1000e_companion_radio_full 0 off 1 no no
# Synthetic inventory: ESP32 and nRF52 repeater/room-server targets with the
# same lean OTA policy. The sensor deliberately has no lean sibling.
+38 -3
View File
@@ -32,8 +32,24 @@ HARNESS = r'''
#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;
@@ -46,6 +62,7 @@ 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; }
int sd_ble_gap_ppcp_set(ble_gap_conn_params_t*) { return NRF_SUCCESS; }
namespace mesh {
struct Logging { void println(const char*) {} };
@@ -85,7 +102,16 @@ struct Settings {
struct BluefruitStub {
Settings Security, Periph, Advertising, ScanResponse;
int starts = 0;
void configPrphBandwidth(int) {}
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;
@@ -154,8 +180,15 @@ int main() {
fail_service = fault >= 2 ? fault - 1 : 0;
SerialBLEInterface port;
const int starts = Bluefruit.starts;
const bool expected = fault == 0;
// 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.
@@ -175,7 +208,9 @@ class Nrf52BleStartupTest(unittest.TestCase):
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)
if "bool _begin_" in line
or "bool _mota_available" in line
or "char _begin_failure" in line)
self.assertTrue(fields)
source = HARNESS.replace("@BEGIN@", begin).replace("@STARTUP_FIELDS@", fields)
with tempfile.TemporaryDirectory(prefix="meshcore-ble-start-") as temp:
+8 -2
View File
@@ -338,6 +338,13 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases():
)[1].split("return 0", 1)[0]
assert "-DCFG_TUD_CDC=2" in nrf52_full
assert "-DMESH_DUAL_CDC_LOGGING=1" in nrf52_full
# Every nRF52 Full image keeps the proven Bluefruit bootstrap and radio
# recovery profile. T1000-E alone disables its optional packet logger
# because its sole CDC endpoint is reserved for Companion data/terminal.
assert "-DCFG_DEBUG=1" in nrf52_full
assert "-DRECOVERABLE_EXTERNAL_RADIO=1" in nrf52_full
assert 'if [ "$env_name" = "t1000e_companion_radio_full" ]; then' in nrf52_full
assert "-DMESH_USB_LOGGING_DISABLED=1" in nrf52_full
esp32_full = full.split("return 0\n fi", 1)[1]
assert "-DCFG_TUD_CDC=2" not in esp32_full
assert "-DMESH_DUAL_CDC_LOGGING=1" not in esp32_full
@@ -410,8 +417,7 @@ def test_canonical_bulk_matrix_omits_runtime_and_transport_aliases():
assert "enterUsbLoggingTerminalMode();" in companion_main
assert "usb_logging_terminal_mode" in companion_main
assert "_prefs.usb_logging_enabled = 0" in companion
assert 'strcmp(value, "on reboot") == 0' in companion
assert 'strcmp(value, "off reboot") == 0' in companion
assert "mesh::cli::parseLoggingToggle(value, enabled, reboot_if_needed)" in companion
assert "reboot required to change USB interfaces" in companion
assert "rebooting to change USB interfaces" in companion
assert "mesh::saveUsbLoggingBootPreference(enabled)" in companion
+8
View File
@@ -80,6 +80,14 @@ bool radio_init() {
return true; // success
}
uint32_t radio_fallback_rng_seed() {
uint32_t seed = micros() ^ NRF_FICR->DEVICEID[0] ^ NRF_FICR->DEVICEID[1];
#ifdef USE_CC310_HW_CRYPTO
mesh::mixCC310Random(reinterpret_cast<uint8_t*>(&seed), sizeof(seed));
#endif
return seed;
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity
+4
View File
@@ -47,4 +47,8 @@ extern VolatileRTCClock rtc_clock;
extern T1000SensorManager sensors;
bool radio_init();
// A Companion must remain serviceable over USB/Bluetooth even if the LR1110
// is temporarily unavailable. This seed keeps the local crypto RNG usable
// until the radio recovery path can collect radio noise again.
uint32_t radio_fallback_rng_seed();
mesh::LocalIdentity radio_new_identity();