esp-nimble-cpp/src/NimBLEClient.cpp

1308 lines
48 KiB
C++
Raw Normal View History

2020-03-30 01:44:20 +02:00
/*
2024-12-13 03:21:03 +01:00
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
* esp-nimble-cpp, NimBLE-Arduino contributors.
2020-03-30 01:44:20 +02:00
*
2024-12-13 03:21:03 +01:00
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
2024-12-13 03:21:03 +01:00
* http://www.apache.org/licenses/LICENSE-2.0
2020-03-30 01:44:20 +02:00
*
2024-12-13 03:21:03 +01:00
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
2020-03-30 01:44:20 +02:00
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# include "NimBLEClient.h"
# include "NimBLERemoteService.h"
# include "NimBLERemoteCharacteristic.h"
# include "NimBLEDevice.h"
# include "NimBLELog.h"
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_port.h"
# else
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# endif
# include <climits>
static const char* LOG_TAG = "NimBLEClient";
2020-03-30 01:44:20 +02:00
static NimBLEClientCallbacks defaultCallbacks;
/*
* Design
* ------
* When we perform a getService() request, we are asking the BLE server to return each of the services
* that it exposes. For each service, we receive a callback which contains details
* of the exposed service including its UUID.
*
* The objects we will invent for a NimBLEClient will be as follows:
* * NimBLERemoteService - A model of a remote service.
* * NimBLERemoteCharacteristic - A model of a remote characteristic
* * NimBLERemoteDescriptor - A model of a remote descriptor.
*
* Since there is a hierarchical relationship here, we will have the idea that from a NimBLERemoteService will own
* zero or more remote characteristics and a NimBLERemoteCharacteristic will own zero or more NimBLERemoteDescriptors.
2020-03-30 01:44:20 +02:00
*
* We will assume that a NimBLERemoteService contains a vector of owned characteristics
* and that a NimBLERemoteCharacteristic contains a vector of owned descriptors.
2020-03-30 01:44:20 +02:00
*/
/**
* @brief Constructor, private - only callable by NimBLEDevice::createClient
* to ensure proper handling of the list of client objects.
*/
NimBLEClient::NimBLEClient(const NimBLEAddress& peerAddress)
: m_peerAddress(peerAddress),
m_lastErr{0},
m_connectTimeout{30000},
m_pTaskData{nullptr},
m_svcVec{},
m_pClientCallbacks{&defaultCallbacks},
m_connHandle{BLE_HS_CONN_HANDLE_NONE},
m_terminateFailCount{0},
m_asyncSecureAttempt{0},
m_config{},
# if CONFIG_BT_NIMBLE_EXT_ADV
m_phyMask{BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK | BLE_GAP_LE_PHY_CODED_MASK},
# endif
m_connParams{16,
16,
BLE_GAP_INITIAL_CONN_ITVL_MIN,
BLE_GAP_INITIAL_CONN_ITVL_MAX,
BLE_GAP_INITIAL_CONN_LATENCY,
BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
BLE_GAP_INITIAL_CONN_MAX_CE_LEN} {
2020-03-30 01:44:20 +02:00
} // NimBLEClient
/**
* @brief Destructor, private - only callable by NimBLEDevice::deleteClient
* to ensure proper disconnect and removal from device list.
*/
NimBLEClient::~NimBLEClient() {
// We may have allocated service references associated with this client.
2020-03-30 01:44:20 +02:00
// Before we are finished with the client, we must release resources.
deleteServices();
if (m_config.deleteCallbacks) {
2020-03-30 01:44:20 +02:00
delete m_pClientCallbacks;
}
} // ~NimBLEClient
/**
* @brief Delete all service objects created by this client and clear the vector.
2020-03-30 01:44:20 +02:00
*/
void NimBLEClient::deleteServices() {
2020-03-30 01:44:20 +02:00
// Delete all the services.
for (auto& it : m_svcVec) {
delete it;
2020-03-30 01:44:20 +02:00
}
std::vector<NimBLERemoteService*>().swap(m_svcVec);
} // deleteServices
/**
* @brief Delete a service by UUID from the local database to free resources.
* @param [in] uuid The UUID of the service to be deleted.
* @return Number of services left.
*/
size_t NimBLEClient::deleteService(const NimBLEUUID& uuid) {
// Delete the requested service.
for (auto it = m_svcVec.begin(); it != m_svcVec.end(); ++it) {
if ((*it)->getUUID() == uuid) {
delete *it;
m_svcVec.erase(it);
break;
}
}
return m_svcVec.size();
} // deleteService
2020-03-30 01:44:20 +02:00
/**
* @brief Connect to the BLE Server using the address of the last connected device, or the address\n
* passed to the constructor.
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
* have created when last connected.
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
* If false, this function will block until the connection is established or the connection attempt times out.
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
* @return true on success.
*/
bool NimBLEClient::connect(bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
return connect(m_peerAddress, deleteAttributes, asyncConnect, exchangeMTU);
2024-12-11 03:04:47 +01:00
} // connect
2020-03-30 01:44:20 +02:00
/**
* @brief Connect to an advertising device.
* @param [in] device The device to connect to.
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
* have created when last connected.
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
* If false, this function will block until the connection is established or the connection attempt times out.
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
* @return true on success.
2020-03-30 01:44:20 +02:00
*/
bool NimBLEClient::connect(const NimBLEAdvertisedDevice* device, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
2020-03-30 01:44:20 +02:00
NimBLEAddress address(device->getAddress());
return connect(address, deleteAttributes, asyncConnect, exchangeMTU);
2024-12-11 03:04:47 +01:00
} // connect
2020-03-30 01:44:20 +02:00
/**
* @brief Connect to a BLE Server by address.
* @param [in] address The address of the server.
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
* have created when last connected.
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
* If false, this function will block until the connection is established or the connection attempt times out.
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
* @return true on success.
2020-03-30 01:44:20 +02:00
*/
bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
2020-03-30 01:44:20 +02:00
NIMBLE_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str());
if (!NimBLEDevice::m_synced) {
NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync.");
2020-03-30 01:44:20 +02:00
return false;
}
if (isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Client already connected");
return false;
}
const ble_addr_t* peerAddr = address.getBase();
if (ble_gap_conn_find_by_addr(peerAddr, NULL) == 0) {
NIMBLE_LOGE(LOG_TAG, "A connection to %s already exists", address.toString().c_str());
return false;
}
if (address.isNull()) {
NIMBLE_LOGE(LOG_TAG, "Invalid peer address; (NULL)");
return false;
} else {
m_peerAddress = address;
}
if (deleteAttributes) {
deleteServices();
}
int rc = 0;
m_config.asyncConnect = asyncConnect;
m_config.exchangeMTU = exchangeMTU;
do {
# if CONFIG_BT_NIMBLE_EXT_ADV
rc = ble_gap_ext_connect(NimBLEDevice::m_ownAddrType,
peerAddr,
m_connectTimeout,
m_phyMask,
&m_connParams,
&m_connParams,
&m_connParams,
NimBLEClient::handleGapEvent,
this);
# else
rc = ble_gap_connect(NimBLEDevice::m_ownAddrType,
peerAddr,
m_connectTimeout,
&m_connParams,
NimBLEClient::handleGapEvent,
this);
# endif
switch (rc) {
case 0:
break;
case BLE_HS_EBUSY:
// Scan was active, stop it through the NimBLEScan API to release any tasks and call the callback.
if (!NimBLEDevice::getScan()->stop()) {
rc = BLE_HS_EUNKNOWN;
}
break;
case BLE_HS_EDONE:
// A connection to this device already exists, do not connect twice.
NIMBLE_LOGE(LOG_TAG, "Already connected to device; addr=%s", std::string(m_peerAddress).c_str());
break;
case BLE_HS_EALREADY:
NIMBLE_LOGE(LOG_TAG, "Already attempting to connect");
break;
default:
NIMBLE_LOGE(LOG_TAG,
"Failed to connect to %s, rc=%d; %s",
std::string(m_peerAddress).c_str(),
rc,
NimBLEUtils::returnCodeToString(rc));
break;
}
} while (rc == BLE_HS_EBUSY);
if (rc != 0) {
m_lastErr = rc;
return false;
}
if (m_config.asyncConnect) {
return true;
}
NimBLETaskData taskData(this);
m_pTaskData = &taskData;
// Wait for the connect timeout time +1 second for the connection to complete
if (!NimBLEUtils::taskWait(taskData, m_connectTimeout + 1000)) {
// If a connection was made but no response from MTU exchange proceed anyway
if (isConnected()) {
taskData.m_flags = 0;
} else {
// workaround; if the controller doesn't cancel the connection at the timeout, cancel it here.
NIMBLE_LOGE(LOG_TAG, "Connect timeout - cancelling");
ble_gap_conn_cancel();
taskData.m_flags = BLE_HS_ETIMEOUT;
}
}
m_pTaskData = nullptr;
rc = taskData.m_flags;
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Connection failed; status=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
m_lastErr = rc;
if (m_config.deleteOnConnectFail) {
NimBLEDevice::deleteClient(this);
}
2020-03-30 01:44:20 +02:00
return false;
}
2020-04-14 03:13:51 +02:00
m_pClientCallbacks->onConnect(this);
2020-03-30 01:44:20 +02:00
NIMBLE_LOGD(LOG_TAG, "<< connect()");
// Check if still connected before returning
return isConnected();
2020-03-30 01:44:20 +02:00
} // connect
/**
* @brief Initiate a secure connection (pair/bond) with the server.\n
* Called automatically when a characteristic or descriptor requires encryption or authentication to access it.
* @param [in] async If true, the connection will be secured asynchronously and this function will return immediately.\n
* If false, this function will block until the connection is secured or the client disconnects.
2020-03-30 01:44:20 +02:00
* @return True on success.
* @details If async=false, this function will block and should not be used in a callback.
2020-03-30 01:44:20 +02:00
*/
bool NimBLEClient::secureConnection(bool async) const {
NIMBLE_LOGD(LOG_TAG, ">> secureConnection()");
int rc = 0;
if (async && !NimBLEDevice::startSecurity(m_connHandle, &rc)) {
2024-12-13 03:21:03 +01:00
m_lastErr = rc;
m_asyncSecureAttempt = 0;
return false;
}
if (async) {
m_asyncSecureAttempt++;
return true;
}
NimBLETaskData taskData(const_cast<NimBLEClient*>(this), BLE_HS_ENOTCONN);
m_pTaskData = &taskData;
int retryCount = 1;
do {
if (NimBLEDevice::startSecurity(m_connHandle)) {
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
}
} while (taskData.m_flags == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING) && retryCount--);
m_pTaskData = nullptr;
if (taskData.m_flags == 0) {
NIMBLE_LOGD(LOG_TAG, "<< secureConnection: success");
return true;
2020-03-30 01:44:20 +02:00
}
m_lastErr = taskData.m_flags;
NIMBLE_LOGE(LOG_TAG, "secureConnection: failed rc=%d", taskData.m_flags);
return false;
} // secureConnection
2020-03-30 01:44:20 +02:00
/**
* @brief Disconnect from the peer.
* @return True if the command was successfully sent.
2020-03-30 01:44:20 +02:00
*/
bool NimBLEClient::disconnect(uint8_t reason) {
int rc = ble_gap_terminate(m_connHandle, reason);
if (rc != 0 && rc != BLE_HS_ENOTCONN && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
m_lastErr = rc;
return false;
2020-03-30 01:44:20 +02:00
}
return true;
2020-03-30 01:44:20 +02:00
} // disconnect
/**
* @brief Cancel an ongoing connection attempt.
* @return True if the command was successfully sent.
*/
bool NimBLEClient::cancelConnect() const {
int rc = ble_gap_conn_cancel();
if (rc != 0 && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_conn_cancel failed: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
m_lastErr = rc;
return false;
}
return true;
} // cancelConnect
/**
* @brief Set or unset a flag to delete this client when disconnected or connection failed.
* @param [in] deleteOnDisconnect Set the client to self delete when disconnected.
* @param [in] deleteOnConnectFail Set the client to self delete when a connection attempt fails.
*/
void NimBLEClient::setSelfDelete(bool deleteOnDisconnect, bool deleteOnConnectFail) {
m_config.deleteOnDisconnect = deleteOnDisconnect;
m_config.deleteOnConnectFail = deleteOnConnectFail;
} // setSelfDelete
/**
* @brief Get a copy of the clients configuration.
* @return A copy of the clients configuration.
*/
NimBLEClient::Config NimBLEClient::getConfig() const {
return m_config;
} // getConfig
/**
* @brief Set the client configuration options.
* @param [in] config The config options instance to set the client configuration to.
*/
void NimBLEClient::setConfig(NimBLEClient::Config config) {
m_config = config;
} // setConfig
# if CONFIG_BT_NIMBLE_EXT_ADV
/**
* @brief Set the PHY types to use when connecting to a server.
* @param [in] mask A bitmask indicating what PHYS to connect with.\n
* The available bits are:
* * 0x01 BLE_GAP_LE_PHY_1M_MASK
* * 0x02 BLE_GAP_LE_PHY_2M_MASK
* * 0x04 BLE_GAP_LE_PHY_CODED_MASK
*/
void NimBLEClient::setConnectPhy(uint8_t mask) {
m_phyMask = mask;
} // setConnectPhy
/**
* @brief Request a change to the PHY used for this peer connection.
* @param [in] txPhyMask TX PHY. Can be mask of following:
* - BLE_GAP_LE_PHY_1M_MASK
* - BLE_GAP_LE_PHY_2M_MASK
* - BLE_GAP_LE_PHY_CODED_MASK
* - BLE_GAP_LE_PHY_ANY_MASK
* @param [in] rxPhyMask RX PHY. Can be mask of following:
* - BLE_GAP_LE_PHY_1M_MASK
* - BLE_GAP_LE_PHY_2M_MASK
* - BLE_GAP_LE_PHY_CODED_MASK
* - BLE_GAP_LE_PHY_ANY_MASK
* @param phyOptions Additional PHY options. Valid values are:
* - BLE_GAP_LE_PHY_CODED_ANY (default)
* - BLE_GAP_LE_PHY_CODED_S2
* - BLE_GAP_LE_PHY_CODED_S8
* @return True if successful.
*/
bool NimBLEClient::updatePhy(uint8_t txPhyMask, uint8_t rxPhyMask, uint16_t phyOptions) {
int rc = ble_gap_set_prefered_le_phy(m_connHandle, txPhyMask, rxPhyMask, phyOptions);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Failed to update phy; rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
}
return rc == 0;
} // updatePhy
/**
* @brief Get the PHY used for this peer connection.
* @param [out] txPhy The TX PHY.
* @param [out] rxPhy The RX PHY.
* @return True if successful.
*/
bool NimBLEClient::getPhy(uint8_t* txPhy, uint8_t* rxPhy) {
int rc = ble_gap_read_le_phy(m_connHandle, txPhy, rxPhy);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Failed to read phy; rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
}
return rc == 0;
} // getPhy
# endif
2020-03-30 01:44:20 +02:00
/**
* @brief Set the connection parameters to use when connecting to a server.
2021-01-20 23:20:11 +01:00
* @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.
* @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.
2020-03-30 01:44:20 +02:00
*/
void NimBLEClient::setConnectionParams(
uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout, uint16_t scanInterval, uint16_t scanWindow)
/*, uint16_t minConnEvtTime, uint16_t maxConnEvtTime)*/
2020-03-30 01:44:20 +02:00
{
m_connParams.itvl_min = minInterval;
m_connParams.itvl_max = maxInterval;
m_connParams.latency = latency;
m_connParams.supervision_timeout = timeout;
m_connParams.scan_itvl = scanInterval;
m_connParams.scan_window = scanWindow;
// These are not used by NimBLE at this time - Must leave at defaults
// m_connParams.min_ce_len = minConnEvtTime; // Minimum length of connection event in 0.625ms units
// m_connParams.max_ce_len = maxConnEvtTime; // Maximum length of connection event in 0.625ms units
} // setConnectionParams
2020-03-30 01:44:20 +02:00
/**
* @brief Update the connection parameters:
* * Can only be used after a connection has been established.
2021-01-20 23:20:11 +01:00
* @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.
2020-03-30 01:44:20 +02:00
*/
bool NimBLEClient::updateConnParams(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) {
ble_gap_upd_params params{.itvl_min = minInterval,
.itvl_max = maxInterval,
.latency = latency,
.supervision_timeout = timeout,
// These are not used by NimBLE at this time - leave at defaults
.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN};
int rc = ble_gap_update_params(m_connHandle, &params);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Update params error: %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
m_lastErr = rc;
}
2020-03-30 01:44:20 +02:00
return rc == 0;
} // updateConnParams
2020-03-30 01:44:20 +02:00
/**
* @brief Request an update of the data packet length.
* * Can only be used after a connection has been established.
* @details Sends a data length update request to the server the client is connected to.
* The Data Length Extension (DLE) allows to increase the Data Channel Payload from 27 bytes to up to 251 bytes.
* The server needs to support the Bluetooth 4.2 specifications, to be capable of DLE.
* @param [in] txOctets The preferred number of payload octets to use (Range 0x001B-0x00FB).
*/
bool NimBLEClient::setDataLen(uint16_t txOctets) {
# if defined(CONFIG_NIMBLE_CPP_IDF) && !defined(ESP_IDF_VERSION) || \
(ESP_IDF_VERSION_MAJOR * 100 + ESP_IDF_VERSION_MINOR * 10 + ESP_IDF_VERSION_PATCH) < 432
return false;
# else
uint16_t txTime = (txOctets + 14) * 8;
int rc = ble_gap_set_data_len(m_connHandle, txOctets, txTime);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Set data length error: %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
}
return rc == 0;
# endif
} // setDataLen
Add connection info class and access methods to server and client. This adds the ability to access information about the current connection. A new class was created to wrap the struct ble_gap_conn_desc with methods to retrieve the connection information. Example server use: ``` for(auto i=0; i<pServer->getConnectedCount();i++) { NimBLEConnInfo connInfo = pServer->getPeerInfo(i); printf("Connected client %d info:\n", i); printf("Peer address: %s\n", connInfo.getAddress().toString().c_str()); printf("Peer ID address: %s\n", connInfo.getIdAddress().toString().c_str()); printf("Handle: %u\n", connInfo.getConnHandle()); printf("Interval: %u\n", connInfo.getConnInterval()); printf("Timeout: %u\n", connInfo.getConnTimeout()); printf("Latency: %u\n", connInfo.getConnLatency()); printf("MTU: %u\n", connInfo.getMTU()); printf("Master: %s\n", connInfo.isMaster()? "true":"false"); printf("Slave: %s\n", connInfo.isSlave()? "true":"false"); printf("Bonded: %s\n", connInfo.isBonded()? "true":"false"); printf("Authenticated: %s\n", connInfo.isAuthenticated()? "true":"false"); printf("Encrypted: %s\n", connInfo.isEncrypted()? "true":"false"); printf("Encryption Key Size: %u\n", connInfo.getSecKeySize()); } ``` Example client use: ``` if (pClient->isConnected()) { NimBLEConnInfo connInfo = pClient->getConnInfo(); printf("Connection info:\n"); printf("Peer address: %s\n", connInfo.getAddress().toString().c_str()); printf("Peer ID address: %s\n", connInfo.getIdAddress().toString().c_str()); printf("Handle: %u\n", connInfo.getConnHandle()); printf("Interval: %u\n", connInfo.getConnInterval()); printf("Timeout: %u\n", connInfo.getConnTimeout()); printf("Latency: %u\n", connInfo.getConnLatency()); printf("MTU: %u\n", connInfo.getMTU()); printf("Master: %s\n", connInfo.isMaster()? "true":"false"); printf("Slave: %s\n", connInfo.isSlave()? "true":"false"); printf("Bonded: %s\n", connInfo.isBonded()? "true":"false"); printf("Authenticated: %s\n", connInfo.isAuthenticated()? "true":"false"); printf("Encrypted: %s\n", connInfo.isEncrypted()? "true":"false"); printf("Encryption Key Size: %u\n", connInfo.getSecKeySize()); } ```
2021-05-07 17:02:43 +02:00
/**
* @brief Get detailed information about the current peer connection.
* @return A NimBLEConnInfo instance with the data, or a NULL instance if not found.
Add connection info class and access methods to server and client. This adds the ability to access information about the current connection. A new class was created to wrap the struct ble_gap_conn_desc with methods to retrieve the connection information. Example server use: ``` for(auto i=0; i<pServer->getConnectedCount();i++) { NimBLEConnInfo connInfo = pServer->getPeerInfo(i); printf("Connected client %d info:\n", i); printf("Peer address: %s\n", connInfo.getAddress().toString().c_str()); printf("Peer ID address: %s\n", connInfo.getIdAddress().toString().c_str()); printf("Handle: %u\n", connInfo.getConnHandle()); printf("Interval: %u\n", connInfo.getConnInterval()); printf("Timeout: %u\n", connInfo.getConnTimeout()); printf("Latency: %u\n", connInfo.getConnLatency()); printf("MTU: %u\n", connInfo.getMTU()); printf("Master: %s\n", connInfo.isMaster()? "true":"false"); printf("Slave: %s\n", connInfo.isSlave()? "true":"false"); printf("Bonded: %s\n", connInfo.isBonded()? "true":"false"); printf("Authenticated: %s\n", connInfo.isAuthenticated()? "true":"false"); printf("Encrypted: %s\n", connInfo.isEncrypted()? "true":"false"); printf("Encryption Key Size: %u\n", connInfo.getSecKeySize()); } ``` Example client use: ``` if (pClient->isConnected()) { NimBLEConnInfo connInfo = pClient->getConnInfo(); printf("Connection info:\n"); printf("Peer address: %s\n", connInfo.getAddress().toString().c_str()); printf("Peer ID address: %s\n", connInfo.getIdAddress().toString().c_str()); printf("Handle: %u\n", connInfo.getConnHandle()); printf("Interval: %u\n", connInfo.getConnInterval()); printf("Timeout: %u\n", connInfo.getConnTimeout()); printf("Latency: %u\n", connInfo.getConnLatency()); printf("MTU: %u\n", connInfo.getMTU()); printf("Master: %s\n", connInfo.isMaster()? "true":"false"); printf("Slave: %s\n", connInfo.isSlave()? "true":"false"); printf("Bonded: %s\n", connInfo.isBonded()? "true":"false"); printf("Authenticated: %s\n", connInfo.isAuthenticated()? "true":"false"); printf("Encrypted: %s\n", connInfo.isEncrypted()? "true":"false"); printf("Encryption Key Size: %u\n", connInfo.getSecKeySize()); } ```
2021-05-07 17:02:43 +02:00
*/
NimBLEConnInfo NimBLEClient::getConnInfo() const {
NimBLEConnInfo connInfo{};
if (ble_gap_conn_find(m_connHandle, &connInfo.m_desc) != 0) {
NIMBLE_LOGE(LOG_TAG, "Connection info not found");
Add connection info class and access methods to server and client. This adds the ability to access information about the current connection. A new class was created to wrap the struct ble_gap_conn_desc with methods to retrieve the connection information. Example server use: ``` for(auto i=0; i<pServer->getConnectedCount();i++) { NimBLEConnInfo connInfo = pServer->getPeerInfo(i); printf("Connected client %d info:\n", i); printf("Peer address: %s\n", connInfo.getAddress().toString().c_str()); printf("Peer ID address: %s\n", connInfo.getIdAddress().toString().c_str()); printf("Handle: %u\n", connInfo.getConnHandle()); printf("Interval: %u\n", connInfo.getConnInterval()); printf("Timeout: %u\n", connInfo.getConnTimeout()); printf("Latency: %u\n", connInfo.getConnLatency()); printf("MTU: %u\n", connInfo.getMTU()); printf("Master: %s\n", connInfo.isMaster()? "true":"false"); printf("Slave: %s\n", connInfo.isSlave()? "true":"false"); printf("Bonded: %s\n", connInfo.isBonded()? "true":"false"); printf("Authenticated: %s\n", connInfo.isAuthenticated()? "true":"false"); printf("Encrypted: %s\n", connInfo.isEncrypted()? "true":"false"); printf("Encryption Key Size: %u\n", connInfo.getSecKeySize()); } ``` Example client use: ``` if (pClient->isConnected()) { NimBLEConnInfo connInfo = pClient->getConnInfo(); printf("Connection info:\n"); printf("Peer address: %s\n", connInfo.getAddress().toString().c_str()); printf("Peer ID address: %s\n", connInfo.getIdAddress().toString().c_str()); printf("Handle: %u\n", connInfo.getConnHandle()); printf("Interval: %u\n", connInfo.getConnInterval()); printf("Timeout: %u\n", connInfo.getConnTimeout()); printf("Latency: %u\n", connInfo.getConnLatency()); printf("MTU: %u\n", connInfo.getMTU()); printf("Master: %s\n", connInfo.isMaster()? "true":"false"); printf("Slave: %s\n", connInfo.isSlave()? "true":"false"); printf("Bonded: %s\n", connInfo.isBonded()? "true":"false"); printf("Authenticated: %s\n", connInfo.isAuthenticated()? "true":"false"); printf("Encrypted: %s\n", connInfo.isEncrypted()? "true":"false"); printf("Encryption Key Size: %u\n", connInfo.getSecKeySize()); } ```
2021-05-07 17:02:43 +02:00
}
return connInfo;
} // getConnInfo
2020-03-30 01:44:20 +02:00
/**
* @brief Set the timeout to wait for connection attempt to complete.
* @param [in] time The number of milliseconds before timeout, default is 30 seconds.
2020-03-30 01:44:20 +02:00
*/
void NimBLEClient::setConnectTimeout(uint32_t time) {
m_connectTimeout = time;
} // setConnectTimeout
2020-03-30 01:44:20 +02:00
/**
* @brief Get the connection handle for this client.
* @return The connection handle.
2020-03-30 01:44:20 +02:00
*/
uint16_t NimBLEClient::getConnHandle() const {
return m_connHandle;
} // getConnHandle
2020-03-30 01:44:20 +02:00
/**
* @brief Retrieve the address of the peer.
* @return A NimBLEAddress instance with the peer address data.
2020-03-30 01:44:20 +02:00
*/
[BREAKING] Refactor attributes Refactor attributes to reduce code duplication and improve maintainability. * Add attribute base classes to provide common code. * Add const where possible to functions and parameters. * `NimBLECharacteristic::notify` no longer takes a `bool is_notification` parameter, instead `indicate()` should be called to send indications. * `NimBLECharacteristic::indicate` now takes the same parameters as `notify`. * `NimBLECharacteristicCallbacks` and `NimBLEDescriptorCallbacks` methods now take `const NimBLEConnInfo&` instead of non-const. * `NimBLECharacteristic::onNotify` callback removed as unnecessary, the library does not call notify without app input. * `NimBLERemoteCharacteristic::getRemoteService` now returns a `const NimBLERemoteService*` instead of non-const. * Add NimBLEUUID constructor that takes a reference to `ble_uuid_any_t`. * `NimBLERemoteService::getCharacteristics` now returns a `const std::vector<NimBLERemoteCharacteristic*>&` instead of non-const `std::vector<NimBLERemoteCharacteristic*>*` * `NimBLERemoteService::getValue` now returns `NimBLEAttValue` instead of `std::string` * `NimBLEService::getCharacteristics` now returns a `const std::vector<NimBLECharacteristic*>&` instead of a copy of std::vector<NimBLECharacteristic *>. * Remove const requirement for NimBLEConnInfo parameter in callbacks. Const is unnecessary as the data can't be changed by application code. * Change NimBLERemoteCharacteristic::getRemoteService to return const pointer.
2024-07-26 22:47:36 +02:00
NimBLEAddress NimBLEClient::getPeerAddress() const {
2020-03-30 01:44:20 +02:00
return m_peerAddress;
} // getPeerAddress
/**
* @brief Set the peer address.
* @param [in] address The address of the peer that this client is connected or should connect to.
* @return True if successful.
*/
bool NimBLEClient::setPeerAddress(const NimBLEAddress& address) {
if (isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Cannot set peer address while connected");
return false;
}
m_peerAddress = address;
return true;
} // setPeerAddress
2020-03-30 01:44:20 +02:00
/**
* @brief Ask the BLE server for the RSSI value.
* @return The RSSI value or 0 if there was an error.
2020-03-30 01:44:20 +02:00
*/
int NimBLEClient::getRssi() const {
2020-03-30 01:44:20 +02:00
if (!isConnected()) {
NIMBLE_LOGE(LOG_TAG, "getRssi(): Not connected");
2020-03-30 01:44:20 +02:00
return 0;
}
int8_t rssi = 0;
int rc = ble_gap_conn_rssi(m_connHandle, &rssi);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Failed to read RSSI error code: %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
2021-12-29 16:12:38 +01:00
m_lastErr = rc;
2020-03-30 01:44:20 +02:00
return 0;
}
return rssi;
2020-03-30 01:44:20 +02:00
} // getRssi
/**
* @brief Get iterator to the beginning of the vector of remote service pointers.
* @return An iterator to the beginning of the vector of remote service pointers.
*/
std::vector<NimBLERemoteService*>::iterator NimBLEClient::begin() {
return m_svcVec.begin();
2024-12-11 03:04:47 +01:00
} // begin
/**
* @brief Get iterator to the end of the vector of remote service pointers.
* @return An iterator to the end of the vector of remote service pointers.
*/
std::vector<NimBLERemoteService*>::iterator NimBLEClient::end() {
return m_svcVec.end();
2024-12-11 03:04:47 +01:00
} // end
2020-03-30 01:44:20 +02:00
/**
* @brief Get the service BLE Remote Service instance corresponding to the uuid.
* @param [in] uuid The UUID of the service being sought.
* @return A pointer to the service or nullptr if not found.
2020-03-30 01:44:20 +02:00
*/
NimBLERemoteService* NimBLEClient::getService(const char* uuid) {
return getService(NimBLEUUID(uuid));
} // getService
/**
* @brief Get the service object corresponding to the uuid.
* @param [in] uuid The UUID of the service being sought.
* @return A pointer to the service or nullptr if not found.
2020-03-30 01:44:20 +02:00
*/
NimBLERemoteService* NimBLEClient::getService(const NimBLEUUID& uuid) {
2020-03-30 01:44:20 +02:00
NIMBLE_LOGD(LOG_TAG, ">> getService: uuid: %s", uuid.toString().c_str());
for (auto& it : m_svcVec) {
if (it->getUUID() == uuid) {
2020-03-30 01:44:20 +02:00
NIMBLE_LOGD(LOG_TAG, "<< getService: found the service with uuid: %s", uuid.toString().c_str());
return it;
2020-03-30 01:44:20 +02:00
}
}
size_t prevSize = m_svcVec.size();
if (retrieveServices(&uuid)) {
if (m_svcVec.size() > prevSize) {
return m_svcVec.back();
}
// If the request was successful but 16/32 bit uuid not found
// try again with the 128 bit uuid.
if (uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) {
NimBLEUUID uuid128(uuid);
uuid128.to128();
if (retrieveServices(&uuid128)) {
if (m_svcVec.size() > prevSize) {
return m_svcVec.back();
}
}
} else {
// If the request was successful but the 128 bit uuid not found
// try again with the 16 bit uuid.
NimBLEUUID uuid16(uuid);
uuid16.to16();
// if the uuid was 128 bit but not of the BLE base type this check will fail
if (uuid16.bitSize() == BLE_UUID_TYPE_16) {
if (retrieveServices(&uuid16)) {
if (m_svcVec.size() > prevSize) {
return m_svcVec.back();
}
}
}
}
}
2020-03-30 01:44:20 +02:00
NIMBLE_LOGD(LOG_TAG, "<< getService: not found");
return nullptr;
} // getService
/**
* @brief Get a pointer to the vector of found services.
* @param [in] refresh If true the current services vector will be cleared and\n
* all services will be retrieved from the peripheral.\n
* If false the vector will be returned with the currently stored services.
* @return A pointer to the vector of available services.
*/
const std::vector<NimBLERemoteService*>& NimBLEClient::getServices(bool refresh) {
if (refresh) {
deleteServices();
if (!retrieveServices()) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to get services");
} else {
NIMBLE_LOGI(LOG_TAG, "Found %d services", m_svcVec.size());
}
}
2020-03-30 01:44:20 +02:00
return m_svcVec;
} // getServices
2020-03-30 01:44:20 +02:00
/**
* @brief Retrieves the full database of attributes that the peripheral has available.
* @return True if successful.
*/
bool NimBLEClient::discoverAttributes() {
deleteServices();
if (!retrieveServices()) {
return false;
}
for (auto svc : m_svcVec) {
if (!svc->retrieveCharacteristics()) {
return false;
}
for (auto chr : svc->m_vChars) {
if (!chr->retrieveDescriptors()) {
return false;
}
}
}
return true;
} // discoverAttributes
2020-03-30 01:44:20 +02:00
/**
* @brief Ask the remote BLE server for its services.
* * Here we ask the server for its set of services and wait until we have received them all.
2020-03-30 01:44:20 +02:00
* @return true on success otherwise false if an error occurred
*/
bool NimBLEClient::retrieveServices(const NimBLEUUID* uuidFilter) {
if (!isConnected()) {
2020-03-30 01:44:20 +02:00
NIMBLE_LOGE(LOG_TAG, "Disconnected, could not retrieve services -aborting");
return false;
}
int rc = 0;
NimBLETaskData taskData(this);
if (uuidFilter == nullptr) {
rc = ble_gattc_disc_all_svcs(m_connHandle, NimBLEClient::serviceDiscoveredCB, &taskData);
} else {
rc = ble_gattc_disc_svc_by_uuid(m_connHandle, uuidFilter->getBase(), NimBLEClient::serviceDiscoveredCB, &taskData);
}
2020-03-30 01:44:20 +02:00
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_svcs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
2021-12-29 16:12:38 +01:00
m_lastErr = rc;
2020-03-30 01:44:20 +02:00
return false;
}
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
rc = taskData.m_flags;
if (rc == 0 || rc == BLE_HS_EDONE) {
2020-03-30 01:44:20 +02:00
return true;
}
m_lastErr = rc;
NIMBLE_LOGE(LOG_TAG, "Could not retrieve services, rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
2020-03-30 01:44:20 +02:00
} // getServices
/**
* @brief Callback for the service discovery API function.
* @details When a service is found or there is none left or there was an error
2020-03-30 01:44:20 +02:00
* the API will call this and report findings.
*/
int NimBLEClient::serviceDiscoveredCB(uint16_t connHandle,
const struct ble_gatt_error* error,
const struct ble_gatt_svc* service,
void* arg) {
NIMBLE_LOGD(LOG_TAG,
"Service Discovered >> status: %d handle: %d",
error->status,
(error->status == 0) ? service->start_handle : -1);
NimBLETaskData* pTaskData = (NimBLETaskData*)arg;
NimBLEClient* pClient = (NimBLEClient*)pTaskData->m_pInstance;
2020-03-30 01:44:20 +02:00
if (error->status == BLE_HS_ENOTCONN) {
2024-12-11 03:04:47 +01:00
NIMBLE_LOGE(LOG_TAG, "<< Service Discovered; Disconnected");
NimBLEUtils::taskRelease(*pTaskData, error->status);
return error->status;
}
2020-03-30 01:44:20 +02:00
// Make sure the service discovery is for this device
if (pClient->getConnHandle() != connHandle) {
2020-03-30 01:44:20 +02:00
return 0;
}
if (error->status == 0) {
// Found a service - add it to the vector
pClient->m_svcVec.push_back(new NimBLERemoteService(pClient, service));
return 0;
2020-03-30 01:44:20 +02:00
}
NimBLEUtils::taskRelease(*pTaskData, error->status);
NIMBLE_LOGD(LOG_TAG, "<< Service Discovered");
return error->status;
2024-12-11 03:04:47 +01:00
} // serviceDiscoveredCB
2020-03-30 01:44:20 +02:00
/**
* @brief Get the value of a specific characteristic associated with a specific service.
* @param [in] serviceUUID The service that owns the characteristic.
* @param [in] characteristicUUID The characteristic whose value we wish to read.
* @returns characteristic value or an empty value if not found.
2020-03-30 01:44:20 +02:00
*/
NimBLEAttValue NimBLEClient::getValue(const NimBLEUUID& serviceUUID, const NimBLEUUID& characteristicUUID) {
NIMBLE_LOGD(LOG_TAG,
">> getValue: serviceUUID: %s, characteristicUUID: %s",
serviceUUID.toString().c_str(),
characteristicUUID.toString().c_str());
NimBLEAttValue ret{};
auto pService = getService(serviceUUID);
if (pService != nullptr) {
auto pChar = pService->getCharacteristic(characteristicUUID);
if (pChar != nullptr) {
2020-03-30 01:44:20 +02:00
ret = pChar->readValue();
}
}
2022-02-20 18:02:30 +01:00
NIMBLE_LOGD(LOG_TAG, "<< getValue");
2020-03-30 01:44:20 +02:00
return ret;
} // getValue
/**
* @brief Set the value of a specific characteristic associated with a specific service.
* @param [in] serviceUUID The service that owns the characteristic.
* @param [in] characteristicUUID The characteristic whose value we wish to write.
* @param [in] value The value to write to the characteristic.
2021-01-16 05:51:49 +01:00
* @param [in] response If true, uses write with response operation.
2020-03-30 01:44:20 +02:00
* @returns true if successful otherwise false
*/
bool NimBLEClient::setValue(const NimBLEUUID& serviceUUID,
const NimBLEUUID& characteristicUUID,
const NimBLEAttValue& value,
bool response) {
NIMBLE_LOGD(LOG_TAG,
">> setValue: serviceUUID: %s, characteristicUUID: %s",
serviceUUID.toString().c_str(),
characteristicUUID.toString().c_str());
bool ret = false;
auto pService = getService(serviceUUID);
if (pService != nullptr) {
2020-03-30 01:44:20 +02:00
NimBLERemoteCharacteristic* pChar = pService->getCharacteristic(characteristicUUID);
if (pChar != nullptr) {
ret = pChar->writeValue(value, response);
2020-03-30 01:44:20 +02:00
}
}
2020-03-30 01:44:20 +02:00
NIMBLE_LOGD(LOG_TAG, "<< setValue");
return ret;
} // setValue
/**
* @brief Get the remote characteristic with the specified handle.
* @param [in] handle The handle of the desired characteristic.
* @returns The matching remote characteristic, nullptr otherwise.
*/
NimBLERemoteCharacteristic* NimBLEClient::getCharacteristic(uint16_t handle) {
for (const auto& svc : m_svcVec) {
if (svc->getStartHandle() <= handle && handle <= svc->getEndHandle()) {
for (const auto& chr : svc->m_vChars) {
if (chr->getHandle() == handle) {
return chr;
}
}
}
}
return nullptr;
2024-12-11 03:04:47 +01:00
} // getCharacteristic
2020-03-30 01:44:20 +02:00
/**
* @brief Get the current mtu of this connection.
* @returns The MTU value.
2020-03-30 01:44:20 +02:00
*/
[BREAKING] Refactor attributes Refactor attributes to reduce code duplication and improve maintainability. * Add attribute base classes to provide common code. * Add const where possible to functions and parameters. * `NimBLECharacteristic::notify` no longer takes a `bool is_notification` parameter, instead `indicate()` should be called to send indications. * `NimBLECharacteristic::indicate` now takes the same parameters as `notify`. * `NimBLECharacteristicCallbacks` and `NimBLEDescriptorCallbacks` methods now take `const NimBLEConnInfo&` instead of non-const. * `NimBLECharacteristic::onNotify` callback removed as unnecessary, the library does not call notify without app input. * `NimBLERemoteCharacteristic::getRemoteService` now returns a `const NimBLERemoteService*` instead of non-const. * Add NimBLEUUID constructor that takes a reference to `ble_uuid_any_t`. * `NimBLERemoteService::getCharacteristics` now returns a `const std::vector<NimBLERemoteCharacteristic*>&` instead of non-const `std::vector<NimBLERemoteCharacteristic*>*` * `NimBLERemoteService::getValue` now returns `NimBLEAttValue` instead of `std::string` * `NimBLEService::getCharacteristics` now returns a `const std::vector<NimBLECharacteristic*>&` instead of a copy of std::vector<NimBLECharacteristic *>. * Remove const requirement for NimBLEConnInfo parameter in callbacks. Const is unnecessary as the data can't be changed by application code. * Change NimBLERemoteCharacteristic::getRemoteService to return const pointer.
2024-07-26 22:47:36 +02:00
uint16_t NimBLEClient::getMTU() const {
return ble_att_mtu(m_connHandle);
} // getMTU
2020-03-30 01:44:20 +02:00
/**
* @brief Callback for the MTU exchange API function.
* @details When the MTU exchange is complete the API will call this and report the new MTU.
*/
int NimBLEClient::exchangeMTUCb(uint16_t conn_handle, const ble_gatt_error* error, uint16_t mtu, void* arg) {
NIMBLE_LOGD(LOG_TAG, "exchangeMTUCb: status=%d, mtu=%d", error->status, mtu);
NimBLEClient* pClient = (NimBLEClient*)arg;
if (pClient->getConnHandle() != conn_handle) {
return 0;
}
if (error->status != 0) {
NIMBLE_LOGE(LOG_TAG, "exchangeMTUCb() rc=%d %s", error->status, NimBLEUtils::returnCodeToString(error->status));
pClient->m_lastErr = error->status;
}
return 0;
2024-12-11 03:04:47 +01:00
} // exchangeMTUCb
/**
* @brief Begin the MTU exchange process with the server.
* @returns true if the request was sent successfully.
*/
bool NimBLEClient::exchangeMTU() {
int rc = ble_gattc_exchange_mtu(m_connHandle, NimBLEClient::exchangeMTUCb, this);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "MTU exchange error; rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
m_lastErr = rc;
return false;
}
return true;
} // exchangeMTU
2020-03-30 01:44:20 +02:00
/**
* @brief Handle a received GAP event.
* @param [in] event The event structure sent by the NimBLE stack.
* @param [in] arg A pointer to the client instance that registered for this callback.
2020-03-30 01:44:20 +02:00
*/
int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
NimBLEClient* pClient = (NimBLEClient*)arg;
int rc = 0;
NimBLETaskData* pTaskData = pClient->m_pTaskData; // save a copy in case client is deleted
2020-03-30 01:44:20 +02:00
2024-12-11 03:04:47 +01:00
NIMBLE_LOGD(LOG_TAG, ">> handleGapEvent %s", NimBLEUtils::gapEventToString(event->type));
2020-03-30 01:44:20 +02:00
switch (event->type) {
2020-03-30 01:44:20 +02:00
case BLE_GAP_EVENT_DISCONNECT: {
// workaround for bug in NimBLE stack where disconnect event argument is not passed correctly
pClient = NimBLEDevice::getClientByHandle(event->disconnect.conn.conn_handle);
if (pClient == nullptr) {
return 0;
}
rc = event->disconnect.reason;
// If Host reset tell the device now before returning to prevent
// any errors caused by calling host functions before re-syncing.
switch (rc) {
case BLE_HS_ECONTROLLER:
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_ENOTSYNCED:
case BLE_HS_EOS:
NIMBLE_LOGE(LOG_TAG, "Disconnect - host reset, rc=%d", rc);
NimBLEDevice::onReset(rc);
break;
default:
break;
}
NIMBLE_LOGD(LOG_TAG, "disconnect; reason=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
pClient->m_terminateFailCount = 0;
pClient->m_asyncSecureAttempt = 0;
// Don't call the disconnect callback if we are waiting for a connection to complete and it fails
if (rc != (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT) || pClient->m_config.asyncConnect) {
pClient->m_pClientCallbacks->onDisconnect(pClient, rc);
}
pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE;
if (pClient->m_config.deleteOnDisconnect) {
// If we are set to self delete on disconnect but we have a task waiting on the connection
// completion we will set the flag to delete on connect fail instead of deleting here
// to prevent segmentation faults or double deleting
if (pTaskData != nullptr && rc == (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT)) {
pClient->m_config.deleteOnConnectFail = true;
break;
}
NimBLEDevice::deleteClient(pClient);
}
break;
2020-03-30 01:44:20 +02:00
} // BLE_GAP_EVENT_DISCONNECT
case BLE_GAP_EVENT_CONNECT: {
// If we aren't waiting for this connection response we should drop the connection immediately.
if (pClient->isConnected() || (!pClient->m_config.asyncConnect && pClient->m_pTaskData == nullptr)) {
ble_gap_terminate(event->connect.conn_handle, BLE_ERR_REM_USER_CONN_TERM);
2020-03-30 01:44:20 +02:00
return 0;
}
rc = event->connect.status;
if (rc == 0) {
pClient->m_connHandle = event->connect.conn_handle;
if (pClient->m_config.asyncConnect) {
pClient->m_pClientCallbacks->onConnect(pClient);
}
if (pClient->m_config.exchangeMTU) {
if (!pClient->exchangeMTU()) {
rc = pClient->m_lastErr; // sets the error in the task data
break;
}
return 0; // return as we may have a task waiting for the MTU before releasing it.
}
2020-03-30 01:44:20 +02:00
} else {
pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE;
if (pClient->m_config.asyncConnect) {
pClient->m_pClientCallbacks->onConnectFail(pClient, rc);
if (pClient->m_config.deleteOnConnectFail) {
NimBLEDevice::deleteClient(pClient);
}
}
2020-03-30 01:44:20 +02:00
}
break;
2020-03-30 01:44:20 +02:00
} // BLE_GAP_EVENT_CONNECT
case BLE_GAP_EVENT_TERM_FAILURE: {
if (pClient->m_connHandle != event->term_failure.conn_handle) {
2020-03-30 01:44:20 +02:00
return 0;
}
NIMBLE_LOGE(LOG_TAG, "Connection termination failure; rc=%d - retrying", event->term_failure.status);
if (++pClient->m_terminateFailCount > 2) {
ble_hs_sched_reset(BLE_HS_ECONTROLLER);
} else {
ble_gap_terminate(event->term_failure.conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
return 0;
} // BLE_GAP_EVENT_TERM_FAILURE
case BLE_GAP_EVENT_NOTIFY_RX: {
if (pClient->m_connHandle != event->notify_rx.conn_handle) return 0;
NIMBLE_LOGD(LOG_TAG, "Notify Received for handle: %d", event->notify_rx.attr_handle);
for (const auto& svc : pClient->m_svcVec) {
// Dont waste cycles searching services without this handle in its range
if (svc->getEndHandle() < event->notify_rx.attr_handle) {
2020-03-30 01:44:20 +02:00
continue;
}
NIMBLE_LOGD(LOG_TAG,
"checking service %s for handle: %d",
svc->getUUID().toString().c_str(),
event->notify_rx.attr_handle);
for (const auto& chr : svc->m_vChars) {
if (chr->getHandle() == event->notify_rx.attr_handle) {
NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", chr->toString().c_str());
uint32_t data_len = OS_MBUF_PKTLEN(event->notify_rx.om);
chr->m_value.setValue(event->notify_rx.om->om_data, data_len);
if (chr->m_notifyCallback != nullptr) {
chr->m_notifyCallback(chr, event->notify_rx.om->om_data, data_len, !event->notify_rx.indication);
}
break;
2020-03-30 01:44:20 +02:00
}
}
}
2020-03-30 01:44:20 +02:00
return 0;
} // BLE_GAP_EVENT_NOTIFY_RX
case BLE_GAP_EVENT_CONN_UPDATE_REQ:
2020-03-30 01:44:20 +02:00
case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: {
if (pClient->m_connHandle != event->conn_update_req.conn_handle) {
return 0;
2020-03-30 01:44:20 +02:00
}
NIMBLE_LOGD(LOG_TAG, "Peer requesting to update connection parameters");
NIMBLE_LOGD(LOG_TAG,
"MinInterval: %d, MaxInterval: %d, Latency: %d, Timeout: %d",
event->conn_update_req.peer_params->itvl_min,
event->conn_update_req.peer_params->itvl_max,
event->conn_update_req.peer_params->latency,
event->conn_update_req.peer_params->supervision_timeout);
2020-04-14 03:13:51 +02:00
rc = pClient->m_pClientCallbacks->onConnParamsUpdateRequest(pClient, event->conn_update_req.peer_params)
? 0
: BLE_ERR_CONN_PARMS;
2020-04-14 03:13:51 +02:00
if (!rc && event->type == BLE_GAP_EVENT_CONN_UPDATE_REQ) {
event->conn_update_req.self_params->itvl_min = pClient->m_connParams.itvl_min;
event->conn_update_req.self_params->itvl_max = pClient->m_connParams.itvl_max;
event->conn_update_req.self_params->latency = pClient->m_connParams.latency;
event->conn_update_req.self_params->supervision_timeout = pClient->m_connParams.supervision_timeout;
2020-03-30 01:44:20 +02:00
}
2020-04-14 03:13:51 +02:00
NIMBLE_LOGD(LOG_TAG, "%s peer params", (rc == 0) ? "Accepted" : "Rejected");
2020-03-30 01:44:20 +02:00
return rc;
} // BLE_GAP_EVENT_CONN_UPDATE_REQ, BLE_GAP_EVENT_L2CAP_UPDATE_REQ
2020-03-30 01:44:20 +02:00
case BLE_GAP_EVENT_CONN_UPDATE: {
if (pClient->m_connHandle != event->conn_update.conn_handle) {
return 0;
2020-03-30 01:44:20 +02:00
}
if (event->conn_update.status == 0) {
2020-03-30 01:44:20 +02:00
NIMBLE_LOGI(LOG_TAG, "Connection parameters updated.");
} else {
NIMBLE_LOGE(LOG_TAG, "Update connection parameters failed.");
}
return 0;
} // BLE_GAP_EVENT_CONN_UPDATE
2020-03-30 01:44:20 +02:00
case BLE_GAP_EVENT_ENC_CHANGE: {
if (pClient->m_connHandle != event->enc_change.conn_handle) {
return 0;
2020-03-30 01:44:20 +02:00
}
if (event->enc_change.status == 0 ||
event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) {
NimBLEConnInfo peerInfo;
rc = ble_gap_conn_find(event->enc_change.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
rc = 0;
break;
}
2020-08-21 19:46:45 +02:00
if (event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) {
// Key is missing, try deleting.
ble_store_util_delete_peer(&peerInfo.m_desc.peer_id_addr);
// Attempt a retry if async secure failed.
if (pClient->m_asyncSecureAttempt == 1) {
pClient->secureConnection(true);
}
2020-03-30 01:44:20 +02:00
} else {
pClient->m_asyncSecureAttempt = 0;
pClient->m_pClientCallbacks->onAuthenticationComplete(peerInfo);
2020-03-30 01:44:20 +02:00
}
}
rc = event->enc_change.status;
break;
} // BLE_GAP_EVENT_ENC_CHANGE
case BLE_GAP_EVENT_IDENTITY_RESOLVED: {
NimBLEConnInfo peerInfo;
rc = ble_gap_conn_find(event->identity_resolved.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
rc = 0;
break;
}
pClient->m_pClientCallbacks->onIdentity(peerInfo);
break;
} // BLE_GAP_EVENT_IDENTITY_RESOLVED
# if CONFIG_BT_NIMBLE_EXT_ADV
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: {
NimBLEConnInfo peerInfo;
rc = ble_gap_conn_find(event->phy_updated.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
pClient->m_pClientCallbacks->onPhyUpdate(pClient, event->phy_updated.tx_phy, event->phy_updated.rx_phy);
return 0;
} // BLE_GAP_EVENT_PHY_UPDATE_COMPLETE
# endif
2020-03-30 01:44:20 +02:00
case BLE_GAP_EVENT_MTU: {
if (pClient->m_connHandle != event->mtu.conn_handle) {
return 0;
2020-03-30 01:44:20 +02:00
}
NIMBLE_LOGI(LOG_TAG, "mtu update: mtu=%d", event->mtu.value);
pClient->m_pClientCallbacks->onMTUChange(pClient, event->mtu.value);
rc = 0;
break;
2020-03-30 01:44:20 +02:00
} // BLE_GAP_EVENT_MTU
2020-03-30 01:44:20 +02:00
case BLE_GAP_EVENT_PASSKEY_ACTION: {
if (pClient->m_connHandle != event->passkey.conn_handle) {
2020-03-30 01:44:20 +02:00
return 0;
}
2020-03-30 01:44:20 +02:00
NimBLEConnInfo peerInfo;
rc = ble_gap_conn_find(event->passkey.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
rc = 0;
break;
}
if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
2022-01-15 20:43:17 +01:00
NIMBLE_LOGD(LOG_TAG, "Passkey on device's display: %" PRIu32, event->passkey.params.numcmp);
pClient->m_pClientCallbacks->onConfirmPasskey(peerInfo, event->passkey.params.numcmp);
2020-03-30 01:44:20 +02:00
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
NIMBLE_LOGD(LOG_TAG, "OOB request received");
// TODO: Handle out of band pairing
// struct ble_sm_io pkey;
// pkey.action = BLE_SM_IOACT_OOB;
// pClient->onOobPairingRequest(pkey.oob);
// rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
// NIMBLE_LOGD(LOG_TAG, "ble_sm_inject_io result: %d", rc);
2020-03-30 01:44:20 +02:00
} else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
NIMBLE_LOGD(LOG_TAG, "Enter the passkey");
pClient->m_pClientCallbacks->onPassKeyEntry(peerInfo);
2020-03-30 01:44:20 +02:00
} else if (event->passkey.params.action == BLE_SM_IOACT_NONE) {
NIMBLE_LOGD(LOG_TAG, "No passkey action required");
}
return 0;
} // BLE_GAP_EVENT_PASSKEY_ACTION
2020-03-30 01:44:20 +02:00
default: {
return 0;
}
} // Switch
if (pTaskData != nullptr) {
NimBLEUtils::taskRelease(*pTaskData, rc);
}
2024-12-11 03:04:47 +01:00
NIMBLE_LOGD(LOG_TAG, "<< handleGapEvent");
return 0;
2020-03-30 01:44:20 +02:00
} // handleGapEvent
/**
* @brief Are we connected to a server?
* @return True if we are connected and false if we are not connected.
*/
bool NimBLEClient::isConnected() const {
return m_connHandle != BLE_HS_CONN_HANDLE_NONE;
2020-03-30 01:44:20 +02:00
} // isConnected
/**
* @brief Set the callbacks that will be invoked when events are received.
* @param [in] pClientCallbacks A pointer to a class to receive the event callbacks.
* @param [in] deleteCallbacks If true this will delete the callback class sent when the client is destructed.
2020-03-30 01:44:20 +02:00
*/
void NimBLEClient::setClientCallbacks(NimBLEClientCallbacks* pClientCallbacks, bool deleteCallbacks) {
if (pClientCallbacks != nullptr) {
m_pClientCallbacks = pClientCallbacks;
m_config.deleteCallbacks = deleteCallbacks;
} else {
m_pClientCallbacks = &defaultCallbacks;
m_config.deleteCallbacks = false;
}
2020-03-30 01:44:20 +02:00
} // setClientCallbacks
/**
* @brief Return a string representation of this client.
* @return A string representation of this client.
*/
std::string NimBLEClient::toString() const {
std::string res = "peer address: " + m_peerAddress.toString();
res += "\nServices:\n";
for (const auto& it : m_svcVec) {
res += it->toString() + "\n";
2020-03-30 01:44:20 +02:00
}
return res;
} // toString
static const char* CB_TAG = "NimBLEClientCallbacks";
2021-12-29 16:12:38 +01:00
/**
* @brief Get the last error code reported by the NimBLE host
* @return int, the NimBLE error code.
*/
int NimBLEClient::getLastError() const {
2021-12-29 16:12:38 +01:00
return m_lastErr;
} // getLastError
2020-04-14 03:13:51 +02:00
void NimBLEClientCallbacks::onConnect(NimBLEClient* pClient) {
NIMBLE_LOGD(CB_TAG, "onConnect: default");
} // onConnect
2020-03-30 01:44:20 +02:00
void NimBLEClientCallbacks::onConnectFail(NimBLEClient* pClient, int reason) {
NIMBLE_LOGD(CB_TAG, "onConnectFail: default, reason: %d", reason);
} // onConnectFail
void NimBLEClientCallbacks::onDisconnect(NimBLEClient* pClient, int reason) {
NIMBLE_LOGD(CB_TAG, "onDisconnect: default, reason: %d", reason);
} // onDisconnect
2020-03-30 01:44:20 +02:00
2020-04-14 03:13:51 +02:00
bool NimBLEClientCallbacks::onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params) {
NIMBLE_LOGD(CB_TAG, "onConnParamsUpdateRequest: default");
2020-04-14 03:13:51 +02:00
return true;
} // onConnParamsUpdateRequest
2020-04-14 03:13:51 +02:00
void NimBLEClientCallbacks::onPassKeyEntry(NimBLEConnInfo& connInfo) {
NIMBLE_LOGD(CB_TAG, "onPassKeyEntry: default: 123456");
NimBLEDevice::injectPassKey(connInfo, 123456);
} // onPassKeyEntry
2020-03-30 01:44:20 +02:00
void NimBLEClientCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo) {
NIMBLE_LOGD(CB_TAG, "onAuthenticationComplete: default");
} // onAuthenticationComplete
void NimBLEClientCallbacks::onIdentity(NimBLEConnInfo& connInfo) {
NIMBLE_LOGD(CB_TAG, "onIdentity: default");
} // onIdentity
void NimBLEClientCallbacks::onConfirmPasskey(NimBLEConnInfo& connInfo, uint32_t pin) {
NIMBLE_LOGD(CB_TAG, "onConfirmPasskey: default: true");
NimBLEDevice::injectConfirmPasskey(connInfo, true);
} // onConfirmPasskey
2020-03-30 01:44:20 +02:00
void NimBLEClientCallbacks::onMTUChange(NimBLEClient* pClient, uint16_t mtu) {
NIMBLE_LOGD(CB_TAG, "onMTUChange: default");
} // onMTUChange
# if CONFIG_BT_NIMBLE_EXT_ADV
void NimBLEClientCallbacks::onPhyUpdate(NimBLEClient* pClient, uint8_t txPhy, uint8_t rxPhy) {
NIMBLE_LOGD(CB_TAG, "onPhyUpdate: default, txPhy: %d, rxPhy: %d", txPhy, rxPhy);
} // onPhyUpdate
# endif
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */