From cbac8ed5ca303b0e955ab2c85233c737654ef335 Mon Sep 17 00:00:00 2001 From: torlando-tech Date: Sat, 9 May 2026 20:21:08 -0400 Subject: [PATCH] fix(ble): enable NimBLE scan response so service UUID + name fit; add T:BLE hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related changes: 1. NimBLE advertising overflow At boot pyxis was logging "NimBLEAdvertisementData: Data length exceeded" twice. The 128-bit Reticulum service UUID is 18 bytes once you include the AD type+length headers; the device name "TD-XXXXXX" is another 9-11 bytes; flags eat 3 bytes. That's already over the 31-byte legacy adv-packet limit, so NimBLE was silently truncating the advertisement and dropping the service UUID. Android Columba's BleScanner filters by ServiceUuid at the Android BLE driver layer (ScanFilter.Builder().setServiceUuid), so without the UUID in the primary adv data, pyxis was invisible to Columba. Fix: call enableScanResponse(true) BEFORE addServiceUUID + setName. NimBLE then routes the long device name into the secondary 31-byte scan-response payload that active scanners request, leaving the primary adv data with just flags + the service UUID — under budget and visible to the filter. Verified: with the fix, Android system Bluetooth reads pyxis's name as "TD-46cbcf" and Columba's BleGattServer logs "Central connected: FC:69:15:9C:B2:C9" (pyxis as central). The connection holds for ~40s before HCI_CONN_TIMEOUT — separate issue not addressed here, just the unblock so the link can be established at all. 2. T:BLE on|off harness hook Mirrors T:CALL_PROFILE / T:ANNLXST / T:LXSTDEST: persists the ble_en NVS key and starts/stops the interface live so the LXMF harness can flip BLE on/off the same way it drives any other subsystem. Idempotent for "already on" / "already off". Useful for upcoming pyxis ↔ Android Columba BLE smoke tests. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../platforms/NimBLEPlatform.cpp | 12 ++++- src/main.cpp | 48 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/lib/ble_interface/platforms/NimBLEPlatform.cpp b/lib/ble_interface/platforms/NimBLEPlatform.cpp index a3928de3..b8240f91 100644 --- a/lib/ble_interface/platforms/NimBLEPlatform.cpp +++ b/lib/ble_interface/platforms/NimBLEPlatform.cpp @@ -2506,8 +2506,18 @@ bool NimBLEPlatform::setupAdvertising() { _advertising_obj->setMinInterval(_config.adv_interval_min_ms * 1000 / 625); // Convert to 0.625ms units _advertising_obj->setMaxInterval(_config.adv_interval_max_ms * 1000 / 625); + // Enable scan-response payload BEFORE addServiceUUID + setName so the + // 128-bit service UUID (18 bytes once you include the AD type+length + // headers) and the device name (~9-11 bytes once you include its + // header) don't both fight for the 31-byte legacy adv packet. With + // scan-response on, NimBLE places the long name in the secondary 31 + // bytes returned to active scanners. Without this, NimBLE was logging + // "NimBLEAdvertisementData: Data length exceeded" twice at startup + // and emitting truncated advertising data — Android Columba's + // BleScanner never saw pyxis's service UUID. + _advertising_obj->enableScanResponse(true); + // NimBLE 2.x: Use addServiceUUID to include service in advertising packet - // The name goes in scan response automatically when enableScanResponse is used _advertising_obj->addServiceUUID(NimBLEUUID(UUID::SERVICE)); _advertising_obj->setName(_config.device_name); diff --git a/src/main.cpp b/src/main.cpp index 586f8bbf..96bccd67 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1880,6 +1880,54 @@ static void handle_test_hook_command(const String& line) { } Serial.println("T:OK answered"); } + else if (cmd == "T:BLE") { + // T:BLE on|off — toggle the BLE Mesh interface at runtime + persist + // to NVS. Used by the harness to bring BLE up for cross-device + // tests against Android Columba. Mirrors the SettingsScreen save + // path so the change survives a reboot. + bool want_on = (args == "on" || args == "1" || args == "true"); + bool want_off = (args == "off" || args == "0" || args == "false"); + if (!want_on && !want_off) { + // No arg → query current state + Serial.println(String("T:OK ble_enabled=") + (app_settings.ble_enabled ? "1" : "0")); + return; + } + Preferences prefs; + prefs.begin("lxmf", false); + prefs.putBool("ble_en", want_on); + prefs.end(); + app_settings.ble_enabled = want_on; + if (want_on && !ble_interface_impl) { + INFO("T:BLE on — creating BLE interface"); + void* ble_mem = heap_caps_calloc(1, sizeof(BLEInterface), MALLOC_CAP_SPIRAM); + ble_interface_impl = new (ble_mem) BLEInterface("BLE"); + ble_interface_impl->setRole(RNS::BLE::Role::DUAL); + ble_interface_impl->setLocalIdentity(identity->get_public_key().left(16)); + std::string ble_name = "TD-" + identity->get_public_key().toHex().substr(26, 6); + ble_interface_impl->setDeviceName(ble_name); + ble_interface = new Interface(ble_interface_impl); + if (ble_interface->start()) { + Transport::register_interface(*ble_interface); + ble_interface_impl->start_task(1, 0); + Serial.println("T:OK ble_enabled=1 started"); + } else { + Serial.println("T:ERR ble_start_failed"); + } + } else if (want_on) { + // Already exists, just restart + if (ble_interface->start()) { + Serial.println("T:OK ble_enabled=1 restarted"); + } else { + Serial.println("T:ERR ble_restart_failed"); + } + } else if (ble_interface_impl) { + // want_off + ble_interface_impl->stop(); + Serial.println("T:OK ble_enabled=0 stopped"); + } else { + Serial.println("T:OK ble_enabled=0"); + } + } else if (cmd == "T:LXSTDEST") { // T:LXSTDEST — pyxis's lxst.telephony destination hash. Used // by the harness to set up pyxis-as-callee tests (the bot