fix(nrf52): stabilize full companion bluetooth
This commit is contained in:
1 parent
21f877befc
commit
3282b7898e
10 files changed
+283
-59
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
Reference in new issue
Block a user