diff --git a/esphome/components/ble_client/text_sensor/__init__.py b/esphome/components/ble_client/text_sensor/__init__.py new file mode 100644 index 0000000000..e1f97e4a01 --- /dev/null +++ b/esphome/components/ble_client/text_sensor/__init__.py @@ -0,0 +1,121 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor, ble_client, esp32_ble_tracker +from esphome.const import ( + CONF_ID, + CONF_TRIGGER_ID, + CONF_SERVICE_UUID, +) +from esphome import automation +from .. import ble_client_ns + +DEPENDENCIES = ["ble_client"] + +CONF_CHARACTERISTIC_UUID = "characteristic_uuid" +CONF_DESCRIPTOR_UUID = "descriptor_uuid" + +CONF_NOTIFY = "notify" +CONF_ON_NOTIFY = "on_notify" + +adv_data_t = cg.std_vector.template(cg.uint8) +adv_data_t_const_ref = adv_data_t.operator("ref").operator("const") + +BLETextSensor = ble_client_ns.class_( + "BLETextSensor", + text_sensor.TextSensor, + cg.PollingComponent, + ble_client.BLEClientNode, +) +BLETextSensorNotifyTrigger = ble_client_ns.class_( + "BLETextSensorNotifyTrigger", automation.Trigger.template(cg.std_string) +) + +CONFIG_SCHEMA = cv.All( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BLETextSensor), + 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_NOTIFY, default=False): cv.boolean, + cv.Optional(CONF_ON_NOTIFY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLETextSensorNotifyTrigger + ), + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(ble_client.BLE_CLIENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) + ) + elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): + cg.add( + var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) + ) + elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) + cg.add(var.set_service_uuid128(uuid128)) + + if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_char_uuid16( + esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) + ) + ) + elif len(config[CONF_CHARACTERISTIC_UUID]) == len( + esp32_ble_tracker.bt_uuid32_format + ): + cg.add( + var.set_char_uuid32( + esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) + ) + ) + elif len(config[CONF_CHARACTERISTIC_UUID]) == len( + esp32_ble_tracker.bt_uuid128_format + ): + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_CHARACTERISTIC_UUID] + ) + cg.add(var.set_char_uuid128(uuid128)) + + if CONF_DESCRIPTOR_UUID in config: + if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_descr_uuid16( + esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) + ) + ) + elif len(config[CONF_DESCRIPTOR_UUID]) == len( + esp32_ble_tracker.bt_uuid32_format + ): + cg.add( + var.set_descr_uuid32( + esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) + ) + ) + elif len(config[CONF_DESCRIPTOR_UUID]) == len( + esp32_ble_tracker.bt_uuid128_format + ): + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_DESCRIPTOR_UUID] + ) + cg.add(var.set_descr_uuid128(uuid128)) + + await cg.register_component(var, config) + await ble_client.register_ble_node(var, config) + cg.add(var.set_enable_notify(config[CONF_NOTIFY])) + await text_sensor.register_text_sensor(var, config) + for conf in config.get(CONF_ON_NOTIFY, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await ble_client.register_ble_node(trigger, config) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h new file mode 100644 index 0000000000..be85892c5a --- /dev/null +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/components/ble_client/text_sensor/ble_text_sensor.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace ble_client { + +class BLETextSensorNotifyTrigger : public Trigger, public BLETextSensor { + public: + explicit BLETextSensorNotifyTrigger(BLETextSensor *sensor) { sensor_ = sensor; } + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { + switch (event) { + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->sensor_->node_state = espbt::ClientState::ESTABLISHED; + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + break; + this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); + } + default: + break; + } + } + + protected: + BLETextSensor *sensor_; +}; + +} // namespace ble_client +} // namespace esphome + +#endif diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp new file mode 100644 index 0000000000..c4d175faa4 --- /dev/null +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -0,0 +1,137 @@ +#include "ble_text_sensor.h" + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace ble_client { + +static const char *const TAG = "ble_text_sensor"; + +static const std::string EMPTY = ""; + +uint32_t BLETextSensor::hash_base() { return 193967603UL; } + +void BLETextSensor::loop() {} + +void BLETextSensor::dump_config() { + LOG_TEXT_SENSOR("", "BLE Text Sensor", this); + ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str()); + ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str()); + ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str()); + ESP_LOGCONFIG(TAG, " Descriptor UUID : %s", this->descr_uuid_.to_string().c_str()); + ESP_LOGCONFIG(TAG, " Notifications : %s", YESNO(this->notify_)); + LOG_UPDATE_INTERVAL(this); +} + +void BLETextSensor::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(EMPTY); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->handle = 0; + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + if (chr == nullptr) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), + this->char_uuid_.to_string().c_str()); + break; + } + this->handle = chr->handle; + if (this->descr_uuid_.get_uuid().len > 0) { + auto *descr = chr->get_descriptor(this->descr_uuid_); + if (descr == nullptr) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", + this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), + this->descr_uuid_.to_string().c_str()); + break; + } + this->handle = descr->handle; + } + if (this->notify_) { + auto status = + esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + if (status) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); + } + } else { + this->node_state = espbt::ClientState::ESTABLISHED; + } + break; + } + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->parent()->conn_id) + break; + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + break; + } + if (param->read.handle == this->handle) { + this->status_clear_warning(); + this->publish_state(this->parse_data(param->read.value, param->read.value_len)); + } + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + break; + ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), + param->notify.handle, param->notify.value[0]); + this->publish_state(this->parse_data(param->notify.value, param->notify.value_len)); + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::ESTABLISHED; + break; + } + default: + break; + } +} + +std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) { + std::string text(value, value + value_len); + return text; +} + +void BLETextSensor::update() { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); + return; + } + if (this->handle == 0) { + ESP_LOGW(TAG, "[%s] Cannot poll, no service or characteristic found", this->get_name().c_str()); + return; + } + + auto status = + esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + if (status) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status); + } +} + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.h b/esphome/components/ble_client/text_sensor/ble_text_sensor.h new file mode 100644 index 0000000000..37537307de --- /dev/null +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.h @@ -0,0 +1,47 @@ +#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/text_sensor/text_sensor.h" + +#ifdef USE_ESP32 +#include + +namespace esphome { +namespace ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, public BLEClientNode { + public: + void loop() override; + void update() 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; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void set_enable_notify(bool notify) { this->notify_ = notify; } + std::string parse_data(uint8_t *value, uint16_t value_len); + uint16_t handle; + + protected: + uint32_t hash_base() override; + bool notify_; + espbt::ESPBTUUID service_uuid_; + espbt::ESPBTUUID char_uuid_; + espbt::ESPBTUUID descr_uuid_; +}; + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/tests/test1.yaml b/tests/test1.yaml index 9b38d549b3..3d533f33fc 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2496,6 +2496,18 @@ globals: initial_value: "false" text_sensor: + - platform: ble_client + ble_client_id: ble_foo + name: 'Sensor Location' + service_uuid: '180d' + characteristic_uuid: '2a38' + descriptor_uuid: '2902' + notify: true + update_interval: never + on_notify: + then: + - lambda: |- + ESP_LOGD("green_btn", "Location changed: %s", x); - platform: mqtt_subscribe name: "MQTT Subscribe Text" topic: "the/topic"