From 9527c4148629b46ccb730aacf2dc7ee573058a81 Mon Sep 17 00:00:00 2001 From: h2zero Date: Sat, 30 Jan 2021 18:06:29 -0700 Subject: [PATCH] Refactor advertisedDevice (#181) * Stores complete advertisement payload and only performs parsing on demand. The advertised data is no longer parsed automatically as it is discovered, instead it will be parsed to find only the data requested by the user application when it makes a call to do so. This saves processing time and approximately 2.4k in flash size, with the new methods listed below included. Add more advertised device field methods: * Adds haveAdvInterval/getAdvInterval - checks if the interval is advertised / gets the advertisement interval value. * Adds haveConnParams/getMinInterval/getMaxInterval - checks if the parameters are advertised / get min value / get max value. * Adds haveURI/getURI - checks if a URI is advertised / gets the URI data. * Adds haveTargetAddress/getTargetAddressCount/getTargetAddress(index) - checks if a target address is present / gets a count of the addresses targeted / gets the address of the target at index. --- src/NimBLEAdvertisedDevice.cpp | 688 ++++++++++++++++++++------------- src/NimBLEAdvertisedDevice.h | 51 +-- src/NimBLEScan.cpp | 50 ++- src/NimBLEUUID.cpp | 31 +- 4 files changed, 507 insertions(+), 313 deletions(-) diff --git a/src/NimBLEAdvertisedDevice.cpp b/src/NimBLEAdvertisedDevice.cpp index 992b1f3..c697fe7 100644 --- a/src/NimBLEAdvertisedDevice.cpp +++ b/src/NimBLEAdvertisedDevice.cpp @@ -28,25 +28,14 @@ static const char* LOG_TAG = "NimBLEAdvertisedDevice"; /** * @brief Constructor */ -NimBLEAdvertisedDevice::NimBLEAdvertisedDevice() { +NimBLEAdvertisedDevice::NimBLEAdvertisedDevice() : + m_payload(62,0) +{ m_advType = 0; - m_appearance = 0; - m_manufacturerData = ""; - m_name = ""; m_rssi = -9999; - m_txPower = 0; - m_payloadLength = 0; - m_payload = nullptr; - - m_haveAppearance = false; - m_haveManufacturerData = false; - m_haveName = false; - m_haveRSSI = false; - m_haveServiceData = false; - m_haveServiceUUID = false; - m_haveTXPower = false; - m_callbackSent = false; - + m_callbackSent = false; + m_timestamp = 0; + m_advLength = 0; } // NimBLEAdvertisedDevice @@ -82,25 +71,126 @@ uint8_t NimBLEAdvertisedDevice::getAdvType() { * @return The appearance of the advertised device. */ uint16_t NimBLEAdvertisedDevice::getAppearance() { - return m_appearance; + uint8_t data_loc = 0; + + if(findAdvField(BLE_HS_ADV_TYPE_APPEARANCE, 0, &data_loc) > 0) { + ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length == BLE_HS_ADV_APPEARANCE_LEN + 1) { + return *field->value | *(field->value + 1) << 8; + } + } + + return 0; } // getAppearance +/** + * @brief Get the advertisement interval. + * @return The advertisement interval in 0.625ms units. + */ +uint16_t NimBLEAdvertisedDevice::getAdvInterval() { + uint8_t data_loc = 0; + + if(findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL, 0, &data_loc) > 0) { + ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length == BLE_HS_ADV_ADV_ITVL_LEN + 1) { + return *field->value | *(field->value + 1) << 8; + } + } + + return 0; +} // getAdvInterval + + +/** + * @brief Get the preferred min connection interval. + * @return The preferred min connection interval in 1.25ms units. + */ +uint16_t NimBLEAdvertisedDevice::getMinInterval() { + uint8_t data_loc = 0; + + if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) { + ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length == BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1) { + return *field->value | *(field->value + 1) << 8; + } + } + + return 0; +} // getMinInterval + + +/** + * @brief Get the preferred max connection interval. + * @return The preferred max connection interval in 1.25ms units. + */ +uint16_t NimBLEAdvertisedDevice::getMaxInterval() { + uint8_t data_loc = 0; + + if(findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) { + ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length == BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1) { + return *(field->value + 2) | *(field->value + 3) << 8; + } + } + + return 0; +} // getMaxInterval + + /** * @brief Get the manufacturer data. * @return The manufacturer data of the advertised device. */ std::string NimBLEAdvertisedDevice::getManufacturerData() { - return m_manufacturerData; + uint8_t data_loc = 0; + + if(findAdvField(BLE_HS_ADV_TYPE_MFG_DATA, 0, &data_loc) > 0) { + ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length > 1) { + return std::string((char*)field->value, field->length - 1); + } + } + + return ""; } // getManufacturerData +/** + * @brief Get the URI from the advertisement. + * @return The URI data. + */ +std::string NimBLEAdvertisedDevice::getURI() { + uint8_t data_loc = 0; + + if(findAdvField(BLE_HS_ADV_TYPE_URI, 0, &data_loc) > 0) { + ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length > 1) { + return std::string((char*)field->value, field->length - 1); + } + } + + return ""; +} // getURI + + /** * @brief Get the advertised name. * @return The name of the advertised device. */ std::string NimBLEAdvertisedDevice::getName() { - return m_name; + uint8_t data_loc = 0; + + if(findAdvField(BLE_HS_ADV_TYPE_COMP_NAME, 0, &data_loc) > 0 || + findAdvField(BLE_HS_ADV_TYPE_INCOMP_NAME, 0, &data_loc) > 0) + { + ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length > 1) { + return std::string((char*)field->value, field->length - 1); + } + } + + return ""; } // getName @@ -122,17 +212,70 @@ NimBLEScan* NimBLEAdvertisedDevice::getScan() { } // getScan +/** + * @brief Get the number of target addresses. + * @return The number of addresses. + */ +size_t NimBLEAdvertisedDevice::getTargetAddressCount() { + uint8_t count = 0; + + count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR); + count += findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR); + + return count; +} + + +/** + * @brief Get the target address at the index. + * @param [in] index The index of the target address. + * @return The target address. + */ +NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) { + ble_hs_adv_field *field = nullptr; + uint8_t count = 0; + uint8_t data_loc = 0xFF; + + index++; + count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR, index, &data_loc); + + if (count < index) { + index -= count; + count = findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR, index, &data_loc); + } + + if(count > 0 && data_loc != 0xFF) { + field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length < index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) { + index -= count - field->length / BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN; + } + if(field->length > index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) { + return NimBLEAddress(field->value + (index - 1) * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN); + } + } + + return NimBLEAddress(""); +} + + /** * @brief Get the service data. - * @param [in] index The vector index of the service data requested. + * @param [in] index The index of the service data requested. * @return The advertised service data or empty string if no data. */ std::string NimBLEAdvertisedDevice::getServiceData(uint8_t index) { - if(index > m_serviceDataVec.size()) { - NIMBLE_LOGW(LOG_TAG, "getServiceData: index out of range"); - return ""; + ble_hs_adv_field *field = nullptr; + uint8_t bytes; + uint8_t data_loc = findServiceData(index, &bytes); + + if(data_loc != 0xFF) { + field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length > bytes) { + return std::string((char*)(field->value + bytes), field->length - bytes - 1); + } } - return m_serviceDataVec[index].second; + + return ""; } //getServiceData @@ -141,51 +284,148 @@ std::string NimBLEAdvertisedDevice::getServiceData(uint8_t index) { * @param [in] uuid The uuid of the service data requested. * @return The advertised service data or empty string if no data. */ -std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID &uuid) const { - for(auto &it : m_serviceDataVec) { - if(it.first == uuid) { - return it.second; +std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID &uuid) { + ble_hs_adv_field *field = nullptr; + uint8_t bytes; + uint8_t index = 0; + uint8_t data_loc = findServiceData(index, &bytes); + uint8_t uuidBytes = uuid.bitSize() / 8; + uint8_t plSize = m_payload.size() - 2; + + while(data_loc < plSize) { + field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(bytes == uuidBytes && NimBLEUUID(field->value, bytes, false) == uuid) { + return std::string((char*)(field->value + bytes), field->length - bytes - 1); } + + index++; + data_loc = findServiceData(index, &bytes); } - NIMBLE_LOGW(LOG_TAG, "getServiceData: uuid not found"); + + NIMBLE_LOGI(LOG_TAG, "No service data found"); return ""; } //getServiceData /** - * @brief Get the advertised service UUID. - * @param [in] index The vector index of the service data UUID requested. - * @return The advertised service UUID or an empty UUID if not found. + * @brief Get the UUID of the serice data at the index. + * @param [in] index The index of the service data UUID requested. + * @return The advertised service data UUID or an empty UUID if not found. */ NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID(uint8_t index) { - if(!haveServiceData() || index > m_serviceDataVec.size()) { - NIMBLE_LOGW(LOG_TAG, "getServiceDataUUID: index out of range"); - return NimBLEUUID(""); + ble_hs_adv_field *field = nullptr; + uint8_t bytes; + uint8_t data_loc = findServiceData(index, &bytes); + + if(data_loc != 0xFF) { + field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length >= bytes) { + return NimBLEUUID(field->value, bytes, false); + } } - return m_serviceDataVec[index].first; + + return NimBLEUUID(""); } // getServiceDataUUID +/** + * @brief Find the service data at the index. + * @param [in] index The index of the service data to find. + * @param [in] bytes A pointer to storage for the number of the bytes in the UUID. + * @return The index in the vector where the data is located, 0xFF if not found. + */ +uint8_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t *bytes) { + uint8_t data_loc = 0; + uint8_t found = 0; + + *bytes = 0; + index++; + found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16, index, &data_loc); + if(found == index) { + *bytes = 2; + return data_loc; + } + + index -= found; + found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID32, index, &data_loc); + if(found == index) { + *bytes = 4; + return data_loc; + } + + index -= found; + found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID128, index, &data_loc); + if(found == index) { + *bytes = 16; + return data_loc; + } + + return 0xFF; +} + + /** * @brief Get the count of advertised service data UUIDS * @return The number of service data UUIDS in the vector. */ size_t NimBLEAdvertisedDevice::getServiceDataCount() { - return m_serviceDataVec.size(); + uint8_t count = 0; + + count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16); + count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID32); + count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID128); + + return count; } // getServiceDataCount /** * @brief Get the Service UUID. - * @param [in] index The vector index of the service UUID requested. + * @param [in] index The index of the service UUID requested. * @return The Service UUID of the advertised service, or an empty UUID if not found. */ NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) { - if(!haveServiceUUID() || index > m_serviceUUIDs.size()) { - NIMBLE_LOGW(LOG_TAG, "getServiceUUID: index out of range"); - return NimBLEUUID(""); + uint8_t count = 0; + uint8_t data_loc = 0; + uint8_t uuidBytes = 0; + uint8_t type = BLE_HS_ADV_TYPE_INCOMP_UUIDS16; + ble_hs_adv_field *field = nullptr; + + index++; + + do { + count = findAdvField(type, index, &data_loc); + if(count >= index) { + if(type < BLE_HS_ADV_TYPE_INCOMP_UUIDS32) { + uuidBytes = 2; + } else if(type < BLE_HS_ADV_TYPE_INCOMP_UUIDS128) { + uuidBytes = 4; + } else { + uuidBytes = 16; + } + break; + + } else { + type++; + index -= count; + } + + } while(type <= BLE_HS_ADV_TYPE_COMP_UUIDS128); + + if(uuidBytes > 0) { + field = (ble_hs_adv_field *)&m_payload[data_loc]; + // In the case of more than one field of service uuid's we need to adjust + // the index to account for the uuids of the previous fields. + if(field->length < index * uuidBytes) { + index -= count - field->length / uuidBytes; + } + + if(field->length > uuidBytes * index) { + return NimBLEUUID(field->value + uuidBytes * (index - 1), uuidBytes, false); + } } - return m_serviceUUIDs[index]; + + return NimBLEUUID(""); } // getServiceUUID @@ -194,18 +434,32 @@ NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) { * @return The count of services in the advertising packet. */ size_t NimBLEAdvertisedDevice::getServiceUUIDCount() { - return m_serviceUUIDs.size(); + uint8_t count = 0; + + count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS16); + count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS16); + count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS32); + count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS32); + count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS128); + count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS128); + + return count; } // getServiceUUIDCount /** * @brief Check advertised services for existance of the required UUID + * @param [in] uuid The service uuid to look for in the advertisement. * @return Return true if service is advertised */ -bool NimBLEAdvertisedDevice::isAdvertisingService(const NimBLEUUID &uuid) const { - for (int i = 0; i < m_serviceUUIDs.size(); i++) { - if (m_serviceUUIDs[i].equals(uuid)) return true; +bool NimBLEAdvertisedDevice::isAdvertisingService(const NimBLEUUID &uuid) { + size_t count = getServiceUUIDCount(); + for(size_t i = 0; i < count; i++) { + if(uuid == getServiceUUID(i)) { + return true; + } } + return false; } // isAdvertisingService @@ -215,16 +469,43 @@ bool NimBLEAdvertisedDevice::isAdvertisingService(const NimBLEUUID &uuid) const * @return The TX Power of the advertised device. */ int8_t NimBLEAdvertisedDevice::getTXPower() { - return m_txPower; + uint8_t data_loc = 0; + + if(findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL, 0, &data_loc) > 0) { + ble_hs_adv_field *field = (ble_hs_adv_field *)&m_payload[data_loc]; + if(field->length == BLE_HS_ADV_TX_PWR_LVL_LEN + 1) { + return *(int8_t*)field->value; + } + } + + return -99; } // getTXPower +/** + * @brief Does this advertisement have preferred connection parameters? + * @return True if connection parameters are present. + */ +bool NimBLEAdvertisedDevice::haveConnParams() { + return findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE) > 0; +} // haveConnParams + + +/** + * @brief Does this advertisement have have the advertising interval? + * @return True if the advertisement interval is present. + */ +bool NimBLEAdvertisedDevice::haveAdvInterval() { + return findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL) > 0; +} // haveAdvInterval + + /** * @brief Does this advertisement have an appearance value? * @return True if there is an appearance value present. */ bool NimBLEAdvertisedDevice::haveAppearance() { - return m_haveAppearance; + return findAdvField(BLE_HS_ADV_TYPE_APPEARANCE) > 0; } // haveAppearance @@ -233,16 +514,36 @@ bool NimBLEAdvertisedDevice::haveAppearance() { * @return True if there is manufacturer data present. */ bool NimBLEAdvertisedDevice::haveManufacturerData() { - return m_haveManufacturerData; + return findAdvField(BLE_HS_ADV_TYPE_MFG_DATA) > 0; } // haveManufacturerData +/** + * @brief Does this advertisement have a URI? + * @return True if there is a URI present. + */ +bool NimBLEAdvertisedDevice::haveURI() { + return findAdvField(BLE_HS_ADV_TYPE_URI) > 0; +} // haveURI + + +/** + * @brief Does the advertisement contain a target address? + * @return True if an address is present. + */ +bool NimBLEAdvertisedDevice::haveTargetAddress() { + return findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR) > 0 || + findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR) > 0; +} + + /** * @brief Does this advertisement have a name value? * @return True if there is a name value present. */ bool NimBLEAdvertisedDevice::haveName() { - return m_haveName; + return findAdvField(BLE_HS_ADV_TYPE_COMP_NAME) > 0 || + findAdvField(BLE_HS_ADV_TYPE_INCOMP_NAME) > 0; } // haveName @@ -251,7 +552,7 @@ bool NimBLEAdvertisedDevice::haveName() { * @return True if there is a signal strength value present. */ bool NimBLEAdvertisedDevice::haveRSSI() { - return m_haveRSSI; + return m_rssi != -9999; } // haveRSSI @@ -260,7 +561,7 @@ bool NimBLEAdvertisedDevice::haveRSSI() { * @return True if there is a service data value present. */ bool NimBLEAdvertisedDevice::haveServiceData() { - return m_haveServiceData; + return getServiceDataCount() > 0; } // haveServiceData @@ -269,7 +570,7 @@ bool NimBLEAdvertisedDevice::haveServiceData() { * @return True if there is a service UUID value present. */ bool NimBLEAdvertisedDevice::haveServiceUUID() { - return m_haveServiceUUID; + return getServiceUUIDCount() > 0; } // haveServiceUUID @@ -278,143 +579,71 @@ bool NimBLEAdvertisedDevice::haveServiceUUID() { * @return True if there is a transmission power value present. */ bool NimBLEAdvertisedDevice::haveTXPower() { - return m_haveTXPower; + return findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL) > 0; } // haveTXPower -/** - * @brief Parse the advertising pay load. - * - * The pay load is a buffer of bytes that is either 31 bytes long or terminated by - * a 0 length value. Each entry in the buffer has the format: - * [length][type][data...] - * - * The length does not include itself but does include everything after it until the next record. A record - * with a length value of 0 indicates a terminator. - * - * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile - */ - void NimBLEAdvertisedDevice::parseAdvertisement(uint8_t* payload, uint8_t length) { - struct ble_hs_adv_fields fields; - int rc = ble_hs_adv_parse_fields(&fields, payload, length); - if (rc != 0) { - NIMBLE_LOGE(LOG_TAG, "Gap Event Parse ERROR."); - return; +uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, uint8_t *data_loc) { + ble_hs_adv_field *field = nullptr; + uint8_t data = 0; + uint8_t length = m_payload.size(); + uint8_t count = 0; + + if(length < 2) { + return count; } - m_payload = payload; - m_payloadLength = length; + while (length > 1) { + field = (ble_hs_adv_field*)&m_payload[data]; -#if CONFIG_LOG_DEFAULT_LEVEL > 3 || (ARDUINO_ARCH_ESP32 && CORE_DEBUG_LEVEL >= 4) - char* pHex = NimBLEUtils::buildHexData(nullptr, m_payload, m_payloadLength); - NIMBLE_LOGD(LOG_TAG,"payload: %s", pHex); - free(pHex); -#endif - - if (fields.uuids16 != NULL) { - for (int i = 0; i < fields.num_uuids16; i++) { - setServiceUUID(NimBLEUUID(fields.uuids16[i].value)); + if (field->length >= length) { + return count; } - } - if (fields.uuids32 != NULL) { - for (int i = 0; i < fields.num_uuids32; i++) { - setServiceUUID(NimBLEUUID(fields.uuids32[i].value)); - } - } + if (field->type == type) { + switch(type) { + case BLE_HS_ADV_TYPE_INCOMP_UUIDS16: + case BLE_HS_ADV_TYPE_COMP_UUIDS16: + count += field->length / 2; + break; - if (fields.uuids128 != NULL) { - for (int i = 0; i < fields.num_uuids128; i++) { - setServiceUUID(NimBLEUUID(&fields.uuids128[i])); - } - } + case BLE_HS_ADV_TYPE_INCOMP_UUIDS32: + case BLE_HS_ADV_TYPE_COMP_UUIDS32: + count += field->length / 4; + break; - if (fields.name != NULL) { - setName(std::string(reinterpret_cast(fields.name), fields.name_len)); - } + case BLE_HS_ADV_TYPE_INCOMP_UUIDS128: + case BLE_HS_ADV_TYPE_COMP_UUIDS128: + count += field->length / 16; + break; - if (fields.tx_pwr_lvl_is_present) { - setTXPower(fields.tx_pwr_lvl); - } + case BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR: + case BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR: + count += field->length / 6; + break; - if (fields.svc_data_uuid16 != NULL || - fields.svc_data_uuid32 != NULL || - fields.svc_data_uuid128 != NULL) - { - ble_hs_adv_field *field; - uint8_t *data = payload; - while(length > 1) { - field = (ble_hs_adv_field*)data; - - if(field->length > length) { - break; + default: + count++; + break; } - if(field->type == BLE_HS_ADV_TYPE_SVC_DATA_UUID16) { - if(field->length > 2) { - uint16_t uuid; - memcpy(&uuid, field->value, 2); - setServiceData(NimBLEUUID(uuid), std::string(reinterpret_cast(field->value + 2), field->length - 3)); + if(data_loc != nullptr) { + if(index == 0 || count >= index) { + break; } } - - if(field->type == BLE_HS_ADV_TYPE_SVC_DATA_UUID32) { - if(field->length > 4) { - uint32_t uuid; - memcpy(&uuid, field->value, 4); - setServiceData(NimBLEUUID(uuid), std::string(reinterpret_cast(field->value + 4), field->length - 5)); - } - } - - if(field->type == BLE_HS_ADV_TYPE_SVC_DATA_UUID128) { - if(field->length > 16) { - NimBLEUUID uuid(field->value, (size_t)16, false); - setServiceData(uuid, std::string(reinterpret_cast(field->value + 16), field->length - 17)); - } - } - - length -= 1 + field->length; - data += 1 + field->length; } + + length -= 1 + field->length; + data += 1 + field->length; } - if (fields.appearance_is_present) { - setAppearance(fields.appearance); + if(data_loc != nullptr && field != nullptr) { + *data_loc = data; } - if (fields.mfg_data != NULL) { - setManufacturerData(std::string(reinterpret_cast(fields.mfg_data), fields.mfg_data_len)); - } - -/* TODO: create storage and fucntions for these parameters - if (fields.public_tgt_addr != NULL) { - NIMBLE_LOGD(LOG_TAG, " public_tgt_addr="); - u8p = fields.public_tgt_addr; - for (i = 0; i < fields.num_public_tgt_addrs; i++) { - NIMBLE_LOGD(LOG_TAG, "public_tgt_addr=%s ", addr_str(u8p)); - u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN; - } - NIMBLE_LOGD(LOG_TAG, "\n"); - } - - if (fields.slave_itvl_range != NULL) { - NIMBLE_LOGD(LOG_TAG, " slave_itvl_range="); - print_bytes(fields.slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN); - NIMBLE_LOGD(LOG_TAG, "\n"); - } - - if (fields.adv_itvl_is_present) { - NIMBLE_LOGD(LOG_TAG, " adv_itvl=0x%04x\n", fields.adv_itvl); - } - - if (fields.uri != NULL) { - NIMBLE_LOGD(LOG_TAG, " uri="); - print_bytes(fields.uri, fields.uri_len); - NIMBLE_LOGD(LOG_TAG, "\n"); - } -*/ - - } //parseAdvertisement + return count; +} /** @@ -428,106 +657,22 @@ void NimBLEAdvertisedDevice::setAddress(NimBLEAddress address) { /** * @brief Set the adFlag for this device. - * @param [in] The discovered adFlag. + * @param [in] advType The advertisement flag data from the advertisement. */ void NimBLEAdvertisedDevice::setAdvType(uint8_t advType) { m_advType = advType; } // setAdvType -/** - * @brief Set the appearance for this device. - * @param [in] The discovered appearance. - */ -void NimBLEAdvertisedDevice::setAppearance(uint16_t appearance) { - m_appearance = appearance; - m_haveAppearance = true; -} // setAppearance - - -/** - * @brief Set the manufacturer data for this device. - * @param [in] The discovered manufacturer data. - */ -void NimBLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) { - m_manufacturerData = manufacturerData; - m_haveManufacturerData = true; -} // setManufacturerData - - -/** - * @brief Set the name for this device. - * @param [in] name The discovered name. - */ -void NimBLEAdvertisedDevice::setName(std::string name) { - m_name = name; - m_haveName = true; -} // setName - - /** * @brief Set the RSSI for this device. - * @param [in] rssi The discovered RSSI. + * @param [in] rssi The RSSI of the discovered device. */ void NimBLEAdvertisedDevice::setRSSI(int rssi) { - m_rssi = rssi; - m_haveRSSI = true; + m_rssi = rssi; } // setRSSI -/** - * @brief Set the Service UUID for this device. - * @param [in] serviceUUID The discovered serviceUUID - */ - -void NimBLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) { - return setServiceUUID(NimBLEUUID(serviceUUID)); -} // setServiceUUID - - -/** - * @brief Set the Service UUID for this device. - * @param [in] serviceUUID The discovered serviceUUID - */ -void NimBLEAdvertisedDevice::setServiceUUID(NimBLEUUID serviceUUID) { - // Don't add duplicates - for (int i = 0; i < m_serviceUUIDs.size(); i++) { - if (m_serviceUUIDs[i] == serviceUUID) { - return; - } - } - m_serviceUUIDs.push_back(serviceUUID); - m_haveServiceUUID = true; -} // setServiceUUID - - -/** - * @brief Set the ServiceData value. - * @param [in] uuid The UUID that the service data belongs to. - * @param [in] data The service data. - */ -void NimBLEAdvertisedDevice::setServiceData(NimBLEUUID uuid, std::string data) { - m_haveServiceData = true; - for(auto &it : m_serviceDataVec) { - if(it.first == uuid) { - it.second = data; - return; - } - } - m_serviceDataVec.push_back({uuid, data}); -} //setServiceData - - -/** - * @brief Set the power level for this device. - * @param [in] txPower The discovered power level. - */ -void NimBLEAdvertisedDevice::setTXPower(int8_t txPower) { - m_txPower = txPower; - m_haveTXPower = true; -} // setTXPower - - /** * @brief Create a string representation of this device. * @return A string representation of this device. @@ -579,10 +724,35 @@ std::string NimBLEAdvertisedDevice::toString() { * @return The advertisement payload. */ uint8_t* NimBLEAdvertisedDevice::getPayload() { - return m_payload; + return &m_payload[0]; } // getPayload +/** + * @brief Stores the payload of the advertised device in a vector. + * @param [in] payload The advertisement payload. + * @param [in] length The length of the payload in bytes. + * @param [in] append Indicates if the the data should be appended (scan response). + */ +void NimBLEAdvertisedDevice::setPayload(uint8_t *payload, uint8_t length, bool append) { + if(!append) { + m_advLength = length; + m_payload.assign(payload, payload + length); + } else { + m_payload.insert(m_payload.end(), payload, payload + length); + } +} + + +/** + * @brief Get the length of the advertisement data in the payload. + * @return The number of bytes in the payload that is from the advertisment. + */ +uint8_t NimBLEAdvertisedDevice::getAdvLength() { + return m_advLength; +} + + /** * @brief Get the advertised device address type. * @return The device address type: @@ -610,7 +780,7 @@ time_t NimBLEAdvertisedDevice::getTimestamp() { * @return The size of the payload in bytes. */ size_t NimBLEAdvertisedDevice::getPayloadLength() { - return m_payloadLength; + return m_payload.size(); } // getPayloadLength diff --git a/src/NimBLEAdvertisedDevice.h b/src/NimBLEAdvertisedDevice.h index ebdb85c..ef354bc 100644 --- a/src/NimBLEAdvertisedDevice.h +++ b/src/NimBLEAdvertisedDevice.h @@ -44,7 +44,11 @@ public: NimBLEAddress getAddress(); uint8_t getAdvType(); uint16_t getAppearance(); + uint16_t getAdvInterval(); + uint16_t getMinInterval(); + uint16_t getMaxInterval(); std::string getManufacturerData(); + std::string getURI(); /** * @brief A template to convert the service data to . @@ -67,7 +71,7 @@ public: NimBLEScan* getScan(); size_t getServiceDataCount(); std::string getServiceData(uint8_t index = 0); - std::string getServiceData(const NimBLEUUID &uuid) const; + std::string getServiceData(const NimBLEUUID &uuid); /** * @brief A template to convert the service data to . @@ -106,12 +110,15 @@ public: NimBLEUUID getServiceDataUUID(uint8_t index = 0); NimBLEUUID getServiceUUID(uint8_t index = 0); size_t getServiceUUIDCount(); + NimBLEAddress getTargetAddress(uint8_t index = 0); + size_t getTargetAddressCount(); int8_t getTXPower(); uint8_t* getPayload(); + uint8_t getAdvLength(); size_t getPayloadLength(); uint8_t getAddressType(); time_t getTimestamp(); - bool isAdvertisingService(const NimBLEUUID &uuid) const; + bool isAdvertisingService(const NimBLEUUID &uuid); bool haveAppearance(); bool haveManufacturerData(); bool haveName(); @@ -119,46 +126,30 @@ public: bool haveServiceData(); bool haveServiceUUID(); bool haveTXPower(); + bool haveConnParams(); + bool haveAdvInterval(); + bool haveTargetAddress(); + bool haveURI(); std::string toString(); private: friend class NimBLEScan; - void parseAdvertisement(uint8_t* payload, uint8_t length); - void setAddress(NimBLEAddress address); - void setAdvType(uint8_t advType); - void setAppearance(uint16_t appearance); - void setManufacturerData(std::string manufacturerData); - void setName(std::string name); - void setRSSI(int rssi); - void setServiceData(NimBLEUUID serviceUUID, std::string data); - void setServiceUUID(const char* serviceUUID); - void setServiceUUID(NimBLEUUID serviceUUID); - void setTXPower(int8_t txPower); - - bool m_haveAppearance; - bool m_haveManufacturerData; - bool m_haveName; - bool m_haveRSSI; - bool m_haveServiceData; - bool m_haveServiceUUID; - bool m_haveTXPower; - + void setAddress(NimBLEAddress address); + void setAdvType(uint8_t advType); + void setPayload(uint8_t *payload, uint8_t length, bool append); + void setRSSI(int rssi); + uint8_t findAdvField(uint8_t type, uint8_t index = 0, uint8_t *data_loc = nullptr); + uint8_t findServiceData(uint8_t index, uint8_t* bytes); NimBLEAddress m_address = NimBLEAddress(""); uint8_t m_advType; - uint16_t m_appearance; - std::string m_manufacturerData; - std::string m_name; int m_rssi; - int8_t m_txPower; - uint8_t* m_payload; - size_t m_payloadLength; time_t m_timestamp; bool m_callbackSent; + uint8_t m_advLength; - std::vector m_serviceUUIDs; - std::vector>m_serviceDataVec; + std::vector m_payload; }; /** diff --git a/src/NimBLEScan.cpp b/src/NimBLEScan.cpp index 122ff33..cac6ed6 100644 --- a/src/NimBLEScan.cpp +++ b/src/NimBLEScan.cpp @@ -64,7 +64,7 @@ NimBLEScan::~NimBLEScan() { case BLE_GAP_EVENT_DISC: { if(pScan->m_ignoreResults) { - NIMBLE_LOGE(LOG_TAG, "Scan op in progress - ignoring results"); + NIMBLE_LOGI(LOG_TAG, "Scan op in progress - ignoring results"); return 0; } @@ -93,25 +93,35 @@ NimBLEScan::~NimBLEScan() { advertisedDevice->setAddress(advertisedAddress); advertisedDevice->setAdvType(event->disc.event_type); pScan->m_scanResults.m_advertisedDevicesVector.push_back(advertisedDevice); - NIMBLE_LOGI(LOG_TAG, "NEW DEVICE FOUND: %s", advertisedAddress.toString().c_str()); - } - else{ - NIMBLE_LOGI(LOG_TAG, "UPDATING PREVIOUSLY FOUND DEVICE: %s", advertisedAddress.toString().c_str()); - } - advertisedDevice->setRSSI(event->disc.rssi); - if(event->disc.length_data > 0) { - advertisedDevice->parseAdvertisement(event->disc.data, event->disc.length_data); + NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str()); + } else { + NIMBLE_LOGI(LOG_TAG, "Updated advertiser: %s", advertisedAddress.toString().c_str()); } + advertisedDevice->m_timestamp = time(nullptr); + advertisedDevice->setRSSI(event->disc.rssi); + + if(event->disc.length_data > 0) { + if(event->disc.event_type != BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) { + advertisedDevice->setPayload(event->disc.data, event->disc.length_data, false); + } else { + advertisedDevice->setPayload(event->disc.data, event->disc.length_data, true); + } + } + if (pScan->m_pAdvertisedDeviceCallbacks) { if(pScan->m_wantDuplicates || !advertisedDevice->m_callbackSent) { - // If not active scanning report the result to the listener. - if(pScan->m_scan_params.passive || event->disc.event_type == BLE_HCI_ADV_TYPE_ADV_NONCONN_IND) { + // If not active scanning or scan response is not available + // report the result to the callback now. + if(pScan->m_scan_params.passive || + (event->disc.event_type != BLE_HCI_ADV_TYPE_ADV_IND && + event->disc.event_type != BLE_HCI_ADV_TYPE_ADV_SCAN_IND)) + { advertisedDevice->m_callbackSent = true; pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); - // Otherwise wait for the scan response so we can report all of the data at once. + // Otherwise, wait for the scan response so we can report the complete data. } else if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) { advertisedDevice->m_callbackSent = true; pScan->m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); @@ -123,7 +133,17 @@ NimBLEScan::~NimBLEScan() { } case BLE_GAP_EVENT_DISC_COMPLETE: { NIMBLE_LOGD(LOG_TAG, "discovery complete; reason=%d", - event->disc_complete.reason); + event->disc_complete.reason); + + // If a device advertised with scan reponse available and it was not received + // the callback would not have been invoked, so do it here. + if(pScan->m_pAdvertisedDeviceCallbacks) { + for(auto &it : pScan->m_scanResults.m_advertisedDevicesVector) { + if(!it->m_callbackSent) { + pScan->m_pAdvertisedDeviceCallbacks->onResult(it); + } + } + } if (pScan->m_scanCompleteCB != nullptr) { pScan->m_scanCompleteCB(pScan->m_scanResults); @@ -342,7 +362,7 @@ bool NimBLEScan::stop() { int rc = ble_gap_disc_cancel(); if (rc != 0 && rc != BLE_HS_EALREADY) { - NIMBLE_LOGE(LOG_TAG, "Failed to cancel scan; rc=%d\n", rc); + NIMBLE_LOGE(LOG_TAG, "Failed to cancel scan; rc=%d", rc); return false; } @@ -365,7 +385,7 @@ bool NimBLEScan::stop() { * @details After disconnecting, it may be required in the case we were connected to a device without a public address. */ void NimBLEScan::erase(const NimBLEAddress &address) { - NIMBLE_LOGI(LOG_TAG, "erase device: %s", address.toString().c_str()); + NIMBLE_LOGD(LOG_TAG, "erase device: %s", address.toString().c_str()); for(auto it = m_scanResults.m_advertisedDevicesVector.begin(); it != m_scanResults.m_advertisedDevicesVector.end(); ++it) { if((*it)->getAddress() == address) { diff --git a/src/NimBLEUUID.cpp b/src/NimBLEUUID.cpp index 21ff270..ea58ae7 100644 --- a/src/NimBLEUUID.cpp +++ b/src/NimBLEUUID.cpp @@ -65,29 +65,42 @@ static const char* LOG_TAG = "NimBLEUUID"; *this = NimBLEUUID(first, second, third, (uint64_t(fourth) << 48) + fifth); } else { - NIMBLE_LOGE(LOG_TAG,"ERROR: UUID value not 2, 4, 16 or 36 bytes"); m_valueSet = false; } } // NimBLEUUID(std::string) /** - * @brief Create a UUID from 16 bytes of memory. + * @brief Create a UUID from 2, 4, 16 bytes of memory. * @param [in] pData The pointer to the start of the UUID. * @param [in] size The size of the data. * @param [in] msbFirst Is the MSB first in pData memory? */ NimBLEUUID::NimBLEUUID(const uint8_t* pData, size_t size, bool msbFirst) { - if (size != 16) { - NIMBLE_LOGE(LOG_TAG,"ERROR: UUID length not 16 bytes"); - return; - } - m_uuid.u.type = BLE_UUID_TYPE_128; + uint8_t *uuidValue = nullptr; + switch(size) { + case 2: + uuidValue = (uint8_t*)&m_uuid.u16.value; + m_uuid.u.type = BLE_UUID_TYPE_16; + break; + case 4: + uuidValue = (uint8_t*)&m_uuid.u32.value; + m_uuid.u.type = BLE_UUID_TYPE_32; + break; + case 16: + uuidValue = m_uuid.u128.value; + m_uuid.u.type = BLE_UUID_TYPE_128; + break; + default: + m_valueSet = false; + NIMBLE_LOGE(LOG_TAG, "Invalid UUID size"); + return; + } if (msbFirst) { - std::reverse_copy(pData, pData + 16, m_uuid.u128.value); + std::reverse_copy(pData, pData + size, uuidValue); } else { - memcpy(m_uuid.u128.value, pData, 16); + memcpy(uuidValue, pData, size); } m_valueSet = true; } // NimBLEUUID