mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Implement text_sensor based on ble_client (#3079)
Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
		
							
								
								
									
										121
									
								
								esphome/components/ble_client/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								esphome/components/ble_client/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
							
								
								
									
										38
									
								
								esphome/components/ble_client/text_sensor/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphome/components/ble_client/text_sensor/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<std::string>, 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 | ||||||
							
								
								
									
										137
									
								
								esphome/components/ble_client/text_sensor/ble_text_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								esphome/components/ble_client/text_sensor/ble_text_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										47
									
								
								esphome/components/ble_client/text_sensor/ble_text_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								esphome/components/ble_client/text_sensor/ble_text_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <esp_gattc_api.h> | ||||||
|  |  | ||||||
|  | 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 | ||||||
| @@ -2496,6 +2496,18 @@ globals: | |||||||
|     initial_value: "false" |     initial_value: "false" | ||||||
|  |  | ||||||
| text_sensor: | 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 |   - platform: mqtt_subscribe | ||||||
|     name: "MQTT Subscribe Text" |     name: "MQTT Subscribe Text" | ||||||
|     topic: "the/topic" |     topic: "the/topic" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user