From c98f4a09509a2f35b4027ffe99180e47d5aa5057 Mon Sep 17 00:00:00 2001 From: Pablo Sun Date: Mon, 26 Feb 2018 10:14:17 +0800 Subject: [PATCH] Revise LBLE library (#93) ## Bug Fixes * #89: use `equal_range` when searching for elements in STL multimap, instead of using `find`. `find` is not guaranteed to return the first element in the equal range. * #90: Add a new set of interfaces to `LBLEClient` that allows user to identify a characteristic by using service index and characteristic index, instead of using UUID. Note that it is possible for a device to have multiple characteristics with the same UUID. * #90: Add a new example `EnumerateCharacteristic.ino` that uses the new indices-based interface of `LBLEClient` to list all the services and characteristics in the connected BLE device. * #90: Use `bt_gattc_read_charc` instead of `bt_gattc_read_using_charc_uuid` to read characteristics. * #91: when calling `bt_gattc_discover_charc`, we need to wait for multiple `BT_GATTC_DISCOVER_CHARC` event. We added new helper function `waitAndProcessEventMultiple` that supports such event waiting behavior. * Refactored `LBLEValueBuffer` to support re-interpreting its raw buffer content into String, float, int, and char types. --- .../mt7697/libraries/LBLE/src/LBLE.cpp | 88 +++- .../arduino/mt7697/libraries/LBLE/src/LBLE.h | 94 ++++ .../mt7697/libraries/LBLE/src/LBLECentral.cpp | 402 ++++++++++++------ .../mt7697/libraries/LBLE/src/LBLECentral.h | 296 +++++++++++-- 4 files changed, 705 insertions(+), 175 deletions(-) diff --git a/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLE.cpp b/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLE.cpp index 9da6aad..6945647 100644 --- a/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLE.cpp +++ b/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLE.cpp @@ -480,26 +480,31 @@ void LBLEEventDispatcher::removeObserver(bt_msg_type_t msg, LBLEEventObserver* p // registered to the same key. // So we loop over the matching elements // and check the handler pointer. - auto i = m_table.find(msg); - while(i != m_table.end()) + auto keyRange = m_table.equal_range(msg); + auto i = keyRange.first; + while(i != keyRange.second) { - if(i->second == pObserver) + // advance iterator first before we remove some element. + auto toRemove = i++; + + // if match, remove the element + if(toRemove->second == pObserver) { - m_table.erase(i); + m_table.erase(toRemove); return; } - ++i; } } void LBLEEventDispatcher::dispatch(bt_msg_type_t msg, bt_status_t status, void *buff) { // pr_debug("dispatch: msg:0x%x", msg); - auto i = m_table.find(msg); + auto keyRange = m_table.equal_range(msg); + auto i = keyRange.first; std::vector removeList; // execute observer's callback and pop the element found - while(i != m_table.end()) + while(i != keyRange.second) { if(i->first != msg) { @@ -565,4 +570,73 @@ LBLEValueBuffer::LBLEValueBuffer(const String& strValue) { resize(strValue.length() + 1); strValue.getBytes(&(*this)[0], size()); +} + +// interprets buffer content as null-terminated character string. +// Empty string is returned when there are errors. +String LBLEValueBuffer::asString() const +{ + if(!this->size()) + { + return String(); + } + + // Make sure we have terminating NULL before passing to String(). + if(this->back() == '\0') + { + return String((const char*)&this->front()); + } + else + { + // since String() does not allow us to initialize + // with buffer + length, we use a temporary buffer object instead. + std::vector tempBuffer; + tempBuffer.resize(this->size() + 1, 0); + if(tempBuffer.size() >= this->size()) + { + memcpy(&tempBuffer.front(), &this->front(), this->size()); + return String((const char*)&tempBuffer.front()); + } + } + + return String(); +} + +int LBLEValueBuffer::asInt() const +{ + int ret = 0; + if(this->size() < sizeof(ret)) + { + return 0; + } + + ret = *((const int*)&this->at(0)); + + return ret; +} + +char LBLEValueBuffer::asChar() const +{ + char ret = '\0'; + if(this->size() < sizeof(ret)) + { + return 0; + } + + ret = *((const char*)&this->front()); + + return ret; +} + +float LBLEValueBuffer::asFloat() const +{ + float ret = 0.f; + if(this->size() < sizeof(ret)) + { + return 0; + } + + ret = *((const float*)&this->front()); + + return ret; } \ No newline at end of file diff --git a/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLE.h b/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLE.h index 89b459f..e7d0757 100644 --- a/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLE.h +++ b/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLE.h @@ -186,6 +186,7 @@ class LBLEAddress : public Printable /// values when reading or writing GATT attributes. class LBLEValueBuffer : public std::vector { +// constructors and initialization public: /// Default constructor creates an empty buffer. LBLEValueBuffer(); @@ -204,6 +205,25 @@ class LBLEValueBuffer : public std::vector LBLEValueBuffer(const String& strValue); templatevoid shallowInit(T value); + +// type conversion helpers +public: + /// interprets buffer content as int32_t + /// 0 is returned when there are errors. + int asInt() const; + + /// interprets buffer content as null-terminated character string. + /// Empty string is returned when there are errors. + String asString() const; + + /// interprets buffer content as char + /// 0 is returned when there are errors. + char asChar() const; + + /// interprets buffer content as float + /// 0.f is returned when there are errors. + float asFloat() const; + }; templatevoid LBLEValueBuffer::shallowInit(T value) @@ -386,4 +406,78 @@ template bool waitAndProcessEvent(const A& action, bt_ms return h.done(); } + + +// Modified version of EventBlockerMultiple. +// This EventBlockerMultiple relies on the return value of `handler` +// to determine `done()`. +template class EventBlockerMultiple : public LBLEEventObserver +{ +public: + EventBlockerMultiple(bt_msg_type_t e, const F& handler): + m_handler(handler), + m_event(e), + m_eventArrived(false), + m_allEventProcessed(false) + { + + } + + bool done() const + { + return m_eventArrived && m_allEventProcessed; + } + + virtual bool isOnce() + { + // the client must remove EventBlocker + // manually from the listeners. + return false; + }; + + virtual void onEvent(bt_msg_type_t msg, bt_status_t status, void* buff) + { + if(m_event == msg) + { + m_eventArrived = true; + + // Note that some action, may result in multiple events. + // For example, bt_gattc_discover_charc may invoke + // multiple BT_GATTC_DISCOVER_CHARC events. + // + // We need to rely on the message handlers + // to tell us if all events have been processed or not. + // + // all event processed = `m_handler` returns `true`. + // event not processed or waiting for more event = `m_handler` returns `false`. + m_allEventProcessed = m_handler(msg, status, buff); + } + } + +private: + const F& m_handler; + const bt_msg_type_t m_event; + bool m_eventArrived; + bool m_allEventProcessed; +}; + +// Modified version of waitAndProcessEvent. +// it uses EventBlockerMultiple instead of EventBlocker. +template bool waitAndProcessEventMultiple(const A& action, bt_msg_type_t msg, const F& handler) +{ + EventBlockerMultiple h(msg, handler); + LBLE.registerForEvent(msg, &h); + + action(); + + uint32_t start = millis(); + while(!h.done() && (millis() - start) < 10 * 1000) + { + delay(50); + } + + LBLE.unregisterForEvent(msg, &h); + + return h.done(); +} #endif diff --git a/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLECentral.cpp b/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLECentral.cpp index 6528f77..d9b4822 100644 --- a/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLECentral.cpp +++ b/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLECentral.cpp @@ -588,7 +588,7 @@ void LBLEClient::disconnect() // sucess or not, we proceed to resource clean up m_connection = BT_HANDLE_INVALID; m_services.clear(); - m_characteristics.clear(); + m_uuid2Handle.clear(); } return; } @@ -613,7 +613,7 @@ void LBLEClient::onEvent(bt_msg_type_t msg, bt_status_t status, void *buff) // clear up scanned services and characteristics m_connection = BT_HANDLE_INVALID; m_services.clear(); - m_characteristics.clear(); + m_uuid2Handle.clear(); } } } @@ -631,6 +631,35 @@ bool LBLEClient::hasService(const LBLEUuid& uuid) return (m_services.end() != found); } +/// Get the number of Characteristics available on the connected remote device +/// +/// \param index ranges from 0 to (getServiceCount() - 1). +/// \returns number of characteristics in the service. +int LBLEClient::getCharacteristicCount(int serviceIndex) { + if (serviceIndex < 0 || serviceIndex >= getServiceCount()) { + return 0; + } + + return m_services[serviceIndex].m_characteristics.size(); +} + +LBLECharacteristicInfo LBLEClient::getCharacteristic(int serviceIndex, int characteristicIndex) +{ + if (serviceIndex < 0 || serviceIndex >= getServiceCount()) { + return LBLECharacteristicInfo(); + } + + if (characteristicIndex < 0 || characteristicIndex >= getCharacteristicCount(serviceIndex)) { + return LBLECharacteristicInfo(); + } + + return m_services[serviceIndex].m_characteristics[characteristicIndex]; +} + +LBLEUuid LBLEClient::getCharacteristicUuid(int serviceIndex, int characteristicIndex) { + return getCharacteristic(serviceIndex, characteristicIndex).m_uuid; +} + LBLEUuid LBLEClient::getServiceUuid(int index) { if(index < 0 || index >= getServiceCount()) @@ -689,7 +718,7 @@ int LBLEClient::discoverServices() break; } - done = waitAndProcessEvent( + done = waitAndProcessEventMultiple( // start service discovery [this, &searchRequest]() { @@ -698,7 +727,7 @@ int LBLEClient::discoverServices() // wait for the event... BT_GATTC_DISCOVER_PRIMARY_SERVICE, // and process it in BT task context with this lambda - [this, &searchRequest, &shouldContinue](bt_msg_type_t, bt_status_t status, void* buf) + [this, &searchRequest, &shouldContinue](bt_msg_type_t, bt_status_t status, void* buf) -> bool { // Parse the response to service UUIDs. It can be 16-bit or 128-bit UUID. // We can tell the difference from the response length `att_rsp->length`. @@ -738,6 +767,10 @@ int LBLEClient::discoverServices() // update starting handle for next round of search searchRequest.starting_handle = endIndex; + + // if we should continue, return FALSE so that + // the handler will keep waiting for the next event. + return !shouldContinue; } ); } while(shouldContinue && done); @@ -757,17 +790,18 @@ int LBLEClient::discoverCharacteristics() return 0; } + int totalCharacteristicCount = 0; // for each service, find the value handle of each characteristic. for(size_t i = 0; i < m_services.size(); ++i) { pr_debug("Enumerate charc between handle (%d-%d)", m_services[i].startHandle, m_services[i].endHandle) - discoverCharacteristicsOfService(m_services[i]); + totalCharacteristicCount += discoverCharacteristicsOfService(i); } - return m_characteristics.size(); + return totalCharacteristicCount; } -int LBLEClient::discoverCharacteristicsOfService(const LBLEServiceInfo& s) +int LBLEClient::discoverCharacteristicsOfService(int serviceIndex) { // discover all characteristics and store mapping between (UUID -> value handle) // note that this search could require multiple request-response, @@ -778,6 +812,9 @@ int LBLEClient::discoverCharacteristicsOfService(const LBLEServiceInfo& s) return 0; } + LBLEServiceInfo& service = m_services[serviceIndex]; + const LBLEServiceInfo& s = service; + // Prepare the search request - we will reuse this structure // across multiple requests, by updating the **starting_handle** bt_gattc_discover_charc_req_t searchRequest = { @@ -803,7 +840,7 @@ int LBLEClient::discoverCharacteristicsOfService(const LBLEServiceInfo& s) break; } - done = waitAndProcessEvent( + done = waitAndProcessEventMultiple( // start characteristic discovery [this, &searchRequest]() { @@ -812,7 +849,7 @@ int LBLEClient::discoverCharacteristicsOfService(const LBLEServiceInfo& s) // wait for the event... BT_GATTC_DISCOVER_CHARC, // and process it in BT task context with this lambda - [this, &searchRequest, &shouldContinue](bt_msg_type_t, bt_status_t status, void* buf) + [this, &searchRequest, &shouldContinue, &service](bt_msg_type_t, bt_status_t status, void* buf) -> bool { pr_debug("discoverCharacteristicsOfService=0x%x", (unsigned int)status); @@ -832,16 +869,22 @@ int LBLEClient::discoverCharacteristicsOfService(const LBLEServiceInfo& s) memcpy(&properties, attrDataList + i * rsp->att_rsp->length + 2, 1); memcpy(&valueHandle, attrDataList + i * rsp->att_rsp->length + 3, 2); - + LBLEUuid uuidInsert; if (rsp->att_rsp->length < 20) { // 16-bit UUID case memcpy(&uuid16, attrDataList + i * rsp->att_rsp->length + 5, 2); - m_characteristics.insert(std::make_pair(LBLEUuid(uuid16), valueHandle)); + uuidInsert = LBLEUuid(uuid16); } else { // 128-bit UUID case memcpy(&uuid128.uuid, attrDataList + i * entryLength + 5, 16); - m_characteristics.insert(std::make_pair(LBLEUuid(uuid128), valueHandle)); + uuidInsert = LBLEUuid(uuid128); } + + // insert into LBLEClient's (UUID -> Handle) mapping table if it is not already in the map + m_uuid2Handle.insert(std::make_pair(uuidInsert, valueHandle)); + + // insert into LBLEServiceInfo's characteristic list + service.m_characteristics.push_back(LBLECharacteristicInfo(valueHandle, uuidInsert)); } // check if we need to perform more search @@ -849,6 +892,10 @@ int LBLEClient::discoverCharacteristicsOfService(const LBLEServiceInfo& s) // update starting handle for next round of search searchRequest.starting_handle = valueHandle; + + // if we should continue, return FALSE so that + // the handler will keep waiting for the next event. + return !shouldContinue; } ); } while(shouldContinue && done); @@ -856,80 +903,13 @@ int LBLEClient::discoverCharacteristicsOfService(const LBLEServiceInfo& s) return done; } -int LBLEClient::readCharacteristicInt(const LBLEUuid& uuid) -{ - LBLEValueBuffer b = readCharacterstic(uuid); - int ret = 0; - if(b.size() < sizeof(ret)) - { - return 0; - } - - ret = *((int*)&b[0]); - - return ret; -} - -String LBLEClient::readCharacteristicString(const LBLEUuid& uuid) -{ - LBLEValueBuffer b = readCharacterstic(uuid); - - if(b.size()) - { - // safe guard against missing terminating NULL - if(b[b.size() - 1] != 0) - { - b.resize(b.size() + 1); - b[b.size() - 1] = 0; - } - - return String((const char*)&b[0]); - } - - return String(); -} - -char LBLEClient::readCharacteristicChar(const LBLEUuid& uuid) +//////////////////////////////////////////////////////////////////////////////// +// LBLECharacteristicInfo-based read and write +//////////////////////////////////////////////////////////////////////////////// +LBLEValueBuffer LBLECharacteristicInfo::read(const bt_handle_t connection) const { - LBLEValueBuffer b = readCharacterstic(uuid); - char ret = 0; - if(b.size() < sizeof(ret)) - { - return 0; - } - - ret = *((char*)&b[0]); - - return ret; -} - -float LBLEClient::readCharacteristicFloat(const LBLEUuid& uuid) -{ - LBLEValueBuffer b = readCharacterstic(uuid); - float ret = 0; - if(b.size() < sizeof(ret)) - { - return 0; - } - - ret = *((float*)&b[0]); - - return ret; -} - -LBLEValueBuffer LBLEClient::readCharacterstic(const LBLEUuid& serviceUuid) -{ - if(!connected()) - { - return LBLEValueBuffer(); - } - - // connected, call bt_gattc_read_using_charc_uuid and wait for response event - bt_gattc_read_using_charc_uuid_req_t req; - req.opcode = BT_ATT_OPCODE_READ_BY_TYPE_REQUEST; - req.starting_handle = 0x0001; - req.ending_handle = 0xffff; - req.type = serviceUuid.uuid_data; + // connected, call bt_gattc_read_charc and wait for response event + BT_GATTC_NEW_READ_CHARC_REQ(req, m_handle) // results are stored here by the event callback below. LBLEValueBuffer resultBuffer; @@ -938,17 +918,17 @@ LBLEValueBuffer LBLEClient::readCharacterstic(const LBLEUuid& serviceUuid) // start read request [&]() { - bt_gattc_read_using_charc_uuid(m_connection, &req); + bt_gattc_read_charc(connection, &req); }, // wait for event... - BT_GATTC_READ_USING_CHARC_UUID, + BT_GATTC_READ_CHARC, // and parse event result in bt task context - [this, &resultBuffer](bt_msg_type_t msg, bt_status_t, void* buf) + [this, &resultBuffer, connection](bt_msg_type_t msg, bt_status_t, void* buf) { - const bt_gattc_read_by_type_rsp_t *pReadResponse = (bt_gattc_read_by_type_rsp_t*)buf; - if(BT_GATTC_READ_USING_CHARC_UUID != msg || pReadResponse->connection_handle != m_connection) + const bt_gattc_read_rsp_t *pReadResponse = (bt_gattc_read_rsp_t*)buf; + if(BT_GATTC_READ_CHARC != msg || pReadResponse->connection_handle != connection) { - // not for our request + pr_debug("not for our request, ignored"); return; } @@ -956,23 +936,22 @@ LBLEValueBuffer LBLEClient::readCharacterstic(const LBLEUuid& serviceUuid) // check error case if(BT_ATT_OPCODE_ERROR_RESPONSE == pReadResponse->att_rsp->opcode) { - const bt_gattc_error_rsp_t* pErr = (bt_gattc_error_rsp_t*)buf; pr_debug("error reading attribute"); - pr_debug("error_opcode=%d", pErr->att_rsp->error_opcode); - pr_debug("error_code=%d", pErr->att_rsp->error_code); + pr_debug("error_opcode=%d", reinterpret_cast(buf)->att_rsp->error_opcode); + pr_debug("error_code=%d", reinterpret_cast(buf)->att_rsp->error_code); break; } - if(BT_ATT_OPCODE_READ_BY_TYPE_RESPONSE != pReadResponse->att_rsp->opcode) + if(BT_ATT_OPCODE_READ_RESPONSE != pReadResponse->att_rsp->opcode) { pr_debug("Op code don't match! Opcode=%d", pReadResponse->att_rsp->opcode); break; } // Copy the data buffer content - const uint8_t listLength = pReadResponse->att_rsp->length - 2; - resultBuffer.resize(listLength, 0); - memcpy(&resultBuffer[0], ((uint8_t*)pReadResponse->att_rsp->attribute_data_list) + 2, listLength); + const uint8_t attrLength = pReadResponse->length - sizeof(pReadResponse->att_rsp->opcode); + resultBuffer.resize(attrLength, 0); + memcpy(&resultBuffer[0], ((uint8_t*)pReadResponse->att_rsp->attribute_value), attrLength); } while(false); } ); @@ -980,25 +959,27 @@ LBLEValueBuffer LBLEClient::readCharacterstic(const LBLEUuid& serviceUuid) return resultBuffer; } -int LBLEClient::writeCharacteristic(const LBLEUuid& uuid, const LBLEValueBuffer& value) -{ - if(!connected()) - { - return 0; - } +int LBLECharacteristicInfo::readInt(bt_handle_t connection) { + return read(connection).asInt(); +} + +String LBLECharacteristicInfo::readString(bt_handle_t connection) { + return read(connection).asString(); +} + +char LBLECharacteristicInfo::readChar(bt_handle_t connection) { + return read(connection).asChar(); +} +float LBLECharacteristicInfo::readFloat(bt_handle_t connection) { + return read(connection).asFloat(); +} + +int LBLECharacteristicInfo::write(const bt_handle_t connection, const LBLEValueBuffer& value) +{ // make sure this is smaller than MTU - header size const uint32_t MAXIMUM_WRITE_SIZE = 20; - // we need to check if the UUID have a known value handle. - // Note that discoverCharacteristic() builds the mapping table. - auto found = m_characteristics.find(uuid); - if(found == m_characteristics.end()) - { - pr_debug("cannot find characteristics"); - return 0; - } - // value buffer too big if(value.size() > MAXIMUM_WRITE_SIZE) { @@ -1014,21 +995,21 @@ int LBLEClient::writeCharacteristic(const LBLEUuid& uuid, const LBLEValueBuffer& req.att_req = (bt_att_write_req_t*)&reqBuf[0]; req.att_req->opcode = BT_ATT_OPCODE_WRITE_REQUEST; // the "attribute_handle" field requires a "value handle" actually. - pr_debug("write_charc %s with value handle=%d", found->first.toString().c_str(), found->second); - req.att_req->attribute_handle = found->second; + pr_debug("write_charc %s with value handle=%d", m_uuid.toString().c_str(), m_handle); + req.att_req->attribute_handle = m_handle; memcpy(req.att_req->attribute_value, &value[0], value.size()); bool done = waitAndProcessEvent( // start read request [&]() { - bt_status_t writeResult = bt_gattc_write_charc(m_connection, &req); - pr_debug("write result = 0x%x", writeResult); + const bt_status_t r = bt_gattc_write_charc(connection, &req); + pr_debug("write result = 0x%x", r); }, // wait for event... BT_GATTC_WRITE_CHARC, // and parse event result in bt task context - [this](bt_msg_type_t msg, bt_status_t, void* buf) + [this, connection](bt_msg_type_t msg, bt_status_t, void* buf) { if(BT_GATTC_WRITE_CHARC != msg) { // not for our request @@ -1037,18 +1018,17 @@ int LBLEClient::writeCharacteristic(const LBLEUuid& uuid, const LBLEValueBuffer& } const bt_gattc_write_rsp_t *pWriteResp = (bt_gattc_write_rsp_t*)buf; - if(pWriteResp->connection_handle != this->m_connection) { - pr_debug("got wrong handle=%d (%d)", pWriteResp->connection_handle, this->m_connection); + if(pWriteResp->connection_handle != connection) { + pr_debug("got wrong handle=%d (%d)", pWriteResp->connection_handle, connection); } do { // check error case if(BT_ATT_OPCODE_ERROR_RESPONSE == pWriteResp->att_rsp->opcode) { - const bt_gattc_error_rsp_t* pErr = (bt_gattc_error_rsp_t*)buf; pr_debug("error reading attribute"); - pr_debug("error_opcode=%d", pErr->att_rsp->error_opcode); - pr_debug("error_code=%d", pErr->att_rsp->error_code); + pr_debug("error_opcode=%d", reinterpret_cast(buf)->att_rsp->error_opcode); + pr_debug("error_code=%d", reinterpret_cast(buf)->att_rsp->error_code); break; } @@ -1064,32 +1044,174 @@ int LBLEClient::writeCharacteristic(const LBLEUuid& uuid, const LBLEValueBuffer& return done; } +int LBLECharacteristicInfo::writeInt(bt_handle_t connection, int value) { + const LBLEValueBuffer b(value); + return write(connection, b); +} + +int LBLECharacteristicInfo::writeString(bt_handle_t connection, const String& value) { + const LBLEValueBuffer b(value); + return write(connection, b); +} + +int LBLECharacteristicInfo::writeChar(bt_handle_t connection, char value) { + const LBLEValueBuffer b(value); + return write(connection, b); +} + +int LBLECharacteristicInfo::writeFloat(bt_handle_t connection, float value) { + const LBLEValueBuffer b(value); + return write(connection, b); +} + +//////////////////////////////////////////////////////////////////////////////// +// Index-based read +//////////////////////////////////////////////////////////////////////////////// + +LBLEValueBuffer LBLEClient::readCharacteristic(int serviceIndex, int characteristicIndex) +{ + return getCharacteristic(serviceIndex, characteristicIndex).read(m_connection); +} + +int LBLEClient::readCharacteristicInt(int serviceIndex, int characteristicIndex) +{ + return readCharacteristic(serviceIndex, characteristicIndex).asInt(); +} + +String LBLEClient::readCharacteristicString(int serviceIndex, int characteristicIndex) +{ + return LBLEClient::readCharacteristic(serviceIndex, characteristicIndex).asString(); +} + +char LBLEClient::readCharacteristicChar(int serviceIndex, int characteristicIndex) +{ + return LBLEClient::readCharacteristic(serviceIndex, characteristicIndex).asChar(); +} + +float LBLEClient::readCharacteristicFloat(int serviceIndex, int characteristicIndex) +{ + return readCharacteristic(serviceIndex, characteristicIndex).asFloat(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Index-based write +//////////////////////////////////////////////////////////////////////////////// + +int LBLEClient::writeCharacteristic(int serviceIndex, int characteristicIndex, const LBLEValueBuffer& value) +{ + return getCharacteristic(serviceIndex, characteristicIndex).write(m_connection, value); +} + +int LBLEClient::writeCharacteristicInt(int serviceIndex, int characteristicIndex, int value) +{ + return writeCharacteristic(serviceIndex, characteristicIndex, LBLEValueBuffer(value)); +} + +int LBLEClient::writeCharacteristicString(int serviceIndex, int characteristicIndex, const String& value) +{ + return writeCharacteristic(serviceIndex, characteristicIndex, LBLEValueBuffer(value)); +} + +int LBLEClient::writeCharacteristicChar(int serviceIndex, int characteristicIndex, char value) +{ + return writeCharacteristic(serviceIndex, characteristicIndex, LBLEValueBuffer(value)); +} + +int LBLEClient::writeCharacteristicFloat(int serviceIndex, int characteristicIndex, float value) +{ + return writeCharacteristic(serviceIndex, characteristicIndex, LBLEValueBuffer(value)); +} + +//////////////////////////////////////////////////////////////////////////////// +// UUID-based read +//////////////////////////////////////////////////////////////////////////////// + +LBLEValueBuffer LBLEClient::readCharacteristic(const LBLEUuid& uuid) +{ + if(!connected()) + { + return LBLEValueBuffer(); + } + + // connected, call bt_gattc_read_using_charc_uuid and wait for response event + // we need to check if the UUID have a known value handle. + // Note that discoverCharacteristic() builds the mapping table. + auto found = m_uuid2Handle.find(uuid); + if(found == m_uuid2Handle.end()) + { + pr_debug("cannot find characteristics"); + return 0; + } + + LBLECharacteristicInfo c(found->second, found->first); + return c.read(m_connection); +} + +int LBLEClient::readCharacteristicInt(const LBLEUuid& uuid) +{ + return readCharacterstic(uuid).asInt(); +} + +String LBLEClient::readCharacteristicString(const LBLEUuid& uuid) +{ + return readCharacterstic(uuid).asString(); +} + +char LBLEClient::readCharacteristicChar(const LBLEUuid& uuid) +{ + return readCharacterstic(uuid).asChar(); +} + +float LBLEClient::readCharacteristicFloat(const LBLEUuid& uuid) +{ + return readCharacterstic(uuid).asFloat(); +} + +LBLEValueBuffer LBLEClient::readCharacterstic(const LBLEUuid& serviceUuid) +{ + return readCharacteristic(serviceUuid); +} + +//////////////////////////////////////////////////////////////////////////////// +// UUID-based write +//////////////////////////////////////////////////////////////////////////////// + +int LBLEClient::writeCharacteristic(const LBLEUuid& uuid, const LBLEValueBuffer& value) +{ + if(!connected()) + { + return 0; + } + + // we need to check if the UUID have a known value handle. + // Note that discoverCharacteristic() builds the mapping table. + auto found = m_uuid2Handle.find(uuid); + if(found == m_uuid2Handle.end()) + { + pr_debug("cannot find characteristics"); + return 0; + } + + LBLECharacteristicInfo c(found->second, found->first); + return c.write(m_connection, value); +} + int LBLEClient::writeCharacteristicInt(const LBLEUuid& uuid, int value) { - LBLEValueBuffer b(value); + const LBLEValueBuffer b(value); return writeCharacteristic(uuid, b); } + int LBLEClient::writeCharacteristicString(const LBLEUuid& uuid, const String& value) { - LBLEValueBuffer b(value); + const LBLEValueBuffer b(value); return writeCharacteristic(uuid, b); } + int LBLEClient::writeCharacteristicChar(const LBLEUuid& uuid, char value) { - LBLEValueBuffer b(value); + const LBLEValueBuffer b(value); return writeCharacteristic(uuid, b); } + int LBLEClient::writeCharacteristicFloat(const LBLEUuid& uuid, float value) { - LBLEValueBuffer b(value); + const LBLEValueBuffer b(value); return writeCharacteristic(uuid, b); } - -void _characteristic_event_handler(bt_msg_type_t msg, bt_status_t, void *buff) -{ - if(BT_GATTC_CHARC_VALUE_NOTIFICATION == msg) - { - pr_debug("==============NOTIFICATION COMING==============="); - bt_gatt_handle_value_notification_t* pNoti = (bt_gatt_handle_value_notification_t*)buff; - pr_debug("gatt length=(%d), opcode=(%d), handle=(%d)", - pNoti->length, - pNoti->att_rsp->opcode, - pNoti->att_rsp->handle); - } -} diff --git a/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLECentral.h b/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLECentral.h index 6168062..135ee9e 100644 --- a/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLECentral.h +++ b/middleware/third_party/arduino/hardware/arduino/mt7697/libraries/LBLE/src/LBLECentral.h @@ -252,11 +252,110 @@ class LBLEAdvertisements const bt_gap_le_advertising_report_ind_t& payload); }; +/// This (internal) helper class encapsulates the read/write +/// operations to a remote peripheral's characteristic, based on the ATT handle. +/// This class does not store the connection handle, +/// instead it relies on the user to pass connection when performing +/// read / write operations. +/// It is the user's responsibility to pass the correct connection handle. +class LBLECharacteristicInfo { +public: + + /// Constructing an invalid object. + /// All the subsequent read/write operation fails silently. + LBLECharacteristicInfo(): + m_handle(BT_HANDLE_INVALID), + m_uuid() + { + + } + + /// Constructing from ATT handle and UUID of the characteristic + /// + /// \param attHandle The ATT handle of the characteristic in the remote peripheral's GATT database + /// \param uuid The UUID of the characteristic + LBLECharacteristicInfo(uint16_t attHandle, const LBLEUuid& uuid): + m_handle(attHandle), + m_uuid(uuid) + { + } + + /// Read raw value buffer fomr a characteristic on remote device. + /// + /// \param connection The connection handle to the remote device + /// \returns LBLEValueBuffer object that represents the raw value buffer. + LBLEValueBuffer read(bt_handle_t connection) const; + + /// Read integer value from a characteristic on remote device. + /// + /// \param connection The connection handle to the remote device + /// \returns integer value of the characteristic. 0 is returned if fails to read the characteristic. + int readInt(bt_handle_t connection); + + /// Read string value from a characteristic on remote device. + /// + /// \param connection The connection handle to the remote device + /// \returns string value of the characteristic. Empty string is returned if fails to read the characteristic. + String readString(bt_handle_t connection); + + /// Read a single char byte value from a characteristic on remote device. + /// + /// \param connection The connection handle to the remote device + /// \returns char value of the characteristic. 0 is returned if fails to read the characteristic. + char readChar(bt_handle_t connection); + + /// Read float value from a characteristic on remote device. + /// + /// \param connection The connection handle to the remote device + /// \returns float value of the characteristic. 0 is returned if fails to read the characteristic. + float readFloat(bt_handle_t connection); + + /// Write the value of a characteristic on the remote device. + /// + /// \param connection the connection handle to the remote peripheral + /// \param value The raw buffer value to write. + int write(bt_handle_t connection, const LBLEValueBuffer& value); + + /// Hepler API that write a characteristic on the remote device as integer. + /// + /// \param connection the connection handle to the remote peripheral + /// \param value The int value to write. + int writeInt(bt_handle_t connection, int value); + + /// Hepler API that write a characteristic on the remote device as a string. + /// + /// \param connection the connection handle to the remote peripheral + /// \param value The int value to write. + int writeString(bt_handle_t connection, const String& value); + + /// Hepler API that write a characteristic on the remote device as char. + /// + /// \param connection the connection handle to the remote peripheral + /// \param value The char value to write. + int writeChar(bt_handle_t connection, char value); + + /// Hepler API that write a characteristic on the remote device as float. + /// + /// \param connection the connection handle to the remote peripheral + /// \param value The float value to write. + int writeFloat(bt_handle_t connection, float value); + + uint16_t m_handle; // The ATT handle in the remote peripheral's GATT database. + LBLEUuid m_uuid; // Supplemental information about the class UUID of this characteristic. +}; + +/// This (internal) helper class encapsulates service information. +/// about a service in the remote peripheral device. +/// It also keeps track of the characteristics of the device. +/// +/// Note the order and index of the m_characteristics are used to +/// identify a characteristic. struct LBLEServiceInfo { - LBLEUuid uuid; - uint16_t startHandle; - uint16_t endHandle; + LBLEUuid uuid; // Service UUID + uint16_t startHandle; // Start of the ATT handle range in the remote GATT database + uint16_t endHandle; // End of the ATT handle range in the remote GATT database + std::vector m_characteristics; // Characteristics that belong to this service }; /// This class allows users to create connections to remote peripheral devices. @@ -314,104 +413,245 @@ class LBLEClient : public LBLEEventObserver /// Disconnect from the remote device void disconnect(); + /// Check if a given service UUID is available on the connected remote device. + /// + /// \param serviceUuid The UUID of the service to check + /// \returns true if the remote device supports the service. false otherwise. + bool hasService(const LBLEUuid& serviceUuid); + /// Get the number of services available on the connected remote device int getServiceCount(); - /// Get service uuid by index. Index should range from 0 to (getServiceCount() - 1). + /// Get service uuid by index. serviceIndex should range from 0 to (getServiceCount() - 1). /// - /// \param index ranges from 0 to (getServiceCount() - 1). + /// \param serviceIndex Ranges from 0 to (getServiceCount() - 1). /// \returns UUID of the service - LBLEUuid getServiceUuid(int index); + LBLEUuid getServiceUuid(int serviceIndex); /// Helper function that returns name of the service if it is know. /// - /// \param index ranges from 0 to (getServiceCount() - 1). + /// \param serviceIndex Ranges from 0 to (getServiceCount() - 1). /// \returns Service name. - String getServiceName(int index); + String getServiceName(int serviceIndex); - /// Check if a given service is available on the connected remote device. + /// Get the number of Characteristics available on the connected remote device /// - /// \param uuid The UUID of the service to check - /// \returns true if the remote device supports the service. false otherwise. - bool hasService(const LBLEUuid& uuid); + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \returns number of characteristics in the given service. + int getCharacteristicCount(int serviceIndex); - /// Read raw value buffer fomr a characteristic on remote device. + /// Get Characteristic uuid by service and characteristic indices. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \returns UUID of the Characteristic + LBLEUuid getCharacteristicUuid(int serviceIndex, int characteristicIndex); + + ///////////////////////////////////////////////////////////////////// + // UUID-based read and write + ///////////////////////////////////////////////////////////////////// + + /// DEPRECATED. There are typo in method name. This method is kept to prevent breaking old examples. /// /// \param uuid The UUID of the characteristic to read from. /// \returns LBLEValueBuffer object that represents the raw value buffer. LBLEValueBuffer readCharacterstic(const LBLEUuid& uuid); - /// Read integer value from a characteristic on remote device. + /// Read raw value buffer fomr a characteristic on remote device, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. + /// + /// \param uuid The UUID of the characteristic to read from. + /// \returns LBLEValueBuffer object that represents the raw value buffer. + LBLEValueBuffer readCharacteristic(const LBLEUuid& uuid); + + /// Read integer value from a characteristic on remote device, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to read from. /// \returns integer value of the characteristic. 0 is returned if fails to read the characteristic. int readCharacteristicInt(const LBLEUuid& uuid); - /// Read string value from a characteristic on remote device. + /// Read string value from a characteristic on remote device, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to read from. /// \returns string value of the characteristic. Empty string is returned if fails to read the characteristic. String readCharacteristicString(const LBLEUuid& uuid); - /// Read a single char byte value from a characteristic on remote device. + /// Read a single char byte value from a characteristic on remote device, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to read from. /// \returns char value of the characteristic. 0 is returned if fails to read the characteristic. char readCharacteristicChar(const LBLEUuid& uuid); - /// Read float value from a characteristic on remote device. + /// Read float value from a characteristic on remote device, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to read from. /// \returns float value of the characteristic. 0 is returned if fails to read the characteristic. float readCharacteristicFloat(const LBLEUuid& uuid); - /// Write the value of a characteristic on the remote device. + /// Write the value of a characteristic on the remote device, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to write to. /// \param value The raw buffer value to write. int writeCharacteristic(const LBLEUuid& uuid, const LBLEValueBuffer& value); - /// Hepler API that write a characteristic on the remote device as integer. + /// Hepler API that write a characteristic on the remote device as integer, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to write to. /// \param value The int value to write. int writeCharacteristicInt(const LBLEUuid& uuid, int value); - /// Hepler API that write a characteristic on the remote device as a string. + /// Hepler API that write a characteristic on the remote device as a string, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to write to. /// \param value The int value to write. int writeCharacteristicString(const LBLEUuid& uuid, const String& value); - /// Hepler API that write a characteristic on the remote device as char. + /// Hepler API that write a characteristic on the remote device as char, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to write to. /// \param value The char value to write. int writeCharacteristicChar(const LBLEUuid& uuid, char value); - /// Hepler API that write a characteristic on the remote device as float. + /// Hepler API that write a characteristic on the remote device as float, given its UUID. + /// Note that it is possible to have multiple characteristics with the same UUID. + /// In this case, only the 1st characteristic with respect to the + /// service and characteristic indices is used. /// /// \param uuid The UUID of the characteristic to write to. /// \param value The float value to write. int writeCharacteristicFloat(const LBLEUuid& uuid, float value); + ///////////////////////////////////////////////////////////////////// + // index-based read and write + ///////////////////////////////////////////////////////////////////// + + /// Read the raw buffer value of a characteristic, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \returns A LBLEValueBuffer object representing the raw value of the characteristic + LBLEValueBuffer readCharacteristic(int serviceIndex, int characteristicIndex); + + /// Read integer value of a characteristic, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \returns A LBLEValueBuffer object representing the raw value of the characteristic + int readCharacteristicInt(int serviceIndex, int characteristicIndex); + + /// Read string value from a characteristic on remote device, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \returns string value of the characteristic. Empty string is returned if fails to read the characteristic. + String readCharacteristicString(int serviceIndex, int characteristicIndex); + + /// Read a single char byte value from a characteristic on remote device, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \returns char value of the characteristic. 0 is returned if fails to read the characteristic. + char readCharacteristicChar(int serviceIndex, int characteristicIndex); + + /// Read float value from a characteristic on remote device, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \returns float value of the characteristic. 0 is returned if fails to read the characteristic. + float readCharacteristicFloat(int serviceIndex, int characteristicIndex); + + /// Write the value of a characteristic on the remote device, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \param value The raw buffer value to write. + int writeCharacteristic(int serviceIndex, int characteristicIndex, const LBLEValueBuffer& value); + + /// Hepler API that write a characteristic on the remote device as integer, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \param value The int value to write. + int writeCharacteristicInt(int serviceIndex, int characteristicIndex, int value); + + /// Hepler API that write a characteristic on the remote device as a string, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \param value The int value to write. + int writeCharacteristicString(int serviceIndex, int characteristicIndex, const String& value); + + /// Hepler API that write a characteristic on the remote device as char, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \param value The char value to write. + int writeCharacteristicChar(int serviceIndex, int characteristicIndex, char value); + + /// Hepler API that write a characteristic on the remote device as float, given its service index + /// and characteristic index. + /// + /// \param serviceIndex ranges from 0 to (getServiceCount() - 1). + /// \param characteristicIndex ranges from 0 to (getCharacteristicCount(serviceIndex) - 1). + /// \param value The float value to write. + int writeCharacteristicFloat(int serviceIndex, int characteristicIndex, float value); + public: + // internal event handler for BT module to callback to virtual void onEvent(bt_msg_type_t msg, bt_status_t status, void *buff); protected: - bt_handle_t m_connection; - std::vector m_services; - std::map m_characteristics; + bt_handle_t m_connection; // connection handle + std::vector m_services; // services discovered by discoverServices() + std::map m_uuid2Handle; // a look-up table for UUID-based read and write operations - // enumerate all service info from remote device + // Enumerate all service info from remote device int discoverServices(); // Read all characteristic on remote device. // This could take a while. int discoverCharacteristics(); - // Enumerate all characteristics, given a service ID. - int discoverCharacteristicsOfService(const LBLEServiceInfo& s); + // Enumerate all characteristics, given a service index. + int discoverCharacteristicsOfService(int serviceIndex); + + // Retrieve a characteristic by its service and characteristic indices. + LBLECharacteristicInfo getCharacteristic(int serviceIndex, int characteristicIndex); }; extern LBLECentralClass LBLECentral;