From 219fe418319c70ffffff25278f8680e06f789bc2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 4 Dec 2019 17:12:26 +0100 Subject: [PATCH] Update ESP32 BLE ADV parse to match BLE spec (#904) * Update ESP32 BLE ADV parse to match BLE spec * Update xiaomi * Update ruuvi * Format * Update esp32_ble_tracker.cpp * Fix log * Format * Update xiaomi_ble.cpp --- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 130 ++++++++++++------ .../esp32_ble_tracker/esp32_ble_tracker.h | 56 +++++--- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 31 +++-- esphome/core/helpers.h | 1 + 4 files changed, 142 insertions(+), 76 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 30a448d082..3deaddf4b8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -203,10 +203,6 @@ void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_res } } -std::string hexencode_string(const std::string &raw_data) { - return hexencode(reinterpret_cast(raw_data.c_str()), raw_data.size()); -} - ESPBTUUID::ESPBTUUID() : uuid_() {} ESPBTUUID ESPBTUUID::from_uint16(uint16_t uuid) { ESPBTUUID ret; @@ -266,13 +262,13 @@ std::string ESPBTUUID::to_string() { } ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); } -optional ESPBLEiBeacon::from_manufacturer_data(const std::string &data) { - if (data.size() != 25) - return {}; - if (data[0] != 0x4C || data[1] != 0x00) +optional ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) { + if (!data.uuid.contains(0x4C, 0x00)) return {}; - return ESPBLEiBeacon(reinterpret_cast(data.data())); + if (data.data.size() != 23) + return {}; + return ESPBLEiBeacon(data.data.data()); } void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { @@ -304,8 +300,8 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e ESP_LOGVV(TAG, " RSSI: %d", this->rssi_); ESP_LOGVV(TAG, " Name: '%s'", this->name_.c_str()); - if (this->tx_power_.has_value()) { - ESP_LOGVV(TAG, " TX Power: %d", *this->tx_power_); + for (auto &it : this->tx_powers_) { + ESP_LOGVV(TAG, " TX Power: %d", it); } if (this->appearance_.has_value()) { ESP_LOGVV(TAG, " Appearance: %u", *this->appearance_); @@ -313,20 +309,19 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e if (this->ad_flag_.has_value()) { ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_); } - for (auto uuid : this->service_uuids_) { + for (auto &uuid : this->service_uuids_) { ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); } - ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode_string(this->manufacturer_data_).c_str()); - ESP_LOGVV(TAG, " Service data: %s", hexencode_string(this->service_data_).c_str()); - - if (this->service_data_uuid_.has_value()) { - ESP_LOGVV(TAG, " Service Data UUID: %s", this->service_data_uuid_->to_string().c_str()); + for (auto &data : this->manufacturer_datas_) { + ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str()); + } + for (auto &data : this->service_datas_) { + ESP_LOGVV(TAG, " Service data:"); + ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str()); + ESP_LOGVV(TAG, " Data: %s", hexencode(data.data).c_str()); } - ESP_LOGVV(TAG, "Adv data: %s", - hexencode_string( - std::string(reinterpret_cast(param.ble_adv), param.adv_data_len + param.scan_rsp_len)) - .c_str()); + ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); #endif } void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { @@ -345,25 +340,52 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p const uint8_t record_length = field_length - 1; offset += record_length; + // See also Generic Access Profile Assigned Numbers: + // https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/ See also ADVERTISING AND SCAN + // RESPONSE DATA FORMAT: https://www.bluetooth.com/specifications/bluetooth-core-specification/ (vol 3, part C, 11) + // See also Core Specification Supplement: https://www.bluetooth.com/specifications/bluetooth-core-specification/ + // (called CSS here) + switch (record_type) { case ESP_BLE_AD_TYPE_NAME_CMPL: { + // CSS 1.2 LOCAL NAME + // "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the + // device." CSS 1: Optional in this context; shall not appear more than once in a block. this->name_ = std::string(reinterpret_cast(record), record_length); break; } case ESP_BLE_AD_TYPE_TX_PWR: { - this->tx_power_ = *payload; + // CSS 1.5 TX POWER LEVEL + // "The TX Power Level data type indicates the transmitted power level of the packet containing the data type." + // CSS 1: Optional in this context (may appear more than once in a block). + this->tx_powers_.push_back(*payload); break; } case ESP_BLE_AD_TYPE_APPEARANCE: { + // CSS 1.12 APPEARANCE + // "The Appearance data type defines the external appearance of the device." + // See also https://www.bluetooth.com/specifications/gatt/characteristics/ + // CSS 1: Optional in this context; shall not appear more than once in a block and shall not appear in both + // the AD and SRD of the same extended advertising interval. this->appearance_ = *reinterpret_cast(record); break; } case ESP_BLE_AD_TYPE_FLAG: { + // CSS 1.3 FLAGS + // "The Flags data type contains one bit Boolean flags. The Flags data type shall be included when any of the + // Flag bits are non-zero and the advertising packet is connectable, otherwise the Flags data type may be + // omitted." + // CSS 1: Optional in this context; shall not appear more than once in a block. this->ad_flag_ = *record; break; } + // CSS 1.1 SERVICE UUID + // The Service UUID data type is used to include a list of Service or Service Class UUIDs. + // There are six data types defined for the three sizes of Service UUIDs that may be returned: + // CSS 1: Optional in this context (may appear more than once in a block). case ESP_BLE_AD_TYPE_16SRV_CMPL: case ESP_BLE_AD_TYPE_16SRV_PART: { + // • 16-bit Bluetooth Service UUIDs for (uint8_t i = 0; i < record_length / 2; i++) { this->service_uuids_.push_back(ESPBTUUID::from_uint16(*reinterpret_cast(record + 2 * i))); } @@ -371,6 +393,7 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p } case ESP_BLE_AD_TYPE_32SRV_CMPL: case ESP_BLE_AD_TYPE_32SRV_PART: { + // • 32-bit Bluetooth Service UUIDs for (uint8_t i = 0; i < record_length / 4; i++) { this->service_uuids_.push_back(ESPBTUUID::from_uint32(*reinterpret_cast(record + 4 * i))); } @@ -378,41 +401,70 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p } case ESP_BLE_AD_TYPE_128SRV_CMPL: case ESP_BLE_AD_TYPE_128SRV_PART: { + // • Global 128-bit Service UUIDs this->service_uuids_.push_back(ESPBTUUID::from_raw(record)); break; } case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: { - this->manufacturer_data_ = std::string(reinterpret_cast(record), record_length); + // CSS 1.4 MANUFACTURER SPECIFIC DATA + // "The Manufacturer Specific data type is used for manufacturer specific data. The first two data octets shall + // contain a company identifier from Assigned Numbers. The interpretation of any other octets within the data + // shall be defined by the manufacturer specified by the company identifier." + // CSS 1: Optional in this context (may appear more than once in a block). + if (record_length < 2) { + ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"); + break; + } + ServiceData data{}; + data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast(record)); + data.data.assign(record + 2UL, record + record_length); + this->manufacturer_datas_.push_back(data); break; } + + // CSS 1.11 SERVICE DATA + // "The Service Data data type consists of a service UUID with the data associated with that service." + // CSS 1: Optional in this context (may appear more than once in a block). case ESP_BLE_AD_TYPE_SERVICE_DATA: { + // «Service Data - 16 bit UUID» + // Size: 2 or more octets + // The first 2 octets contain the 16 bit Service UUID fol- lowed by additional service data if (record_length < 2) { ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_SERVICE_DATA"); break; } - this->service_data_uuid_ = ESPBTUUID::from_uint16(*reinterpret_cast(record)); - if (record_length > 2) - this->service_data_ = std::string(reinterpret_cast(record + 2), record_length - 2UL); + ServiceData data{}; + data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast(record)); + data.data.assign(record + 2UL, record + record_length); + this->service_datas_.push_back(data); break; } case ESP_BLE_AD_TYPE_32SERVICE_DATA: { + // «Service Data - 32 bit UUID» + // Size: 4 or more octets + // The first 4 octets contain the 32 bit Service UUID fol- lowed by additional service data if (record_length < 4) { ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA"); break; } - this->service_data_uuid_ = ESPBTUUID::from_uint32(*reinterpret_cast(record)); - if (record_length > 4) - this->service_data_ = std::string(reinterpret_cast(record + 4), record_length - 4UL); + ServiceData data{}; + data.uuid = ESPBTUUID::from_uint32(*reinterpret_cast(record)); + data.data.assign(record + 4UL, record + record_length); + this->service_datas_.push_back(data); break; } case ESP_BLE_AD_TYPE_128SERVICE_DATA: { + // «Service Data - 128 bit UUID» + // Size: 16 or more octets + // The first 16 octets contain the 128 bit Service UUID followed by additional service data if (record_length < 16) { ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA"); break; } - this->service_data_uuid_ = ESPBTUUID::from_raw(record); - if (record_length > 16) - this->service_data_ = std::string(reinterpret_cast(record + 16), record_length - 16UL); + ServiceData data{}; + data.uuid = ESPBTUUID::from_raw(record); + data.data.assign(record + 16UL, record + record_length); + this->service_datas_.push_back(data); break; } default: { @@ -429,16 +481,6 @@ std::string ESPBTDevice::address_str() const { return mac; } uint64_t ESPBTDevice::address_uint64() const { return ble_addr_to_uint64(this->address_); } -esp_ble_addr_type_t ESPBTDevice::get_address_type() const { return this->address_type_; } -int ESPBTDevice::get_rssi() const { return this->rssi_; } -const std::string &ESPBTDevice::get_name() const { return this->name_; } -const optional &ESPBTDevice::get_tx_power() const { return this->tx_power_; } -const optional &ESPBTDevice::get_appearance() const { return this->appearance_; } -const optional &ESPBTDevice::get_ad_flag() const { return this->ad_flag_; } -const std::vector &ESPBTDevice::get_service_uuids() const { return this->service_uuids_; } -const std::string &ESPBTDevice::get_manufacturer_data() const { return this->manufacturer_data_; } -const std::string &ESPBTDevice::get_service_data() const { return this->service_data_; } -const optional &ESPBTDevice::get_service_data_uuid() const { return this->service_data_uuid_; } void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, "BLE Tracker:"); @@ -479,8 +521,8 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { ESP_LOGD(TAG, " Address Type: %s", address_type_s); if (!device.get_name().empty()) ESP_LOGD(TAG, " Name: '%s'", device.get_name().c_str()); - if (device.get_tx_power().has_value()) { - ESP_LOGD(TAG, " TX Power: %d", *device.get_tx_power()); + for (auto &tx_power : device.get_tx_powers()) { + ESP_LOGD(TAG, " TX Power: %d", tx_power); } } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 82e8e553fc..4cf3dd432b 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -31,11 +31,18 @@ class ESPBTUUID { esp_bt_uuid_t uuid_; }; +using adv_data_t = std::vector; + +struct ServiceData { + ESPBTUUID uuid; + adv_data_t data; +}; + class ESPBLEiBeacon { public: ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); } ESPBLEiBeacon(const uint8_t *data); - static optional from_manufacturer_data(const std::string &data); + static optional from_manufacturer_data(const ServiceData &data); uint16_t get_major() { return reverse_bits_16(this->beacon_data_.major); } uint16_t get_minor() { return reverse_bits_16(this->beacon_data_.minor); } @@ -44,7 +51,6 @@ class ESPBLEiBeacon { protected: struct { - uint16_t manufacturer_id; uint8_t sub_type; uint8_t proximity_uuid[16]; uint16_t major; @@ -61,18 +67,33 @@ class ESPBTDevice { uint64_t address_uint64() const; - esp_ble_addr_type_t get_address_type() const; - int get_rssi() const; - const std::string &get_name() const; - const optional &get_tx_power() const; - const optional &get_appearance() const; - const optional &get_ad_flag() const; - const std::vector &get_service_uuids() const; - const std::string &get_manufacturer_data() const; - const std::string &get_service_data() const; - const optional &get_service_data_uuid() const; - const optional get_ibeacon() const { - return ESPBLEiBeacon::from_manufacturer_data(this->manufacturer_data_); + esp_ble_addr_type_t get_address_type() const { return this->address_type_; } + int get_rssi() const { return rssi_; } + const std::string &get_name() const { return this->name_; } + + ESPDEPRECATED("Use get_tx_powers() instead") + optional get_tx_power() const { + if (this->tx_powers_.empty()) + return {}; + return this->tx_powers_[0]; + } + const std::vector &get_tx_powers() const { return tx_powers_; } + + const optional &get_appearance() const { return appearance_; } + const optional &get_ad_flag() const { return ad_flag_; } + const std::vector &get_service_uuids() const { return service_uuids_; } + + const std::vector &get_manufacturer_datas() const { return manufacturer_datas_; } + + const std::vector &get_service_datas() const { return service_datas_; } + + optional get_ibeacon() const { + for (auto &it : this->manufacturer_datas_) { + auto res = ESPBLEiBeacon::from_manufacturer_data(it); + if (res.has_value()) + return *res; + } + return {}; } protected: @@ -84,13 +105,12 @@ class ESPBTDevice { esp_ble_addr_type_t address_type_{BLE_ADDR_TYPE_PUBLIC}; int rssi_{0}; std::string name_{}; - optional tx_power_{}; + std::vector tx_powers_{}; optional appearance_{}; optional ad_flag_{}; std::vector service_uuids_; - std::string manufacturer_data_{}; - std::string service_data_{}; - optional service_data_uuid_{}; + std::vector manufacturer_datas_{}; + std::vector service_datas_{}; }; class ESP32BLETracker; diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index e6884e5ea6..18eaffed06 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -63,22 +63,17 @@ bool parse_xiaomi_data_byte(uint8_t data_type, const uint8_t *data, uint8_t data return false; } } -optional parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &device) { - if (!device.get_service_data_uuid().has_value()) { - // ESP_LOGVV(TAG, "Xiaomi no service data"); - return {}; - } - - if (!device.get_service_data_uuid()->contains(0x95, 0xFE)) { +bool parse_xiaomi_service_data(XiaomiParseResult &result, const esp32_ble_tracker::ServiceData &service_data) { + if (!service_data.uuid.contains(0x95, 0xFE)) { // ESP_LOGVV(TAG, "Xiaomi no service data UUID magic bytes"); - return {}; + return false; } - const auto *raw = reinterpret_cast(device.get_service_data().data()); + const auto raw = service_data.data; - if (device.get_service_data().size() < 14) { + if (raw.size() < 14) { // ESP_LOGVV(TAG, "Xiaomi service data too short!"); - return {}; + return false; } bool is_lywsdcgq = (raw[1] & 0x20) == 0x20 && raw[2] == 0xAA && raw[3] == 0x01; @@ -88,10 +83,9 @@ optional parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &d if (!is_lywsdcgq && !is_hhccjcy01 && !is_lywsd02 && !is_cgg1) { // ESP_LOGVV(TAG, "Xiaomi no magic bytes"); - return {}; + return false; } - XiaomiParseResult result; result.type = XiaomiParseResult::TYPE_HHCCJCY01; if (is_lywsdcgq) { result.type = XiaomiParseResult::TYPE_LYWSDCGQ; @@ -111,7 +105,7 @@ optional parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &d const uint8_t *raw_data = &raw[raw_offset]; uint8_t data_offset = 0; - uint8_t data_length = device.get_service_data().size() - raw_offset; + uint8_t data_length = raw.size() - raw_offset; bool success = false; while (true) { @@ -136,6 +130,15 @@ optional parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &d data_offset += 3 + datapoint_length; } + return success; +} +optional parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &device) { + XiaomiParseResult result; + bool success = false; + for (auto &service_data : device.get_service_datas()) { + if (parse_xiaomi_service_data(result, service_data)) + success = true; + } if (!success) return {}; return result; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 88f0d587e5..91b78b73a0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -158,6 +158,7 @@ ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const ch // Encode raw data to a human-readable string (for debugging) std::string hexencode(const uint8_t *data, uint32_t len); +template std::string hexencode(const T &data) { return hexencode(data.data(), data.size()); } // https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 template struct seq {}; // NOLINT