mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Ble client additions and fixes (#5277)
* Add config to disable auto-connect of BLE client. Correct initialise MAC address of BLE client. * Checkpont * Fixes for automation progress. * Fixes for automation progress. * Checkpoint; fix notify for ble_client * Fix BLE client binary_output * Various fixes * Consider notifications on when receiving REG_FOR event. * Add testing branch to workflow * Add workflow * CI changes * CI changes * CI clang * CI changes * CI changes * Add comment about logging macros * Add test, sanitise comment * Revert testing change to ci config * Update codeowners * Revert ci config change * Fix some state changes * Add default case. * Minor fixes * Add auto-connect to logconfig
This commit is contained in:
		| @@ -52,7 +52,7 @@ esphome/components/bk72xx/* @kuba2k2 | ||||
| esphome/components/bl0939/* @ziceva | ||||
| esphome/components/bl0940/* @tobias- | ||||
| esphome/components/bl0942/* @dbuezas | ||||
| esphome/components/ble_client/* @buxtronix | ||||
| esphome/components/ble_client/* @buxtronix @clydebarrow | ||||
| esphome/components/bluetooth_proxy/* @jesserockz | ||||
| esphome/components/bme680_bsec/* @trvrnrth | ||||
| esphome/components/bmi160/* @flaviut | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.automation import maybe_simple_id | ||||
| from esphome.components import esp32_ble_tracker, esp32_ble_client | ||||
| from esphome.const import ( | ||||
|     CONF_CHARACTERISTIC_UUID, | ||||
| @@ -15,7 +16,7 @@ from esphome.const import ( | ||||
| from esphome import automation | ||||
|  | ||||
| AUTO_LOAD = ["esp32_ble_client"] | ||||
| CODEOWNERS = ["@buxtronix"] | ||||
| CODEOWNERS = ["@buxtronix", "@clydebarrow"] | ||||
| DEPENDENCIES = ["esp32_ble_tracker"] | ||||
|  | ||||
| ble_client_ns = cg.esphome_ns.namespace("ble_client") | ||||
| @@ -43,6 +44,10 @@ BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_( | ||||
|  | ||||
| # Actions | ||||
| BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) | ||||
| BLEConnectAction = ble_client_ns.class_("BLEClientConnectAction", automation.Action) | ||||
| BLEDisconnectAction = ble_client_ns.class_( | ||||
|     "BLEClientDisconnectAction", automation.Action | ||||
| ) | ||||
| BLEPasskeyReplyAction = ble_client_ns.class_( | ||||
|     "BLEClientPasskeyReplyAction", automation.Action | ||||
| ) | ||||
| @@ -58,6 +63,7 @@ CONF_ACCEPT = "accept" | ||||
| CONF_ON_PASSKEY_REQUEST = "on_passkey_request" | ||||
| CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" | ||||
| CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" | ||||
| CONF_AUTO_CONNECT = "auto_connect" | ||||
|  | ||||
| # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | ||||
| # enforce this in yaml checks. | ||||
| @@ -69,6 +75,7 @@ CONFIG_SCHEMA = ( | ||||
|             cv.GenerateID(): cv.declare_id(BLEClient), | ||||
|             cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|             cv.Optional(CONF_NAME): cv.string, | ||||
|             cv.Optional(CONF_AUTO_CONNECT, default=True): cv.boolean, | ||||
|             cv.Optional(CONF_ON_CONNECT): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
| @@ -135,6 +142,12 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema( | ||||
|     } | ||||
| ) | ||||
|  | ||||
| BLE_CONNECT_ACTION_SCHEMA = maybe_simple_id( | ||||
|     { | ||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), | ||||
| @@ -157,6 +170,24 @@ BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema( | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.disconnect", BLEDisconnectAction, BLE_CONNECT_ACTION_SCHEMA | ||||
| ) | ||||
| async def ble_disconnect_to_code(config, action_id, template_arg, args): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.connect", BLEConnectAction, BLE_CONNECT_ACTION_SCHEMA | ||||
| ) | ||||
| async def ble_connect_to_code(config, action_id, template_arg, args): | ||||
|     parent = await cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, parent) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA | ||||
| ) | ||||
| @@ -261,6 +292,7 @@ async def to_code(config): | ||||
|     await cg.register_component(var, config) | ||||
|     await esp32_ble_tracker.register_client(var, config) | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|     cg.add(var.set_auto_connect(config[CONF_AUTO_CONNECT])) | ||||
|     for conf in config.get(CONF_ON_CONNECT, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|   | ||||
| @@ -2,76 +2,10 @@ | ||||
|  | ||||
| #include "automation.h" | ||||
|  | ||||
| #include <esp_bt_defs.h> | ||||
| #include <esp_gap_ble_api.h> | ||||
| #include <esp_gattc_api.h> | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ble_client { | ||||
| static const char *const TAG = "ble_client.automation"; | ||||
|  | ||||
| void BLEWriterClientNode::write(const std::vector<uint8_t> &value) { | ||||
|   if (this->node_state != espbt::ClientState::ESTABLISHED) { | ||||
|     ESP_LOGW(TAG, "Cannot write to BLE characteristic - not connected"); | ||||
|     return; | ||||
|   } else if (this->ble_char_handle_ == 0) { | ||||
|     ESP_LOGW(TAG, "Cannot write to BLE characteristic - characteristic not found"); | ||||
|     return; | ||||
|   } | ||||
|   esp_gatt_write_type_t write_type; | ||||
|   if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) { | ||||
|     write_type = ESP_GATT_WRITE_TYPE_RSP; | ||||
|     ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP"); | ||||
|   } else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) { | ||||
|     write_type = ESP_GATT_WRITE_TYPE_NO_RSP; | ||||
|     ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); | ||||
|   } else { | ||||
|     ESP_LOGE(TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str()); | ||||
|     return; | ||||
|   } | ||||
|   ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); | ||||
|   esp_err_t err = | ||||
|       esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_, | ||||
|                                value.size(), const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); | ||||
|   if (err != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void BLEWriterClientNode::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_REG_EVT: | ||||
|       break; | ||||
|     case ESP_GATTC_OPEN_EVT: | ||||
|       this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|       ESP_LOGD(TAG, "Connection established with %s", ble_client_->address_str().c_str()); | ||||
|       break; | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|       auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||
|       if (chr == nullptr) { | ||||
|         ESP_LOGW("ble_write_action", "Characteristic %s was not found in service %s", | ||||
|                  this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str()); | ||||
|         break; | ||||
|       } | ||||
|       this->ble_char_handle_ = chr->handle; | ||||
|       this->char_props_ = chr->properties; | ||||
|       this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|       ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), | ||||
|                ble_client_->address_str().c_str()); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_DISCONNECT_EVT: | ||||
|       this->node_state = espbt::ClientState::IDLE; | ||||
|       this->ble_char_handle_ = 0; | ||||
|       ESP_LOGD(TAG, "Disconnected from %s", ble_client_->address_str().c_str()); | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| const char *const Automation::TAG = "ble_client.automation"; | ||||
|  | ||||
| }  // namespace ble_client | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -7,9 +7,19 @@ | ||||
|  | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/components/ble_client/ble_client.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ble_client { | ||||
|  | ||||
| // placeholder class for static TAG . | ||||
| class Automation { | ||||
|  public: | ||||
|   // could be made inline with C++17 | ||||
|   static const char *const TAG; | ||||
| }; | ||||
|  | ||||
| // implement on_connect automation. | ||||
| class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { | ||||
|  public: | ||||
|   explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
| @@ -23,17 +33,28 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // on_disconnect automation | ||||
| class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { | ||||
|  public: | ||||
|   explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() 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 { | ||||
|     if (event == ESP_GATTC_DISCONNECT_EVT && | ||||
|         memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0) | ||||
|       this->trigger(); | ||||
|     if (event == ESP_GATTC_SEARCH_CMPL_EVT) | ||||
|       this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|     // test for CLOSE and not DISCONNECT - DISCONNECT can occur even if no virtual connection (OPEN event) occurred. | ||||
|     // So this will not trigger unless a complete open has previously succeeded. | ||||
|     switch (event) { | ||||
|       case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|         this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|         break; | ||||
|       } | ||||
|       case ESP_GATTC_CLOSE_EVT: { | ||||
|         this->trigger(); | ||||
|         break; | ||||
|       } | ||||
|       default: { | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| @@ -42,10 +63,8 @@ class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode { | ||||
|   explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|     if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) | ||||
|       this->trigger(); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| @@ -54,10 +73,8 @@ class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLE | ||||
|   explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|       uint32_t passkey = param->ble_security.key_notif.passkey; | ||||
|       this->trigger(passkey); | ||||
|     if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) { | ||||
|       this->trigger(param->ble_security.key_notif.passkey); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| @@ -67,24 +84,20 @@ class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, publi | ||||
|   explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||
|   void loop() override {} | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { | ||||
|     if (event == ESP_GAP_BLE_NC_REQ_EVT && | ||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { | ||||
|       uint32_t passkey = param->ble_security.key_notif.passkey; | ||||
|       this->trigger(passkey); | ||||
|     if (event == ESP_GAP_BLE_NC_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) { | ||||
|       this->trigger(param->ble_security.key_notif.passkey); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class BLEWriterClientNode : public BLEClientNode { | ||||
| // implement the ble_client.ble_write action. | ||||
| template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, public BLEClientNode { | ||||
|  public: | ||||
|   BLEWriterClientNode(BLEClient *ble_client) { | ||||
|   BLEClientWriteAction(BLEClient *ble_client) { | ||||
|     ble_client->register_ble_node(this); | ||||
|     ble_client_ = ble_client; | ||||
|   } | ||||
|  | ||||
|   // Attempts to write the contents of value to char_uuid_. | ||||
|   void write(const std::vector<uint8_t> &value); | ||||
|  | ||||
|   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); } | ||||
| @@ -93,29 +106,6 @@ class BLEWriterClientNode : public BLEClientNode { | ||||
|   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 gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|  | ||||
|  private: | ||||
|   BLEClient *ble_client_; | ||||
|   int ble_char_handle_ = 0; | ||||
|   esp_gatt_char_prop_t char_props_; | ||||
|   espbt::ESPBTUUID service_uuid_; | ||||
|   espbt::ESPBTUUID char_uuid_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, public BLEWriterClientNode { | ||||
|  public: | ||||
|   BLEClientWriteAction(BLEClient *ble_client) : BLEWriterClientNode(ble_client) {} | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     if (has_simple_value_) { | ||||
|       return write(this->value_simple_); | ||||
|     } else { | ||||
|       return write(this->value_template_(x...)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) { | ||||
|     this->value_template_ = std::move(func); | ||||
|     has_simple_value_ = false; | ||||
| @@ -126,10 +116,94 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ | ||||
|     has_simple_value_ = true; | ||||
|   } | ||||
|  | ||||
|   void play(Ts... x) override {} | ||||
|  | ||||
|   void play_complex(Ts... x) override { | ||||
|     this->num_running_++; | ||||
|     this->var_ = std::make_tuple(x...); | ||||
|     auto value = this->has_simple_value_ ? this->value_simple_ : this->value_template_(x...); | ||||
|     // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work. | ||||
|     if (!write(value)) | ||||
|       this->play_next_(x...); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Note about logging: the esph_log_X macros are used here because the CI checks complain about use of the ESP LOG | ||||
|    * macros in header files (Can't even write it in a comment!) | ||||
|    * Not sure why, because they seem to work just fine. | ||||
|    * The problem is that the implementation of a templated class can't be placed in a .cpp file when using C++ less than | ||||
|    * 17, so the methods have to be here.  The esph_log_X macros are equivalent in function, but don't trigger the CI | ||||
|    * errors. | ||||
|    */ | ||||
|   // initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event. | ||||
|   bool write(const std::vector<uint8_t> &value) { | ||||
|     if (this->node_state != espbt::ClientState::ESTABLISHED) { | ||||
|       esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected"); | ||||
|       return false; | ||||
|     } | ||||
|     esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); | ||||
|     esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), | ||||
|                                              this->char_handle_, value.size(), const_cast<uint8_t *>(value.data()), | ||||
|                                              this->write_type_, ESP_GATT_AUTH_REQ_NONE); | ||||
|     if (err != ESP_OK) { | ||||
|       esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); | ||||
|       return false; | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   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_WRITE_CHAR_EVT: | ||||
|         // upstream code checked the MAC address, verify the characteristic. | ||||
|         if (param->write.handle == this->char_handle_) | ||||
|           this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); }); | ||||
|         break; | ||||
|       case ESP_GATTC_DISCONNECT_EVT: | ||||
|         if (this->num_running_ != 0) | ||||
|           this->stop_complex(); | ||||
|         break; | ||||
|       case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|         auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||
|         if (chr == nullptr) { | ||||
|           esph_log_w("ble_write_action", "Characteristic %s was not found in service %s", | ||||
|                      this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str()); | ||||
|           break; | ||||
|         } | ||||
|         this->char_handle_ = chr->handle; | ||||
|         this->char_props_ = chr->properties; | ||||
|         if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) { | ||||
|           this->write_type_ = ESP_GATT_WRITE_TYPE_RSP; | ||||
|           esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP"); | ||||
|         } else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) { | ||||
|           this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; | ||||
|           esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); | ||||
|         } else { | ||||
|           esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str()); | ||||
|           break; | ||||
|         } | ||||
|         this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|         esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), | ||||
|                    ble_client_->address_str().c_str()); | ||||
|         break; | ||||
|       } | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *ble_client_; | ||||
|   bool has_simple_value_ = true; | ||||
|   std::vector<uint8_t> value_simple_; | ||||
|   std::function<std::vector<uint8_t>(Ts...)> value_template_{}; | ||||
|   espbt::ESPBTUUID service_uuid_; | ||||
|   espbt::ESPBTUUID char_uuid_; | ||||
|   std::tuple<Ts...> var_{}; | ||||
|   uint16_t char_handle_{}; | ||||
|   esp_gatt_char_prop_t char_props_{}; | ||||
|   esp_gatt_write_type_t write_type_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> { | ||||
| @@ -212,6 +286,92 @@ template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> | ||||
|   BLEClient *parent_{nullptr}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientConnectAction : public Action<Ts...>, public BLEClientNode { | ||||
|  public: | ||||
|   BLEClientConnectAction(BLEClient *ble_client) { | ||||
|     ble_client->register_ble_node(this); | ||||
|     ble_client_ = ble_client; | ||||
|   } | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override { | ||||
|     if (this->num_running_ == 0) | ||||
|       return; | ||||
|     switch (event) { | ||||
|       case ESP_GATTC_SEARCH_CMPL_EVT: | ||||
|         this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|         this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); }); | ||||
|         break; | ||||
|       // if the connection is closed, terminate the automation chain. | ||||
|       case ESP_GATTC_DISCONNECT_EVT: | ||||
|         this->stop_complex(); | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // not used since we override play_complex_ | ||||
|   void play(Ts... x) override {} | ||||
|  | ||||
|   void play_complex(Ts... x) override { | ||||
|     // it makes no sense to have multiple instances of this running at the same time. | ||||
|     // this would occur only if the same automation was re-triggered while still | ||||
|     // running. So just cancel the second chain if this is detected. | ||||
|     if (this->num_running_ != 0) { | ||||
|       this->stop_complex(); | ||||
|       return; | ||||
|     } | ||||
|     this->num_running_++; | ||||
|     if (this->node_state == espbt::ClientState::ESTABLISHED) { | ||||
|       this->play_next_(x...); | ||||
|     } else { | ||||
|       this->var_ = std::make_tuple(x...); | ||||
|       this->ble_client_->connect(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *ble_client_; | ||||
|   std::tuple<Ts...> var_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BLEClientDisconnectAction : public Action<Ts...>, public BLEClientNode { | ||||
|  public: | ||||
|   BLEClientDisconnectAction(BLEClient *ble_client) { | ||||
|     ble_client->register_ble_node(this); | ||||
|     ble_client_ = ble_client; | ||||
|   } | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override { | ||||
|     if (this->num_running_ == 0) | ||||
|       return; | ||||
|     switch (event) { | ||||
|       case ESP_GATTC_CLOSE_EVT: | ||||
|       case ESP_GATTC_DISCONNECT_EVT: | ||||
|         this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); }); | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // not used since we override play_complex_ | ||||
|   void play(Ts... x) override {} | ||||
|  | ||||
|   void play_complex(Ts... x) override { | ||||
|     this->num_running_++; | ||||
|     if (this->node_state == espbt::ClientState::IDLE) { | ||||
|       this->play_next_(x...); | ||||
|     } else { | ||||
|       this->var_ = std::make_tuple(x...); | ||||
|       this->ble_client_->disconnect(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   BLEClient *ble_client_; | ||||
|   std::tuple<Ts...> var_{}; | ||||
| }; | ||||
| }  // namespace ble_client | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -26,6 +26,7 @@ void BLEClient::loop() { | ||||
| void BLEClient::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "BLE Client:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Address: %s", this->address_str().c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  Auto-Connect: %s", TRUEFALSE(this->auto_connect_)); | ||||
| } | ||||
|  | ||||
| bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { | ||||
| @@ -37,31 +38,24 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { | ||||
| void BLEClient::set_enabled(bool enabled) { | ||||
|   if (enabled == this->enabled) | ||||
|     return; | ||||
|   if (!enabled && this->state() != espbt::ClientState::IDLE) { | ||||
|     ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); | ||||
|     auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); | ||||
|     if (ret) { | ||||
|       ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret); | ||||
|     } | ||||
|   } | ||||
|   this->enabled = enabled; | ||||
|   if (!enabled) { | ||||
|     ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); | ||||
|     this->disconnect(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, | ||||
|                                     esp_ble_gattc_cb_param_t *param) { | ||||
|   bool all_established = this->all_nodes_established_(); | ||||
|  | ||||
|   if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param)) | ||||
|     return false; | ||||
|  | ||||
|   for (auto *node : this->nodes_) | ||||
|     node->gattc_event_handler(event, esp_gattc_if, param); | ||||
|  | ||||
|   // Delete characteristics after clients have used them to save RAM. | ||||
|   if (!all_established && this->all_nodes_established_()) { | ||||
|     for (auto &svc : this->services_) | ||||
|       delete svc;  // NOLINT(cppcoreguidelines-owning-memory) | ||||
|     this->services_.clear(); | ||||
|   if (!this->services_.empty() && this->all_nodes_established_()) { | ||||
|     this->release_services(); | ||||
|     ESP_LOGD(TAG, "All clients established, services released"); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|   | ||||
| @@ -19,26 +19,36 @@ void BLEBinaryOutput::dump_config() { | ||||
| void BLEBinaryOutput::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: | ||||
|       this->client_state_ = espbt::ClientState::ESTABLISHED; | ||||
|       ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str()); | ||||
|       break; | ||||
|     case ESP_GATTC_DISCONNECT_EVT: | ||||
|       ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str()); | ||||
|       this->client_state_ = espbt::ClientState::IDLE; | ||||
|       break; | ||||
|     case ESP_GATTC_WRITE_CHAR_EVT: { | ||||
|       if (param->write.status == 0) { | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|       auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||
|       if (chr == nullptr) { | ||||
|         ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str()); | ||||
|         ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(), | ||||
|                  this->service_uuid_.to_string().c_str()); | ||||
|         break; | ||||
|       } | ||||
|       if (param->write.handle == chr->handle) { | ||||
|         ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); | ||||
|       this->char_handle_ = chr->handle; | ||||
|       this->char_props_ = chr->properties; | ||||
|       if (this->require_response_ && this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) { | ||||
|         this->write_type_ = ESP_GATT_WRITE_TYPE_RSP; | ||||
|         ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP"); | ||||
|       } else if (!this->require_response_ && this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) { | ||||
|         this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; | ||||
|         ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); | ||||
|       } else { | ||||
|         ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(), | ||||
|                  this->require_response_ ? "" : "out"); | ||||
|         break; | ||||
|       } | ||||
|       this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|       ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), | ||||
|                this->parent()->address_str().c_str()); | ||||
|       this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_WRITE_CHAR_EVT: { | ||||
|       if (param->write.handle == this->char_handle_) { | ||||
|         if (param->write.status != 0) | ||||
|           ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
| @@ -48,26 +58,18 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i | ||||
| } | ||||
|  | ||||
| void BLEBinaryOutput::write_state(bool state) { | ||||
|   if (this->client_state_ != espbt::ClientState::ESTABLISHED) { | ||||
|   if (this->node_state != espbt::ClientState::ESTABLISHED) { | ||||
|     ESP_LOGW(TAG, "[%s] Not connected to BLE client.  State update can not be written.", | ||||
|              this->char_uuid_.to_string().c_str()); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||
|   if (chr == nullptr) { | ||||
|     ESP_LOGW(TAG, "[%s] Characteristic not found.  State update can not be written.", | ||||
|              this->char_uuid_.to_string().c_str()); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   uint8_t state_as_uint = (uint8_t) state; | ||||
|   ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); | ||||
|   if (this->require_response_) { | ||||
|     chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP); | ||||
|   } else { | ||||
|     chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP); | ||||
|   } | ||||
|   esp_err_t err = | ||||
|       esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, | ||||
|                                sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE); | ||||
|   if (err != ESP_GATT_OK) | ||||
|     ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err); | ||||
| } | ||||
|  | ||||
| }  // namespace ble_client | ||||
|   | ||||
| @@ -32,7 +32,9 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi | ||||
|   bool require_response_; | ||||
|   espbt::ESPBTUUID service_uuid_; | ||||
|   espbt::ESPBTUUID char_uuid_; | ||||
|   espbt::ClientState client_state_; | ||||
|   uint16_t char_handle_{}; | ||||
|   esp_gatt_char_prop_t char_props_{}; | ||||
|   esp_gatt_write_type_t write_type_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace ble_client | ||||
|   | ||||
| @@ -14,15 +14,17 @@ class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor { | ||||
|   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; | ||||
|       case ESP_GATTC_NOTIFY_EVT: { | ||||
|         if (param->notify.handle == this->sensor_->handle) | ||||
|           this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); | ||||
|         break; | ||||
|       } | ||||
|       case ESP_GATTC_NOTIFY_EVT: { | ||||
|         if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || | ||||
|             param->notify.handle != this->sensor_->handle) | ||||
|           break; | ||||
|         this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); | ||||
|       case ESP_GATTC_REG_FOR_NOTIFY_EVT: { | ||||
|         // confirms notifications are being listened for. While enabling of notifications may still be in | ||||
|         // progress by the parent, we assume it will happen. | ||||
|         if (param->reg_for_notify.status == ESP_GATT_OK && param->reg_for_notify.handle == this->sensor_->handle) | ||||
|           this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|         break; | ||||
|       } | ||||
|       default: | ||||
|         break; | ||||
|   | ||||
| @@ -22,26 +22,19 @@ void BLEClientRSSISensor::dump_config() { | ||||
| 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()); | ||||
|     case ESP_GATTC_CLOSE_EVT: { | ||||
|       this->status_set_warning(); | ||||
|       this->publish_state(NAN); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|       this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|       if (this->should_update_) { | ||||
|         this->should_update_ = false; | ||||
|         this->get_rssi_(); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
|   | ||||
| @@ -33,7 +33,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_DISCONNECT_EVT: { | ||||
|     case ESP_GATTC_CLOSE_EVT: { | ||||
|       ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); | ||||
|       this->status_set_warning(); | ||||
|       this->publish_state(NAN); | ||||
| @@ -74,8 +74,6 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_READ_CHAR_EVT: { | ||||
|       if (param->read.conn_id != this->parent()->get_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; | ||||
| @@ -87,15 +85,23 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_NOTIFY_EVT: { | ||||
|       if (param->notify.conn_id != this->parent()->get_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(), | ||||
|       ESP_LOGD(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), | ||||
|                param->notify.handle, param->notify.value[0]); | ||||
|       if (param->notify.handle != this->handle) | ||||
|         break; | ||||
|       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; | ||||
|       if (param->reg_for_notify.handle == this->handle) { | ||||
|         if (param->reg_for_notify.status != ESP_GATT_OK) { | ||||
|           ESP_LOGW(TAG, "Error registering for notifications at handle %d, status=%d", param->reg_for_notify.handle, | ||||
|                    param->reg_for_notify.status); | ||||
|           break; | ||||
|         } | ||||
|         this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|         ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str()); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     default: | ||||
|   | ||||
| @@ -17,14 +17,11 @@ void BLEClientSwitch::write_state(bool state) { | ||||
| void BLEClientSwitch::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_REG_EVT: | ||||
|     case ESP_GATTC_CLOSE_EVT: | ||||
|       this->publish_state(this->parent_->enabled); | ||||
|       break; | ||||
|     case ESP_GATTC_OPEN_EVT: | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: | ||||
|       this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|       break; | ||||
|     case ESP_GATTC_DISCONNECT_EVT: | ||||
|       this->node_state = espbt::ClientState::IDLE; | ||||
|       this->publish_state(this->parent_->enabled); | ||||
|       break; | ||||
|     default: | ||||
|   | ||||
| @@ -36,8 +36,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_DISCONNECT_EVT: { | ||||
|       ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); | ||||
|     case ESP_GATTC_CLOSE_EVT: { | ||||
|       this->status_set_warning(); | ||||
|       this->publish_state(EMPTY); | ||||
|       break; | ||||
| @@ -77,20 +76,18 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_READ_CHAR_EVT: { | ||||
|       if (param->read.conn_id != this->parent()->get_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) { | ||||
|         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; | ||||
|         } | ||||
|         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()->get_conn_id() || param->notify.handle != this->handle) | ||||
|       if (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]); | ||||
| @@ -98,7 +95,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_REG_FOR_NOTIFY_EVT: { | ||||
|       this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|       if (param->reg_for_notify.status == ESP_GATT_OK && param->reg_for_notify.handle == this->handle) | ||||
|         this->node_state = espbt::ClientState::ESTABLISHED; | ||||
|       break; | ||||
|     } | ||||
|     default: | ||||
|   | ||||
| @@ -45,21 +45,19 @@ void BLEClientBase::loop() { | ||||
| float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | ||||
|  | ||||
| bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { | ||||
|   if (!this->auto_connect_) | ||||
|     return false; | ||||
|   if (this->address_ == 0 || device.address_uint64() != this->address_) | ||||
|     return false; | ||||
|   if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING) | ||||
|     return false; | ||||
|  | ||||
|   ESP_LOGD(TAG, "[%d] [%s] Found device", this->connection_index_, this->address_str_.c_str()); | ||||
|   this->set_state(espbt::ClientState::DISCOVERED); | ||||
|   this->log_event_("Found device"); | ||||
|   if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) | ||||
|     esp32_ble_tracker::global_esp32_ble_tracker->print_bt_device_info(device); | ||||
|  | ||||
|   auto addr = device.address_uint64(); | ||||
|   this->remote_bda_[0] = (addr >> 40) & 0xFF; | ||||
|   this->remote_bda_[1] = (addr >> 32) & 0xFF; | ||||
|   this->remote_bda_[2] = (addr >> 24) & 0xFF; | ||||
|   this->remote_bda_[3] = (addr >> 16) & 0xFF; | ||||
|   this->remote_bda_[4] = (addr >> 8) & 0xFF; | ||||
|   this->remote_bda_[5] = (addr >> 0) & 0xFF; | ||||
|   this->set_state(espbt::ClientState::DISCOVERED); | ||||
|   this->set_address(device.address_uint64()); | ||||
|   this->remote_addr_type_ = device.get_address_type(); | ||||
|   return true; | ||||
| } | ||||
| @@ -108,6 +106,10 @@ void BLEClientBase::release_services() { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void BLEClientBase::log_event_(const char *name) { | ||||
|   ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name); | ||||
| } | ||||
|  | ||||
| bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, | ||||
|                                         esp_ble_gattc_cb_param_t *param) { | ||||
|   if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) | ||||
| @@ -131,51 +133,73 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_OPEN_EVT: { | ||||
|       ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str()); | ||||
|       if (!this->check_addr(param->open.remote_bda)) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_OPEN_EVT"); | ||||
|       this->conn_id_ = param->open.conn_id; | ||||
|       this->service_count_ = 0; | ||||
|       if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { | ||||
|         ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(), | ||||
|                  param->open.status); | ||||
|         this->set_state(espbt::ClientState::IDLE); | ||||
|         break; | ||||
|         return false; | ||||
|       } | ||||
|       auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id); | ||||
|       if (ret) { | ||||
|         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_, | ||||
|                  this->address_str_.c_str(), ret); | ||||
|       } | ||||
|       this->set_state(espbt::ClientState::CONNECTED); | ||||
|       if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { | ||||
|         ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); | ||||
|         this->set_state(espbt::ClientState::CONNECTED); | ||||
|         // only set our state, subclients might have more stuff to do yet. | ||||
|         this->state_ = espbt::ClientState::ESTABLISHED; | ||||
|         break; | ||||
|       } | ||||
|       esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_CFG_MTU_EVT: { | ||||
|       if (param->cfg_mtu.status != ESP_GATT_OK) { | ||||
|         ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_, | ||||
|                  this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status); | ||||
|         this->set_state(espbt::ClientState::IDLE); | ||||
|         break; | ||||
|       } | ||||
|       ESP_LOGV(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(), | ||||
|                param->cfg_mtu.status, param->cfg_mtu.mtu); | ||||
|       this->mtu_ = param->cfg_mtu.mtu; | ||||
|     case ESP_GATTC_CONNECT_EVT: { | ||||
|       if (!this->check_addr(param->connect.remote_bda)) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_CONNECT_EVT"); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_DISCONNECT_EVT: { | ||||
|       if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) | ||||
|       if (!this->check_addr(param->disconnect.remote_bda)) | ||||
|         return false; | ||||
|       ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_, | ||||
|       ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_, | ||||
|                this->address_str_.c_str(), param->disconnect.reason); | ||||
|       this->release_services(); | ||||
|       this->set_state(espbt::ClientState::IDLE); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_CFG_MTU_EVT: { | ||||
|       if (this->conn_id_ != param->cfg_mtu.conn_id) | ||||
|         return false; | ||||
|       if (param->cfg_mtu.status != ESP_GATT_OK) { | ||||
|         ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_, | ||||
|                  this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status); | ||||
|         // No state change required here - disconnect event will follow if needed. | ||||
|         break; | ||||
|       } | ||||
|       ESP_LOGD(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(), | ||||
|                param->cfg_mtu.status, param->cfg_mtu.mtu); | ||||
|       this->mtu_ = param->cfg_mtu.mtu; | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_CLOSE_EVT: { | ||||
|       if (this->conn_id_ != param->close.conn_id) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_CLOSE_EVT"); | ||||
|       this->release_services(); | ||||
|       this->set_state(espbt::ClientState::IDLE); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_SEARCH_RES_EVT: { | ||||
|       if (this->conn_id_ != param->search_res.conn_id) | ||||
|         return false; | ||||
|       this->service_count_++; | ||||
|       if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { | ||||
|         // V3 clients don't need services initialized since | ||||
| @@ -191,7 +215,9 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|       ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str()); | ||||
|       if (this->conn_id_ != param->search_cmpl.conn_id) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_SEARCH_CMPL_EVT"); | ||||
|       for (auto &svc : this->services_) { | ||||
|         ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(), | ||||
|                  svc->uuid.to_string().c_str()); | ||||
| @@ -199,11 +225,41 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|                  this->address_str_.c_str(), svc->start_handle, svc->end_handle); | ||||
|       } | ||||
|       ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); | ||||
|       this->set_state(espbt::ClientState::CONNECTED); | ||||
|       this->state_ = espbt::ClientState::ESTABLISHED; | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_READ_DESCR_EVT: { | ||||
|       if (this->conn_id_ != param->write.conn_id) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_READ_DESCR_EVT"); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_WRITE_DESCR_EVT: { | ||||
|       if (this->conn_id_ != param->write.conn_id) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_WRITE_DESCR_EVT"); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_WRITE_CHAR_EVT: { | ||||
|       if (this->conn_id_ != param->write.conn_id) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_WRITE_CHAR_EVT"); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_READ_CHAR_EVT: { | ||||
|       if (this->conn_id_ != param->read.conn_id) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_READ_CHAR_EVT"); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_NOTIFY_EVT: { | ||||
|       if (this->conn_id_ != param->notify.conn_id) | ||||
|         return false; | ||||
|       this->log_event_("ESP_GATTC_NOTIFY_EVT"); | ||||
|       break; | ||||
|     } | ||||
|     case ESP_GATTC_REG_FOR_NOTIFY_EVT: { | ||||
|       this->log_event_("ESP_GATTC_REG_FOR_NOTIFY_EVT"); | ||||
|       if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || | ||||
|           this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { | ||||
|         // Client is responsible for flipping the descriptor value | ||||
| @@ -212,9 +268,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       } | ||||
|       esp_gattc_descr_elem_t desc_result; | ||||
|       uint16_t count = 1; | ||||
|       esp_gatt_status_t descr_status = | ||||
|           esp_ble_gattc_get_descr_by_char_handle(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle, | ||||
|                                                  NOTIFY_DESC_UUID, &desc_result, &count); | ||||
|       esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle( | ||||
|           this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count); | ||||
|       if (descr_status != ESP_GATT_OK) { | ||||
|         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_descr_by_char_handle error, status=%d", this->connection_index_, | ||||
|                  this->address_str_.c_str(), descr_status); | ||||
| @@ -222,7 +277,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       } | ||||
|       esp_gattc_char_elem_t char_result; | ||||
|       esp_gatt_status_t char_status = | ||||
|           esp_ble_gattc_get_all_char(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle, | ||||
|           esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, | ||||
|                                      param->reg_for_notify.handle, &char_result, &count, 0); | ||||
|       if (char_status != ESP_GATT_OK) { | ||||
|         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, | ||||
| @@ -238,6 +293,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       esp_err_t status = | ||||
|           esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en), | ||||
|                                          (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); | ||||
|       ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties); | ||||
|       if (status) { | ||||
|         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_, | ||||
|                  this->address_str_.c_str(), status); | ||||
| @@ -246,24 +302,31 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|     } | ||||
|  | ||||
|     default: | ||||
|       // ideally would check all other events for matching conn_id | ||||
|       ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event); | ||||
|       break; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| // clients can't call defer() directly since it's protected. | ||||
| void BLEClientBase::run_later(std::function<void()> &&f) {  // NOLINT | ||||
|   this->defer(std::move(f)); | ||||
| } | ||||
|  | ||||
| void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||
|   switch (event) { | ||||
|     // This event is sent by the server when it requests security | ||||
|     case ESP_GAP_BLE_SEC_REQ_EVT: | ||||
|       if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) | ||||
|         break; | ||||
|       if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) | ||||
|         return; | ||||
|       ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event); | ||||
|       esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); | ||||
|       break; | ||||
|     // This event is sent once authentication has completed | ||||
|     case ESP_GAP_BLE_AUTH_CMPL_EVT: | ||||
|       if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) | ||||
|         break; | ||||
|       if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) | ||||
|         return; | ||||
|       esp_bd_addr_t bd_addr; | ||||
|       memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); | ||||
|       ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(), | ||||
| @@ -273,11 +336,12 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ | ||||
|                  param->ble_security.auth_cmpl.fail_reason); | ||||
|       } else { | ||||
|         this->paired_ = true; | ||||
|         ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, | ||||
|         ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, | ||||
|                  this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type, | ||||
|                  param->ble_security.auth_cmpl.auth_mode); | ||||
|       } | ||||
|       break; | ||||
|  | ||||
|     // There are other events we'll want to implement at some point to support things like pass key | ||||
|     // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md | ||||
|     default: | ||||
|   | ||||
| @@ -27,6 +27,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|   void loop() override; | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|   void run_later(std::function<void()> &&f);  // NOLINT | ||||
|   bool parse_device(const espbt::ESPBTDevice &device) override; | ||||
|   void on_scan_end() override {} | ||||
|   bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
| @@ -39,10 +40,17 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|  | ||||
|   bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; } | ||||
|  | ||||
|   void set_auto_connect(bool auto_connect) { this->auto_connect_ = auto_connect; } | ||||
|  | ||||
|   void set_address(uint64_t address) { | ||||
|     this->address_ = address; | ||||
|     this->remote_bda_[0] = (address >> 40) & 0xFF; | ||||
|     this->remote_bda_[1] = (address >> 32) & 0xFF; | ||||
|     this->remote_bda_[2] = (address >> 24) & 0xFF; | ||||
|     this->remote_bda_[3] = (address >> 16) & 0xFF; | ||||
|     this->remote_bda_[4] = (address >> 8) & 0xFF; | ||||
|     this->remote_bda_[5] = (address >> 0) & 0xFF; | ||||
|     if (address == 0) { | ||||
|       memset(this->remote_bda_, 0, sizeof(this->remote_bda_)); | ||||
|       this->address_str_ = ""; | ||||
|     } else { | ||||
|       this->address_str_ = | ||||
| @@ -79,20 +87,24 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|  | ||||
|   virtual void set_connection_type(espbt::ConnectionType ct) { this->connection_type_ = ct; } | ||||
|  | ||||
|   bool check_addr(esp_bd_addr_t &addr) { return memcmp(addr, this->remote_bda_, sizeof(esp_bd_addr_t)) == 0; } | ||||
|  | ||||
|  protected: | ||||
|   int gattc_if_; | ||||
|   esp_bd_addr_t remote_bda_; | ||||
|   esp_ble_addr_type_t remote_addr_type_; | ||||
|   esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC}; | ||||
|   uint16_t conn_id_{0xFFFF}; | ||||
|   uint64_t address_{0}; | ||||
|   bool auto_connect_{false}; | ||||
|   std::string address_str_{}; | ||||
|   uint8_t connection_index_; | ||||
|   int16_t service_count_{0}; | ||||
|   uint16_t mtu_{23}; | ||||
|   bool paired_{false}; | ||||
|   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; | ||||
|  | ||||
|   std::vector<BLEService *> services_; | ||||
|  | ||||
|   void log_event_(const char *name); | ||||
| }; | ||||
|  | ||||
| }  // namespace esp32_ble_client | ||||
|   | ||||
| @@ -358,8 +358,10 @@ esp32_ble_tracker: | ||||
| ble_client: | ||||
|   - mac_address: AA:BB:CC:DD:EE:FF | ||||
|     id: ble_foo | ||||
|     auto_connect: true | ||||
|   - mac_address: 11:22:33:44:55:66 | ||||
|     id: ble_blah | ||||
|     auto_connect: false | ||||
|     on_connect: | ||||
|       then: | ||||
|         - switch.turn_on: ble1_status | ||||
| @@ -3026,6 +3028,16 @@ interval: | ||||
|               page_id: page1 | ||||
|           then: | ||||
|             - logger.log: Seeing page 1 | ||||
|   - interval: 60min | ||||
|     then: | ||||
|       - ble_client.connect: ble_blah | ||||
|       - ble_client.ble_write: | ||||
|           id: ble_blah | ||||
|           service_uuid: EBE0CCB0-7A0A-4B0C-8A1A-6FF2997DA3A6 | ||||
|           characteristic_uuid: EBE0CCB7-7A0A-4B0C-8A1A-6FF2997DA3A6 | ||||
|           value: !lambda |- | ||||
|             return {1, 0}; | ||||
|       - ble_client.disconnect: ble_blah | ||||
|  | ||||
| color: | ||||
|   - id: kbx_red | ||||
|   | ||||
		Reference in New Issue
	
	Block a user