diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 8450235a..d2db88b8 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -6663,11 +6663,19 @@ bool MyMesh::applyAndSaveBluetoothName(const char* value, char* reply, if (use_default) { snprintf(reply, reply_size, +#if defined(NRF52_PLATFORM) + "OK - Bluetooth name follows %s; advertising refreshes after disconnect", +#else "OK - Bluetooth name follows %s; reboot required", +#endif BLE_NAME_PREFIX); } else { snprintf(reply, reply_size, +#if defined(NRF52_PLATFORM) + "OK - Bluetooth name saved as '%s'; advertising refreshes after disconnect", value); +#else "OK - Bluetooth name saved as '%s'; reboot required", value); +#endif } return true; } diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7ea38f87..24bb21b8 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -2323,6 +2323,24 @@ void halt() { startCompanionBluetooth(); } +#if defined(NRF52_PLATFORM) + static void serviceCompanionBluetoothName() { + if (!companion_bluetooth_initialized) return; + static uint32_t last_check = 0; + const uint32_t now = millis(); + if ((uint32_t)(now - last_check) < 250) return; + last_check = now; + + const CompanionNodePrefs* prefs = the_mesh.getNodePrefs(); + if (prefs == nullptr) return; + const bool custom = + mesh::companion::hasCustomBluetoothName(prefs->bluetooth_name); + bluetooth_interface.refreshName(custom ? "" : BLE_NAME_PREFIX, + custom ? prefs->bluetooth_name + : prefs->node_name); + } +#endif + static void serviceCompanionBluetoothIdentity() { if (!companion_bluetooth_initialized) return; @@ -3472,5 +3490,8 @@ void loop() { #endif serviceCompanionBluetoothIdentity(); serviceCompanionBluetoothControl(); +#if defined(NRF52_PLATFORM) + serviceCompanionBluetoothName(); +#endif #endif } diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 72ea18b7..a1212865 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -30,6 +30,27 @@ static SerialBLEInterface* instance = nullptr; +static bool formatDeviceName(char* dest, size_t capacity, + const char* prefix, const char* name) { + if (dest == nullptr || capacity == 0 || prefix == nullptr || name == nullptr) { + return false; + } + char resolved_name[32]; + const char* suffix = name; + if (strcmp(name, "@@MAC") == 0) { + ble_gap_addr_t addr; + if (sd_ble_gap_addr_get(&addr) == NRF_SUCCESS) { + snprintf(resolved_name, sizeof(resolved_name), + "%02X%02X%02X%02X%02X%02X", + addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2], + addr.addr[1], addr.addr[0]); + suffix = resolved_name; + } + } + const int length = snprintf(dest, capacity, "%s%s", prefix, suffix); + return length >= 0 && length < (int)capacity; +} + #if defined(COMPANION_RADIO_FULL) && COMPANION_RADIO_FULL // Bluefruit's bonded CCCD flash write resets some nRF52 Full Companions. // Retain the small SoftDevice system-attribute image in RAM for reconnects @@ -616,22 +637,8 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name, } } - char resolved_name[32]; - const char* suffix = name; - if (strcmp(name, "@@MAC") == 0) { - ble_gap_addr_t addr; - if (sd_ble_gap_addr_get(&addr) == NRF_SUCCESS) { - snprintf(resolved_name, sizeof(resolved_name), - "%02X%02X%02X%02X%02X%02X", - addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2], - addr.addr[1], addr.addr[0]); - suffix = resolved_name; - } - } - char dev_name[32+16]; - 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)) { + char dev_name[sizeof(_active_name)]; + if (!formatDeviceName(dev_name, sizeof(dev_name), prefix, name)) { strncpy(_begin_failure, "Bluetooth device name is too long", sizeof(_begin_failure) - 1); _begin_failure[sizeof(_begin_failure) - 1] = 0; @@ -660,6 +667,7 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name, Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.setName(dev_name); + memcpy(_active_name, dev_name, strlen(dev_name) + 1); Bluefruit.Security.setMITM(true); Bluefruit.Security.setPIN(charpin); @@ -752,6 +760,35 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name, return true; } +bool SerialBLEInterface::refreshName(const char* prefix, const char* name) { + if (!_begin_ready || _conn_handle != BLE_CONN_HANDLE_INVALID) return false; + + char desired[sizeof(_active_name)]; + if (!formatDeviceName(desired, sizeof(desired), prefix, name)) return false; + if (strcmp(desired, _active_name) == 0) return true; + + const bool was_advertising = isAdvertising(); + if (was_advertising && !Bluefruit.Advertising.stop()) return false; + + Bluefruit.setName(desired); + char actual[sizeof(_active_name)]; + const size_t actual_len = Bluefruit.getName(actual, sizeof(actual)); + if (actual_len != strlen(desired) + || memcmp(actual, desired, actual_len) != 0) { + if (was_advertising) startAdvertising("advertising failed after name refresh"); + return false; + } + + // Directed bonded-only advertisements have no payload to refresh. + if (!_bonded_only) { + Bluefruit.ScanResponse.clearData(); + Bluefruit.ScanResponse.addName(); + } + memcpy(_active_name, desired, strlen(desired) + 1); + if (was_advertising) startAdvertising("advertising failed after name refresh"); + return true; +} + bool SerialBLEInterface::takeSuccessfulConnection( mesh::companion::BluetoothPeerIdentity* peer) { if (!_successfulConnectionPending.load(std::memory_order_acquire)) { diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index 8de096fc..d63392d8 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -65,6 +65,7 @@ class SerialBLEInterface : public BaseSerialInterface { bool _begin_attempted = false; bool _begin_ready = false; char _begin_failure[96] = {}; + char _active_name[48] = {}; bool _isDeviceConnected; uint16_t _conn_handle; unsigned long _last_health_check; @@ -166,6 +167,9 @@ public: bool clear_bonds = false, bool stealth_pair_once = false, const mesh::companion::BluetoothPeerIdentity* bonded_only_peer = nullptr); + // Refresh the GAP and scan-response name after a saved node/Bluetooth name + // changes. A connected phone is left alone; the next idle pass applies it. + bool refreshName(const char* prefix, const char* name); // 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; }