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/bl0939/* @ziceva | ||||||
| esphome/components/bl0940/* @tobias- | esphome/components/bl0940/* @tobias- | ||||||
| esphome/components/bl0942/* @dbuezas | esphome/components/bl0942/* @dbuezas | ||||||
| esphome/components/ble_client/* @buxtronix | esphome/components/ble_client/* @buxtronix @clydebarrow | ||||||
| esphome/components/bluetooth_proxy/* @jesserockz | esphome/components/bluetooth_proxy/* @jesserockz | ||||||
| esphome/components/bme680_bsec/* @trvrnrth | esphome/components/bme680_bsec/* @trvrnrth | ||||||
| esphome/components/bmi160/* @flaviut | esphome/components/bmi160/* @flaviut | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | 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.components import esp32_ble_tracker, esp32_ble_client | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_CHARACTERISTIC_UUID, |     CONF_CHARACTERISTIC_UUID, | ||||||
| @@ -15,7 +16,7 @@ from esphome.const import ( | |||||||
| from esphome import automation | from esphome import automation | ||||||
|  |  | ||||||
| AUTO_LOAD = ["esp32_ble_client"] | AUTO_LOAD = ["esp32_ble_client"] | ||||||
| CODEOWNERS = ["@buxtronix"] | CODEOWNERS = ["@buxtronix", "@clydebarrow"] | ||||||
| DEPENDENCIES = ["esp32_ble_tracker"] | DEPENDENCIES = ["esp32_ble_tracker"] | ||||||
|  |  | ||||||
| ble_client_ns = cg.esphome_ns.namespace("ble_client") | ble_client_ns = cg.esphome_ns.namespace("ble_client") | ||||||
| @@ -43,6 +44,10 @@ BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_( | |||||||
|  |  | ||||||
| # Actions | # Actions | ||||||
| BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) | 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_( | BLEPasskeyReplyAction = ble_client_ns.class_( | ||||||
|     "BLEClientPasskeyReplyAction", automation.Action |     "BLEClientPasskeyReplyAction", automation.Action | ||||||
| ) | ) | ||||||
| @@ -58,6 +63,7 @@ CONF_ACCEPT = "accept" | |||||||
| CONF_ON_PASSKEY_REQUEST = "on_passkey_request" | CONF_ON_PASSKEY_REQUEST = "on_passkey_request" | ||||||
| CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" | CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" | ||||||
| CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" | 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 | # Espressif platformio framework is built with MAX_BLE_CONN to 3, so | ||||||
| # enforce this in yaml checks. | # enforce this in yaml checks. | ||||||
| @@ -69,6 +75,7 @@ CONFIG_SCHEMA = ( | |||||||
|             cv.GenerateID(): cv.declare_id(BLEClient), |             cv.GenerateID(): cv.declare_id(BLEClient), | ||||||
|             cv.Required(CONF_MAC_ADDRESS): cv.mac_address, |             cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||||
|             cv.Optional(CONF_NAME): cv.string, |             cv.Optional(CONF_NAME): cv.string, | ||||||
|  |             cv.Optional(CONF_AUTO_CONNECT, default=True): cv.boolean, | ||||||
|             cv.Optional(CONF_ON_CONNECT): automation.validate_automation( |             cv.Optional(CONF_ON_CONNECT): automation.validate_automation( | ||||||
|                 { |                 { | ||||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( |                     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( | BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(CONF_ID): cv.use_id(BLEClient), |         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( | @automation.register_action( | ||||||
|     "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA |     "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 cg.register_component(var, config) | ||||||
|     await esp32_ble_tracker.register_client(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_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, []): |     for conf in config.get(CONF_ON_CONNECT, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         await automation.build_automation(trigger, [], conf) |         await automation.build_automation(trigger, [], conf) | ||||||
|   | |||||||
| @@ -2,76 +2,10 @@ | |||||||
|  |  | ||||||
| #include "automation.h" | #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 esphome { | ||||||
| namespace ble_client { | namespace ble_client { | ||||||
| static const char *const TAG = "ble_client.automation"; |  | ||||||
|  |  | ||||||
| void BLEWriterClientNode::write(const std::vector<uint8_t> &value) { | const char *const Automation::TAG = "ble_client.automation"; | ||||||
|   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; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| }  // namespace ble_client | }  // namespace ble_client | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -7,9 +7,19 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/components/ble_client/ble_client.h" | #include "esphome/components/ble_client/ble_client.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ble_client { | 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 { | class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { | ||||||
|  public: |  public: | ||||||
|   explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } |   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 { | class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { | ||||||
|  public: |  public: | ||||||
|   explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } |   explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||||
|   void loop() override {} |   void loop() override {} | ||||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                            esp_ble_gattc_cb_param_t *param) override { |                            esp_ble_gattc_cb_param_t *param) override { | ||||||
|     if (event == ESP_GATTC_DISCONNECT_EVT && |     // test for CLOSE and not DISCONNECT - DISCONNECT can occur even if no virtual connection (OPEN event) occurred. | ||||||
|         memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0) |     // So this will not trigger unless a complete open has previously succeeded. | ||||||
|       this->trigger(); |     switch (event) { | ||||||
|     if (event == ESP_GATTC_SEARCH_CMPL_EVT) |       case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||||
|         this->node_state = espbt::ClientState::ESTABLISHED; |         this->node_state = espbt::ClientState::ESTABLISHED; | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       case ESP_GATTC_CLOSE_EVT: { | ||||||
|  |         this->trigger(); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       default: { | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -42,11 +63,9 @@ class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode { | |||||||
|   explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } |   explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||||
|   void loop() override {} |   void loop() override {} | ||||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) 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 && |     if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) | ||||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { |  | ||||||
|       this->trigger(); |       this->trigger(); | ||||||
|   } |   } | ||||||
|   } |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode { | class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode { | ||||||
| @@ -54,10 +73,8 @@ class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLE | |||||||
|   explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); } |   explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||||
|   void loop() override {} |   void loop() override {} | ||||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) 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 && |     if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) { | ||||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { |       this->trigger(param->ble_security.key_notif.passkey); | ||||||
|       uint32_t passkey = param->ble_security.key_notif.passkey; |  | ||||||
|       this->trigger(passkey); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| @@ -67,24 +84,20 @@ class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, publi | |||||||
|   explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } |   explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } | ||||||
|   void loop() override {} |   void loop() override {} | ||||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) 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 && |     if (event == ESP_GAP_BLE_NC_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) { | ||||||
|         memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { |       this->trigger(param->ble_security.key_notif.passkey); | ||||||
|       uint32_t passkey = param->ble_security.key_notif.passkey; |  | ||||||
|       this->trigger(passkey); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class BLEWriterClientNode : public BLEClientNode { | // implement the ble_client.ble_write action. | ||||||
|  | template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, public BLEClientNode { | ||||||
|  public: |  public: | ||||||
|   BLEWriterClientNode(BLEClient *ble_client) { |   BLEClientWriteAction(BLEClient *ble_client) { | ||||||
|     ble_client->register_ble_node(this); |     ble_client->register_ble_node(this); | ||||||
|     ble_client_ = ble_client; |     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_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_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_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_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_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) { |   void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) { | ||||||
|     this->value_template_ = std::move(func); |     this->value_template_ = std::move(func); | ||||||
|     has_simple_value_ = false; |     has_simple_value_ = false; | ||||||
| @@ -126,10 +116,94 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ | |||||||
|     has_simple_value_ = true; |     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: |  private: | ||||||
|  |   BLEClient *ble_client_; | ||||||
|   bool has_simple_value_ = true; |   bool has_simple_value_ = true; | ||||||
|   std::vector<uint8_t> value_simple_; |   std::vector<uint8_t> value_simple_; | ||||||
|   std::function<std::vector<uint8_t>(Ts...)> value_template_{}; |   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...> { | template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> { | ||||||
| @@ -212,6 +286,92 @@ template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> | |||||||
|   BLEClient *parent_{nullptr}; |   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 ble_client | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ void BLEClient::loop() { | |||||||
| void BLEClient::dump_config() { | void BLEClient::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "BLE Client:"); |   ESP_LOGCONFIG(TAG, "BLE Client:"); | ||||||
|   ESP_LOGCONFIG(TAG, "  Address: %s", this->address_str().c_str()); |   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) { | 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) { | void BLEClient::set_enabled(bool enabled) { | ||||||
|   if (enabled == this->enabled) |   if (enabled == this->enabled) | ||||||
|     return; |     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; |   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, | 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) { |                                     esp_ble_gattc_cb_param_t *param) { | ||||||
|   bool all_established = this->all_nodes_established_(); |  | ||||||
|  |  | ||||||
|   if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param)) |   if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param)) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   for (auto *node : this->nodes_) |   for (auto *node : this->nodes_) | ||||||
|     node->gattc_event_handler(event, esp_gattc_if, param); |     node->gattc_event_handler(event, esp_gattc_if, param); | ||||||
|  |  | ||||||
|   // Delete characteristics after clients have used them to save RAM. |   if (!this->services_.empty() && this->all_nodes_established_()) { | ||||||
|   if (!all_established && this->all_nodes_established_()) { |     this->release_services(); | ||||||
|     for (auto &svc : this->services_) |     ESP_LOGD(TAG, "All clients established, services released"); | ||||||
|       delete svc;  // NOLINT(cppcoreguidelines-owning-memory) |  | ||||||
|     this->services_.clear(); |  | ||||||
|   } |   } | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,25 +19,35 @@ void BLEBinaryOutput::dump_config() { | |||||||
| void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                           esp_ble_gattc_cb_param_t *param) { |                                           esp_ble_gattc_cb_param_t *param) { | ||||||
|   switch (event) { |   switch (event) { | ||||||
|     case ESP_GATTC_OPEN_EVT: |     case ESP_GATTC_SEARCH_CMPL_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; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); |       auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||||
|       if (chr == nullptr) { |       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; |         break; | ||||||
|       } |       } | ||||||
|       if (param->write.handle == chr->handle) { |       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); |           ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); | ||||||
|       } |       } | ||||||
|       break; |       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) { | 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.", |     ESP_LOGW(TAG, "[%s] Not connected to BLE client.  State update can not be written.", | ||||||
|              this->char_uuid_.to_string().c_str()); |              this->char_uuid_.to_string().c_str()); | ||||||
|     return; |     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; |   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); |   ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); | ||||||
|   if (this->require_response_) { |   esp_err_t err = | ||||||
|     chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP); |       esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, | ||||||
|   } else { |                                sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE); | ||||||
|     chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP); |   if (err != ESP_GATT_OK) | ||||||
|   } |     ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err); | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace ble_client | }  // namespace ble_client | ||||||
|   | |||||||
| @@ -32,7 +32,9 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi | |||||||
|   bool require_response_; |   bool require_response_; | ||||||
|   espbt::ESPBTUUID service_uuid_; |   espbt::ESPBTUUID service_uuid_; | ||||||
|   espbt::ESPBTUUID char_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 | }  // 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, |   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                            esp_ble_gattc_cb_param_t *param) override { |                            esp_ble_gattc_cb_param_t *param) override { | ||||||
|     switch (event) { |     switch (event) { | ||||||
|       case ESP_GATTC_SEARCH_CMPL_EVT: { |       case ESP_GATTC_NOTIFY_EVT: { | ||||||
|         this->sensor_->node_state = espbt::ClientState::ESTABLISHED; |         if (param->notify.handle == this->sensor_->handle) | ||||||
|  |           this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|       case ESP_GATTC_NOTIFY_EVT: { |       case ESP_GATTC_REG_FOR_NOTIFY_EVT: { | ||||||
|         if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || |         // confirms notifications are being listened for. While enabling of notifications may still be in | ||||||
|             param->notify.handle != this->sensor_->handle) |         // 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; |         break; | ||||||
|         this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); |  | ||||||
|       } |       } | ||||||
|       default: |       default: | ||||||
|         break; |         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, | void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                               esp_ble_gattc_cb_param_t *param) { |                                               esp_ble_gattc_cb_param_t *param) { | ||||||
|   switch (event) { |   switch (event) { | ||||||
|     case ESP_GATTC_OPEN_EVT: { |     case ESP_GATTC_CLOSE_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->status_set_warning(); | ||||||
|       this->publish_state(NAN); |       this->publish_state(NAN); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: |     case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||||
|       this->node_state = espbt::ClientState::ESTABLISHED; |       this->node_state = espbt::ClientState::ESTABLISHED; | ||||||
|       if (this->should_update_) { |       if (this->should_update_) { | ||||||
|         this->should_update_ = false; |         this->should_update_ = false; | ||||||
|         this->get_rssi_(); |         this->get_rssi_(); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_DISCONNECT_EVT: { |     case ESP_GATTC_CLOSE_EVT: { | ||||||
|       ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); |       ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); | ||||||
|       this->status_set_warning(); |       this->status_set_warning(); | ||||||
|       this->publish_state(NAN); |       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; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_READ_CHAR_EVT: { |     case ESP_GATTC_READ_CHAR_EVT: { | ||||||
|       if (param->read.conn_id != this->parent()->get_conn_id()) |  | ||||||
|         break; |  | ||||||
|       if (param->read.status != ESP_GATT_OK) { |       if (param->read.status != ESP_GATT_OK) { | ||||||
|         ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); |         ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); | ||||||
|         break; |         break; | ||||||
| @@ -87,15 +85,23 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga | |||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_NOTIFY_EVT: { |     case ESP_GATTC_NOTIFY_EVT: { | ||||||
|       if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) |       ESP_LOGD(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), | ||||||
|         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]); |                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)); |       this->publish_state(this->parse_data_(param->notify.value, param->notify.value_len)); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_REG_FOR_NOTIFY_EVT: { |     case ESP_GATTC_REG_FOR_NOTIFY_EVT: { | ||||||
|  |       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; |         this->node_state = espbt::ClientState::ESTABLISHED; | ||||||
|  |         ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str()); | ||||||
|  |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     default: |     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, | void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                           esp_ble_gattc_cb_param_t *param) { |                                           esp_ble_gattc_cb_param_t *param) { | ||||||
|   switch (event) { |   switch (event) { | ||||||
|     case ESP_GATTC_REG_EVT: |     case ESP_GATTC_CLOSE_EVT: | ||||||
|       this->publish_state(this->parent_->enabled); |       this->publish_state(this->parent_->enabled); | ||||||
|       break; |       break; | ||||||
|     case ESP_GATTC_OPEN_EVT: |     case ESP_GATTC_SEARCH_CMPL_EVT: | ||||||
|       this->node_state = espbt::ClientState::ESTABLISHED; |       this->node_state = espbt::ClientState::ESTABLISHED; | ||||||
|       break; |  | ||||||
|     case ESP_GATTC_DISCONNECT_EVT: |  | ||||||
|       this->node_state = espbt::ClientState::IDLE; |  | ||||||
|       this->publish_state(this->parent_->enabled); |       this->publish_state(this->parent_->enabled); | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -36,8 +36,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | |||||||
|       } |       } | ||||||
|       break; |       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->status_set_warning(); | ||||||
|       this->publish_state(EMPTY); |       this->publish_state(EMPTY); | ||||||
|       break; |       break; | ||||||
| @@ -77,20 +76,18 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | |||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_READ_CHAR_EVT: { |     case ESP_GATTC_READ_CHAR_EVT: { | ||||||
|       if (param->read.conn_id != this->parent()->get_conn_id()) |       if (param->read.handle == this->handle) { | ||||||
|         break; |  | ||||||
|         if (param->read.status != ESP_GATT_OK) { |         if (param->read.status != ESP_GATT_OK) { | ||||||
|           ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); |           ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|       if (param->read.handle == this->handle) { |  | ||||||
|         this->status_clear_warning(); |         this->status_clear_warning(); | ||||||
|         this->publish_state(this->parse_data(param->read.value, param->read.value_len)); |         this->publish_state(this->parse_data(param->read.value, param->read.value_len)); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_NOTIFY_EVT: { |     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; |         break; | ||||||
|       ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), |       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]); |                param->notify.handle, param->notify.value[0]); | ||||||
| @@ -98,6 +95,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | |||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_REG_FOR_NOTIFY_EVT: { |     case ESP_GATTC_REG_FOR_NOTIFY_EVT: { | ||||||
|  |       if (param->reg_for_notify.status == ESP_GATT_OK && param->reg_for_notify.handle == this->handle) | ||||||
|         this->node_state = espbt::ClientState::ESTABLISHED; |         this->node_state = espbt::ClientState::ESTABLISHED; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -45,21 +45,19 @@ void BLEClientBase::loop() { | |||||||
| float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | ||||||
|  |  | ||||||
| bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { | bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { | ||||||
|  |   if (!this->auto_connect_) | ||||||
|  |     return false; | ||||||
|   if (this->address_ == 0 || device.address_uint64() != this->address_) |   if (this->address_ == 0 || device.address_uint64() != this->address_) | ||||||
|     return false; |     return false; | ||||||
|   if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING) |   if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "[%d] [%s] Found device", this->connection_index_, this->address_str_.c_str()); |   this->log_event_("Found device"); | ||||||
|   this->set_state(espbt::ClientState::DISCOVERED); |   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->set_state(espbt::ClientState::DISCOVERED); | ||||||
|   this->remote_bda_[0] = (addr >> 40) & 0xFF; |   this->set_address(device.address_uint64()); | ||||||
|   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->remote_addr_type_ = device.get_address_type(); |   this->remote_addr_type_ = device.get_address_type(); | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
| @@ -108,6 +106,10 @@ void BLEClientBase::release_services() { | |||||||
| #endif | #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, | 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) { |                                         esp_ble_gattc_cb_param_t *param) { | ||||||
|   if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) |   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; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_OPEN_EVT: { |     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->conn_id_ = param->open.conn_id; | ||||||
|       this->service_count_ = 0; |       this->service_count_ = 0; | ||||||
|       if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { |       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(), |         ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(), | ||||||
|                  param->open.status); |                  param->open.status); | ||||||
|         this->set_state(espbt::ClientState::IDLE); |         this->set_state(espbt::ClientState::IDLE); | ||||||
|         break; |         return false; | ||||||
|       } |       } | ||||||
|       auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id); |       auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id); | ||||||
|       if (ret) { |       if (ret) { | ||||||
|         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_, |         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_, | ||||||
|                  this->address_str_.c_str(), ret); |                  this->address_str_.c_str(), ret); | ||||||
|       } |       } | ||||||
|  |       this->set_state(espbt::ClientState::CONNECTED); | ||||||
|       if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { |       if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { | ||||||
|         ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); |         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; |         this->state_ = espbt::ClientState::ESTABLISHED; | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|       esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); |       esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_CFG_MTU_EVT: { |     case ESP_GATTC_CONNECT_EVT: { | ||||||
|       if (param->cfg_mtu.status != ESP_GATT_OK) { |       if (!this->check_addr(param->connect.remote_bda)) | ||||||
|         ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_, |         return false; | ||||||
|                  this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status); |       this->log_event_("ESP_GATTC_CONNECT_EVT"); | ||||||
|         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; |  | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_DISCONNECT_EVT: { |     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; |         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->address_str_.c_str(), param->disconnect.reason); | ||||||
|       this->release_services(); |       this->release_services(); | ||||||
|       this->set_state(espbt::ClientState::IDLE); |       this->set_state(espbt::ClientState::IDLE); | ||||||
|       break; |       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: { |     case ESP_GATTC_SEARCH_RES_EVT: { | ||||||
|  |       if (this->conn_id_ != param->search_res.conn_id) | ||||||
|  |         return false; | ||||||
|       this->service_count_++; |       this->service_count_++; | ||||||
|       if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { |       if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { | ||||||
|         // V3 clients don't need services initialized since |         // 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; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: { |     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_) { |       for (auto &svc : this->services_) { | ||||||
|         ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(), |         ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(), | ||||||
|                  svc->uuid.to_string().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); |                  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()); |       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; |       this->state_ = espbt::ClientState::ESTABLISHED; | ||||||
|       break; |       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: { |     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 || |       if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || | ||||||
|           this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { |           this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { | ||||||
|         // Client is responsible for flipping the descriptor value |         // 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; |       esp_gattc_descr_elem_t desc_result; | ||||||
|       uint16_t count = 1; |       uint16_t count = 1; | ||||||
|       esp_gatt_status_t descr_status = |       esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle( | ||||||
|           esp_ble_gattc_get_descr_by_char_handle(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle, |           this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count); | ||||||
|                                                  NOTIFY_DESC_UUID, &desc_result, &count); |  | ||||||
|       if (descr_status != ESP_GATT_OK) { |       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_, |         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); |                  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_gattc_char_elem_t char_result; | ||||||
|       esp_gatt_status_t char_status = |       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); |                                      param->reg_for_notify.handle, &char_result, &count, 0); | ||||||
|       if (char_status != ESP_GATT_OK) { |       if (char_status != ESP_GATT_OK) { | ||||||
|         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, |         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_err_t status = | ||||||
|           esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en), |           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); |                                          (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) { |       if (status) { | ||||||
|         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_, |         ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_, | ||||||
|                  this->address_str_.c_str(), status); |                  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: |     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; |       break; | ||||||
|   } |   } | ||||||
|   return true; |   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) { | void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|   switch (event) { |   switch (event) { | ||||||
|     // This event is sent by the server when it requests security |     // This event is sent by the server when it requests security | ||||||
|     case ESP_GAP_BLE_SEC_REQ_EVT: |     case ESP_GAP_BLE_SEC_REQ_EVT: | ||||||
|       if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) |       if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) | ||||||
|         break; |         return; | ||||||
|       ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event); |       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); |       esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); | ||||||
|       break; |       break; | ||||||
|     // This event is sent once authentication has completed |     // This event is sent once authentication has completed | ||||||
|     case ESP_GAP_BLE_AUTH_CMPL_EVT: |     case ESP_GAP_BLE_AUTH_CMPL_EVT: | ||||||
|       if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) |       if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) | ||||||
|         break; |         return; | ||||||
|       esp_bd_addr_t bd_addr; |       esp_bd_addr_t bd_addr; | ||||||
|       memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); |       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(), |       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); |                  param->ble_security.auth_cmpl.fail_reason); | ||||||
|       } else { |       } else { | ||||||
|         this->paired_ = true; |         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, |                  this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type, | ||||||
|                  param->ble_security.auth_cmpl.auth_mode); |                  param->ble_security.auth_cmpl.auth_mode); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|  |  | ||||||
|     // There are other events we'll want to implement at some point to support things like pass key |     // 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 |     // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | |||||||
|   void loop() override; |   void loop() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |   void run_later(std::function<void()> &&f);  // NOLINT | ||||||
|   bool parse_device(const espbt::ESPBTDevice &device) override; |   bool parse_device(const espbt::ESPBTDevice &device) override; | ||||||
|   void on_scan_end() override {} |   void on_scan_end() override {} | ||||||
|   bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   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; } |   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) { |   void set_address(uint64_t address) { | ||||||
|     this->address_ = 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) { |     if (address == 0) { | ||||||
|       memset(this->remote_bda_, 0, sizeof(this->remote_bda_)); |  | ||||||
|       this->address_str_ = ""; |       this->address_str_ = ""; | ||||||
|     } else { |     } else { | ||||||
|       this->address_str_ = |       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; } |   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: |  protected: | ||||||
|   int gattc_if_; |   int gattc_if_; | ||||||
|   esp_bd_addr_t remote_bda_; |   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}; |   uint16_t conn_id_{0xFFFF}; | ||||||
|   uint64_t address_{0}; |   uint64_t address_{0}; | ||||||
|  |   bool auto_connect_{false}; | ||||||
|   std::string address_str_{}; |   std::string address_str_{}; | ||||||
|   uint8_t connection_index_; |   uint8_t connection_index_; | ||||||
|   int16_t service_count_{0}; |   int16_t service_count_{0}; | ||||||
|   uint16_t mtu_{23}; |   uint16_t mtu_{23}; | ||||||
|   bool paired_{false}; |   bool paired_{false}; | ||||||
|   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; |   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; | ||||||
|  |  | ||||||
|   std::vector<BLEService *> services_; |   std::vector<BLEService *> services_; | ||||||
|  |  | ||||||
|  |   void log_event_(const char *name); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace esp32_ble_client | }  // namespace esp32_ble_client | ||||||
|   | |||||||
| @@ -358,8 +358,10 @@ esp32_ble_tracker: | |||||||
| ble_client: | ble_client: | ||||||
|   - mac_address: AA:BB:CC:DD:EE:FF |   - mac_address: AA:BB:CC:DD:EE:FF | ||||||
|     id: ble_foo |     id: ble_foo | ||||||
|  |     auto_connect: true | ||||||
|   - mac_address: 11:22:33:44:55:66 |   - mac_address: 11:22:33:44:55:66 | ||||||
|     id: ble_blah |     id: ble_blah | ||||||
|  |     auto_connect: false | ||||||
|     on_connect: |     on_connect: | ||||||
|       then: |       then: | ||||||
|         - switch.turn_on: ble1_status |         - switch.turn_on: ble1_status | ||||||
| @@ -3026,6 +3028,16 @@ interval: | |||||||
|               page_id: page1 |               page_id: page1 | ||||||
|           then: |           then: | ||||||
|             - logger.log: Seeing page 1 |             - 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: | color: | ||||||
|   - id: kbx_red |   - id: kbx_red | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user