Update NimBLE to 1.1.0 release.

Remove BLE stats from Sensor, publish in BLE.
Set default address filter to 0
Add HA MQTT discovery to MI
This commit is contained in:
Simon Hailes 2021-01-26 07:35:00 +00:00
commit 84620f70ea
11 changed files with 500 additions and 284 deletions

View File

@ -2,7 +2,7 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [Unreleased] ## [1.1.0] - 2021-01-20
### Added ### Added
- `NimBLEDevice::setOwnAddrType` added to enable the use of random and random-resolvable addresses, by asukiaaa - `NimBLEDevice::setOwnAddrType` added to enable the use of random and random-resolvable addresses, by asukiaaa

View File

@ -1,5 +1,7 @@
[Latest release ![Release Version](https://img.shields.io/github/release/h2zero/NimBLE-Arduino.svg?style=plastic) [Latest release ![Release Version](https://img.shields.io/github/release/h2zero/NimBLE-Arduino.svg?style=plastic)
![Release Date](https://img.shields.io/github/release-date/h2zero/NimBLE-Arduino.svg?style=plastic)](https://github.com/h2zero/NimBLE-Arduino/releases/latest/) ![Release Date](https://img.shields.io/github/release-date/h2zero/NimBLE-Arduino.svg?style=plastic)](https://github.com/h2zero/NimBLE-Arduino/releases/latest/)
Need help? Have questions or suggestions? Join the [![Gitter](https://badges.gitter.im/NimBLE-Arduino/community.svg)](https://gitter.im/NimBLE-Arduino/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
<br/> <br/>
# NimBLE-Arduino # NimBLE-Arduino
@ -57,6 +59,8 @@ Also see [Improvements_and_updates](docs/Improvements_and_updates.md) for inform
[Full API documentation and class list can be found here.](https://h2zero.github.io/esp-nimble-cpp/) [Full API documentation and class list can be found here.](https://h2zero.github.io/esp-nimble-cpp/)
For added performance and optimizations see [Usage tips](docs/Usage_tips.md).
Check the Refactored_original_examples in the examples folder for highlights of the differences with the original library. Check the Refactored_original_examples in the examples folder for highlights of the differences with the original library.
More advanced examples highlighting many available features are in examples/ NimBLE_Server, NimBLE_Client. More advanced examples highlighting many available features are in examples/ NimBLE_Server, NimBLE_Client.

View File

@ -79,27 +79,24 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
return; return;
} }
uint8_t *payLoad = advertisedDevice->getPayload(); BLEUUID eddyUUID = (uint16_t)0xfeaa;
BLEUUID checkUrlUUID = (uint16_t)0xfeaa; if (advertisedDevice->getServiceUUID().equals(eddyUUID))
if (advertisedDevice->getServiceUUID().equals(checkUrlUUID))
{ {
if (payLoad[11] == 0x10) std::string serviceData = advertisedDevice->getServiceData(eddyUUID);
if (serviceData[0] == 0x10)
{ {
Serial.println("Found an EddystoneURL beacon!"); Serial.println("Found an EddystoneURL beacon!");
BLEEddystoneURL foundEddyURL = BLEEddystoneURL(); BLEEddystoneURL foundEddyURL = BLEEddystoneURL();
std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct!
foundEddyURL.setData(eddyContent); foundEddyURL.setData(serviceData);
std::string bareURL = foundEddyURL.getURL(); std::string bareURL = foundEddyURL.getURL();
if (bareURL[0] == 0x00) if (bareURL[0] == 0x00)
{ {
size_t payLoadLen = advertisedDevice->getPayloadLength();
Serial.println("DATA-->"); Serial.println("DATA-->");
for (int idx = 0; idx < payLoadLen; idx++) for (int idx = 0; idx < serviceData.length(); idx++)
{ {
Serial.printf("0x%08X ", payLoad[idx]); Serial.printf("0x%08X ", serviceData[idx]);
} }
Serial.println("\nInvalid Data"); Serial.println("\nInvalid Data");
return; return;
@ -110,23 +107,15 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
Serial.printf("TX power %d\n", foundEddyURL.getPower()); Serial.printf("TX power %d\n", foundEddyURL.getPower());
Serial.println("\n"); Serial.println("\n");
} }
else if (payLoad[11] == 0x20) else if (serviceData[0] == 0x20)
{ {
Serial.println("Found an EddystoneTLM beacon!"); Serial.println("Found an EddystoneTLM beacon!");
BLEEddystoneTLM foundEddyURL = BLEEddystoneTLM(); BLEEddystoneTLM foundEddyURL = BLEEddystoneTLM();
std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct! foundEddyURL.setData(serviceData);
eddyContent = "01234567890123";
for (int idx = 0; idx < 14; idx++)
{
eddyContent[idx] = payLoad[idx + 11];
}
foundEddyURL.setData(eddyContent);
Serial.printf("Reported battery voltage: %dmV\n", foundEddyURL.getVolt()); Serial.printf("Reported battery voltage: %dmV\n", foundEddyURL.getVolt());
Serial.printf("Reported temperature from TLM class: %.2fC\n", (double)foundEddyURL.getTemp()); Serial.printf("Reported temperature from TLM class: %.2fC\n", (double)foundEddyURL.getTemp());
int temp = (int)payLoad[16] + (int)(payLoad[15] << 8); int temp = (int)serviceData[5] + (int)(serviceData[4] << 8);
float calcTemp = temp / 256.0f; float calcTemp = temp / 256.0f;
Serial.printf("Reported temperature from data: %.2fC\n", calcTemp); Serial.printf("Reported temperature from data: %.2fC\n", calcTemp);
Serial.printf("Reported advertise count: %d\n", foundEddyURL.getCount()); Serial.printf("Reported advertise count: %d\n", foundEddyURL.getCount());

View File

@ -2,10 +2,10 @@
/** NimBLE_Server Demo: /** NimBLE_Server Demo:
* *
* Demonstrates many of the available features of the NimBLE client library. * Demonstrates many of the available features of the NimBLE client library.
* *
* Created: on March 24 2020 * Created: on March 24 2020
* Author: H2zero * Author: H2zero
* *
*/ */
#include <NimBLEDevice.h> #include <NimBLEDevice.h>
@ -19,15 +19,15 @@ static uint32_t scanTime = 0; /** 0 = scan forever */
/** None of these are required as they will be handled by the library with defaults. ** /** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */ ** Remove as you see fit for your needs */
class ClientCallbacks : public NimBLEClientCallbacks { class ClientCallbacks : public NimBLEClientCallbacks {
void onConnect(NimBLEClient* pClient) { void onConnect(NimBLEClient* pClient) {
Serial.println("Connected"); Serial.println("Connected");
/** After connection we should change the parameters if we don't need fast response times. /** After connection we should change the parameters if we don't need fast response times.
* These settings are 150ms interval, 0 latency, 450ms timout. * These settings are 150ms interval, 0 latency, 450ms timout.
* Timeout should be a multiple of the interval, minimum is 100ms. * Timeout should be a multiple of the interval, minimum is 100ms.
* I find a multiple of 3-5 * the interval works best for quick response/reconnect. * I find a multiple of 3-5 * the interval works best for quick response/reconnect.
* Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout
*/ */
pClient->updateConnParams(120,120,0,60); pClient->updateConnParams(120,120,0,60);
}; };
@ -37,9 +37,9 @@ class ClientCallbacks : public NimBLEClientCallbacks {
Serial.println(" Disconnected - Starting scan"); Serial.println(" Disconnected - Starting scan");
NimBLEDevice::getScan()->start(scanTime, scanEndedCB); NimBLEDevice::getScan()->start(scanTime, scanEndedCB);
}; };
/** Called when the peripheral requests a change to the connection parameters. /** Called when the peripheral requests a change to the connection parameters.
* Return true to accept and apply them or false to reject and keep * Return true to accept and apply them or false to reject and keep
* the currently used parameters. Default will return true. * the currently used parameters. Default will return true.
*/ */
bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params) { bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params) {
@ -55,7 +55,7 @@ class ClientCallbacks : public NimBLEClientCallbacks {
return true; return true;
}; };
/********************* Security handled here ********************** /********************* Security handled here **********************
****** Note: these are the same return values as defaults ********/ ****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){ uint32_t onPassKeyRequest(){
@ -85,7 +85,7 @@ class ClientCallbacks : public NimBLEClientCallbacks {
/** Define a class to handle the callbacks when advertisments are received */ /** Define a class to handle the callbacks when advertisments are received */
class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
void onResult(NimBLEAdvertisedDevice* advertisedDevice) { void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
Serial.print("Advertised Device found: "); Serial.print("Advertised Device found: ");
Serial.println(advertisedDevice->toString().c_str()); Serial.println(advertisedDevice->toString().c_str());
@ -94,9 +94,9 @@ class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
Serial.println("Found Our Service"); Serial.println("Found Our Service");
/** stop scan before connecting */ /** stop scan before connecting */
NimBLEDevice::getScan()->stop(); NimBLEDevice::getScan()->stop();
/** Save the device reference in a global for the client to use*/ /** Save the device reference in a global for the client to use*/
advDevice = advertisedDevice; advDevice = advertisedDevice;
/** Ready to connect now */ /** Ready to connect now */
doConnect = true; doConnect = true;
} }
}; };
@ -105,7 +105,7 @@ class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
/** Notification / Indication receiving handler callback */ /** Notification / Indication receiving handler callback */
void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){ void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){
std::string str = (isNotify == true) ? "Notification" : "Indication"; std::string str = (isNotify == true) ? "Notification" : "Indication";
str += " from "; str += " from ";
/** NimBLEAddress and NimBLEUUID have std::string operators */ /** NimBLEAddress and NimBLEUUID have std::string operators */
str += std::string(pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress()); str += std::string(pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress());
@ -128,10 +128,10 @@ static ClientCallbacks clientCB;
/** Handles the provisioning of clients and connects / interfaces with the server */ /** Handles the provisioning of clients and connects / interfaces with the server */
bool connectToServer() { bool connectToServer() {
NimBLEClient* pClient = nullptr; NimBLEClient* pClient = nullptr;
/** Check if we have a client we should reuse first **/ /** Check if we have a client we should reuse first **/
if(NimBLEDevice::getClientListSize()) { if(NimBLEDevice::getClientListSize()) {
/** Special case when we already know this device, we send false as the /** Special case when we already know this device, we send false as the
* second argument in connect() to prevent refreshing the service database. * second argument in connect() to prevent refreshing the service database.
* This saves considerable time and power. * This saves considerable time and power.
*/ */
@ -142,7 +142,7 @@ bool connectToServer() {
return false; return false;
} }
Serial.println("Reconnected client"); Serial.println("Reconnected client");
} }
/** We don't already have a client that knows this device, /** We don't already have a client that knows this device,
* we will check for a client that is disconnected that we can use. * we will check for a client that is disconnected that we can use.
*/ */
@ -150,28 +150,28 @@ bool connectToServer() {
pClient = NimBLEDevice::getDisconnectedClient(); pClient = NimBLEDevice::getDisconnectedClient();
} }
} }
/** No client to reuse? Create a new one. */ /** No client to reuse? Create a new one. */
if(!pClient) { if(!pClient) {
if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
Serial.println("Max clients reached - no more connections available"); Serial.println("Max clients reached - no more connections available");
return false; return false;
} }
pClient = NimBLEDevice::createClient(); pClient = NimBLEDevice::createClient();
Serial.println("New client created"); Serial.println("New client created");
pClient->setClientCallbacks(&clientCB, false); pClient->setClientCallbacks(&clientCB, false);
/** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
* These settings are safe for 3 clients to connect reliably, can go faster if you have less * These settings are safe for 3 clients to connect reliably, can go faster if you have less
* connections. Timeout should be a multiple of the interval, minimum is 100ms. * connections. Timeout should be a multiple of the interval, minimum is 100ms.
* Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
*/ */
pClient->setConnectionParams(12,12,0,51); pClient->setConnectionParams(12,12,0,51);
/** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
pClient->setConnectTimeout(5); pClient->setConnectTimeout(5);
if (!pClient->connect(advDevice)) { if (!pClient->connect(advDevice)) {
/** Created a client but failed to connect, don't need to keep it as it has no data */ /** Created a client but failed to connect, don't need to keep it as it has no data */
@ -179,149 +179,147 @@ bool connectToServer() {
Serial.println("Failed to connect, deleted client"); Serial.println("Failed to connect, deleted client");
return false; return false;
} }
} }
if(!pClient->isConnected()) { if(!pClient->isConnected()) {
if (!pClient->connect(advDevice)) { if (!pClient->connect(advDevice)) {
Serial.println("Failed to connect"); Serial.println("Failed to connect");
return false; return false;
} }
} }
Serial.print("Connected to: "); Serial.print("Connected to: ");
Serial.println(pClient->getPeerAddress().toString().c_str()); Serial.println(pClient->getPeerAddress().toString().c_str());
Serial.print("RSSI: "); Serial.print("RSSI: ");
Serial.println(pClient->getRssi()); Serial.println(pClient->getRssi());
/** Now we can read/write/subscribe the charateristics of the services we are interested in */ /** Now we can read/write/subscribe the charateristics of the services we are interested in */
NimBLERemoteService* pSvc = nullptr; NimBLERemoteService* pSvc = nullptr;
NimBLERemoteCharacteristic* pChr = nullptr; NimBLERemoteCharacteristic* pChr = nullptr;
NimBLERemoteDescriptor* pDsc = nullptr; NimBLERemoteDescriptor* pDsc = nullptr;
pSvc = pClient->getService("DEAD"); pSvc = pClient->getService("DEAD");
if(pSvc) { /** make sure it's not null */ if(pSvc) { /** make sure it's not null */
pChr = pSvc->getCharacteristic("BEEF"); pChr = pSvc->getCharacteristic("BEEF");
}
if(pChr) { /** make sure it's not null */ if(pChr) { /** make sure it's not null */
if(pChr->canRead()) {
Serial.print(pChr->getUUID().toString().c_str());
Serial.print(" Value: ");
Serial.println(pChr->readValue().c_str());
}
if(pChr->canWrite()) {
if(pChr->writeValue("Tasty")) {
Serial.print("Wrote new value to: ");
Serial.println(pChr->getUUID().toString().c_str());
}
else {
/** Disconnect if write failed */
pClient->disconnect();
return false;
}
if(pChr->canRead()) { if(pChr->canRead()) {
Serial.print("The value of: ");
Serial.print(pChr->getUUID().toString().c_str()); Serial.print(pChr->getUUID().toString().c_str());
Serial.print(" is now: "); Serial.print(" Value: ");
Serial.println(pChr->readValue().c_str()); Serial.println(pChr->readValue().c_str());
} }
}
if(pChr->canWrite()) {
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). if(pChr->writeValue("Tasty")) {
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. Serial.print("Wrote new value to: ");
* Unsubscribe parameter defaults are: response=false. Serial.println(pChr->getUUID().toString().c_str());
*/ }
if(pChr->canNotify()) { else {
//if(!pChr->registerForNotify(notifyCB)) { /** Disconnect if write failed */
if(!pChr->subscribe(true, notifyCB)) { pClient->disconnect();
/** Disconnect if subscribe failed */ return false;
pClient->disconnect(); }
return false;
if(pChr->canRead()) {
Serial.print("The value of: ");
Serial.print(pChr->getUUID().toString().c_str());
Serial.print(" is now: ");
Serial.println(pChr->readValue().c_str());
}
}
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
* Unsubscribe parameter defaults are: response=false.
*/
if(pChr->canNotify()) {
//if(!pChr->registerForNotify(notifyCB)) {
if(!pChr->subscribe(true, notifyCB)) {
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
else if(pChr->canIndicate()) {
/** Send false as first argument to subscribe to indications instead of notifications */
//if(!pChr->registerForNotify(notifyCB, false)) {
if(!pChr->subscribe(false, notifyCB)) {
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
} }
} }
else if(pChr->canIndicate()) {
/** Send false as first argument to subscribe to indications instead of notifications */ } else {
//if(!pChr->registerForNotify(notifyCB, false)) {
if(!pChr->subscribe(false, notifyCB)) {
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
}
else{
Serial.println("DEAD service not found."); Serial.println("DEAD service not found.");
} }
pSvc = pClient->getService("BAAD"); pSvc = pClient->getService("BAAD");
if(pSvc) { /** make sure it's not null */ if(pSvc) { /** make sure it's not null */
pChr = pSvc->getCharacteristic("F00D"); pChr = pSvc->getCharacteristic("F00D");
}
if(pChr) { /** make sure it's not null */ if(pChr) { /** make sure it's not null */
if(pChr->canRead()) {
Serial.print(pChr->getUUID().toString().c_str());
Serial.print(" Value: ");
Serial.println(pChr->readValue().c_str());
}
pDsc = pChr->getDescriptor(NimBLEUUID("C01D"));
if(pDsc) { /** make sure it's not null */
Serial.print("Descriptor: ");
Serial.print(pDsc->getUUID().toString().c_str());
Serial.print(" Value: ");
Serial.println(pDsc->readValue().c_str());
}
if(pChr->canWrite()) {
if(pChr->writeValue("No tip!")) {
Serial.print("Wrote new value to: ");
Serial.println(pChr->getUUID().toString().c_str());
}
else {
/** Disconnect if write failed */
pClient->disconnect();
return false;
}
if(pChr->canRead()) { if(pChr->canRead()) {
Serial.print("The value of: ");
Serial.print(pChr->getUUID().toString().c_str()); Serial.print(pChr->getUUID().toString().c_str());
Serial.print(" is now: "); Serial.print(" Value: ");
Serial.println(pChr->readValue().c_str()); Serial.println(pChr->readValue().c_str());
} }
}
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
* Unsubscribe parameter defaults are: response=false.
*/
if(pChr->canNotify()) {
//if(!pChr->registerForNotify(notifyCB)) {
if(!pChr->subscribe(true, notifyCB)) {
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
else if(pChr->canIndicate()) {
/** Send false as first argument to subscribe to indications instead of notifications */
//if(!pChr->registerForNotify(notifyCB, false)) {
if(!pChr->subscribe(false, notifyCB)) {
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
}
else{ pDsc = pChr->getDescriptor(NimBLEUUID("C01D"));
if(pDsc) { /** make sure it's not null */
Serial.print("Descriptor: ");
Serial.print(pDsc->getUUID().toString().c_str());
Serial.print(" Value: ");
Serial.println(pDsc->readValue().c_str());
}
if(pChr->canWrite()) {
if(pChr->writeValue("No tip!")) {
Serial.print("Wrote new value to: ");
Serial.println(pChr->getUUID().toString().c_str());
}
else {
/** Disconnect if write failed */
pClient->disconnect();
return false;
}
if(pChr->canRead()) {
Serial.print("The value of: ");
Serial.print(pChr->getUUID().toString().c_str());
Serial.print(" is now: ");
Serial.println(pChr->readValue().c_str());
}
}
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
* Unsubscribe parameter defaults are: response=false.
*/
if(pChr->canNotify()) {
//if(!pChr->registerForNotify(notifyCB)) {
if(!pChr->subscribe(true, notifyCB)) {
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
else if(pChr->canIndicate()) {
/** Send false as first argument to subscribe to indications instead of notifications */
//if(!pChr->registerForNotify(notifyCB, false)) {
if(!pChr->subscribe(false, notifyCB)) {
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
}
} else {
Serial.println("BAAD service not found."); Serial.println("BAAD service not found.");
} }
Serial.println("Done with this device!"); Serial.println("Done with this device!");
return true; return true;
} }
@ -331,7 +329,7 @@ void setup (){
Serial.println("Starting NimBLE Client"); Serial.println("Starting NimBLE Client");
/** Initialize NimBLE, no device name spcified as we are not advertising */ /** Initialize NimBLE, no device name spcified as we are not advertising */
NimBLEDevice::init(""); NimBLEDevice::init("");
/** Set the IO capabilities of the device, each option will trigger a different pairing method. /** Set the IO capabilities of the device, each option will trigger a different pairing method.
* BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing
* BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing
@ -339,37 +337,37 @@ void setup (){
*/ */
//NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey
//NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison
/** 2 different ways to set security - both calls achieve the same result. /** 2 different ways to set security - both calls achieve the same result.
* no bonding, no man in the middle protection, secure connections. * no bonding, no man in the middle protection, secure connections.
* *
* These are the default values, only shown here for demonstration. * These are the default values, only shown here for demonstration.
*/ */
//NimBLEDevice::setSecurityAuth(false, false, true); //NimBLEDevice::setSecurityAuth(false, false, true);
NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC); NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);
/** Optional: set the transmit power, default is 3db */ /** Optional: set the transmit power, default is 3db */
NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */
/** Optional: set any devices you don't want to get advertisments from */ /** Optional: set any devices you don't want to get advertisments from */
// NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff")); // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff"));
/** create new scan */ /** create new scan */
NimBLEScan* pScan = NimBLEDevice::getScan(); NimBLEScan* pScan = NimBLEDevice::getScan();
/** create a callback that gets called when advertisers are found */ /** create a callback that gets called when advertisers are found */
pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
/** Set scan interval (how often) and window (how long) in milliseconds */ /** Set scan interval (how often) and window (how long) in milliseconds */
pScan->setInterval(45); pScan->setInterval(45);
pScan->setWindow(15); pScan->setWindow(15);
/** Active scan will gather scan response data from advertisers /** Active scan will gather scan response data from advertisers
* but will use more energy from both devices * but will use more energy from both devices
*/ */
pScan->setActiveScan(true); pScan->setActiveScan(true);
/** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever /** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever
* Optional callback for when scanning stops. * Optional callback for when scanning stops.
*/ */
pScan->start(scanTime, scanEndedCB); pScan->start(scanTime, scanEndedCB);
} }
@ -380,15 +378,15 @@ void loop (){
while(!doConnect){ while(!doConnect){
delay(1); delay(1);
} }
doConnect = false; doConnect = false;
/** Found a device we want to connect to, do it now */ /** Found a device we want to connect to, do it now */
if(connectToServer()) { if(connectToServer()) {
Serial.println("Success! we should now be getting notifications, scanning for more!"); Serial.println("Success! we should now be getting notifications, scanning for more!");
} else { } else {
Serial.println("Failed to connect, starting scan"); Serial.println("Failed to connect, starting scan");
} }
NimBLEDevice::getScan()->start(scanTime,scanEndedCB); NimBLEDevice::getScan()->start(scanTime,scanEndedCB);
} }

View File

@ -1,5 +1,5 @@
name=NimBLE-Arduino name=NimBLE-Arduino
version=1.0.2 version=1.1.0
author=h2zero author=h2zero
maintainer=h2zero <powellperalta@gmail.com> maintainer=h2zero <powellperalta@gmail.com>
sentence=Bluetooth low energy (BLE) library for arduino-esp32 based on NimBLE. sentence=Bluetooth low energy (BLE) library for arduino-esp32 based on NimBLE.

View File

@ -100,9 +100,10 @@ NimBLEClient::~NimBLEClient() {
* be called to reset the host in the case of a stalled controller. * be called to reset the host in the case of a stalled controller.
*/ */
void NimBLEClient::dcTimerCb(ble_npl_event *event) { void NimBLEClient::dcTimerCb(ble_npl_event *event) {
NimBLEClient *pClient = (NimBLEClient*)event->arg; /* NimBLEClient *pClient = (NimBLEClient*)event->arg;
NIMBLE_LOGC(LOG_TAG, "Timed out disconnecting from %s - resetting host", NIMBLE_LOGC(LOG_TAG, "Timed out disconnecting from %s - resetting host",
std::string(pClient->getPeerAddress()).c_str()); std::string(pClient->getPeerAddress()).c_str());
*/
ble_hs_sched_reset(BLE_HS_ECONTROLLER); ble_hs_sched_reset(BLE_HS_ECONTROLLER);
} }
@ -182,25 +183,30 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
return false; return false;
} }
if(isConnected() || m_pTaskData != nullptr) { if(isConnected() || m_connEstablished || m_pTaskData != nullptr) {
NIMBLE_LOGE(LOG_TAG, "Client busy, connected to %s, id=%d", NIMBLE_LOGE(LOG_TAG, "Client busy, connected to %s, id=%d",
std::string(m_peerAddress).c_str(), getConnId()); std::string(m_peerAddress).c_str(), getConnId());
return false; return false;
} }
ble_addr_t peerAddr_t;
memcpy(&peerAddr_t.val, address.getNative(),6);
peerAddr_t.type = address.getType();
if(ble_gap_conn_find_by_addr(&peerAddr_t, NULL) == 0) {
NIMBLE_LOGE(LOG_TAG, "A connection to %s already exists",
address.toString().c_str());
return false;
}
if(address == NimBLEAddress("")) { if(address == NimBLEAddress("")) {
NIMBLE_LOGE(LOG_TAG, "Invalid peer address;(NULL)"); NIMBLE_LOGE(LOG_TAG, "Invalid peer address;(NULL)");
return false; return false;
} else if(m_peerAddress != address) { } else {
m_peerAddress = address; m_peerAddress = address;
} }
ble_addr_t peerAddr_t;
memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6);
peerAddr_t.type = m_peerAddress.getType();
ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr};
m_pTaskData = &taskData;
int rc = 0; int rc = 0;
/* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for /* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for
@ -213,13 +219,12 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
NimBLEClient::handleGapEvent, this); NimBLEClient::handleGapEvent, this);
switch (rc) { switch (rc) {
case 0: case 0:
m_pTaskData = &taskData;
break; break;
case BLE_HS_EBUSY: case BLE_HS_EBUSY:
// Scan was still running, stop it and try again // Scan was still running, stop it and try again
if (!NimBLEDevice::getScan()->stop()) { if (!NimBLEDevice::getScan()->stop()) {
return false; rc = BLE_HS_EUNKNOWN;
} }
break; break;
@ -227,7 +232,7 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
// A connection to this device already exists, do not connect twice. // A connection to this device already exists, do not connect twice.
NIMBLE_LOGE(LOG_TAG, "Already connected to device; addr=%s", NIMBLE_LOGE(LOG_TAG, "Already connected to device; addr=%s",
std::string(m_peerAddress).c_str()); std::string(m_peerAddress).c_str());
return false; break;
case BLE_HS_EALREADY: case BLE_HS_EALREADY:
// Already attemting to connect to this device, cancel the previous // Already attemting to connect to this device, cancel the previous
@ -235,17 +240,22 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
NIMBLE_LOGE(LOG_TAG, "Already attempting to connect to %s - cancelling", NIMBLE_LOGE(LOG_TAG, "Already attempting to connect to %s - cancelling",
std::string(m_peerAddress).c_str()); std::string(m_peerAddress).c_str());
ble_gap_conn_cancel(); ble_gap_conn_cancel();
return false; break;
default: default:
NIMBLE_LOGE(LOG_TAG, "Failed to connect to %s, rc=%d; %s", NIMBLE_LOGE(LOG_TAG, "Failed to connect to %s, rc=%d; %s",
std::string(m_peerAddress).c_str(), std::string(m_peerAddress).c_str(),
rc, NimBLEUtils::returnCodeToString(rc)); rc, NimBLEUtils::returnCodeToString(rc));
return false; break;
} }
} while (rc == BLE_HS_EBUSY); } while (rc == BLE_HS_EBUSY);
if(rc != 0) {
m_pTaskData = nullptr;
return false;
}
// Wait for the connect timeout time +1 second for the connection to complete // Wait for the connect timeout time +1 second for the connection to complete
if(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(m_connectTimeout + 1000)) == pdFALSE) { if(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(m_connectTimeout + 1000)) == pdFALSE) {
m_pTaskData = nullptr; m_pTaskData = nullptr;
@ -255,7 +265,7 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
disconnect(); disconnect();
} else { } else {
// workaround; if the controller doesn't cancel the connection // workaround; if the controller doesn't cancel the connection
// at the timeout cancel it here. // at the timeout, cancel it here.
NIMBLE_LOGE(LOG_TAG, "Connect timeout - cancelling"); NIMBLE_LOGE(LOG_TAG, "Connect timeout - cancelling");
ble_gap_conn_cancel(); ble_gap_conn_cancel();
} }
@ -269,7 +279,7 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
// If the failure was not a result of a disconnection // If the failure was not a result of a disconnection
// make sure we disconnect now to avoid dangling connections // make sure we disconnect now to avoid dangling connections
if(isConnected()) { if(isConnected()) {
ble_gap_terminate(m_conn_id, BLE_ERR_REM_USER_CONN_TERM); disconnect();
} }
return false; return false;
} else { } else {
@ -325,26 +335,35 @@ bool NimBLEClient::secureConnection() {
*/ */
int NimBLEClient::disconnect(uint8_t reason) { int NimBLEClient::disconnect(uint8_t reason) {
NIMBLE_LOGD(LOG_TAG, ">> disconnect()"); NIMBLE_LOGD(LOG_TAG, ">> disconnect()");
int rc = 0; int rc = 0;
if(isConnected()){ if(isConnected()) {
rc = ble_gap_terminate(m_conn_id, reason); // If the timer was already started, ignore this call.
if (rc == 0) { if(ble_npl_callout_is_active(&m_dcTimer)) {
ble_addr_t peerAddr_t; NIMBLE_LOGI(LOG_TAG, "Already disconnecting, timer started");
memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6); return BLE_HS_EALREADY;
peerAddr_t.type = m_peerAddress.getType(); }
// Set the disconnect timeout to the supervison timeout time + 1 second ble_gap_conn_desc desc;
// In case the event triggers shortly after the supervision timeout. if(ble_gap_conn_find(m_conn_id, &desc) != 0){
// We don't want to prematurely reset the host. NIMBLE_LOGI(LOG_TAG, "Connection ID not found");
ble_gap_conn_desc desc; return BLE_HS_EALREADY;
if(ble_gap_conn_find_by_addr(&peerAddr_t, &desc) == 0){ }
ble_npl_time_t ticks;
ble_npl_time_ms_to_ticks((desc.supervision_timeout + 100) * 10, &ticks); // We use a timer to detect a controller error in the event that it does
ble_npl_callout_reset(&m_dcTimer, ticks); // not inform the stack when disconnection is complete.
NIMBLE_LOGD(LOG_TAG, "DC TIMEOUT = %dms", (desc.supervision_timeout + 100) * 10); // This is a common error in certain esp-idf versions.
// The disconnect timeout time is the supervison timeout time + 1 second.
// In the case that the event happenss shortly after the supervision timeout
// we don't want to prematurely reset the host.
ble_npl_time_t ticks;
ble_npl_time_ms_to_ticks((desc.supervision_timeout + 100) * 10, &ticks);
ble_npl_callout_reset(&m_dcTimer, ticks);
rc = ble_gap_terminate(m_conn_id, reason);
if (rc != 0) {
if(rc != BLE_HS_EALREADY) {
ble_npl_callout_stop(&m_dcTimer);
} }
} else if (rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s",
rc, NimBLEUtils::returnCodeToString(rc)); rc, NimBLEUtils::returnCodeToString(rc));
} }
@ -359,12 +378,12 @@ int NimBLEClient::disconnect(uint8_t reason) {
/** /**
* @brief Set the connection paramaters to use when connecting to a server. * @brief Set the connection paramaters to use when connecting to a server.
* @param [in] minInterval minimum connection interval in 0.625ms units. * @param [in] minInterval The minimum connection interval in 1.25ms units.
* @param [in] maxInterval maximum connection interval in 0.625ms units. * @param [in] maxInterval The maximum connection interval in 1.25ms units.
* @param [in] latency number of packets allowed to skip (extends max interval) * @param [in] latency The number of packets allowed to skip (extends max interval).
* @param [in] timeout the timeout time in 10ms units before disconnecting * @param [in] timeout The timeout time in 10ms units before disconnecting.
* @param [in] scanInterval the scan interval to use when attempting to connect in 0.625ms units. * @param [in] scanInterval The scan interval to use when attempting to connect in 0.625ms units.
* @param [in] scanWindow the scan window to use when attempting to connect in 0.625ms units. * @param [in] scanWindow The scan window to use when attempting to connect in 0.625ms units.
*/ */
void NimBLEClient::setConnectionParams(uint16_t minInterval, uint16_t maxInterval, void NimBLEClient::setConnectionParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout, uint16_t latency, uint16_t timeout,
@ -391,10 +410,10 @@ void NimBLEClient::setConnectionParams(uint16_t minInterval, uint16_t maxInterva
/** /**
* @brief Update the connection parameters: * @brief Update the connection parameters:
* * Can only be used after a connection has been established. * * Can only be used after a connection has been established.
* @param [in] minInterval minimum connection interval in 0.625ms units. * @param [in] minInterval The minimum connection interval in 1.25ms units.
* @param [in] maxInterval maximum connection interval in 0.625ms units. * @param [in] maxInterval The maximum connection interval in 1.25ms units.
* @param [in] latency number of packets allowed to skip (extends max interval) * @param [in] latency The number of packets allowed to skip (extends max interval).
* @param [in] timeout the timeout time in 10ms units before disconnecting * @param [in] timeout The timeout time in 10ms units before disconnecting.
*/ */
void NimBLEClient::updateConnParams(uint16_t minInterval, uint16_t maxInterval, void NimBLEClient::updateConnParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout) uint16_t latency, uint16_t timeout)
@ -797,14 +816,15 @@ uint16_t NimBLEClient::getMTU() {
break; break;
} }
client->m_conn_id = BLE_HS_CONN_HANDLE_NONE;
// Stop the disconnect timer since we are now disconnected. // Stop the disconnect timer since we are now disconnected.
ble_npl_callout_stop(&client->m_dcTimer); ble_npl_callout_stop(&client->m_dcTimer);
// Remove the device from ignore list so we will scan it again // Remove the device from ignore list so we will scan it again
NimBLEDevice::removeIgnored(client->m_peerAddress); NimBLEDevice::removeIgnored(client->m_peerAddress);
// No longer connected, clear the connection ID.
client->m_conn_id = BLE_HS_CONN_HANDLE_NONE;
// If we received a connected event but did not get established (no PDU) // If we received a connected event but did not get established (no PDU)
// then a disconnect event will be sent but we should not send it to the // then a disconnect event will be sent but we should not send it to the
// app for processing. Instead we will ensure the task is released // app for processing. Instead we will ensure the task is released

View File

@ -20,6 +20,10 @@
#include "NimBLEHIDDevice.h" #include "NimBLEHIDDevice.h"
#include "NimBLE2904.h" #include "NimBLE2904.h"
/**
* @brief Construct a default NimBLEHIDDevice object.
* @param [in] server A pointer to the server instance this HID Device will use.
*/
NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) { NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) {
/* /*
* Here we create mandatory services described in bluetooth specification * Here we create mandatory services described in bluetooth specification
@ -61,15 +65,18 @@ NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) {
NimBLEHIDDevice::~NimBLEHIDDevice() { NimBLEHIDDevice::~NimBLEHIDDevice() {
} }
/* /**
* @brief * @brief Set the report map data formatting information.
* @param [in] map A pointer to an array with the values to set.
* @param [in] size The number of values in the array.
*/ */
void NimBLEHIDDevice::reportMap(uint8_t* map, uint16_t size) { void NimBLEHIDDevice::reportMap(uint8_t* map, uint16_t size) {
m_reportMapCharacteristic->setValue(map, size); m_reportMapCharacteristic->setValue(map, size);
} }
/* /**
* @brief This function suppose to be called at the end, when we have created all characteristics we need to build HID service * @brief Start the HID device services.\n
* This function called when all the services have been created.
*/ */
void NimBLEHIDDevice::startServices() { void NimBLEHIDDevice::startServices() {
m_deviceInfoService->start(); m_deviceInfoService->start();
@ -77,41 +84,47 @@ void NimBLEHIDDevice::startServices() {
m_batteryService->start(); m_batteryService->start();
} }
/* /**
* @brief Create manufacturer characteristic (this characteristic is optional) * @brief Create a manufacturer characteristic (this characteristic is optional).
*/ */
NimBLECharacteristic* NimBLEHIDDevice::manufacturer() { NimBLECharacteristic* NimBLEHIDDevice::manufacturer() {
m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, NIMBLE_PROPERTY::READ); m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, NIMBLE_PROPERTY::READ);
return m_manufacturerCharacteristic; return m_manufacturerCharacteristic;
} }
/* /**
* @brief Set manufacturer name * @brief Set manufacturer name
* @param [in] name manufacturer name * @param [in] name The manufacturer name of this HID device.
*/ */
void NimBLEHIDDevice::manufacturer(std::string name) { void NimBLEHIDDevice::manufacturer(std::string name) {
m_manufacturerCharacteristic->setValue(name); m_manufacturerCharacteristic->setValue(name);
} }
/* /**
* @brief * @brief Sets the Plug n Play characterisc value.
* @param [in] sig The vendor ID source number.
* @param [in[ vid The vendor ID number.
* @param [in] pid The product ID number.
* @param [in] version The produce version number.
*/ */
void NimBLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) { void NimBLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) {
uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version }; uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version };
m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
} }
/* /**
* @brief * @brief Sets the HID Information characteristic value.
* @param [in] country The country code for the device.
* @param [in] flags The HID Class Specification release number to use.
*/ */
void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) { void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) {
uint8_t info[] = { 0x11, 0x1, country, flags }; uint8_t info[] = { 0x11, 0x1, country, flags };
m_hidInfoCharacteristic->setValue(info, sizeof(info)); m_hidInfoCharacteristic->setValue(info, sizeof(info));
} }
/* /**
* @brief Create input report characteristic that need to be saved as new characteristic object so can be further used * @brief Create input report characteristic
* @param [in] reportID input report ID, the same as in report map for input object related to created characteristic * @param [in] reportID input report ID, the same as in report map for input object related to the characteristic
* @return pointer to new input report characteristic * @return pointer to new input report characteristic
*/ */
NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) { NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) {
@ -124,9 +137,9 @@ NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) {
return inputReportCharacteristic; return inputReportCharacteristic;
} }
/* /**
* @brief Create output report characteristic that need to be saved as new characteristic object so can be further used * @brief Create output report characteristic
* @param [in] reportID Output report ID, the same as in report map for output object related to created characteristic * @param [in] reportID Output report ID, the same as in report map for output object related to the characteristic
* @return Pointer to new output report characteristic * @return Pointer to new output report characteristic
*/ */
NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) { NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) {
@ -139,9 +152,9 @@ NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) {
return outputReportCharacteristic; return outputReportCharacteristic;
} }
/* /**
* @brief Create feature report characteristic that need to be saved as new characteristic object so can be further used * @brief Create feature report characteristic.
* @param [in] reportID Feature report ID, the same as in report map for feature object related to created characteristic * @param [in] reportID Feature report ID, the same as in report map for feature object related to the characteristic
* @return Pointer to new feature report characteristic * @return Pointer to new feature report characteristic
*/ */
NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) { NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) {
@ -154,34 +167,38 @@ NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) {
return featureReportCharacteristic; return featureReportCharacteristic;
} }
/* /**
* @brief * @brief Creates a keyboard boot input report characteristic
*/ */
NimBLECharacteristic* NimBLEHIDDevice::bootInput() { NimBLECharacteristic* NimBLEHIDDevice::bootInput() {
return m_hidService->createCharacteristic((uint16_t) 0x2a22, NIMBLE_PROPERTY::NOTIFY); return m_hidService->createCharacteristic((uint16_t) 0x2a22, NIMBLE_PROPERTY::NOTIFY);
} }
/* /**
* @brief * @brief Create a keyboard boot output report characteristic
*/ */
NimBLECharacteristic* NimBLEHIDDevice::bootOutput() { NimBLECharacteristic* NimBLEHIDDevice::bootOutput() {
return m_hidService->createCharacteristic((uint16_t) 0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR); return m_hidService->createCharacteristic((uint16_t) 0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR);
} }
/* /**
* @brief * @brief Returns a pointer to the HID control point characteristic.
*/ */
NimBLECharacteristic* NimBLEHIDDevice::hidControl() { NimBLECharacteristic* NimBLEHIDDevice::hidControl() {
return m_hidControlCharacteristic; return m_hidControlCharacteristic;
} }
/* /**
* @brief * @brief Returns a pointer to the protocol mode characteristic.
*/ */
NimBLECharacteristic* NimBLEHIDDevice::protocolMode() { NimBLECharacteristic* NimBLEHIDDevice::protocolMode() {
return m_protocolModeCharacteristic; return m_protocolModeCharacteristic;
} }
/**
* @brief Set the battery level characteristic value.
* @param [in] level The battery level value.
*/
void NimBLEHIDDevice::setBatteryLevel(uint8_t level) { void NimBLEHIDDevice::setBatteryLevel(uint8_t level) {
m_batteryLevelCharacteristic->setValue(&level, 1); m_batteryLevelCharacteristic->setValue(&level, 1);
} }
@ -208,22 +225,23 @@ BLECharacteristic* BLEHIDDevice::hidInfo() {
return m_hidInfoCharacteristic; return m_hidInfoCharacteristic;
} }
*/ */
/*
* @brief /**
* @brief Returns a pointer to the device information service.
*/ */
NimBLEService* NimBLEHIDDevice::deviceInfo() { NimBLEService* NimBLEHIDDevice::deviceInfo() {
return m_deviceInfoService; return m_deviceInfoService;
} }
/* /**
* @brief * @brief Returns a pointer to the HID service.
*/ */
NimBLEService* NimBLEHIDDevice::hidService() { NimBLEService* NimBLEHIDDevice::hidService() {
return m_hidService; return m_hidService;
} }
/* /**
* @brief * @brief @brief Returns a pointer to the battery service.
*/ */
NimBLEService* NimBLEHIDDevice::batteryService() { NimBLEService* NimBLEHIDDevice::batteryService() {
return m_batteryService; return m_batteryService;

View File

@ -36,6 +36,10 @@
#define HID_DIGITAL_PEN 0x03C7 #define HID_DIGITAL_PEN 0x03C7
#define HID_BARCODE 0x03C8 #define HID_BARCODE 0x03C8
/**
* @brief A model of a %BLE Human Interface Device.
*/
class NimBLEHIDDevice { class NimBLEHIDDevice {
public: public:
NimBLEHIDDevice(NimBLEServer*); NimBLEHIDDevice(NimBLEServer*);

View File

@ -621,7 +621,13 @@ uint16_t NimBLEServer::getPeerMTU(uint16_t conn_id) {
/** /**
* Update connection parameters can be called only after connection has been established * @brief Request an Update the connection parameters:
* * Can only be used after a connection has been established.
* @param [in] conn_handle The connection handle of the peer to send the request to.
* @param [in] minInterval The minimum connection interval in 1.25ms units.
* @param [in] maxInterval The maximum connection interval in 1.25ms units.
* @param [in] latency The number of packets allowed to skip (extends max interval).
* @param [in] timeout The timeout time in 10ms units before disconnecting.
*/ */
void NimBLEServer::updateConnParams(uint16_t conn_handle, void NimBLEServer::updateConnParams(uint16_t conn_handle,
uint16_t minInterval, uint16_t maxInterval, uint16_t minInterval, uint16_t maxInterval,

View File

@ -320,7 +320,7 @@ static void BLEGenNotifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, ui
void BLEPostAdvert(ble_advertisment_t *Advertisment); void BLEPostAdvert(ble_advertisment_t *Advertisment);
static void BLEPostMQTTSeenDevices(int type); static void BLEPostMQTTSeenDevices(int type);
static void BLEShow(bool json); static void BLEShowStats();
static void BLEPostMQTT(bool json); static void BLEPostMQTT(bool json);
static void BLEStartOperationTask(); static void BLEStartOperationTask();
@ -390,7 +390,7 @@ uint8_t BLEAliasListTrigger = 0;
// triggers send for ALL operations known about // triggers send for ALL operations known about
uint8_t BLEPostMQTTTrigger = 0; uint8_t BLEPostMQTTTrigger = 0;
int BLEMaxAge = 60*10; // 10 minutes int BLEMaxAge = 60*10; // 10 minutes
int BLEAddressFilter = 3; int BLEAddressFilter = 0;
////////////////////////////////////////////////// //////////////////////////////////////////////////
@ -2181,6 +2181,7 @@ static void BLEEverySecond(bool restart){
if (BLEPublishDevices){ if (BLEPublishDevices){
BLEPostMQTTSeenDevices(BLEPublishDevices); BLEPostMQTTSeenDevices(BLEPublishDevices);
BLEShowStats();
BLEPublishDevices = 0; BLEPublishDevices = 0;
} }
@ -3209,26 +3210,15 @@ static void mainThreadOpCallbacks() {
} }
} }
static void BLEShowStats(){
static void BLEShow(bool json) uint32_t totalCount = BLEAdvertisment.totalCount;
{ uint32_t deviceCount = seenDevices.size();
if (json){ ResponseTime_P(PSTR(""));
#ifdef BLE_ESP32_DEBUG ResponseAppend_P(PSTR(",\"BLE\":{\"scans\":%u,\"adverts\":%u,\"devices\":%u,\"resets\":%u}}"), BLEScanCount, totalCount, deviceCount, BLEResets);
if (BLEDebugMode > 0) AddLog(LOG_LEVEL_INFO,PSTR("BLE: show json %d"),json); MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), 0);
#endif
uint32_t totalCount = BLEAdvertisment.totalCount;
uint32_t deviceCount = seenDevices.size();
ResponseAppend_P(PSTR(",\"BLE\":{\"scans\":%u,\"adverts\":%u,\"devices\":%u,\"resets\":%u}"), BLEScanCount, totalCount, deviceCount, BLEResets);
}
#ifdef USE_WEBSERVER
else {
//WSContentSend_PD(HTTP_MI32, i+1,stemp,MIBLEsensors.size());
}
#endif // USE_WEBSERVER
} }
/*void BLEAliasMqttList(){ /*void BLEAliasMqttList(){
ResponseTime_P(PSTR(",\"BLEAlias\":[")); ResponseTime_P(PSTR(",\"BLEAlias\":["));
for (int i = 0; i < aliases.size(); i++){ for (int i = 0; i < aliases.size(); i++){
@ -3495,7 +3485,6 @@ bool Xdrv52(uint8_t function)
result = DecodeCommand(BLE_ESP32::kBLE_Commands, BLE_ESP32::BLE_Commands); result = DecodeCommand(BLE_ESP32::kBLE_Commands, BLE_ESP32::BLE_Commands);
break; break;
case FUNC_JSON_APPEND: case FUNC_JSON_APPEND:
BLE_ESP32::BLEShow(1);
break; break;
// next second, we will publish to our MQTT topic. // next second, we will publish to our MQTT topic.
@ -3510,10 +3499,6 @@ bool Xdrv52(uint8_t function)
case FUNC_WEB_ADD_HANDLER: case FUNC_WEB_ADD_HANDLER:
WebServer_on(PSTR("/" WEB_HANDLE_BLE), BLE_ESP32::HandleBleConfiguration); WebServer_on(PSTR("/" WEB_HANDLE_BLE), BLE_ESP32::HandleBleConfiguration);
break; break;
case FUNC_WEB_SENSOR:
BLE_ESP32::BLEShow(0);
break;
#endif // USE_WEBSERVER #endif // USE_WEBSERVER
} }
return result; return result;

View File

@ -79,6 +79,7 @@ void MI32notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pD
struct { struct {
uint16_t perPage = 4; uint16_t perPage = 4;
uint8_t mqttCurrentSlot = 0; uint8_t mqttCurrentSlot = 0;
uint8_t mqttCurrentSingleSlot = 0;
uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start
int secondsCounter = 0; // counts up in MI32EverySecond to period int secondsCounter = 0; // counts up in MI32EverySecond to period
int secondsCounter2 = 0; // counts up in MI32EverySecond to period int secondsCounter2 = 0; // counts up in MI32EverySecond to period
@ -255,6 +256,8 @@ struct mi_sensor_t{
uint8_t type; //MI_Flora = 1; MI_MI-HT_V1=2; MI_LYWSD02=3; MI_LYWSD03=4; MI_CGG1=5; MI_CGD1=6 uint8_t type; //MI_Flora = 1; MI_MI-HT_V1=2; MI_LYWSD02=3; MI_LYWSD03=4; MI_CGG1=5; MI_CGD1=6
uint8_t needkey; // tells http to display needkey message with link uint8_t needkey; // tells http to display needkey message with link
uint8_t lastCnt; //device generated counter of the packet uint8_t lastCnt; //device generated counter of the packet
uint8_t nextDiscoveryData; // used to lkimit discovery to one MQTT per sec
uint8_t shallSendMQTT; uint8_t shallSendMQTT;
uint8_t MAC[6]; uint8_t MAC[6];
union { union {
@ -1880,6 +1883,9 @@ void MI32EverySecond(bool restart){
MI32ShowSomeSensors(); MI32ShowSomeSensors();
MI32DiscoveryOneMISensor();
MI32ShowOneMISensor();
// read a battery if // read a battery if
// MI32.batteryreader.slot < filled and !MI32.batteryreader.active // MI32.batteryreader.slot < filled and !MI32.batteryreader.active
readOneBat(); readOneBat();
@ -1906,10 +1912,12 @@ void MI32EverySecond(bool restart){
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Kick off tele sending")); AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Kick off tele sending"));
MI32.mqttCurrentSlot = 0; MI32.mqttCurrentSlot = 0;
MI32.secondsCounter2 = 0; MI32.secondsCounter2 = 0;
MI32.mqttCurrentSingleSlot = 0;
} else { } else {
AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Hit tele time, restarted but not finished last - lost from slot %d")+MI32.mqttCurrentSlot); AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Hit tele time, restarted but not finished last - lost from slot %d")+MI32.mqttCurrentSlot);
MI32.mqttCurrentSlot = 0; MI32.mqttCurrentSlot = 0;
MI32.secondsCounter2 = 0; MI32.secondsCounter2 = 0;
MI32.mqttCurrentSingleSlot = 0;
} }
} }
MI32.secondsCounter2++; MI32.secondsCounter2++;
@ -2302,15 +2310,18 @@ void MI32TimeoutSensors(){
// this assumes that we're adding to a ResponseTime_P // this assumes that we're adding to a ResponseTime_P
void MI32GetOneSensorJson(int slot){ void MI32GetOneSensorJson(int slot, int hidename){
mi_sensor_t *p; mi_sensor_t *p;
p = &MIBLEsensors[slot]; p = &MIBLEsensors[slot];
ResponseAppend_P(PSTR(",\"%s-%02x%02x%02x\":{"), // remove hyphen - make it difficult to configure HASS
kMI32DeviceType[p->type-1], if (!hidename) {
p->MAC[3], p->MAC[4], p->MAC[5]); ResponseAppend_P(PSTR("\"%s%02x%02x%02x\":{"),
kMI32DeviceType[p->type-1],
p->MAC[3], p->MAC[4], p->MAC[5]);
}
ResponseAppend_P(PSTR("\"MAC\":\"%02x%02x%02x%02x%02x%02x\""), ResponseAppend_P(PSTR("\"mac\":\"%02x%02x%02x%02x%02x%02x\""),
p->MAC[0], p->MAC[1], p->MAC[2], p->MAC[0], p->MAC[1], p->MAC[2],
p->MAC[3], p->MAC[4], p->MAC[5]); p->MAC[3], p->MAC[4], p->MAC[5]);
@ -2449,7 +2460,9 @@ void MI32GetOneSensorJson(int slot){
} }
if (MI32.option.showRSSI) ResponseAppend_P(PSTR(",\"RSSI\":%d"), p->RSSI); if (MI32.option.showRSSI) ResponseAppend_P(PSTR(",\"RSSI\":%d"), p->RSSI);
ResponseAppend_P(PSTR("}")); if (!hidename) {
ResponseAppend_P(PSTR("}"));
}
p->eventType.raw = 0; p->eventType.raw = 0;
p->shallSendMQTT = 0; p->shallSendMQTT = 0;
@ -2486,7 +2499,8 @@ void MI32ShowSomeSensors(){
ResponseTime_P(PSTR("")); ResponseTime_P(PSTR(""));
int cnt = 0; int cnt = 0;
for (; (MI32.mqttCurrentSlot < numsensors) && (cnt < 4); MI32.mqttCurrentSlot++, cnt++) { for (; (MI32.mqttCurrentSlot < numsensors) && (cnt < 4); MI32.mqttCurrentSlot++, cnt++) {
MI32GetOneSensorJson(MI32.mqttCurrentSlot); ResponseAppend_P(PSTR(","));
MI32GetOneSensorJson(MI32.mqttCurrentSlot, 0);
int mlen = strlen(TasmotaGlobal.mqtt_data); int mlen = strlen(TasmotaGlobal.mqtt_data);
// if we ran out of room, leave here. // if we ran out of room, leave here.
@ -2511,6 +2525,183 @@ void MI32ShowSomeSensors(){
#endif //USE_HOME_ASSISTANT #endif //USE_HOME_ASSISTANT
} }
///////////////////////////////////////////////
// starts a completely fresh MQTT message.
// sends ONE sensor on a dedicated topic NOT related to this TAS
// triggered by setting MI32.mqttCurrentSingleSlot = 0
void MI32ShowOneMISensor(){
// don't detect half-added ones here
int numsensors = MIBLEsensors.size();
if (MI32.mqttCurrentSingleSlot >= numsensors){
// if we got to the end of the sensors, then don't send more
return;
}
#ifdef USE_HOME_ASSISTANT
if(Settings.flag.hass_discovery){
ResponseTime_P(PSTR(","));
MI32GetOneSensorJson(MI32.mqttCurrentSingleSlot, 1);
mi_sensor_t *p;
p = &MIBLEsensors[MI32.mqttCurrentSingleSlot];
ResponseAppend_P(PSTR("}"));
char idstr[32];
const char *alias = BLE_ESP32::getAlias(p->MAC);
const char *id = idstr;
if (alias && *alias){
id = alias;
} else {
sprintf(idstr, PSTR("%s%02x%02x%02x"),
kMI32DeviceType[p->type-1],
p->MAC[3], p->MAC[4], p->MAC[5]);
}
char SensorTopic[60];
sprintf(SensorTopic, "tele/tasmota_ble/%s",
id);
MqttPublish(SensorTopic);
//AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: %s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, TasmotaGlobal.mqtt_data);
}
#endif //USE_HOME_ASSISTANT
MI32.mqttCurrentSingleSlot++;
}
///////////////////////////////////////////////
// starts a completely fresh MQTT message.
// sends ONE sensor's worth of HA discovery msg
#define MI_HA_DISCOVERY_TEMPLATE PSTR("{\"availability\":[],\"device\": \
{\"identifiers\":[\"BLE%s\"],\
\"name\":\"%s\",\
\"manufacturer\":\"tas\",\
\"model\":\"%s\",\
\"via_device\":\"%s\"\
}, \
\"dev_cla\":\"%s\",\
\"expire_after\":600,\
\"json_attr_t\":\"%s\",\
\"name\":\"%s_%s\",\
\"state_topic\":\"%s\",\
\"uniq_id\":\"%s_%s\",\
\"unit_of_meas\":\"%s\",\
\"val_tpl\":\"{{ value_json.%s }}\"}")
void MI32DiscoveryOneMISensor(){
// don't detect half-added ones here
int numsensors = MIBLEsensors.size();
if (MI32.mqttCurrentSingleSlot >= numsensors){
// if we got to the end of the sensors, then don't send more
return;
}
#ifdef USE_HOME_ASSISTANT
if(Settings.flag.hass_discovery){
mi_sensor_t *p;
p = &MIBLEsensors[MI32.mqttCurrentSingleSlot];
// careful - a missing comma causes a crash!!!!
// because of the way we loop?
const char *classes[] = {
"temperature",
"Temperature",
"°C",
"humidity",
"Humidity",
"%",
"temperature",
"DewPoint",
"°C",
"battery",
"Battery",
"%",
"signal_strength",
"RSSI",
"dB"
};
int datacount = (sizeof(classes)/sizeof(*classes))/3;
if (p->nextDiscoveryData >= datacount){
p->nextDiscoveryData = 0;
}
//int i = p->nextDiscoveryData*3;
for (int i = 0; i < datacount*3; i += 3){
if (!classes[i] || !classes[i+1] || !classes[i+2]){
return;
}
char idstr[32];
const char *alias = BLE_ESP32::getAlias(p->MAC);
const char *id = idstr;
if (alias && *alias){
id = alias;
} else {
sprintf(idstr, PSTR("%s%02x%02x%02x"),
kMI32DeviceType[p->type-1],
p->MAC[3], p->MAC[4], p->MAC[5]);
}
char SensorTopic[60];
sprintf(SensorTopic, "tele/tasmota_ble/%s",
id);
ResponseClear();
/*
{"availability":[],"device":{"identifiers":["TasmotaBLEa4c1387fc1e1"],"manufacturer":"simon","model":"someBLEsensor","name":"TASBLEa4c1387fc1e1","sw_version":"0.0.0"},"dev_cla":"temperature","json_attr_t":"tele/tasmota_esp32/SENSOR","name":"TASLYWSD037fc1e1Temp","state_topic":"tele/tasmota_esp32/SENSOR","uniq_id":"Tasmotaa4c1387fc1e1temp","unit_of_meas":"°C","val_tpl":"{{ value_json.LYWSD037fc1e1.Temperature }}"}
{"availability":[],"device":{"identifiers":["TasmotaBLEa4c1387fc1e1"],
"name":"TASBLEa4c1387fc1e1"},"dev_cla":"temperature",
"json_attr_t":"tele/tasmota_esp32/SENSOR",
"name":"TASLYWSD037fc1e1Temp","state_topic": "tele/tasmota_esp32/SENSOR",
"uniq_id":"Tasmotaa4c1387fc1e1temp","unit_of_meas":"°C",
"val_tpl":"{{ value_json.LYWSD037fc1e1.Temperature }}"}
*/
ResponseAppend_P(MI_HA_DISCOVERY_TEMPLATE,
//"{\"identifiers\":[\"BLE%s\"],"
id,
//"\"name\":\"%s\"},"
id,
//\"model\":\"%s\",
kMI32DeviceType[p->type-1],
//\"via_device\":\"%s\"
NetworkHostname(),
//"\"dev_cla\":\"%s\","
classes[i],
//"\"json_attr_t\":\"%s\"," - the topic the sensor publishes on
SensorTopic,
//"\"name\":\"%s_%s\"," - the name of this DATA
id, classes[i+1],
//"\"state_topic\":\"%s\"," - the topic the sensor publishes on?
SensorTopic,
//"\"uniq_id\":\"%s_%s\"," - unique for this data,
id, classes[i+1],
//"\"unit_of_meas\":\"%s\"," - the measure of this type of data
classes[i+2],
//"\"val_tpl\":\"{{ value_json.%s }}") // e.g. Temperature
classes[i+1]
//
);
char DiscoveryTopic[80];
sprintf(DiscoveryTopic, "homeassistant/sensor/%s/%s/config",
id, classes[i+1]);
MqttPublish(DiscoveryTopic);
p->nextDiscoveryData++;
//vTaskDelay(100/ portTICK_PERIOD_MS);
}
} // end if hass discovery
//AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: %s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, TasmotaGlobal.mqtt_data);
#endif //USE_HOME_ASSISTANT
}
/////////////////////////////////////////////// ///////////////////////////////////////////////
// starts a completely fresh MQTT message. // starts a completely fresh MQTT message.
// sends up to 4 sensors pe5r msg // sends up to 4 sensors pe5r msg
@ -2535,7 +2726,8 @@ void MI32ShowTriggeredSensors(){
if(p->shallSendMQTT==0) continue; if(p->shallSendMQTT==0) continue;
cnt++; cnt++;
MI32GetOneSensorJson(sensor); ResponseAppend_P(PSTR(","));
MI32GetOneSensorJson(sensor, 0);
int mlen = strlen(TasmotaGlobal.mqtt_data); int mlen = strlen(TasmotaGlobal.mqtt_data);
// if we ran out of room, leave here. // if we ran out of room, leave here.