mirror of
https://github.com/h2zero/esp-nimble-cpp.git
synced 2024-12-22 11:00:47 +01:00
369 lines
14 KiB
C++
369 lines
14 KiB
C++
|
|
/** NimBLE_Client Demo:
|
|
*
|
|
* Demonstrates many of the available features of the NimBLE client library.
|
|
*
|
|
* Created: on March 24 2020
|
|
* Author: H2zero
|
|
*
|
|
*/
|
|
#include <NimBLEDevice.h>
|
|
|
|
extern "C" {void app_main(void);}
|
|
|
|
static const NimBLEAdvertisedDevice* advDevice;
|
|
|
|
static bool doConnect = false;
|
|
static uint32_t scanTime = 0; /** scan time in milliseconds, 0 = scan forever */
|
|
|
|
|
|
/** None of these are required as they will be handled by the library with defaults. **
|
|
** Remove as you see fit for your needs */
|
|
class ClientCallbacks : public NimBLEClientCallbacks {
|
|
void onConnect(NimBLEClient* pClient) {
|
|
printf("Connected\n");
|
|
/** After connection we should change the parameters if we don't need fast response times.
|
|
* These settings are 150ms interval, 0 latency, 450ms timout.
|
|
* 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.
|
|
* Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 45 * 10ms = 450ms timeout
|
|
*/
|
|
pClient->updateConnParams(120,120,0,45);
|
|
}
|
|
|
|
void onDisconnect(NimBLEClient* pClient, int reason) {
|
|
printf("%s Disconnected, reason = %d - Starting scan\n",
|
|
pClient->getPeerAddress().toString().c_str(), reason);
|
|
NimBLEDevice::getScan()->start(scanTime);
|
|
}
|
|
|
|
/********************* Security handled here **********************
|
|
****** Note: these are the same return values as defaults ********/
|
|
void onPassKeyEntry(NimBLEConnInfo& connInfo){
|
|
printf("Server Passkey Entry\n");
|
|
/** This should prompt the user to enter the passkey displayed
|
|
* on the peer device.
|
|
*/
|
|
NimBLEDevice::injectPassKey(connInfo, 123456);
|
|
};
|
|
|
|
void onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pass_key){
|
|
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
|
|
/** Inject false if passkeys don't match. */
|
|
NimBLEDevice::injectConfirmPasskey(connInfo, true);
|
|
};
|
|
|
|
/** Pairing process complete, we can check the results in connInfo */
|
|
void onAuthenticationComplete(NimBLEConnInfo& connInfo){
|
|
if(!connInfo.isEncrypted()) {
|
|
printf("Encrypt connection failed - disconnecting\n");
|
|
/** Find the client with the connection handle provided in desc */
|
|
NimBLEDevice::getClientByHandle(connInfo.getConnHandle())->disconnect();
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/** Define a class to handle the callbacks when advertisments are received */
|
|
class scanCallbacks: public NimBLEScanCallbacks {
|
|
void onResult(const NimBLEAdvertisedDevice* advertisedDevice) {
|
|
printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str());
|
|
if(advertisedDevice->isAdvertisingService(NimBLEUUID("DEAD")))
|
|
{
|
|
printf("Found Our Service\n");
|
|
/** stop scan before connecting */
|
|
NimBLEDevice::getScan()->stop();
|
|
/** Save the device reference in a global for the client to use*/
|
|
advDevice = advertisedDevice;
|
|
/** Ready to connect now */
|
|
doConnect = true;
|
|
}
|
|
}
|
|
|
|
/** Callback to process the results of the completed scan or restart it */
|
|
void onScanEnd(const NimBLEScanResults& results, int reason) {
|
|
printf("Scan Ended, reason: %d, device count: %d\n", reason, results.getCount());
|
|
}
|
|
};
|
|
|
|
|
|
/** Notification / Indication receiving handler callback */
|
|
void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){
|
|
std::string str = (isNotify == true) ? "Notification" : "Indication";
|
|
str += " from ";
|
|
str += pRemoteCharacteristic->getClient()->getPeerAddress().toString();
|
|
str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
|
|
str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
|
|
str += ", Value = " + std::string((char*)pData, length);
|
|
printf("%s\n", str.c_str());
|
|
}
|
|
|
|
|
|
/** Create a single global instance of the callback class to be used by all clients */
|
|
static ClientCallbacks clientCB;
|
|
|
|
|
|
/** Handles the provisioning of clients and connects / interfaces with the server */
|
|
bool connectToServer() {
|
|
NimBLEClient* pClient = nullptr;
|
|
|
|
/** Check if we have a client we should reuse first **/
|
|
if(NimBLEDevice::getCreatedClientCount()) {
|
|
/** Special case when we already know this device, we send false as the
|
|
* second argument in connect() to prevent refreshing the service database.
|
|
* This saves considerable time and power.
|
|
*/
|
|
pClient = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress());
|
|
if(pClient){
|
|
if(!pClient->connect(advDevice, false)) {
|
|
printf("Reconnect failed\n");
|
|
return false;
|
|
}
|
|
printf("Reconnected client\n");
|
|
}
|
|
/** We don't already have a client that knows this device,
|
|
* we will check for a client that is disconnected that we can use.
|
|
*/
|
|
else {
|
|
pClient = NimBLEDevice::getDisconnectedClient();
|
|
}
|
|
}
|
|
|
|
/** No client to reuse? Create a new one. */
|
|
if(!pClient) {
|
|
if(NimBLEDevice::getCreatedClientCount() >= NIMBLE_MAX_CONNECTIONS) {
|
|
printf("Max clients reached - no more connections available\n");
|
|
return false;
|
|
}
|
|
|
|
pClient = NimBLEDevice::createClient();
|
|
|
|
printf("New client created\n");
|
|
|
|
pClient->setClientCallbacks(&clientCB, false);
|
|
/** 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
|
|
* 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, 12 * 10ms = 120ms timeout
|
|
*/
|
|
pClient->setConnectionParams(6,6,0,15);
|
|
/** Set how long we are willing to wait for the connection to complete (milliseconds), default is 30000. */
|
|
pClient->setConnectTimeout(5 * 1000);
|
|
|
|
|
|
if (!pClient->connect(advDevice)) {
|
|
/** Created a client but failed to connect, don't need to keep it as it has no data */
|
|
NimBLEDevice::deleteClient(pClient);
|
|
printf("Failed to connect, deleted client\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!pClient->isConnected()) {
|
|
if (!pClient->connect(advDevice)) {
|
|
printf("Failed to connect\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
printf("Connected to: %s RSSI: %d\n",
|
|
pClient->getPeerAddress().toString().c_str(),
|
|
pClient->getRssi());
|
|
|
|
/** Now we can read/write/subscribe the charateristics of the services we are interested in */
|
|
NimBLERemoteService* pSvc = nullptr;
|
|
NimBLERemoteCharacteristic* pChr = nullptr;
|
|
NimBLERemoteDescriptor* pDsc = nullptr;
|
|
|
|
pSvc = pClient->getService("DEAD");
|
|
if(pSvc) { /** make sure it's not null */
|
|
pChr = pSvc->getCharacteristic("BEEF");
|
|
}
|
|
|
|
if(pChr) { /** make sure it's not null */
|
|
if(pChr->canRead()) {
|
|
printf("%s Value: %s\n",
|
|
pChr->getUUID().toString().c_str(),
|
|
pChr->readValue().c_str());
|
|
}
|
|
|
|
if(pChr->canWrite()) {
|
|
if(pChr->writeValue("Tasty")) {
|
|
printf("Wrote new value to: %s\n", pChr->getUUID().toString().c_str());
|
|
}
|
|
else {
|
|
/** Disconnect if write failed */
|
|
pClient->disconnect();
|
|
return false;
|
|
}
|
|
|
|
if(pChr->canRead()) {
|
|
printf("The value of: %s is now: %s\n",
|
|
pChr->getUUID().toString().c_str(),
|
|
pChr->readValue().c_str());
|
|
}
|
|
}
|
|
|
|
/** registerForNotify() has been removed and replaced with subscribe() / unsubscribe().
|
|
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=true.
|
|
* Unsubscribe parameter defaults are: response=true.
|
|
*/
|
|
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{
|
|
printf("DEAD service not found.\n");
|
|
}
|
|
|
|
pSvc = pClient->getService("BAAD");
|
|
if(pSvc) { /** make sure it's not null */
|
|
pChr = pSvc->getCharacteristic("F00D");
|
|
}
|
|
|
|
if(pChr) { /** make sure it's not null */
|
|
if(pChr->canRead()) {
|
|
printf("%s Value: %s\n",
|
|
pChr->getUUID().toString().c_str(),
|
|
pChr->readValue().c_str());
|
|
}
|
|
|
|
pDsc = pChr->getDescriptor(NimBLEUUID("C01D"));
|
|
if(pDsc) { /** make sure it's not null */
|
|
printf("Descriptor: %s Value: %s\n",
|
|
pDsc->getUUID().toString().c_str(),
|
|
pDsc->readValue().c_str());
|
|
}
|
|
|
|
if(pChr->canWrite()) {
|
|
if(pChr->writeValue("No tip!")) {
|
|
printf("Wrote new value to: %s\n", pChr->getUUID().toString().c_str());
|
|
}
|
|
else {
|
|
/** Disconnect if write failed */
|
|
pClient->disconnect();
|
|
return false;
|
|
}
|
|
|
|
if(pChr->canRead()) {
|
|
printf("The value of: %s is now: %s\n",
|
|
pChr->getUUID().toString().c_str(),
|
|
pChr->readValue().c_str());
|
|
}
|
|
}
|
|
|
|
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
|
|
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=true.
|
|
* Unsubscribe parameter defaults are: response=true.
|
|
*/
|
|
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{
|
|
printf("BAAD service not found.\n");
|
|
}
|
|
|
|
printf("Done with this device!\n");
|
|
return true;
|
|
}
|
|
|
|
void connectTask (void * parameter){
|
|
/** Loop here until we find a device we want to connect to */
|
|
for(;;) {
|
|
if(doConnect) {
|
|
doConnect = false;
|
|
/** Found a device we want to connect to, do it now */
|
|
if(connectToServer()) {
|
|
printf("Success! we should now be getting notifications, scanning for more!\n");
|
|
} else {
|
|
printf("Failed to connect, starting scan\n");
|
|
}
|
|
|
|
NimBLEDevice::getScan()->start(scanTime);
|
|
}
|
|
vTaskDelay(10/portTICK_PERIOD_MS);
|
|
}
|
|
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
void app_main (void){
|
|
printf("Starting NimBLE Client\n");
|
|
/** Initialize NimBLE, no device name spcified as we are not advertising */
|
|
NimBLEDevice::init("");
|
|
|
|
/** 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_DISPLAY_YESNO - Numeric comparison pairing
|
|
* BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing
|
|
*/
|
|
//NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey
|
|
//NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison
|
|
|
|
/** 2 different ways to set security - both calls achieve the same result.
|
|
* no bonding, no man in the middle protection, secure connections.
|
|
*
|
|
* These are the default values, only shown here for demonstration.
|
|
*/
|
|
//NimBLEDevice::setSecurityAuth(false, false, true);
|
|
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 */
|
|
NimBLEDevice::setPower(9); // 9dbm
|
|
|
|
/** create new scan */
|
|
NimBLEScan* pScan = NimBLEDevice::getScan();
|
|
|
|
/** create a callback that gets called when advertisers are found */
|
|
pScan->setScanCallbacks (new scanCallbacks());
|
|
|
|
/** Set scan interval (how often) and window (how long) in milliseconds */
|
|
pScan->setInterval(400);
|
|
pScan->setWindow(100);
|
|
|
|
/** Active scan will gather scan response data from advertisers
|
|
* but will use more energy from both devices
|
|
*/
|
|
pScan->setActiveScan(true);
|
|
/** Start scanning for advertisers for the scan time specified (in milliseconds) 0 = forever
|
|
* Optional callback for when scanning stops.
|
|
*/
|
|
pScan->start(scanTime);
|
|
|
|
printf("Scanning for peripherals\n");
|
|
|
|
xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL);
|
|
}
|
|
|