From 01e0476a4b02fcbe2c822c31d9ad0a767845897f Mon Sep 17 00:00:00 2001 From: Matthias Prinke <83612361+matthias-bs@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:46:23 +0100 Subject: [PATCH] Update to NimBLE-Arduino v2.x.x (#125) * Updated for NimBLE-Arduino v2.x * Updated Theengs/Decoder to v1.8.4 * Added workaround for unavailable setScanFilterMode() on ESP32-S3 --- .github/workflows/CI.yml | 2 +- extras/BLETest/BleSensors.cpp | 69 ++++++------ extras/BLETest/BleSensors.h | 4 +- package.json | 4 +- src/BleSensors/BleSensors.cpp | 200 ++++++++++++++++++---------------- src/BleSensors/BleSensors.h | 8 +- 6 files changed, 152 insertions(+), 135 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index aa06684..a92c56c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -83,7 +83,7 @@ jobs: "ESP32Time@2.0.6" "OneWireNg@0.13.3" "DallasTemperature@3.9.0" - "NimBLE-Arduino@1.4.3" + "NimBLE-Arduino@2.2.0" "ATC_MiThermometer@0.4.2" "TheengsDecoder@1.8.4" "Preferences@2.1.0" diff --git a/extras/BLETest/BleSensors.cpp b/extras/BLETest/BleSensors.cpp index d927c4c..8ff0ac2 100644 --- a/extras/BLETest/BleSensors.cpp +++ b/extras/BLETest/BleSensors.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// BleSensors.cpp +// BleSensors.h // // Wrapper class for Theeengs Decoder (https://github.com/theengs/decoder) // @@ -36,6 +36,7 @@ // // 20230211 Created // 20240427 Added paramter activeScan to getData() +// 20250121 Updated for NimBLE-Arduino v2.x // // ToDo: // - @@ -46,31 +47,33 @@ #include "BleSensors.h" -/*! - * \brief Callbacks for advertised BLE devices - */ class ScanCallbacks : public NimBLEScanCallbacks { public: - NimBLEScan *m_pBLEScan; /// NimBLEScan object std::vector m_knownBLEAddresses; /// MAC addresses of known sensors std::vector *m_sensorData; /// Sensor data + NimBLEScan *m_pBLEScan; private: int m_devices_found = 0; /// Number of known devices found + void onDiscovered(const NimBLEAdvertisedDevice *advertisedDevice) override + { + log_v("Discovered Advertised Device: %s", advertisedDevice->toString().c_str()); + } + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { TheengsDecoder decoder; - bool device_found = false; unsigned idx; + bool device_found = false; JsonDocument doc; - log_v("Advertised Device: %s", advertisedDevice->toString().c_str()); - + log_v("Advertised Device Result: %s", advertisedDevice->toString().c_str()); JsonObject BLEdata = doc.to(); String mac_adress = advertisedDevice->getAddress().toString().c_str(); + BLEdata["id"] = (char *)mac_adress.c_str(); for (idx = 0; idx < m_knownBLEAddresses.size(); idx++) { if (mac_adress == m_knownBLEAddresses[idx].c_str()) @@ -78,7 +81,6 @@ class ScanCallbacks : public NimBLEScanCallbacks log_v("BLE device found at index %d", idx); device_found = true; m_devices_found++; - log_d("BLE devices found: %d", m_devices_found); break; } } @@ -138,7 +140,17 @@ class ScanCallbacks : public NimBLEScanCallbacks m_pBLEScan->stop(); } } -}; + + void onScanEnd(const NimBLEScanResults &results, int reason) override + { + log_v("Scan Ended; reason = %d", reason); + } +} scanCallbacks; + +void BleSensors::clearScanResults(void) +{ + _pBLEScan->clearResults(); +} // Set all array members invalid void BleSensors::resetData(void) @@ -152,28 +164,23 @@ void BleSensors::resetData(void) /** * \brief Get BLE sensor data */ -unsigned BleSensors::getData(uint32_t duration, bool activeScan) +unsigned BleSensors::getData(uint32_t scanTime, bool activeScan) { - NimBLEDevice::init(""); - - _pBLEScan = NimBLEDevice::getScan(); // create new scan - - // Set the callback for when devices are discovered, no duplicates. - ScanCallbacks *myCb = new ScanCallbacks(); - - // Copy some data required by the Callback - myCb->m_pBLEScan = _pBLEScan; - myCb->m_knownBLEAddresses = _known_sensors; - myCb->m_sensorData = &data; - - //_pBLEScan->setAdvertisedDeviceCallbacks(myCb); - _pBLEScan->setScanCallbacks(myCb); - _pBLEScan->setFilterPolicy(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); - _pBLEScan->setActiveScan(activeScan); // Set active scanning, this will get more data from the advertiser. - _pBLEScan->setInterval(97); // How often the scan occurs / switches channels; in milliseconds, - _pBLEScan->setWindow(37); // How long to scan during the interval; in milliseconds. - _pBLEScan->setMaxResults(0); // do not store the scan results, use callback only. - NimBLEScanResults results = _pBLEScan->getResults(duration * 1000, false); + NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); + NimBLEDevice::setScanDuplicateCacheSize(200); + + NimBLEDevice::init("ble-scan"); + _pBLEScan = NimBLEDevice::getScan(); + _pBLEScan->setScanCallbacks(&scanCallbacks); + _pBLEScan->setActiveScan(activeScan); + _pBLEScan->setInterval(97); + _pBLEScan->setWindow(37); + scanCallbacks.m_knownBLEAddresses = _known_sensors; + scanCallbacks.m_sensorData = &data; + scanCallbacks.m_pBLEScan = _pBLEScan; + // Start scanning + // Blocks until all known devices are found or scanTime is expired + _pBLEScan->getResults(scanTime * 1000, false); return 0; } diff --git a/extras/BLETest/BleSensors.h b/extras/BLETest/BleSensors.h index 4384604..4706ccb 100644 --- a/extras/BLETest/BleSensors.h +++ b/extras/BLETest/BleSensors.h @@ -106,9 +106,7 @@ class BleSensors { /*! * \brief Delete results from BLEScan buffer to release memory. */ - void clearScanResults(void) { - _pBLEScan->clearResults(); - }; + void clearScanResults(void); /*! * \brief Get data from sensors by running a BLE scan. diff --git a/package.json b/package.json index e05c57a..e015e87 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,9 @@ "ESP32Time": "fbiego/ESP32Time#semver:^2.0.6", "OneWireNg": "https://github.com/pstolarz/OneWireNg#semver:^0.13.3", "Arduino-Temperature-Control-Library": "milesburton/Arduino-Temperature-Control-Library#semver:^3.9.1", - "NimBLE-Arduino": "h2zero/NimBLE-Arduino#semver:^1.4.3", + "NimBLE-Arduino": "h2zero/NimBLE-Arduino#semver:^2.2.0", "ATC_MiThermometer": "matthias-bs/ATC_MiThermometer#semver:^0.4.2", - "TheengsDecoder": "theengs/decoder#semver:^1.8.2", + "TheengsDecoder": "theengs/decoder#semver:^1.8.4", "Preferences": "vshymanskyy/Preferences#semver:^2.1.0", "ArduinoJson": "bblanchon/ArduinoJson#semver:^7.2.1" } diff --git a/src/BleSensors/BleSensors.cpp b/src/BleSensors/BleSensors.cpp index 8f8cef3..0c267b3 100644 --- a/src/BleSensors/BleSensors.cpp +++ b/src/BleSensors/BleSensors.cpp @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// // BleSensors.h -// +// // Wrapper class for Theeengs Decoder (https://github.com/theengs/decoder) // // Intended for compatibility to the ATC_MiThermometer library @@ -12,17 +12,17 @@ // MIT License // // Copyright (c) 2023 Matthias Prinke -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -36,9 +36,10 @@ // // 20230211 Created // 20240427 Added paramter activeScan to getData() +// 20250121 Updated for NimBLE-Arduino v2.x // // ToDo: -// - +// - // /////////////////////////////////////////////////////////////////////////////// @@ -46,142 +47,153 @@ #include "BleSensors.h" -/*! - * \brief Callbacks for advertised BLE devices - */ -class MyAdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { +class ScanCallbacks : public NimBLEScanCallbacks +{ public: - NimBLEScan* m_pBLEScan; /// NimBLEScan object - std::vector m_knownBLEAddresses; /// MAC addresses of known sensors - std::vector* m_sensorData; /// Sensor data + std::vector m_knownBLEAddresses; /// MAC addresses of known sensors + std::vector *m_sensorData; /// Sensor data + NimBLEScan *m_pBLEScan; private: int m_devices_found = 0; /// Number of known devices found - std::string convertServiceData(std::string deviceServiceData) { - int serviceDataLength = (int)deviceServiceData.length(); - char spr[2 * serviceDataLength + 1]; - for (int i = 0; i < serviceDataLength; i++) sprintf(spr + 2 * i, "%.2x", (unsigned char)deviceServiceData[i]); - spr[2 * serviceDataLength] = 0; - return spr; + void onDiscovered(const NimBLEAdvertisedDevice *advertisedDevice) override + { + log_v("Discovered Advertised Device: %s", advertisedDevice->toString().c_str()); } - // Callback on scan result - void onResult(BLEAdvertisedDevice* advertisedDevice) { + void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override + { TheengsDecoder decoder; - bool device_found = false; unsigned idx; + bool device_found = false; JsonDocument doc; - log_v("Advertised Device: %s", advertisedDevice->toString().c_str()); + log_v("Advertised Device Result: %s", advertisedDevice->toString().c_str()); JsonObject BLEdata = doc.to(); String mac_adress = advertisedDevice->getAddress().toString().c_str(); - - BLEdata["id"] = (char*)mac_adress.c_str(); - for (idx = 0; idx < m_knownBLEAddresses.size(); idx++) { - if (mac_adress == m_knownBLEAddresses[idx].c_str()) { - log_v("BLE device found at index %d", idx); - device_found = true; - break; + + BLEdata["id"] = (char *)mac_adress.c_str(); + for (idx = 0; idx < m_knownBLEAddresses.size(); idx++) + { + if (mac_adress == m_knownBLEAddresses[idx].c_str()) + { + log_v("BLE device found at index %d", idx); + device_found = true; + + // Only count the device as found once + // Workaround for + // https://github.com/h2zero/NimBLE-Arduino/issues/826 + if (!(*m_sensorData)[idx].found) + { + (*m_sensorData)[idx].found = true; + m_devices_found++; } + break; + } } - + if (advertisedDevice->haveName()) - BLEdata["name"] = (char*)advertisedDevice->getName().c_str(); + BLEdata["name"] = (char *)advertisedDevice->getName().c_str(); - if (advertisedDevice->haveManufacturerData()) { - char* manufacturerdata = BLEUtils::buildHexData(NULL, (uint8_t*)advertisedDevice->getManufacturerData().data(), advertisedDevice->getManufacturerData().length()); - BLEdata["manufacturerdata"] = manufacturerdata; - free(manufacturerdata); + if (advertisedDevice->haveManufacturerData()) + { + std::string manufacturerdata = advertisedDevice->getManufacturerData(); + std::string mfgdata_hex = NimBLEUtils::dataToHexString((const uint8_t *)manufacturerdata.c_str(), manufacturerdata.length()); + BLEdata["manufacturerdata"] = (char *)mfgdata_hex.c_str(); } - - if (advertisedDevice->haveRSSI()) - BLEdata["rssi"] = (int)advertisedDevice->getRSSI(); + + BLEdata["rssi"] = (int)advertisedDevice->getRSSI(); if (advertisedDevice->haveTXPower()) BLEdata["txpower"] = (int8_t)advertisedDevice->getTXPower(); - if (advertisedDevice->haveServiceData()) { - int serviceDataCount = advertisedDevice->getServiceDataCount(); - for (int j = 0; j < serviceDataCount; j++) { - std::string service_data = convertServiceData(advertisedDevice->getServiceData(j)); - BLEdata["servicedata"] = (char*)service_data.c_str(); - std::string serviceDatauuid = advertisedDevice->getServiceDataUUID(j).toString(); - BLEdata["servicedatauuid"] = (char*)serviceDatauuid.c_str(); - } + if (advertisedDevice->haveServiceData()) + { + std::string servicedata = advertisedDevice->getServiceData(NimBLEUUID((uint16_t)0x181a)); + std::string servicedata_hex = NimBLEUtils::dataToHexString((const uint8_t *)servicedata.c_str(), servicedata.length()); + BLEdata["servicedata"] = (char *)servicedata_hex.c_str(); + BLEdata["servicedatauuid"] = "0x181a"; } - if (decoder.decodeBLEJson(BLEdata) && device_found) { - if (CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG) { + if (decoder.decodeBLEJson(BLEdata) && device_found) + { + if (CORE_DEBUG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG) + { char buf[512]; serializeJson(BLEdata, buf); log_d("TheengsDecoder found device: %s", buf); } - BLEdata.remove("manufacturerdata"); - BLEdata.remove("servicedata"); - + // see https://stackoverflow.com/questions/5348089/passing-a-vector-between-functions-via-pointers - (*m_sensorData)[idx].temperature = (float)BLEdata["tempc"]; - (*m_sensorData)[idx].humidity = (float)BLEdata["hum"]; - (*m_sensorData)[idx].batt_level = (uint8_t)BLEdata["batt"]; - (*m_sensorData)[idx].rssi = (int)BLEdata["rssi"]; - (*m_sensorData)[idx].valid = ((*m_sensorData)[idx].batt_level > 0); + (*m_sensorData)[idx].temperature = (float)BLEdata["tempc"]; + (*m_sensorData)[idx].humidity = (float)BLEdata["hum"]; + (*m_sensorData)[idx].batt_level = (uint8_t)BLEdata["batt"]; + (*m_sensorData)[idx].rssi = (int)BLEdata["rssi"]; + (*m_sensorData)[idx].valid = ((*m_sensorData)[idx].batt_level > 0); log_i("Temperature: %.1f°C", (*m_sensorData)[idx].temperature); log_i("Humidity: %.1f%%", (*m_sensorData)[idx].humidity); - log_i("Battery level: %d%%", (*m_sensorData)[idx].batt_level); - log_i("RSSI: %ddBm", (*m_sensorData)[idx].rssi = (int)BLEdata["rssi"]); - m_devices_found++; + log_i("Battery level: %d%%", (*m_sensorData)[idx].batt_level); + log_i("RSSI: %ddBm", (*m_sensorData)[idx].rssi = (int)BLEdata["rssi"]); log_d("BLE devices found: %d", m_devices_found); + + BLEdata.remove("manufacturerdata"); + BLEdata.remove("servicedata"); } - + // Abort scanning because all known devices have been found - if (m_devices_found == m_knownBLEAddresses.size()) { + if (m_devices_found == m_knownBLEAddresses.size()) + { log_i("All devices found."); m_pBLEScan->stop(); } } -}; + + void onScanEnd(const NimBLEScanResults &results, int reason) override + { + log_v("Scan Ended; reason = %d", reason); + } +} scanCallbacks; + +void BleSensors::clearScanResults(void) +{ + _pBLEScan->clearResults(); +} // Set all array members invalid void BleSensors::resetData(void) { - for (int i=0; i < _known_sensors.size(); i++) { - data[i].valid = false; - } + for (int i = 0; i < _known_sensors.size(); i++) + { + data[i].valid = false; + data[i].found = false; + } } /** * \brief Get BLE sensor data */ -unsigned BleSensors::getData(uint32_t duration, bool activeScan) { - // From https://github.com/theengs/decoder/blob/development/examples/ESP32/ScanAndDecode/ScanAndDecode.ino: - // MyAdvertisedDeviceCallbacks are still triggered multiple times; this makes keeping track of received - // sensors difficult. Setting ScanFilterMode to CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE seems to - // restrict callback invocation to once per device as desired. - //NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE); - NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); - NimBLEDevice::setScanDuplicateCacheSize(200); - NimBLEDevice::init(""); - - _pBLEScan = NimBLEDevice::getScan(); //create new scan - - // Set the callback for when devices are discovered, no duplicates. - //_pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), false); - MyAdvertisedDeviceCallbacks *myCb = new MyAdvertisedDeviceCallbacks(); - - // Copy some data required by the Callback - myCb->m_pBLEScan = _pBLEScan; - myCb->m_knownBLEAddresses = _known_sensors; - myCb->m_sensorData = &data; - - _pBLEScan->setAdvertisedDeviceCallbacks(myCb); - _pBLEScan->setActiveScan(activeScan); // Set active scanning, this will get more data from the advertiser. - _pBLEScan->setInterval(97); // How often the scan occurs / switches channels; in milliseconds, - _pBLEScan->setWindow(37); // How long to scan during the interval; in milliseconds. - _pBLEScan->setMaxResults(0); // do not store the scan results, use callback only. - _pBLEScan->start(duration, false /* is_continue */); - - return 0; +unsigned BleSensors::getData(uint32_t scanTime, bool activeScan) +{ + // setScanFilterMode() is not available for ESP32-S3 + // see https://github.com/h2zero/NimBLE-Arduino/issues/826 + //NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE); + + NimBLEDevice::init("ble-scan"); + _pBLEScan = NimBLEDevice::getScan(); + _pBLEScan->setScanCallbacks(&scanCallbacks); + _pBLEScan->setActiveScan(activeScan); + _pBLEScan->setInterval(97); + _pBLEScan->setWindow(37); + scanCallbacks.m_knownBLEAddresses = _known_sensors; + scanCallbacks.m_sensorData = &data; + scanCallbacks.m_pBLEScan = _pBLEScan; + + // Start scanning + // Blocks until all known devices are found or scanTime is expired + _pBLEScan->getResults(scanTime * 1000, false); + + return 0; } #endif diff --git a/src/BleSensors/BleSensors.h b/src/BleSensors/BleSensors.h index 23f20ff..0f90442 100644 --- a/src/BleSensors/BleSensors.h +++ b/src/BleSensors/BleSensors.h @@ -37,6 +37,7 @@ // 20230211 Created // 20240417 Added additional constructor and method setAddresses() // 20240427 Added paramter activeScan to getData() +// 20250121 Updated for NimBLE-Arduino v2.x // // ToDo: // - @@ -55,6 +56,7 @@ * \brief BLE sensor data */ struct BleDataS { + bool found; //!< device found bool valid; //!< data valid float temperature; //!< temperature in degC float humidity; //!< humidity in % @@ -104,10 +106,8 @@ class BleSensors { /*! * \brief Delete results from BLEScan buffer to release memory. - */ - void clearScanResults(void) { - _pBLEScan->clearResults(); - }; + */ + void clearScanResults(void); /*! * \brief Get data from sensors by running a BLE scan.