summaryrefslogtreecommitdiff
path: root/sensor/patchedBLE/src/BLEClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sensor/patchedBLE/src/BLEClient.cpp')
-rw-r--r--sensor/patchedBLE/src/BLEClient.cpp471
1 files changed, 471 insertions, 0 deletions
diff --git a/sensor/patchedBLE/src/BLEClient.cpp b/sensor/patchedBLE/src/BLEClient.cpp
new file mode 100644
index 0000000..141cf0f
--- /dev/null
+++ b/sensor/patchedBLE/src/BLEClient.cpp
@@ -0,0 +1,471 @@
+/*
+ * BLEDevice.cpp
+ *
+ * Created on: Mar 22, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_log.h>
+#include <esp_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>
+#ifdef ARDUINO_ARCH_ESP32
+#include "esp32-hal-log.h"
+#endif
+
+/*
+ * 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;
+ m_isConnected = false; // Initially, we are flagged as not connected.
+} // BLEClient
+
+
+/**
+ * @brief Destructor.
+ */
+BLEClient::~BLEClient() {
+ // We may have allocated service references associated with this client. Before we are finished
+ // with the client, we must release resources.
+ for (auto &myPair : m_servicesMap) {
+ delete myPair.second;
+ }
+ m_servicesMap.clear();
+} // ~BLEClient
+
+
+/**
+ * @brief Clear any existing services.
+ *
+ */
+void BLEClient::clearServices() {
+ ESP_LOGD(LOG_TAG, ">> clearServices");
+ // Delete all the services.
+ for (auto &myPair : m_servicesMap) {
+ delete myPair.second;
+ }
+ m_servicesMap.clear();
+ m_haveServices = false;
+ ESP_LOGD(LOG_TAG, "<< clearServices");
+} // clearServices
+
+
+/**
+ * @brief Connect to the partner (BLE Server).
+ * @param [in] address The address of the partner.
+ * @return True on success.
+ */
+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");
+
+ clearServices(); // Delete any services that may exist.
+
+ 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;
+
+ // Perform the open connection request against the target BLE Server.
+ m_semaphoreOpenEvt.take("connect");
+ errRc = ::esp_ble_gattc_open(
+ getGattcIf(),
+ *getPeerAddress().getNative(), // address
+ BLE_ADDR_TYPE_PUBLIC, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature.
+ 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"); // Wait for the connection to complete.
+ 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;
+ }
+ esp_ble_gattc_app_unregister(getGattcIf());
+ 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_DISCONNECT_EVT
+ //
+ // disconnect:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ // - esp_bd_addr_t remote_bda
+ case ESP_GATTC_DISCONNECT_EVT: {
+ // If we receive a disconnect event, set the class flag that indicates that we are
+ // no longer connected.
+ if (m_pClientCallbacks != nullptr) {
+ m_pClientCallbacks->onDisconnect(this);
+ }
+ m_isConnected = false;
+ m_semaphoreRssiCmplEvt.give();
+ m_semaphoreSearchCmplEvt.give(1);
+ break;
+ } // ESP_GATTC_DISCONNECT_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);
+ }
+ if (evtParam->open.status == ESP_GATT_OK) {
+ m_isConnected = true; // Flag us as connected.
+ }
+ m_semaphoreOpenEvt.give(evtParam->open.status);
+ 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(0);
+ break;
+ } // ESP_GATTC_SEARCH_CMPL_EVT
+
+
+ //
+ // ESP_GATTC_SEARCH_RES_EVT
+ //
+ // search_res:
+ // - uint16_t conn_id
+ // - uint16_t start_handle
+ // - uint16_t end_handle
+ // - esp_gatt_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,
+ evtParam->search_res.start_handle,
+ evtParam->search_res.end_handle
+ );
+ m_servicesMap.insert(std::pair<std::string, BLERemoteService *>(uuid.toString(), pRemoteService));
+ break;
+ } // ESP_GATTC_SEARCH_RES_EVT
+
+
+ default: {
+ break;
+ }
+ } // Switch
+
+ // Pass the request on to all services.
+ for (auto &myPair : m_servicesMap) {
+ myPair.second->gattClientEventHandler(event, gattc_if, evtParam);
+ }
+
+} // gattClientEventHandler
+
+
+uint16_t BLEClient::getConnId() {
+ return m_conn_id;
+} // getConnId
+
+
+
+esp_gatt_if_t BLEClient::getGattcIf() {
+ return m_gattc_if;
+} // getGattcIf
+
+
+/**
+ * @brief Retrieve the address of the peer.
+ *
+ * Returns the Bluetooth device address of the %BLE peer to which this client is connected.
+ */
+BLEAddress BLEClient::getPeerAddress() {
+ return m_peerAddress;
+} // getAddress
+
+
+/**
+ * @brief Ask the BLE server for the RSSI value.
+ * @return The RSSI value.
+ */
+int BLEClient::getRssi() {
+ ESP_LOGD(LOG_TAG, ">> getRssi()");
+ if (!isConnected()) {
+ ESP_LOGD(LOG_TAG, "<< getRssi(): Not connected");
+ return 0;
+ }
+ // We make the API call to read the RSSI value which is an asynchronous operation. We expect to receive
+ // an ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT to indicate completion.
+ //
+ m_semaphoreRssiCmplEvt.take("getRssi");
+ esp_err_t rc = ::esp_ble_gap_read_rssi(*getPeerAddress().getNative());
+ if (rc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "<< getRssi: esp_ble_gap_read_rssi: rc=%d %s", rc, GeneralUtils::errorToString(rc));
+ return 0;
+ }
+ int rssiValue = m_semaphoreRssiCmplEvt.wait("getRssi");
+ ESP_LOGD(LOG_TAG, "<< getRssi(): %d", rssiValue);
+ return rssiValue;
+} // getRssi
+
+
+/**
+ * @brief Get the service BLE Remote Service instance 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));
+} // getService
+
+
+/**
+ * @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.
+ * @throws BLEUuidNotFound
+ */
+BLERemoteService* BLEClient::getService(BLEUUID uuid) {
+ ESP_LOGD(LOG_TAG, ">> getService: uuid: %s", uuid.toString().c_str());
+// 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 uuidStr = uuid.toString();
+ for (auto &myPair : m_servicesMap) {
+ if (myPair.first == uuidStr) {
+ ESP_LOGD(LOG_TAG, "<< getService: found the service with uuid: %s", uuid.toString().c_str());
+ return myPair.second;
+ }
+ } // End of each of the services.
+ ESP_LOGD(LOG_TAG, "<< getService: not found");
+ throw new BLEUuidNotFoundException;
+} // 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");
+
+ clearServices(); // Clear any services that may exist.
+
+ esp_err_t errRc = esp_ble_gattc_search_service(
+ getGattcIf(),
+ getConnId(),
+ nullptr // 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;
+ }
+ // If sucessfull, remember that we now have services.
+ m_haveServices = (m_semaphoreSearchCmplEvt.wait("getServices") == 0);
+ ESP_LOGD(LOG_TAG, "<< getServices");
+ return &m_servicesMap;
+} // getServices
+
+
+/**
+ * @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.
+ * @throws BLEUuidNotFound
+ */
+std::string BLEClient::getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID) {
+ ESP_LOGD(LOG_TAG, ">> getValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str());
+ std::string ret = getService(serviceUUID)->getCharacteristic(characteristicUUID)->readValue();
+ ESP_LOGD(LOG_TAG, "<<getValue");
+ return ret;
+} // getValue
+
+
+/**
+ * @brief Handle a received GAP event.
+ *
+ * @param [in] event
+ * @param [in] param
+ */
+void BLEClient::handleGAPEvent(
+ esp_gap_ble_cb_event_t event,
+ esp_ble_gap_cb_param_t* param) {
+ ESP_LOGD(LOG_TAG, "BLEClient ... handling GAP event!");
+ switch(event) {
+ //
+ // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT
+ //
+ // read_rssi_cmpl
+ // - esp_bt_status_t status
+ // - int8_t rssi
+ // - esp_bd_addr_t remote_addr
+ //
+ case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: {
+ m_semaphoreRssiCmplEvt.give((uint32_t)param->read_rssi_cmpl.rssi);
+ break;
+ } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT
+
+ default:
+ break;
+ }
+} // handleGAPEvent
+
+
+/**
+ * @brief Are we connected to a partner?
+ * @return True if we are connected and false if we are not connected.
+ */
+bool BLEClient::isConnected() {
+ return m_isConnected;
+} // isConnected
+
+
+
+
+/**
+ * @brief Set the callbacks that will be invoked.
+ */
+void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks) {
+ m_pClientCallbacks = pClientCallbacks;
+} // setClientCallbacks
+
+
+/**
+ * @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.
+ * @throws BLEUuidNotFound
+ */
+void BLEClient::setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) {
+ ESP_LOGD(LOG_TAG, ">> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str());
+ getService(serviceUUID)->getCharacteristic(characteristicUUID)->writeValue(value);
+ ESP_LOGD(LOG_TAG, "<< setValue");
+} // setValue
+
+
+/**
+ * @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