From ba1222eae444ef498fa0af0f6a363b7585f4161f Mon Sep 17 00:00:00 2001 From: puuu Date: Tue, 28 Apr 2020 08:57:02 +0900 Subject: [PATCH] Bluetooth advertising automation (#995) * esp32_ble_tracker: introduce UUID comparison function * ble_presence, ble_rssi: use new UUID comparison function * esp32_ble_tracker: introduce automation on BLE advertising * test2.yaml: remove deep_sleep due to firmware size restrictions --- .../ble_presence/ble_presence_device.h | 33 +------- esphome/components/ble_rssi/ble_rssi_sensor.h | 33 +------- .../components/esp32_ble_tracker/__init__.py | 59 ++++++++++++- .../components/esp32_ble_tracker/automation.h | 82 +++++++++++++++++++ .../esp32_ble_tracker/esp32_ble_tracker.cpp | 49 ++++++++++- .../esp32_ble_tracker/esp32_ble_tracker.h | 4 + esphome/const.py | 4 + tests/test2.yaml | 23 +++++- 8 files changed, 221 insertions(+), 66 deletions(-) create mode 100644 esphome/components/esp32_ble_tracker/automation.h diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index e721db7dcd..bce6a9cf98 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -43,35 +43,10 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, } } else { for (auto uuid : device.get_service_uuids()) { - switch (this->uuid_.get_uuid().len) { - case ESP_UUID_LEN_16: - if (uuid.get_uuid().len == ESP_UUID_LEN_16 && - uuid.get_uuid().uuid.uuid16 == this->uuid_.get_uuid().uuid.uuid16) { - this->publish_state(true); - this->found_ = true; - return true; - } - break; - case ESP_UUID_LEN_32: - if (uuid.get_uuid().len == ESP_UUID_LEN_32 && - uuid.get_uuid().uuid.uuid32 == this->uuid_.get_uuid().uuid.uuid32) { - this->publish_state(true); - this->found_ = true; - return true; - } - break; - case ESP_UUID_LEN_128: - if (uuid.get_uuid().len == ESP_UUID_LEN_128) { - for (int i = 0; i < ESP_UUID_LEN_128; i++) { - if (this->uuid_.get_uuid().uuid.uuid128[i] != uuid.get_uuid().uuid.uuid128[i]) { - return false; - } - } - this->publish_state(true); - this->found_ = true; - return true; - } - break; + if (this->uuid_ == uuid) { + this->publish_state(device.get_rssi()); + this->found_ = true; + return true; } } } diff --git a/esphome/components/ble_rssi/ble_rssi_sensor.h b/esphome/components/ble_rssi/ble_rssi_sensor.h index 17dd0d4a7d..2082a52469 100644 --- a/esphome/components/ble_rssi/ble_rssi_sensor.h +++ b/esphome/components/ble_rssi/ble_rssi_sensor.h @@ -41,35 +41,10 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi } } else { for (auto uuid : device.get_service_uuids()) { - switch (this->uuid_.get_uuid().len) { - case ESP_UUID_LEN_16: - if (uuid.get_uuid().len == ESP_UUID_LEN_16 && - uuid.get_uuid().uuid.uuid16 == this->uuid_.get_uuid().uuid.uuid16) { - this->publish_state(device.get_rssi()); - this->found_ = true; - return true; - } - break; - case ESP_UUID_LEN_32: - if (uuid.get_uuid().len == ESP_UUID_LEN_32 && - uuid.get_uuid().uuid.uuid32 == this->uuid_.get_uuid().uuid.uuid32) { - this->publish_state(device.get_rssi()); - this->found_ = true; - return true; - } - break; - case ESP_UUID_LEN_128: - if (uuid.get_uuid().len == ESP_UUID_LEN_128) { - for (int i = 0; i < ESP_UUID_LEN_128; i++) { - if (uuid.get_uuid().uuid.uuid128[i] != this->uuid_.get_uuid().uuid.uuid128[i]) { - return false; - } - } - this->publish_state(device.get_rssi()); - this->found_ = true; - return true; - } - break; + if (this->uuid_ == uuid) { + this->publish_state(device.get_rssi()); + this->found_ = true; + return true; } } } diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 3311801b6c..c4cc7260fd 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -1,8 +1,11 @@ import re import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.const import CONF_ID, ESP_PLATFORM_ESP32, CONF_INTERVAL, \ - CONF_DURATION + CONF_DURATION, CONF_TRIGGER_ID, CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_MANUFACTURER_ID, \ + CONF_ON_BLE_ADVERTISE, CONF_ON_BLE_SERVICE_DATA_ADVERTISE, \ + CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE from esphome.core import coroutine ESP_PLATFORMS = [ESP_PLATFORM_ESP32] @@ -15,6 +18,17 @@ CONF_ACTIVE = 'active' esp32_ble_tracker_ns = cg.esphome_ns.namespace('esp32_ble_tracker') ESP32BLETracker = esp32_ble_tracker_ns.class_('ESP32BLETracker', cg.Component) ESPBTDeviceListener = esp32_ble_tracker_ns.class_('ESPBTDeviceListener') +ESPBTDevice = esp32_ble_tracker_ns.class_('ESPBTDevice') +ESPBTDeviceConstRef = ESPBTDevice.operator('ref').operator('const') +adv_data_t = cg.std_vector.template(cg.uint8) +adv_data_t_const_ref = adv_data_t.operator('ref').operator('const') +# Triggers +ESPBTAdvertiseTrigger = esp32_ble_tracker_ns.class_( + 'ESPBTAdvertiseTrigger', automation.Trigger.template(ESPBTDeviceConstRef)) +BLEServiceDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( + 'BLEServiceDataAdvertiseTrigger', automation.Trigger.template(adv_data_t_const_ref)) +BLEManufacturerDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( + 'BLEManufacturerDataAdvertiseTrigger', automation.Trigger.template(adv_data_t_const_ref)) def validate_scan_parameters(config): @@ -85,6 +99,20 @@ CONFIG_SCHEMA = cv.Schema({ cv.Optional(CONF_WINDOW, default='30ms'): cv.positive_time_period_milliseconds, cv.Optional(CONF_ACTIVE, default=True): cv.boolean, }), validate_scan_parameters), + cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger), + cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + }), + cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEServiceDataAdvertiseTrigger), + cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + cv.Required(CONF_SERVICE_UUID): bt_uuid, + }), + cv.Optional(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEManufacturerDataAdvertiseTrigger), + cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + cv.Required(CONF_MANUFACTURER_ID): bt_uuid, + }), cv.Optional('scan_interval'): cv.invalid("This option has been removed in 1.14 (Reason: " "it never had an effect)"), @@ -103,6 +131,35 @@ def to_code(config): cg.add(var.set_scan_interval(int(params[CONF_INTERVAL].total_milliseconds / 0.625))) cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) cg.add(var.set_scan_active(params[CONF_ACTIVE])) + for conf in config.get(CONF_ON_BLE_ADVERTISE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + if CONF_MAC_ADDRESS in conf: + cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) + yield automation.build_automation(trigger, [(ESPBTDeviceConstRef, 'x')], conf) + for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + if len(conf[CONF_SERVICE_UUID]) == len(bt_uuid16_format): + cg.add(trigger.set_service_uuid16(as_hex(conf[CONF_SERVICE_UUID]))) + elif len(conf[CONF_SERVICE_UUID]) == len(bt_uuid32_format): + cg.add(trigger.set_service_uuid32(as_hex(conf[CONF_SERVICE_UUID]))) + elif len(conf[CONF_SERVICE_UUID]) == len(bt_uuid128_format): + uuid128 = as_hex_array(conf[CONF_SERVICE_UUID]) + cg.add(trigger.set_service_uuid128(uuid128)) + if CONF_MAC_ADDRESS in conf: + cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) + yield automation.build_automation(trigger, [(adv_data_t_const_ref, 'x')], conf) + for conf in config.get(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + if len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid16_format): + cg.add(trigger.set_manufacturer_uuid16(as_hex(conf[CONF_MANUFACTURER_ID]))) + elif len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid32_format): + cg.add(trigger.set_manufacturer_uuid32(as_hex(conf[CONF_MANUFACTURER_ID]))) + elif len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid128_format): + uuid128 = as_hex_array(conf[CONF_MANUFACTURER_ID]) + cg.add(trigger.set_manufacturer_uuid128(uuid128)) + if CONF_MAC_ADDRESS in conf: + cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) + yield automation.build_automation(trigger, [(adv_data_t_const_ref, 'x')], conf) @coroutine diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h new file mode 100644 index 0000000000..9df2587ede --- /dev/null +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -0,0 +1,82 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace esp32_ble_tracker { +class ESPBTAdvertiseTrigger : public Trigger, public ESPBTDeviceListener { + public: + explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } + void set_address(uint64_t address) { this->address_ = address; } + + bool parse_device(const ESPBTDevice &device) override { + if (this->address_ && device.address_uint64() != this->address_) { + return false; + } + this->trigger(device); + return true; + } + + protected: + uint64_t address_ = 0; +}; + +class BLEServiceDataAdvertiseTrigger : public Trigger, public ESPBTDeviceListener { + public: + explicit BLEServiceDataAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } + void set_address(uint64_t address) { this->address_ = address; } + void set_service_uuid16(uint16_t uuid) { this->uuid_ = ESPBTUUID::from_uint16(uuid); } + void set_service_uuid32(uint32_t uuid) { this->uuid_ = ESPBTUUID::from_uint32(uuid); } + void set_service_uuid128(uint8_t *uuid) { this->uuid_ = ESPBTUUID::from_raw(uuid); } + + bool parse_device(const ESPBTDevice &device) override { + if (this->address_ && device.address_uint64() != this->address_) { + return false; + } + for (auto &service_data : device.get_service_datas()) { + if (service_data.uuid == this->uuid_) { + this->trigger(service_data.data); + return true; + } + } + return false; + } + + protected: + uint64_t address_ = 0; + ESPBTUUID uuid_; +}; + +class BLEManufacturerDataAdvertiseTrigger : public Trigger, public ESPBTDeviceListener { + public: + explicit BLEManufacturerDataAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } + void set_address(uint64_t address) { this->address_ = address; } + void set_manufacturer_uuid16(uint16_t uuid) { this->uuid_ = ESPBTUUID::from_uint16(uuid); } + void set_manufacturer_uuid32(uint32_t uuid) { this->uuid_ = ESPBTUUID::from_uint32(uuid); } + void set_manufacturer_uuid128(uint8_t *uuid) { this->uuid_ = ESPBTUUID::from_raw(uuid); } + + bool parse_device(const ESPBTDevice &device) override { + if (this->address_ && device.address_uint64() != this->address_) { + return false; + } + for (auto &manufacturer_data : device.get_manufacturer_datas()) { + if (manufacturer_data.uuid == this->uuid_) { + this->trigger(manufacturer_data.data); + return true; + } + } + return false; + } + + protected: + uint64_t address_ = 0; + ESPBTUUID uuid_; +}; + +} // namespace esp32_ble_tracker +} // namespace esphome + +#endif diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index ab6bfa681c..5109af21fa 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -223,6 +223,22 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) { ret.uuid_.uuid.uuid128[i] = data[i]; return ret; } +ESPBTUUID ESPBTUUID::as_128bit() const { + if (this->uuid_.len == ESP_UUID_LEN_128) { + return *this; + } + uint8_t data[] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint32_t uuid32; + if (this->uuid_.len == ESP_UUID_LEN_32) { + uuid32 = this->uuid_.uuid.uuid32; + } else { + uuid32 = this->uuid_.uuid.uuid16; + } + for (uint8_t i = 0; i < this->uuid_.len; i++) { + data[12 + i] = ((uuid32 >> i * 8) & 0xFF); + } + return ESPBTUUID::from_raw(data); +} bool ESPBTUUID::contains(uint8_t data1, uint8_t data2) const { if (this->uuid_.len == ESP_UUID_LEN_16) { return (this->uuid_.uuid.uuid16 >> 8) == data2 || (this->uuid_.uuid.uuid16 & 0xFF) == data1; @@ -241,16 +257,43 @@ bool ESPBTUUID::contains(uint8_t data1, uint8_t data2) const { } return false; } +bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { + if (this->uuid_.len == uuid.uuid_.len) { + switch (this->uuid_.len) { + case ESP_UUID_LEN_16: + if (uuid.uuid_.uuid.uuid16 == this->uuid_.uuid.uuid16) { + return true; + } + break; + case ESP_UUID_LEN_32: + if (uuid.uuid_.uuid.uuid32 == this->uuid_.uuid.uuid32) { + return true; + } + break; + case ESP_UUID_LEN_128: + for (int i = 0; i < ESP_UUID_LEN_128; i++) { + if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) { + return false; + } + } + return true; + break; + } + } else { + return this->as_128bit() == uuid.as_128bit(); + } + return false; +} esp_bt_uuid_t ESPBTUUID::get_uuid() { return this->uuid_; } std::string ESPBTUUID::to_string() { char sbuf[64]; switch (this->uuid_.len) { case ESP_UUID_LEN_16: - sprintf(sbuf, "%02X:%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16); + sprintf(sbuf, "%02X:%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); break; case ESP_UUID_LEN_32: - sprintf(sbuf, "%02X:%02X:%02X:%02X", this->uuid_.uuid.uuid32 >> 24, this->uuid_.uuid.uuid32 >> 16, - this->uuid_.uuid.uuid32 >> 8, this->uuid_.uuid.uuid32); + sprintf(sbuf, "%02X:%02X:%02X:%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff), + (this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff); break; default: case ESP_UUID_LEN_128: diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 5456adbfe5..8d011abfe3 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -23,8 +23,12 @@ class ESPBTUUID { static ESPBTUUID from_raw(const uint8_t *data); + ESPBTUUID as_128bit() const; + bool contains(uint8_t data1, uint8_t data2) const; + bool operator==(const ESPBTUUID &uuid) const; + esp_bt_uuid_t get_uuid(); std::string to_string(); diff --git a/esphome/const.py b/esphome/const.py index 36ffbed254..8974009d30 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -235,6 +235,7 @@ CONF_MAC_ADDRESS = 'mac_address' CONF_MAINS_FILTER = 'mains_filter' CONF_MAKE_ID = 'make_id' CONF_MANUAL_IP = 'manual_ip' +CONF_MANUFACTURER_ID = 'manufacturer_id' CONF_MASK_DISTURBER = 'mask_disturber' CONF_MAX_CURRENT = 'max_current' CONF_MAX_DURATION = 'max_duration' @@ -280,6 +281,9 @@ CONF_NUM_LEDS = 'num_leds' CONF_NUMBER = 'number' CONF_OFFSET = 'offset' CONF_ON = 'on' +CONF_ON_BLE_ADVERTISE = 'on_ble_advertise' +CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = 'on_ble_manufacturer_data_advertise' +CONF_ON_BLE_SERVICE_DATA_ADVERTISE = 'on_ble_service_data_advertise' CONF_ON_BOOT = 'on_boot' CONF_ON_CLICK = 'on_click' CONF_ON_DOUBLE_CLICK = 'on_double_click' diff --git a/tests/test2.yaml b/tests/test2.yaml index bcc777b83b..d02c093b86 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -49,10 +49,6 @@ web_server: username: admin password: admin -deep_sleep: - run_duration: 20s - sleep_duration: 50s - as3935_i2c: irq_pin: GPIO12 @@ -233,6 +229,25 @@ binary_sensor: name: "Storm Alert" esp32_ble_tracker: + on_ble_advertise: + - mac_address: AC:37:43:77:5F:4C + then: + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + - then: + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + on_ble_service_data_advertise: + - service_uuid: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of service data is %i", x.size()); + on_ble_manufacturer_data_advertise: + - manufacturer_id: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of manufacturer data is %i", x.size()); + #esp32_ble_beacon: # type: iBeacon