From baad92515bded7d80f42374c25855a9d6726a535 Mon Sep 17 00:00:00 2001 From: rbaron Date: Mon, 8 Aug 2022 02:59:55 +0200 Subject: [PATCH] Introduces ble_client.ble_write Action (#3398) --- esphome/components/ble_client/__init__.py | 32 ++++++++ esphome/components/ble_client/automation.cpp | 74 +++++++++++++++++++ esphome/components/ble_client/automation.h | 37 ++++++++++ .../components/ble_client/output/__init__.py | 3 +- .../components/ble_client/sensor/__init__.py | 2 +- .../ble_client/text_sensor/__init__.py | 2 +- esphome/const.py | 1 + tests/test2.yaml | 10 +++ 8 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 esphome/components/ble_client/automation.cpp diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 4bd5c25246..6c13e7fcf5 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -2,12 +2,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import esp32_ble_tracker from esphome.const import ( + CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_MAC_ADDRESS, CONF_NAME, CONF_ON_CONNECT, CONF_ON_DISCONNECT, + CONF_SERVICE_UUID, CONF_TRIGGER_ID, + CONF_VALUE, ) from esphome import automation @@ -27,6 +30,8 @@ BLEClientConnectTrigger = ble_client_ns.class_( BLEClientDisconnectTrigger = ble_client_ns.class_( "BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef) ) +# Actions +BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) # Espressif platformio framework is built with MAX_BLE_CONN to 3, so # enforce this in yaml checks. @@ -72,6 +77,33 @@ async def register_ble_node(var, config): cg.add(parent.register_ble_node(var)) +BLE_WRITE_ACTION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(BLEClient), + cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, + cv.Required(CONF_VALUE): cv.ensure_list(cv.hex_uint8_t), + } +) + + +@automation.register_action( + "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA +) +async def ble_write_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + value = config[CONF_VALUE] + cg.add(var.set_value(value)) + serv_uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) + cg.add(var.set_service_uuid128(serv_uuid128)) + char_uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_CHARACTERISTIC_UUID] + ) + cg.add(var.set_char_uuid128(char_uuid128)) + return var + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/ble_client/automation.cpp b/esphome/components/ble_client/automation.cpp new file mode 100644 index 0000000000..8d5fe96570 --- /dev/null +++ b/esphome/components/ble_client/automation.cpp @@ -0,0 +1,74 @@ +#include "automation.h" + +#include +#include +#include + +#include "esphome/core/log.h" + +namespace esphome { +namespace ble_client { +static const char *const TAG = "ble_client.automation"; + +void BLEWriterClientNode::write() { + 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", this->value_.size(), format_hex_pretty(this->value_).c_str()); + esp_err_t err = esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, + value_.size(), 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 esphome diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 6c374046ba..12c22345b4 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/automation.h" #include "esphome/components/ble_client/ble_client.h" @@ -33,6 +35,41 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { } }; +class BLEWriterClientNode : public BLEClientNode { + public: + BLEWriterClientNode(BLEClient *ble_client) { + ble_client->register_ble_node(this); + ble_client_ = ble_client; + } + + void set_value(std::vector value) { value_ = std::move(value); } + + // Attempts to write the contents of value_ to char_uuid_. + void write(); + + void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + + void set_service_uuid128(uint8_t *uuid) { this->service_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_; + std::vector value_; +}; + +template class BLEClientWriteAction : public Action, public BLEWriterClientNode { + public: + BLEClientWriteAction(BLEClient *ble_client) : BLEWriterClientNode(ble_client) {} + + void play(Ts... x) override { return write(); } +}; + } // namespace ble_client } // namespace esphome diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py index e28421d1a7..fd847d80b8 100644 --- a/esphome/components/ble_client/output/__init__.py +++ b/esphome/components/ble_client/output/__init__.py @@ -1,13 +1,12 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import ble_client, esp32_ble_tracker, output -from esphome.const import CONF_ID, CONF_SERVICE_UUID +from esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID from .. import ble_client_ns DEPENDENCIES = ["ble_client"] -CONF_CHARACTERISTIC_UUID = "characteristic_uuid" CONF_REQUIRE_RESPONSE = "require_response" BLEBinaryOutput = ble_client_ns.class_( diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index 71cfb03ae0..e8f84d2542 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client, esp32_ble_tracker from esphome.const import ( + CONF_CHARACTERISTIC_UUID, CONF_LAMBDA, CONF_TRIGGER_ID, CONF_SERVICE_UUID, @@ -11,7 +12,6 @@ from .. import ble_client_ns DEPENDENCIES = ["ble_client"] -CONF_CHARACTERISTIC_UUID = "characteristic_uuid" CONF_DESCRIPTOR_UUID = "descriptor_uuid" CONF_NOTIFY = "notify" diff --git a/esphome/components/ble_client/text_sensor/__init__.py b/esphome/components/ble_client/text_sensor/__init__.py index e1f97e4a01..66f00c551b 100644 --- a/esphome/components/ble_client/text_sensor/__init__.py +++ b/esphome/components/ble_client/text_sensor/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor, ble_client, esp32_ble_tracker from esphome.const import ( + CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_TRIGGER_ID, CONF_SERVICE_UUID, @@ -11,7 +12,6 @@ from .. import ble_client_ns DEPENDENCIES = ["ble_client"] -CONF_CHARACTERISTIC_UUID = "characteristic_uuid" CONF_DESCRIPTOR_UUID = "descriptor_uuid" CONF_NOTIFY = "notify" diff --git a/esphome/const.py b/esphome/const.py index 694609480d..6de2581fc8 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -88,6 +88,7 @@ CONF_CERTIFICATE_AUTHORITY = "certificate_authority" CONF_CHANGE_MODE_EVERY = "change_mode_every" CONF_CHANNEL = "channel" CONF_CHANNELS = "channels" +CONF_CHARACTERISTIC_UUID = "characteristic_uuid" CONF_CHIPSET = "chipset" CONF_CLEAR_IMPEDANCE = "clear_impedance" CONF_CLIENT_ID = "client_id" diff --git a/tests/test2.yaml b/tests/test2.yaml index f88486524f..5dd9e76a2f 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -605,3 +605,13 @@ cap1188: touch_threshold: 0x20 allow_multiple_touches: true reset_pin: 14 + +switch: + - platform: template + name: "Test BLE Write Action" + turn_on_action: + - ble_client.ble_write: + id: airthings01 + service_uuid: F61E3BE9-2826-A81B-970A-4D4DECFABBAE + characteristic_uuid: 6490FAFE-0734-732C-8705-91B653A081FC + value: [0x01, 0xab, 0xff]