mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 21:44:55 +01:00
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
This commit is contained in:
parent
f68a3a9334
commit
33c08812cc
5 changed files with 158 additions and 95 deletions
|
@ -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<const uint8_t *>(raw_data.c_str()), raw_data.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
ESPBTUUID::ESPBTUUID() : uuid_() {}
|
ESPBTUUID::ESPBTUUID() : uuid_() {}
|
||||||
ESPBTUUID ESPBTUUID::from_uint16(uint16_t uuid) {
|
ESPBTUUID ESPBTUUID::from_uint16(uint16_t uuid) {
|
||||||
ESPBTUUID ret;
|
ESPBTUUID ret;
|
||||||
|
@ -267,13 +263,13 @@ std::string ESPBTUUID::to_string() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
|
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
|
||||||
optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const std::string &data) {
|
optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) {
|
||||||
if (data.size() != 25)
|
if (!data.uuid.contains(0x4C, 0x00))
|
||||||
return {};
|
|
||||||
if (data[0] != 0x4C || data[1] != 0x00)
|
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return ESPBLEiBeacon(reinterpret_cast<const uint8_t *>(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) {
|
void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||||
|
@ -305,8 +301,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, " RSSI: %d", this->rssi_);
|
||||||
ESP_LOGVV(TAG, " Name: '%s'", this->name_.c_str());
|
ESP_LOGVV(TAG, " Name: '%s'", this->name_.c_str());
|
||||||
if (this->tx_power_.has_value()) {
|
for (auto &it : this->tx_powers_) {
|
||||||
ESP_LOGVV(TAG, " TX Power: %d", *this->tx_power_);
|
ESP_LOGVV(TAG, " TX Power: %d", it);
|
||||||
}
|
}
|
||||||
if (this->appearance_.has_value()) {
|
if (this->appearance_.has_value()) {
|
||||||
ESP_LOGVV(TAG, " Appearance: %u", *this->appearance_);
|
ESP_LOGVV(TAG, " Appearance: %u", *this->appearance_);
|
||||||
|
@ -314,20 +310,19 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
|
||||||
if (this->ad_flag_.has_value()) {
|
if (this->ad_flag_.has_value()) {
|
||||||
ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_);
|
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, " Service UUID: %s", uuid.to_string().c_str());
|
||||||
}
|
}
|
||||||
ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode_string(this->manufacturer_data_).c_str());
|
for (auto &data : this->manufacturer_datas_) {
|
||||||
ESP_LOGVV(TAG, " Service data: %s", hexencode_string(this->service_data_).c_str());
|
ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str());
|
||||||
|
}
|
||||||
if (this->service_data_uuid_.has_value()) {
|
for (auto &data : this->service_datas_) {
|
||||||
ESP_LOGVV(TAG, " Service Data UUID: %s", this->service_data_uuid_->to_string().c_str());
|
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",
|
ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
|
||||||
hexencode_string(
|
|
||||||
std::string(reinterpret_cast<const char *>(param.ble_adv), param.adv_data_len + param.scan_rsp_len))
|
|
||||||
.c_str());
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||||
|
@ -346,25 +341,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;
|
const uint8_t record_length = field_length - 1;
|
||||||
offset += record_length;
|
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) {
|
switch (record_type) {
|
||||||
case ESP_BLE_AD_TYPE_NAME_CMPL: {
|
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<const char *>(record), record_length);
|
this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_BLE_AD_TYPE_TX_PWR: {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_BLE_AD_TYPE_APPEARANCE: {
|
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<const uint16_t *>(record);
|
this->appearance_ = *reinterpret_cast<const uint16_t *>(record);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_BLE_AD_TYPE_FLAG: {
|
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;
|
this->ad_flag_ = *record;
|
||||||
break;
|
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_CMPL:
|
||||||
case ESP_BLE_AD_TYPE_16SRV_PART: {
|
case ESP_BLE_AD_TYPE_16SRV_PART: {
|
||||||
|
// • 16-bit Bluetooth Service UUIDs
|
||||||
for (uint8_t i = 0; i < record_length / 2; i++) {
|
for (uint8_t i = 0; i < record_length / 2; i++) {
|
||||||
this->service_uuids_.push_back(ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record + 2 * i)));
|
this->service_uuids_.push_back(ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record + 2 * i)));
|
||||||
}
|
}
|
||||||
|
@ -372,6 +394,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_CMPL:
|
||||||
case ESP_BLE_AD_TYPE_32SRV_PART: {
|
case ESP_BLE_AD_TYPE_32SRV_PART: {
|
||||||
|
// • 32-bit Bluetooth Service UUIDs
|
||||||
for (uint8_t i = 0; i < record_length / 4; i++) {
|
for (uint8_t i = 0; i < record_length / 4; i++) {
|
||||||
this->service_uuids_.push_back(ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record + 4 * i)));
|
this->service_uuids_.push_back(ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record + 4 * i)));
|
||||||
}
|
}
|
||||||
|
@ -379,41 +402,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_CMPL:
|
||||||
case ESP_BLE_AD_TYPE_128SRV_PART: {
|
case ESP_BLE_AD_TYPE_128SRV_PART: {
|
||||||
|
// • Global 128-bit Service UUIDs
|
||||||
this->service_uuids_.push_back(ESPBTUUID::from_raw(record));
|
this->service_uuids_.push_back(ESPBTUUID::from_raw(record));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: {
|
case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: {
|
||||||
this->manufacturer_data_ = std::string(reinterpret_cast<const char *>(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;
|
break;
|
||||||
}
|
}
|
||||||
|
ServiceData data{};
|
||||||
|
data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(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: {
|
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) {
|
if (record_length < 2) {
|
||||||
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_SERVICE_DATA");
|
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_SERVICE_DATA");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->service_data_uuid_ = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record));
|
ServiceData data{};
|
||||||
if (record_length > 2)
|
data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record));
|
||||||
this->service_data_ = std::string(reinterpret_cast<const char *>(record + 2), record_length - 2UL);
|
data.data.assign(record + 2UL, record + record_length);
|
||||||
|
this->service_datas_.push_back(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_BLE_AD_TYPE_32SERVICE_DATA: {
|
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) {
|
if (record_length < 4) {
|
||||||
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA");
|
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->service_data_uuid_ = ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record));
|
ServiceData data{};
|
||||||
if (record_length > 4)
|
data.uuid = ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record));
|
||||||
this->service_data_ = std::string(reinterpret_cast<const char *>(record + 4), record_length - 4UL);
|
data.data.assign(record + 4UL, record + record_length);
|
||||||
|
this->service_datas_.push_back(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_BLE_AD_TYPE_128SERVICE_DATA: {
|
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) {
|
if (record_length < 16) {
|
||||||
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA");
|
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->service_data_uuid_ = ESPBTUUID::from_raw(record);
|
ServiceData data{};
|
||||||
if (record_length > 16)
|
data.uuid = ESPBTUUID::from_raw(record);
|
||||||
this->service_data_ = std::string(reinterpret_cast<const char *>(record + 16), record_length - 16UL);
|
data.data.assign(record + 16UL, record + record_length);
|
||||||
|
this->service_datas_.push_back(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -430,16 +482,6 @@ std::string ESPBTDevice::address_str() const {
|
||||||
return mac;
|
return mac;
|
||||||
}
|
}
|
||||||
uint64_t ESPBTDevice::address_uint64() const { return ble_addr_to_uint64(this->address_); }
|
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<int8_t> &ESPBTDevice::get_tx_power() const { return this->tx_power_; }
|
|
||||||
const optional<uint16_t> &ESPBTDevice::get_appearance() const { return this->appearance_; }
|
|
||||||
const optional<uint8_t> &ESPBTDevice::get_ad_flag() const { return this->ad_flag_; }
|
|
||||||
const std::vector<ESPBTUUID> &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<ESPBTUUID> &ESPBTDevice::get_service_data_uuid() const { return this->service_data_uuid_; }
|
|
||||||
|
|
||||||
void ESP32BLETracker::dump_config() {
|
void ESP32BLETracker::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "BLE Tracker:");
|
ESP_LOGCONFIG(TAG, "BLE Tracker:");
|
||||||
|
@ -480,8 +522,8 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) {
|
||||||
ESP_LOGD(TAG, " Address Type: %s", address_type_s);
|
ESP_LOGD(TAG, " Address Type: %s", address_type_s);
|
||||||
if (!device.get_name().empty())
|
if (!device.get_name().empty())
|
||||||
ESP_LOGD(TAG, " Name: '%s'", device.get_name().c_str());
|
ESP_LOGD(TAG, " Name: '%s'", device.get_name().c_str());
|
||||||
if (device.get_tx_power().has_value()) {
|
for (auto &tx_power : device.get_tx_powers()) {
|
||||||
ESP_LOGD(TAG, " TX Power: %d", *device.get_tx_power());
|
ESP_LOGD(TAG, " TX Power: %d", tx_power);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,11 +33,18 @@ class ESPBTUUID {
|
||||||
esp_bt_uuid_t uuid_;
|
esp_bt_uuid_t uuid_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using adv_data_t = std::vector<uint8_t>;
|
||||||
|
|
||||||
|
struct ServiceData {
|
||||||
|
ESPBTUUID uuid;
|
||||||
|
adv_data_t data;
|
||||||
|
};
|
||||||
|
|
||||||
class ESPBLEiBeacon {
|
class ESPBLEiBeacon {
|
||||||
public:
|
public:
|
||||||
ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); }
|
ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); }
|
||||||
ESPBLEiBeacon(const uint8_t *data);
|
ESPBLEiBeacon(const uint8_t *data);
|
||||||
static optional<ESPBLEiBeacon> from_manufacturer_data(const std::string &data);
|
static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data);
|
||||||
|
|
||||||
uint16_t get_major() { return reverse_bits_16(this->beacon_data_.major); }
|
uint16_t get_major() { return reverse_bits_16(this->beacon_data_.major); }
|
||||||
uint16_t get_minor() { return reverse_bits_16(this->beacon_data_.minor); }
|
uint16_t get_minor() { return reverse_bits_16(this->beacon_data_.minor); }
|
||||||
|
@ -46,7 +53,6 @@ class ESPBLEiBeacon {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct {
|
struct {
|
||||||
uint16_t manufacturer_id;
|
|
||||||
uint8_t sub_type;
|
uint8_t sub_type;
|
||||||
uint8_t proximity_uuid[16];
|
uint8_t proximity_uuid[16];
|
||||||
uint16_t major;
|
uint16_t major;
|
||||||
|
@ -63,18 +69,33 @@ class ESPBTDevice {
|
||||||
|
|
||||||
uint64_t address_uint64() const;
|
uint64_t address_uint64() const;
|
||||||
|
|
||||||
esp_ble_addr_type_t get_address_type() const;
|
esp_ble_addr_type_t get_address_type() const { return this->address_type_; }
|
||||||
int get_rssi() const;
|
int get_rssi() const { return rssi_; }
|
||||||
const std::string &get_name() const;
|
const std::string &get_name() const { return this->name_; }
|
||||||
const optional<int8_t> &get_tx_power() const;
|
|
||||||
const optional<uint16_t> &get_appearance() const;
|
ESPDEPRECATED("Use get_tx_powers() instead")
|
||||||
const optional<uint8_t> &get_ad_flag() const;
|
optional<int8_t> get_tx_power() const {
|
||||||
const std::vector<ESPBTUUID> &get_service_uuids() const;
|
if (this->tx_powers_.empty())
|
||||||
const std::string &get_manufacturer_data() const;
|
return {};
|
||||||
const std::string &get_service_data() const;
|
return this->tx_powers_[0];
|
||||||
const optional<ESPBTUUID> &get_service_data_uuid() const;
|
}
|
||||||
const optional<ESPBLEiBeacon> get_ibeacon() const {
|
const std::vector<int8_t> &get_tx_powers() const { return tx_powers_; }
|
||||||
return ESPBLEiBeacon::from_manufacturer_data(this->manufacturer_data_);
|
|
||||||
|
const optional<uint16_t> &get_appearance() const { return appearance_; }
|
||||||
|
const optional<uint8_t> &get_ad_flag() const { return ad_flag_; }
|
||||||
|
const std::vector<ESPBTUUID> &get_service_uuids() const { return service_uuids_; }
|
||||||
|
|
||||||
|
const std::vector<ServiceData> &get_manufacturer_datas() const { return manufacturer_datas_; }
|
||||||
|
|
||||||
|
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
|
||||||
|
|
||||||
|
optional<ESPBLEiBeacon> get_ibeacon() const {
|
||||||
|
for (auto &it : this->manufacturer_datas_) {
|
||||||
|
auto res = ESPBLEiBeacon::from_manufacturer_data(it);
|
||||||
|
if (res.has_value())
|
||||||
|
return *res;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -86,13 +107,12 @@ class ESPBTDevice {
|
||||||
esp_ble_addr_type_t address_type_{BLE_ADDR_TYPE_PUBLIC};
|
esp_ble_addr_type_t address_type_{BLE_ADDR_TYPE_PUBLIC};
|
||||||
int rssi_{0};
|
int rssi_{0};
|
||||||
std::string name_{};
|
std::string name_{};
|
||||||
optional<int8_t> tx_power_{};
|
std::vector<int8_t> tx_powers_{};
|
||||||
optional<uint16_t> appearance_{};
|
optional<uint16_t> appearance_{};
|
||||||
optional<uint8_t> ad_flag_{};
|
optional<uint8_t> ad_flag_{};
|
||||||
std::vector<ESPBTUUID> service_uuids_;
|
std::vector<ESPBTUUID> service_uuids_;
|
||||||
std::string manufacturer_data_{};
|
std::vector<ServiceData> manufacturer_datas_{};
|
||||||
std::string service_data_{};
|
std::vector<ServiceData> service_datas_{};
|
||||||
optional<ESPBTUUID> service_data_uuid_{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ESP32BLETracker;
|
class ESP32BLETracker;
|
||||||
|
|
|
@ -8,10 +8,12 @@ namespace ruuvi_ble {
|
||||||
|
|
||||||
static const char *TAG = "ruuvi_ble";
|
static const char *TAG = "ruuvi_ble";
|
||||||
|
|
||||||
bool parse_ruuvi_data_byte(uint8_t data_type, uint8_t data_length, const uint8_t *data, RuuviParseResult &result) {
|
bool parse_ruuvi_data_byte(const esp32_ble_tracker::adv_data_t &adv_data, RuuviParseResult &result) {
|
||||||
|
const uint8_t data_type = adv_data[0];
|
||||||
|
const auto *data = &adv_data[1];
|
||||||
switch (data_type) {
|
switch (data_type) {
|
||||||
case 0x03: { // RAWv1
|
case 0x03: { // RAWv1
|
||||||
if (data_length != 16)
|
if (adv_data.size() != 14)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const uint8_t temp_sign = (data[1] >> 7) & 1;
|
const uint8_t temp_sign = (data[1] >> 7) & 1;
|
||||||
|
@ -32,13 +34,13 @@ bool parse_ruuvi_data_byte(uint8_t data_type, uint8_t data_length, const uint8_t
|
||||||
result.acceleration_y = acceleration_y;
|
result.acceleration_y = acceleration_y;
|
||||||
result.acceleration_z = acceleration_z;
|
result.acceleration_z = acceleration_z;
|
||||||
result.acceleration =
|
result.acceleration =
|
||||||
sqrt(acceleration_x * acceleration_x + acceleration_y * acceleration_y + acceleration_z * acceleration_z);
|
sqrtf(acceleration_x * acceleration_x + acceleration_y * acceleration_y + acceleration_z * acceleration_z);
|
||||||
result.battery_voltage = battery_voltage;
|
result.battery_voltage = battery_voltage;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case 0x05: { // RAWv2
|
case 0x05: { // RAWv2
|
||||||
if (data_length != 26)
|
if (adv_data.size() != 24)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const float temperature = (int16_t(data[0] << 8) + int16_t(data[1])) * 0.005f;
|
const float temperature = (int16_t(data[0] << 8) + int16_t(data[1])) * 0.005f;
|
||||||
|
@ -63,7 +65,7 @@ bool parse_ruuvi_data_byte(uint8_t data_type, uint8_t data_length, const uint8_t
|
||||||
result.acceleration_z = data[10] == 0xFF && data[11] == 0xFF ? NAN : acceleration_z;
|
result.acceleration_z = data[10] == 0xFF && data[11] == 0xFF ? NAN : acceleration_z;
|
||||||
result.acceleration = result.acceleration_x == NAN || result.acceleration_y == NAN || result.acceleration_z == NAN
|
result.acceleration = result.acceleration_x == NAN || result.acceleration_y == NAN || result.acceleration_z == NAN
|
||||||
? NAN
|
? NAN
|
||||||
: sqrt(acceleration_x * acceleration_x + acceleration_y * acceleration_y +
|
: sqrtf(acceleration_x * acceleration_x + acceleration_y * acceleration_y +
|
||||||
acceleration_z * acceleration_z);
|
acceleration_z * acceleration_z);
|
||||||
result.battery_voltage = (power_info >> 5) == 0x7FF ? NAN : battery_voltage;
|
result.battery_voltage = (power_info >> 5) == 0x7FF ? NAN : battery_voltage;
|
||||||
result.tx_power = (power_info & 0x1F) == 0x1F ? NAN : tx_power;
|
result.tx_power = (power_info & 0x1F) == 0x1F ? NAN : tx_power;
|
||||||
|
@ -77,21 +79,16 @@ bool parse_ruuvi_data_byte(uint8_t data_type, uint8_t data_length, const uint8_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
optional<RuuviParseResult> parse_ruuvi(const esp32_ble_tracker::ESPBTDevice &device) {
|
optional<RuuviParseResult> parse_ruuvi(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||||
const auto *raw = reinterpret_cast<const uint8_t *>(device.get_manufacturer_data().data());
|
bool success = false;
|
||||||
|
RuuviParseResult result{};
|
||||||
|
for (auto &it : device.get_manufacturer_datas()) {
|
||||||
|
bool is_ruuvi = it.uuid.contains(0x99, 0x04);
|
||||||
|
if (!is_ruuvi)
|
||||||
|
continue;
|
||||||
|
|
||||||
bool is_ruuvi = raw[0] == 0x99 && raw[1] == 0x04;
|
if (parse_ruuvi_data_byte(it.data, result))
|
||||||
|
success = true;
|
||||||
if (!is_ruuvi) {
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t data_length = device.get_manufacturer_data().size();
|
|
||||||
const uint8_t format = raw[2];
|
|
||||||
const uint8_t *data = &raw[3];
|
|
||||||
|
|
||||||
RuuviParseResult result;
|
|
||||||
|
|
||||||
bool success = parse_ruuvi_data_byte(format, data_length, data, result);
|
|
||||||
if (!success)
|
if (!success)
|
||||||
return {};
|
return {};
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -63,22 +63,17 @@ bool parse_xiaomi_data_byte(uint8_t data_type, const uint8_t *data, uint8_t data
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
optional<XiaomiParseResult> parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &device) {
|
bool parse_xiaomi_service_data(XiaomiParseResult &result, const esp32_ble_tracker::ServiceData &service_data) {
|
||||||
if (!device.get_service_data_uuid().has_value()) {
|
if (!service_data.uuid.contains(0x95, 0xFE)) {
|
||||||
// ESP_LOGVV(TAG, "Xiaomi no service data");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!device.get_service_data_uuid()->contains(0x95, 0xFE)) {
|
|
||||||
// ESP_LOGVV(TAG, "Xiaomi no service data UUID magic bytes");
|
// ESP_LOGVV(TAG, "Xiaomi no service data UUID magic bytes");
|
||||||
return {};
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto *raw = reinterpret_cast<const uint8_t *>(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!");
|
// ESP_LOGVV(TAG, "Xiaomi service data too short!");
|
||||||
return {};
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_lywsdcgq = (raw[1] & 0x20) == 0x20 && raw[2] == 0xAA && raw[3] == 0x01;
|
bool is_lywsdcgq = (raw[1] & 0x20) == 0x20 && raw[2] == 0xAA && raw[3] == 0x01;
|
||||||
|
@ -88,10 +83,9 @@ optional<XiaomiParseResult> parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &d
|
||||||
|
|
||||||
if (!is_lywsdcgq && !is_hhccjcy01 && !is_lywsd02 && !is_cgg1) {
|
if (!is_lywsdcgq && !is_hhccjcy01 && !is_lywsd02 && !is_cgg1) {
|
||||||
// ESP_LOGVV(TAG, "Xiaomi no magic bytes");
|
// ESP_LOGVV(TAG, "Xiaomi no magic bytes");
|
||||||
return {};
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
XiaomiParseResult result;
|
|
||||||
result.type = XiaomiParseResult::TYPE_HHCCJCY01;
|
result.type = XiaomiParseResult::TYPE_HHCCJCY01;
|
||||||
if (is_lywsdcgq) {
|
if (is_lywsdcgq) {
|
||||||
result.type = XiaomiParseResult::TYPE_LYWSDCGQ;
|
result.type = XiaomiParseResult::TYPE_LYWSDCGQ;
|
||||||
|
@ -111,7 +105,7 @@ optional<XiaomiParseResult> parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &d
|
||||||
|
|
||||||
const uint8_t *raw_data = &raw[raw_offset];
|
const uint8_t *raw_data = &raw[raw_offset];
|
||||||
uint8_t data_offset = 0;
|
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;
|
bool success = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -136,6 +130,15 @@ optional<XiaomiParseResult> parse_xiaomi(const esp32_ble_tracker::ESPBTDevice &d
|
||||||
data_offset += 3 + datapoint_length;
|
data_offset += 3 + datapoint_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
optional<XiaomiParseResult> 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)
|
if (!success)
|
||||||
return {};
|
return {};
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -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)
|
// Encode raw data to a human-readable string (for debugging)
|
||||||
std::string hexencode(const uint8_t *data, uint32_t len);
|
std::string hexencode(const uint8_t *data, uint32_t len);
|
||||||
|
template<typename T> 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
|
// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971
|
||||||
template<int...> struct seq {}; // NOLINT
|
template<int...> struct seq {}; // NOLINT
|
||||||
|
|
Loading…
Reference in a new issue