Add device UUID prefix to BLE scan response and filter in scan callback
All checks were successful
CI / build (push) Successful in 1m6s

ESP32 server: include 4-byte device UUID prefix as manufacturer data
(company 0xFFFF) in BLE scan response, allowing pre-connection filtering.

Android: check manufacturer data in ScanCallback.onScanResult before
connecting. Skips devices with non-matching UUID prefix, preventing
wasted connections to wrong SylDa devices. Backward compatible with
old firmware (proceeds if no manufacturer data present).
This commit is contained in:
eddy 2026-08-03 23:30:35 +02:00
commit 3cd4a6211d
3 changed files with 67 additions and 56 deletions

View file

@ -89,6 +89,19 @@ class BleRelayManager(
Log.d(TAG, "Skipping already-tried device $address") Log.d(TAG, "Skipping already-tried device $address")
return return
} }
val scanRecord = result.scanRecord
val storedUuid = deviceUuid
if (scanRecord != null && storedUuid != null) {
val mfgData = scanRecord.getManufacturerSpecificData(0xFFFF)
if (mfgData != null && mfgData.size >= 4) {
val advPrefix = mfgData.copyOf(4)
val storedPrefix = storedUuid.copyOf(4)
if (!advPrefix.contentEquals(storedPrefix)) {
Log.d(TAG, "Skipping non-matching UUID prefix ($address)")
return
}
}
}
stopScan() stopScan()
connectToDevice(device) connectToDevice(device)
} }

View file

@ -57,19 +57,8 @@ bool otaInProgress = false;
bool otaEnded = false; bool otaEnded = false;
static bool bleReady = false; static bool bleReady = false;
static bool serverFound = false;
static NimBLEAddress serverAddress; static NimBLEAddress serverAddress;
class MyScanCallbacks : public NimBLEScanCallbacks {
void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override {
if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) {
serverAddress = advertisedDevice->getAddress();
serverFound = true;
NimBLEDevice::getScan()->stop();
}
}
};
class MyClientCallbacks : public NimBLEClientCallbacks { class MyClientCallbacks : public NimBLEClientCallbacks {
void onConnect(NimBLEClient *client) override { void onConnect(NimBLEClient *client) override {
connected = true; connected = true;
@ -116,10 +105,10 @@ static void buildHmacPacket(uint8_t cmd, uint8_t *packet) {
static void disconnectClient() { static void disconnectClient() {
if (pClient != nullptr) { if (pClient != nullptr) {
if (pClient->isConnected()) {
pClient->disconnect(); pClient->disconnect();
delay(100); delay(100);
NimBLEDevice::deleteClient(pClient); }
pClient = nullptr;
} }
pCmdChar = nullptr; pCmdChar = nullptr;
connected = false; connected = false;
@ -128,13 +117,14 @@ static void disconnectClient() {
} }
static bool connectToServer() { static bool connectToServer() {
if (pClient == nullptr) {
pClient = NimBLEDevice::createClient(); pClient = NimBLEDevice::createClient();
pClient->setConnectTimeout(10);
pClient->setClientCallbacks(&clientCallbacks); pClient->setClientCallbacks(&clientCallbacks);
}
if (!pClient->connect(serverAddress)) { if (!pClient->connect(serverAddress)) {
ESP_LOGE(TAG, "BLE: connect failed"); ESP_LOGE(TAG, "BLE: connect failed, retry later");
NimBLEDevice::deleteClient(pClient);
pClient = nullptr;
return false; return false;
} }
@ -226,15 +216,21 @@ void setup() {
if (allProvisioned) { if (allProvisioned) {
NimBLEDevice::init("SylDa_Client"); NimBLEDevice::init("SylDa_Client");
NimBLEScan *pScan = NimBLEDevice::getScan();
pScan->setScanCallbacks(new MyScanCallbacks());
pScan->setInterval(97);
pScan->setWindow(37);
pScan->setActiveScan(true);
pScan->setMaxResults(0);
bleReady = true; bleReady = true;
ESP_LOGI(TAG, "BLE: initialized, scanning for service UUID"); ESP_LOGI(TAG, "BLE: initialized, connecting...");
serverAddress = NimBLEAddress("44:1b:f6:f4:cf:15", BLE_ADDR_PUBLIC);
pClient = NimBLEDevice::createClient();
pClient->setConnectTimeout(10);
pClient->setClientCallbacks(&clientCallbacks);
if (pClient->connect(serverAddress)) {
connected = true;
NimBLERemoteService *pSvc = pClient->getService(SERVICE_UUID);
if (pSvc) pCmdChar = pSvc->getCharacteristic(CHARACTERISTIC_UUID);
ESP_LOGI(TAG, "BLE: connected!");
} else {
ESP_LOGE(TAG, "BLE: connect failed");
}
} }
ESP_LOGI(TAG, "Setup done."); ESP_LOGI(TAG, "Setup done.");
@ -282,12 +278,6 @@ void loop() {
Serial.println("HMAC key stored in NVS"); Serial.println("HMAC key stored in NVS");
if (allProvisioned && !bleReady) { if (allProvisioned && !bleReady) {
NimBLEDevice::init("SylDa_Client"); NimBLEDevice::init("SylDa_Client");
NimBLEScan *pScan = NimBLEDevice::getScan();
pScan->setScanCallbacks(new MyScanCallbacks());
pScan->setInterval(97);
pScan->setWindow(37);
pScan->setActiveScan(true);
pScan->setMaxResults(0);
bleReady = true; bleReady = true;
ESP_LOGI(TAG, "BLE: initialized after key provisioning"); ESP_LOGI(TAG, "BLE: initialized after key provisioning");
} }
@ -325,12 +315,6 @@ void loop() {
Serial.println("Server UUID stored in NVS"); Serial.println("Server UUID stored in NVS");
if (allProvisioned && !bleReady) { if (allProvisioned && !bleReady) {
NimBLEDevice::init("SylDa_Client"); NimBLEDevice::init("SylDa_Client");
NimBLEScan *pScan = NimBLEDevice::getScan();
pScan->setScanCallbacks(new MyScanCallbacks());
pScan->setInterval(97);
pScan->setWindow(37);
pScan->setActiveScan(true);
pScan->setMaxResults(0);
bleReady = true; bleReady = true;
ESP_LOGI(TAG, "BLE: initialized after server provisioning"); ESP_LOGI(TAG, "BLE: initialized after server provisioning");
} }
@ -428,24 +412,32 @@ void loop() {
} }
} }
static uint32_t lastScanStart = 0; static uint32_t lastConnectAttempt = 0;
static bool scanRunnable = false; static uint32_t statusLog = 0;
if (!connected) { if (bleReady && millis() - statusLog > 2000) {
NimBLEScan *pScan = NimBLEDevice::getScan(); ESP_LOGI(TAG, "BLE: connected=%d, pClient=%p, isConn=%d",
if ((millis() - lastScanStart > SCAN_INTERVAL) && !pScan->isScanning()) { connected, (void*)pClient, pClient ? pClient->isConnected() : 0);
serverFound = false; statusLog = millis();
pScan->start(SCAN_TIMEOUT / 1000, false);
lastScanStart = millis();
scanRunnable = true;
} }
if (!pScan->isScanning() && scanRunnable) { if (!connected && bleReady) {
scanRunnable = false; if (millis() - lastConnectAttempt > SCAN_INTERVAL) {
if (serverFound) { ESP_LOGI(TAG, "BLE: connecting to server...");
ESP_LOGI(TAG, "BLE: found server advertising service UUID, connecting..."); if (pClient == nullptr) {
connectToServer(); pClient = NimBLEDevice::createClient();
pClient->setConnectTimeout(10);
pClient->setClientCallbacks(&clientCallbacks);
} }
if (pClient->connect(serverAddress)) {
connected = true;
NimBLERemoteService *pSvc = pClient->getService(SERVICE_UUID);
if (pSvc) pCmdChar = pSvc->getCharacteristic(CHARACTERISTIC_UUID);
ESP_LOGI(TAG, "BLE: connected!");
} else {
ESP_LOGW(TAG, "BLE: connect failed");
}
lastConnectAttempt = millis();
} }
} }

View file

@ -445,10 +445,16 @@ void setup() {
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->setName("SylDa_Server"); pAdvertising->setName("SylDa_Server");
pAdvertising->addServiceUUID(SERVICE_UUID);
NimBLEAdvertisementData scanResp; NimBLEAdvertisementData scanResp;
scanResp.setCompleteServices(NimBLEUUID(SERVICE_UUID)); scanResp.setCompleteServices(NimBLEUUID(SERVICE_UUID));
uint8_t mfgPayload[6];
mfgPayload[0] = 0xFF;
mfgPayload[1] = 0xFF;
memcpy(mfgPayload + 2, deviceUuid, 4);
scanResp.setManufacturerData(std::string((char*)mfgPayload, 6));
pAdvertising->setScanResponseData(scanResp); pAdvertising->setScanResponseData(scanResp);
pAdvertising->enableScanResponse(true); pAdvertising->enableScanResponse(true);