mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -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; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										82
									
								
								esphome/components/esp32_ble_tracker/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								esphome/components/esp32_ble_tracker/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<const ESPBTDevice &>, 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<const adv_data_t &>, 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<const adv_data_t &>, 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 | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user