From 9f9980e3382038ca43270110d8a6f1e5c946eb08 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Wed, 12 Oct 2022 04:23:56 +0200 Subject: [PATCH] Add ble RSSI sensor for connected devices (#3860) --- esphome/components/ble_client/ble_client.cpp | 7 ++ esphome/components/ble_client/ble_client.h | 5 +- .../components/ble_client/sensor/__init__.py | 95 ++++++++++++++----- .../ble_client/sensor/ble_rssi_sensor.cpp | 78 +++++++++++++++ .../ble_client/sensor/ble_rssi_sensor.h | 31 ++++++ tests/test1.yaml | 6 ++ 6 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 esphome/components/ble_client/sensor/ble_rssi_sensor.cpp create mode 100644 esphome/components/ble_client/sensor/ble_rssi_sensor.h diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 3f86df32f5..a757d6f903 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -64,6 +64,13 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } } +void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + BLEClientBase::gap_event_handler(event, param); + + for (auto *node : this->nodes_) + node->gap_event_handler(event, param); +} + void BLEClient::set_state(espbt::ClientState state) { BLEClientBase::set_state(state); for (auto &node : nodes_) diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 0f8373ab1f..177cab2e3c 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -27,7 +27,8 @@ class BLEClientNode { public: virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) = 0; - virtual void loop(){}; + virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {} + virtual void loop() {} void set_address(uint64_t address) { address_ = address; } espbt::ESPBTClient *client; // This should be transitioned to Established once the node no longer needs @@ -51,6 +52,8 @@ class BLEClient : public BLEClientBase { void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; + + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; bool parse_device(const espbt::ESPBTDevice &device) override; void set_enabled(bool enabled); diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index e8f84d2542..c9bf2995b1 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -5,7 +5,11 @@ from esphome.const import ( CONF_CHARACTERISTIC_UUID, CONF_LAMBDA, CONF_TRIGGER_ID, + CONF_TYPE, CONF_SERVICE_UUID, + DEVICE_CLASS_SIGNAL_STRENGTH, + STATE_CLASS_MEASUREMENT, + UNIT_DECIBEL_MILLIWATT, ) from esphome import automation from .. import ble_client_ns @@ -16,6 +20,8 @@ CONF_DESCRIPTOR_UUID = "descriptor_uuid" CONF_NOTIFY = "notify" CONF_ON_NOTIFY = "on_notify" +TYPE_CHARACTERISTIC = "characteristic" +TYPE_RSSI = "rssi" adv_data_t = cg.std_vector.template(cg.uint8) adv_data_t_const_ref = adv_data_t.operator("ref").operator("const") @@ -27,33 +33,67 @@ BLESensorNotifyTrigger = ble_client_ns.class_( "BLESensorNotifyTrigger", automation.Trigger.template(cg.float_) ) -CONFIG_SCHEMA = cv.All( - sensor.sensor_schema( - BLESensor, - accuracy_decimals=0, - ) - .extend( - { - cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, - cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, - cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_NOTIFY, default=False): cv.boolean, - cv.Optional(CONF_ON_NOTIFY): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - BLESensorNotifyTrigger - ), - } - ), - } - ) - .extend(cv.polling_component_schema("60s")) - .extend(ble_client.BLE_CLIENT_SCHEMA) +BLEClientRssiSensor = ble_client_ns.class_( + "BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode ) -async def to_code(config): +def checkType(value): + if CONF_TYPE not in value and CONF_SERVICE_UUID in value: + raise cv.Invalid( + "Looks like you're trying to create a ble characteristic sensor. Please add `type: characteristic` to your sensor config." + ) + return value + + +CONFIG_SCHEMA = cv.All( + checkType, + cv.typed_schema( + { + TYPE_CHARACTERISTIC: sensor.sensor_schema( + BLESensor, + accuracy_decimals=0, + ) + .extend(cv.polling_component_schema("60s")) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend( + { + cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_NOTIFY, default=False): cv.boolean, + cv.Optional(CONF_ON_NOTIFY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLESensorNotifyTrigger + ), + } + ), + } + ), + TYPE_RSSI: sensor.sensor_schema( + BLEClientRssiSensor, + accuracy_decimals=0, + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(ble_client.BLE_CLIENT_SCHEMA), + }, + lower=True, + ), +) + + +async def rssi_sensor_to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await ble_client.register_ble_node(var, config) + + +async def characteristic_sensor_to_code(config): var = await sensor.new_sensor(config) if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): cg.add( @@ -125,3 +165,10 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await ble_client.register_ble_node(trigger, config) await automation.build_automation(trigger, [(float, "x")], conf) + + +async def to_code(config): + if config[CONF_TYPE] == TYPE_RSSI: + await rssi_sensor_to_code(config) + elif config[CONF_TYPE] == TYPE_CHARACTERISTIC: + await characteristic_sensor_to_code(config) diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp new file mode 100644 index 0000000000..13e51ed5b3 --- /dev/null +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp @@ -0,0 +1,78 @@ +#include "ble_rssi_sensor.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace ble_client { + +static const char *const TAG = "ble_rssi_sensor"; + +void BLEClientRSSISensor::loop() {} + +void BLEClientRSSISensor::dump_config() { + LOG_SENSOR("", "BLE Client RSSI Sensor", this); + ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str()); + LOG_UPDATE_INTERVAL(this); +} + +void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + if (param->open.status == ESP_GATT_OK) { + ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str()); + break; + } + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); + this->status_set_warning(); + this->publish_state(NAN); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + this->node_state = espbt::ClientState::ESTABLISHED; + break; + default: + break; + } +} + +void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + switch (event) { + // server response on RSSI request: + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) { + int8_t rssi = param->read_rssi_cmpl.rssi; + ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi); + this->publish_state(rssi); + } + break; + default: + break; + } +} + +void BLEClientRSSISensor::update() { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); + return; + } + + ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str()); + auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda()); + if (status != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status); + this->status_set_warning(); + this->publish_state(NAN); + } +} + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.h b/esphome/components/ble_client/sensor/ble_rssi_sensor.h new file mode 100644 index 0000000000..028df83832 --- /dev/null +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" + +#ifdef USE_ESP32 +#include + +namespace esphome { +namespace ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, public BLEClientNode { + public: + void loop() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; +}; + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/tests/test1.yaml b/tests/test1.yaml index e213a8b041..01672fcc01 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -321,6 +321,7 @@ mcp23s17: sensor: - platform: ble_client + type: characteristic ble_client_id: ble_foo name: Green iTag btn service_uuid: ffe0 @@ -335,6 +336,11 @@ sensor: then: - lambda: |- ESP_LOGD("green_btn", "Button was pressed, val%f", x); + - platform: ble_client + type: rssi + ble_client_id: ble_foo + name: Green iTag RSSI + update_interval: 15s - platform: adc pin: A0 name: Living Room Brightness