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:
puuu 2020-04-28 08:57:02 +09:00 committed by GitHub
parent 31ae337931
commit ba1222eae4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 221 additions and 66 deletions

View file

@ -43,35 +43,10 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
} }
} else { } else {
for (auto uuid : device.get_service_uuids()) { for (auto uuid : device.get_service_uuids()) {
switch (this->uuid_.get_uuid().len) { if (this->uuid_ == uuid) {
case ESP_UUID_LEN_16: this->publish_state(device.get_rssi());
if (uuid.get_uuid().len == ESP_UUID_LEN_16 && this->found_ = true;
uuid.get_uuid().uuid.uuid16 == this->uuid_.get_uuid().uuid.uuid16) { return true;
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;
} }
} }
} }

View file

@ -41,35 +41,10 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi
} }
} else { } else {
for (auto uuid : device.get_service_uuids()) { for (auto uuid : device.get_service_uuids()) {
switch (this->uuid_.get_uuid().len) { if (this->uuid_ == uuid) {
case ESP_UUID_LEN_16: this->publish_state(device.get_rssi());
if (uuid.get_uuid().len == ESP_UUID_LEN_16 && this->found_ = true;
uuid.get_uuid().uuid.uuid16 == this->uuid_.get_uuid().uuid.uuid16) { return true;
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;
} }
} }
} }

View file

@ -1,8 +1,11 @@
import re import re
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_ID, ESP_PLATFORM_ESP32, CONF_INTERVAL, \ 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 from esphome.core import coroutine
ESP_PLATFORMS = [ESP_PLATFORM_ESP32] ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
@ -15,6 +18,17 @@ CONF_ACTIVE = 'active'
esp32_ble_tracker_ns = cg.esphome_ns.namespace('esp32_ble_tracker') esp32_ble_tracker_ns = cg.esphome_ns.namespace('esp32_ble_tracker')
ESP32BLETracker = esp32_ble_tracker_ns.class_('ESP32BLETracker', cg.Component) ESP32BLETracker = esp32_ble_tracker_ns.class_('ESP32BLETracker', cg.Component)
ESPBTDeviceListener = esp32_ble_tracker_ns.class_('ESPBTDeviceListener') 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): 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_WINDOW, default='30ms'): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ACTIVE, default=True): cv.boolean, cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
}), validate_scan_parameters), }), 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: " cv.Optional('scan_interval'): cv.invalid("This option has been removed in 1.14 (Reason: "
"it never had an effect)"), "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_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_window(int(params[CONF_WINDOW].total_milliseconds / 0.625)))
cg.add(var.set_scan_active(params[CONF_ACTIVE])) 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 @coroutine

View 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

View file

@ -223,6 +223,22 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
ret.uuid_.uuid.uuid128[i] = data[i]; ret.uuid_.uuid.uuid128[i] = data[i];
return ret; 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 { bool ESPBTUUID::contains(uint8_t data1, uint8_t data2) const {
if (this->uuid_.len == ESP_UUID_LEN_16) { if (this->uuid_.len == ESP_UUID_LEN_16) {
return (this->uuid_.uuid.uuid16 >> 8) == data2 || (this->uuid_.uuid.uuid16 & 0xFF) == data1; 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; 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_; } esp_bt_uuid_t ESPBTUUID::get_uuid() { return this->uuid_; }
std::string ESPBTUUID::to_string() { std::string ESPBTUUID::to_string() {
char sbuf[64]; char sbuf[64];
switch (this->uuid_.len) { switch (this->uuid_.len) {
case ESP_UUID_LEN_16: 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; break;
case ESP_UUID_LEN_32: case ESP_UUID_LEN_32:
sprintf(sbuf, "%02X:%02X:%02X:%02X", this->uuid_.uuid.uuid32 >> 24, this->uuid_.uuid.uuid32 >> 16, sprintf(sbuf, "%02X:%02X:%02X:%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff),
this->uuid_.uuid.uuid32 >> 8, this->uuid_.uuid.uuid32); (this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff);
break; break;
default: default:
case ESP_UUID_LEN_128: case ESP_UUID_LEN_128:

View file

@ -23,8 +23,12 @@ class ESPBTUUID {
static ESPBTUUID from_raw(const uint8_t *data); static ESPBTUUID from_raw(const uint8_t *data);
ESPBTUUID as_128bit() const;
bool contains(uint8_t data1, uint8_t data2) const; bool contains(uint8_t data1, uint8_t data2) const;
bool operator==(const ESPBTUUID &uuid) const;
esp_bt_uuid_t get_uuid(); esp_bt_uuid_t get_uuid();
std::string to_string(); std::string to_string();

View file

@ -235,6 +235,7 @@ CONF_MAC_ADDRESS = 'mac_address'
CONF_MAINS_FILTER = 'mains_filter' CONF_MAINS_FILTER = 'mains_filter'
CONF_MAKE_ID = 'make_id' CONF_MAKE_ID = 'make_id'
CONF_MANUAL_IP = 'manual_ip' CONF_MANUAL_IP = 'manual_ip'
CONF_MANUFACTURER_ID = 'manufacturer_id'
CONF_MASK_DISTURBER = 'mask_disturber' CONF_MASK_DISTURBER = 'mask_disturber'
CONF_MAX_CURRENT = 'max_current' CONF_MAX_CURRENT = 'max_current'
CONF_MAX_DURATION = 'max_duration' CONF_MAX_DURATION = 'max_duration'
@ -280,6 +281,9 @@ CONF_NUM_LEDS = 'num_leds'
CONF_NUMBER = 'number' CONF_NUMBER = 'number'
CONF_OFFSET = 'offset' CONF_OFFSET = 'offset'
CONF_ON = 'on' 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_BOOT = 'on_boot'
CONF_ON_CLICK = 'on_click' CONF_ON_CLICK = 'on_click'
CONF_ON_DOUBLE_CLICK = 'on_double_click' CONF_ON_DOUBLE_CLICK = 'on_double_click'

View file

@ -49,10 +49,6 @@ web_server:
username: admin username: admin
password: admin password: admin
deep_sleep:
run_duration: 20s
sleep_duration: 50s
as3935_i2c: as3935_i2c:
irq_pin: GPIO12 irq_pin: GPIO12
@ -233,6 +229,25 @@ binary_sensor:
name: "Storm Alert" name: "Storm Alert"
esp32_ble_tracker: 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: #esp32_ble_beacon:
# type: iBeacon # type: iBeacon