summaryrefslogtreecommitdiff
path: root/src/BLEClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/BLEClient.cpp')
-rw-r--r--src/BLEClient.cpp327
1 files changed, 327 insertions, 0 deletions
diff --git a/src/BLEClient.cpp b/src/BLEClient.cpp
new file mode 100644
index 0000000..a5d60cf
--- /dev/null
+++ b/src/BLEClient.cpp
@@ -0,0 +1,327 @@
+/*
+ * BLEDevice.cpp
+ *
+ * Created on: Mar 22, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_log.h>
+#include <bt.h>
+#include <esp_bt_main.h>
+#include <esp_gap_ble_api.h>
+#include <esp_gattc_api.h>
+#include "BLEClient.h"
+#include "BLEUtils.h"
+#include "BLEService.h"
+#include "GeneralUtils.h"
+#include <string>
+#include <sstream>
+#include <unordered_set>
+
+/*
+ * Design
+ * ------
+ * When we perform a searchService() requests, we are asking the BLE server to return each of the services
+ * that it exposes. For each service, we received an ESP_GATTC_SEARCH_RES_EVT event which contains details
+ * of the exposed service including its UUID.
+ *
+ * The objects we will invent for a BLEClient will be as follows:
+ * * BLERemoteService - A model of a remote service.
+ * * BLERemoteCharacteristic - A model of a remote characteristic
+ * * BLERemoteDescriptor - A model of a remote descriptor.
+ *
+ * Since there is a hierarchical relationship here, we will have the idea that from a BLERemoteService will own
+ * zero or more remote characteristics and a BLERemoteCharacteristic will own zero or more remote BLEDescriptors.
+ *
+ * We will assume that a BLERemoteService contains a map that maps BLEUUIDs to the set of owned characteristics
+ * and that a BLECharacteristic contains a map that maps BLEUUIDs to the set of owned descriptors.
+ *
+ *
+ */
+static const char* LOG_TAG = "BLEClient";
+
+BLEClient::BLEClient() {
+ m_pClientCallbacks = nullptr;
+ m_conn_id = 0;
+ m_gattc_if = 0;
+ m_haveServices = false;
+} // BLEClient
+
+/**
+ * @brief Connect to the partner.
+ * @param [in] address The address of the partner.
+ */
+bool BLEClient::connect(BLEAddress address) {
+ ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str());
+// We need the connection handle that we get from registering the application. We register the app
+// and then block on its completion. When the event has arrived, we will have the handle.
+ m_semaphoreRegEvt.take("connect");
+ esp_err_t errRc = esp_ble_gattc_app_register(0);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return false;
+ }
+ m_semaphoreRegEvt.wait("connect");
+
+ m_peerAddress = address;
+
+ m_semaphoreOpenEvt.take("connect");
+ errRc = ::esp_ble_gattc_open(
+ getGattcIf(),
+ *getPeerAddress().getNative(), // address
+ 1 // direct connection
+ );
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return false;
+ }
+
+ uint32_t rc = m_semaphoreOpenEvt.wait("connect");
+ ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK);
+ return rc == ESP_GATT_OK;
+} // connect
+
+
+/**
+ * @brief Disconnect from the peer.
+ * @return N/A.
+ */
+void BLEClient::disconnect() {
+ ESP_LOGD(LOG_TAG, ">> disconnect()");
+ esp_err_t errRc = ::esp_ble_gattc_close(getGattcIf(), getConnId());
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+ m_peerAddress = BLEAddress("00:00:00:00:00:00");
+ ESP_LOGD(LOG_TAG, "<< disconnect()");
+} // disconnect
+
+
+/**
+ * @brief Handle GATT Client events
+ */
+void BLEClient::gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t* evtParam) {
+
+ // Execute handler code based on the type of event received.
+ switch(event) {
+ //
+ // ESP_GATTC_NOTIFY_EVT
+ //
+ // notify
+ // uint16_t conn_id
+ // esp_bd_addr_t remote_bda
+ // esp_gatt_srvc_id_t srvc_id
+ // esp_gatt_id_t char_id
+ // esp_gatt_id_t descr_id
+ // uint16_t value_len
+ // uint8_t* value
+ // bool is_notify
+ //
+ case ESP_GATTC_NOTIFY_EVT: {
+ BLERemoteService *pBLERemoteService = getService(BLEUUID(evtParam->notify.srvc_id.id.uuid));
+ if (pBLERemoteService == nullptr) {
+ ESP_LOGE(LOG_TAG, "Could not find service with UUID %s for notification", BLEUUID(evtParam->notify.srvc_id.id.uuid).toString().c_str());
+ break;
+ }
+ BLERemoteCharacteristic* pBLERemoteCharacteristic = pBLERemoteService->getCharacteristic(BLEUUID(evtParam->notify.char_id.uuid));
+ if (pBLERemoteCharacteristic == nullptr) {
+ ESP_LOGE(LOG_TAG, "Could not find characteristic with UUID %s for notification", BLEUUID(evtParam->notify.char_id.uuid).toString().c_str());
+ break;
+ }
+ if (pBLERemoteCharacteristic->m_notifyCallback != nullptr) {
+ pBLERemoteCharacteristic->m_notifyCallback(pBLERemoteCharacteristic, evtParam->notify.value, evtParam->notify.value_len, evtParam->notify.is_notify);
+ }
+ break;
+ } // ESP_GATTC_NOTIFY_EVT
+
+ //
+ // ESP_GATTC_OPEN_EVT
+ //
+ // open:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ // - esp_bd_addr_t remote_bda
+ // - uint16_t mtu
+ //
+ case ESP_GATTC_OPEN_EVT: {
+ m_conn_id = evtParam->open.conn_id;
+ if (m_pClientCallbacks != nullptr) {
+ m_pClientCallbacks->onConnect(this);
+ }
+ m_semaphoreOpenEvt.give();
+ break;
+ } // ESP_GATTC_OPEN_EVT
+
+
+ //
+ // ESP_GATTC_REG_EVT
+ //
+ // reg:
+ // esp_gatt_status_t status
+ // uint16_t app_id
+ //
+ case ESP_GATTC_REG_EVT: {
+ m_gattc_if = gattc_if;
+ m_semaphoreRegEvt.give();
+ break;
+ } // ESP_GATTC_REG_EVT
+
+
+ //
+ // ESP_GATTC_SEARCH_CMPL_EVT
+ //
+ // search_cmpl:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ //
+ case ESP_GATTC_SEARCH_CMPL_EVT: {
+ m_semaphoreSearchCmplEvt.give();
+ break;
+ } // ESP_GATTC_SEARCH_CMPL_EVT
+
+
+ //
+ // ESP_GATTC_SEARCH_RES_EVT
+ //
+ // search_res:
+ // - uint16_t conn_id
+ // - esp_gatt_srvc_id_t srvc_id
+ //
+ case ESP_GATTC_SEARCH_RES_EVT: {
+ BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id);
+ BLERemoteService* pRemoteService = new BLERemoteService(evtParam->search_res.srvc_id, this);
+ m_servicesMap.insert(std::pair<std::string, BLERemoteService *>(uuid.toString(), pRemoteService));
+ break;
+ } // ESP_GATTC_SEARCH_RES_EVT
+
+
+ default: {
+ break;
+ }
+ } // Switch
+
+ for (auto &myPair : m_servicesMap) {
+ myPair.second->gattClientEventHandler(event, gattc_if, evtParam);
+ }
+
+} // gattClientEventHandler
+
+
+/**
+ * @brief Retrieve the address of the peer.
+ *
+ * Returns the address of the %BLE peer to which this client is connected.
+ */
+BLEAddress BLEClient::getPeerAddress() {
+ return m_peerAddress;
+} // getAddress
+
+
+uint16_t BLEClient::getConnId() {
+ return m_conn_id;
+} // getConnId
+
+
+esp_gatt_if_t BLEClient::getGattcIf() {
+ return m_gattc_if;
+} // getGattcIf
+
+
+
+/**
+ * @brief Get the service object corresponding to the uuid.
+ * @param [in] uuid The UUID of the service being sought.
+ * @return A reference to the Service or nullptr if don't know about it.
+ */
+BLERemoteService* BLEClient::getService(const char* uuid) {
+ return getService(BLEUUID(uuid));
+}
+
+
+/**
+ * @brief Get the service object corresponding to the uuid.
+ * @param [in] uuid The UUID of the service being sought.
+ * @return A reference to the Service or nullptr if don't know about it.
+ */
+BLERemoteService* BLEClient::getService(BLEUUID uuid) {
+// Design
+// ------
+// We wish to retrieve the service given its UUID. It is possible that we have not yet asked the
+// device what services it has in which case we have nothing to match against. If we have not
+// asked the device about its services, then we do that now. Once we get the results we can then
+// examine the services map to see if it has the service we are looking for.
+ if (!m_haveServices) {
+ getServices();
+ }
+ std::string v = uuid.toString();
+ for (auto &myPair : m_servicesMap) {
+ if (myPair.first == v) {
+ return myPair.second;
+ }
+ }
+ return nullptr;
+} // getService
+
+
+/**
+ * @brief Ask the remote %BLE server for its services.
+ * A %BLE Server exposes a set of services for its partners. Here we ask the server for its set of
+ * services and wait until we have received them all.
+ * @return N/A
+ */
+std::map<std::string, BLERemoteService*>* BLEClient::getServices() {
+/*
+ * Design
+ * ------
+ * We invoke esp_ble_gattc_search_service. This will request a list of the service exposed by the
+ * peer BLE partner to be returned as events. Each event will be an an instance of ESP_GATTC_SEARCH_RES_EVT
+ * and will culminate with an ESP_GATTC_SEARCH_CMPL_EVT when all have been received.
+ */
+ ESP_LOGD(LOG_TAG, ">> getServices");
+ m_servicesMap.empty();
+ esp_err_t errRc = esp_ble_gattc_search_service(
+ getGattcIf(),
+ getConnId(),
+ NULL // Filter UUID
+ );
+ m_semaphoreSearchCmplEvt.take("getServices");
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_search_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return &m_servicesMap;
+ }
+ m_semaphoreSearchCmplEvt.wait("getServices");
+ m_haveServices = true; // Remember that we now have services.
+ ESP_LOGD(LOG_TAG, "<< getServices");
+ return &m_servicesMap;
+} // getServices
+
+
+/**
+ * @brief Set the callbacks that will be invoked.
+ */
+void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks) {
+ m_pClientCallbacks = pClientCallbacks;
+} // setClientCallbacks
+
+
+/**
+ * @brief Return a string representation of this client.
+ * @return A string representation of this client.
+ */
+std::string BLEClient::toString() {
+ std::ostringstream ss;
+ ss << "peer address: " << m_peerAddress.toString();
+ ss << "\nServices:\n";
+ for (auto &myPair : m_servicesMap) {
+ ss << myPair.second->toString() << "\n";
+ // myPair.second is the value
+ }
+ return ss.str();
+} // toString
+
+#endif // CONFIG_BT_ENABLED