diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 9d12ca1f50..5f72732907 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_UUID, CONF_SERVICES, CONF_VALUE, - CONF_MAX_LENGTH, ) from esphome.components import esp32_ble from esphome.core import CORE @@ -24,8 +23,14 @@ CONF_ADVERTISE = "advertise" CONF_NUM_HANDLES = "num_handles" CONF_ON_WRITE = "on_write" CONF_CHARACTERISTICS = "characteristics" -CONF_PROPERTIES = "properties" +CONF_READ = "read" +CONF_WRITE = "write" +CONF_NOTIFY = "notify" +CONF_BROADCAST = "broadcast" +CONF_INDICATE = "indicate" +CONF_WRITE_NO_RESPONSE = "write_no_response" CONF_DESCRIPTORS = "descriptors" +CONF_VALUE_ACTION_ID = "value_action_id_" esp32_ble_server_ns = cg.esphome_ns.namespace("esp32_ble_server") ESPBTUUID_ns = cg.esphome_ns.namespace("esp32_ble").namespace("ESPBTUUID") @@ -36,15 +41,20 @@ BLEServer = esp32_ble_server_ns.class_( esp32_ble.GATTsEventHandler, cg.Parented.template(esp32_ble.ESP32BLE), ) -BLEServerAutomationInterface = esp32_ble_server_ns.namespace( - "BLEServerAutomationInterface" +esp32_ble_server_automations_ns = esp32_ble_server_ns.namespace( + "esp32_ble_server_automations" ) +BLETriggers_ns = esp32_ble_server_automations_ns.namespace("BLETriggers") BLEDescriptor = esp32_ble_server_ns.class_("BLEDescriptor") BLECharacteristic = esp32_ble_server_ns.class_("BLECharacteristic") BLEService = esp32_ble_server_ns.class_("BLEService") -BLECharacteristicSetValueAction = BLEServerAutomationInterface.class_( +BLECharacteristicSetValueAction = esp32_ble_server_automations_ns.class_( "BLECharacteristicSetValueAction", automation.Action ) +BLECharacteristicNotifyAction = esp32_ble_server_automations_ns.class_( + "BLECharacteristicNotifyAction", automation.Action +) +_ble_server_config = None def validate_uuid(value): @@ -53,40 +63,35 @@ def validate_uuid(value): return value -PROPERTIES_SCHEMA = cv.All( - cv.ensure_list( - cv.one_of( - "READ", - "WRITE", - "NOTIFY", - "BROADCAST", - "INDICATE", - "WRITE_NR", - upper=True, - ) - ), - cv.Length(min=1), -) - UUID_SCHEMA = cv.Any(cv.All(cv.string, validate_uuid), cv.hex_uint32_t) -CHARACTERISTIC_VALUE_SCHEMA = cv.Any( - cv.All(cv.ensure_list(cv.hex_uint8_t), cv.Length(min=1)), - cv.string, +DESCRIPTOR_VALUE_SCHEMA = cv.Any( + cv.boolean, + cv.float_, cv.hex_uint8_t, cv.hex_uint16_t, cv.hex_uint32_t, cv.int_, - cv.float_, - cv.boolean, + cv.All(cv.ensure_list(cv.hex_uint8_t), cv.Length(min=1)), + cv.string, ) -SERVICE_CHARACTERISTIC_DESCRIPTOR_SCHEMA = cv.Schema( +CHARACTERISTIC_VALUE_SCHEMA = cv.Any( + cv.boolean, + cv.float_, + cv.hex_uint8_t, + cv.hex_uint16_t, + cv.hex_uint32_t, + cv.int_, + cv.templatable(cv.All(cv.ensure_list(cv.hex_uint8_t), cv.Length(min=1))), + cv.string, +) + +DESCRIPTOR_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(BLEDescriptor), cv.Required(CONF_UUID): UUID_SCHEMA, - cv.Optional(CONF_MAX_LENGTH, default=0): cv.int_, - cv.Required(CONF_VALUE): CHARACTERISTIC_VALUE_SCHEMA, + cv.Required(CONF_VALUE): DESCRIPTOR_VALUE_SCHEMA, } ) @@ -94,10 +99,16 @@ SERVICE_CHARACTERISTIC_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(BLECharacteristic), cv.Required(CONF_UUID): UUID_SCHEMA, - cv.Required(CONF_PROPERTIES): PROPERTIES_SCHEMA, + cv.Optional(CONF_READ, default=False): cv.boolean, + cv.Optional(CONF_WRITE, default=False): cv.boolean, + cv.Optional(CONF_NOTIFY, default=False): cv.boolean, + cv.Optional(CONF_BROADCAST, default=False): cv.boolean, + cv.Optional(CONF_INDICATE, default=False): cv.boolean, + cv.Optional(CONF_WRITE_NO_RESPONSE, default=False): cv.boolean, cv.Optional(CONF_VALUE): CHARACTERISTIC_VALUE_SCHEMA, + cv.GenerateID(CONF_VALUE_ACTION_ID): cv.declare_id(BLECharacteristicSetValueAction), cv.Optional(CONF_DESCRIPTORS, default=[]): cv.ensure_list( - SERVICE_CHARACTERISTIC_DESCRIPTOR_SCHEMA + DESCRIPTOR_SCHEMA ), cv.Optional(CONF_ON_WRITE): automation.validate_automation( {cv.GenerateID(): cv.declare_id(BLECharacteristic)}, single=True @@ -129,21 +140,20 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def parse_properties(properties): +def parse_properties(char_conf): result = 0 - for prop in properties: - if prop == "READ": - result = result | BLECharacteristic_ns.PROPERTY_READ - elif prop == "WRITE": - result = result | BLECharacteristic_ns.PROPERTY_WRITE - elif prop == "NOTIFY": - result = result | BLECharacteristic_ns.PROPERTY_NOTIFY - elif prop == "BROADCAST": - result = result | BLECharacteristic_ns.PROPERTY_BROADCAST - elif prop == "INDICATE": - result = result | BLECharacteristic_ns.PROPERTY_INDICATE - elif prop == "WRITE_NR": - result = result | BLECharacteristic_ns.PROPERTY_WRITE_NR + if char_conf[CONF_READ]: + result = result | BLECharacteristic_ns.PROPERTY_READ + if char_conf[CONF_WRITE]: + result = result | BLECharacteristic_ns.PROPERTY_WRITE + if char_conf[CONF_NOTIFY]: + result = result | BLECharacteristic_ns.PROPERTY_NOTIFY + if char_conf[CONF_BROADCAST]: + result = result | BLECharacteristic_ns.PROPERTY_BROADCAST + if char_conf[CONF_INDICATE]: + result = result | BLECharacteristic_ns.PROPERTY_INDICATE + if char_conf[CONF_WRITE_NO_RESPONSE]: + result = result | BLECharacteristic_ns.PROPERTY_WRITE_NR return result @@ -155,10 +165,47 @@ def parse_uuid(uuid): return ESPBTUUID_ns.from_uint32(uuid) -def parse_value(value): - if isinstance(value, list): - return cg.std_vector.template(cg.uint8)(value) - return value +def parse_descriptor_value(value): + # Compute the maximum length of the descriptor value + # Also parse the value for byte arrays + try: + cv.boolean(value) + return value, 1 + except cv.Invalid: + pass + try: + cv.float_(value) + return value, 8 + except cv.Invalid: + pass + try: + cv.hex_uint8_t(value) + return value, 1 + except cv.Invalid: + pass + try: + cv.hex_uint16_t(value) + return value, 2 + except cv.Invalid: + pass + try: + cv.hex_uint32_t(value) + return value, 4 + except cv.Invalid: + pass + try: + cv.int_(value) + return value, 4 + except cv.Invalid: + pass + try: + cv.string(value) + # Count the bytes in the string + value_len = len(value.encode("utf-8")) + return value, value_len + except cv.Invalid: + pass + return cg.std_vector.template(cg.uint8)(value), len(value) # Assume it's a list of bytes def calculate_num_handles(service_config): @@ -172,6 +219,8 @@ def calculate_num_handles(service_config): async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) + global _ble_server_config + _ble_server_config = config await cg.register_component(var, config) @@ -202,51 +251,106 @@ async def to_code(config): char_conf[CONF_ID], service_var.create_characteristic( parse_uuid(char_conf[CONF_UUID]), - parse_properties(char_conf[CONF_PROPERTIES]), + parse_properties(char_conf), ), ) if CONF_ON_WRITE in char_conf: on_write_conf = char_conf[CONF_ON_WRITE] - if "WRITE" not in char_conf[CONF_PROPERTIES]: - raise cv.Invalid("on_write requires the WRITE property") + if not char_conf[CONF_WRITE] and not char_conf[CONF_WRITE_NO_RESPONSE]: + raise cv.Invalid(f"on_write requires the {CONF_WRITE} or {CONF_WRITE_NO_RESPONSE} property to be set") await automation.build_automation( - BLEServerAutomationInterface.create_on_write_trigger(char_var), - [(cg.std_string, "x")], + BLETriggers_ns.create_on_write_trigger(char_var), + [(cg.std_vector.template(cg.uint8), "x")], on_write_conf, ) if CONF_VALUE in char_conf: - cg.add(char_var.set_value(parse_value(char_conf[CONF_VALUE]))) + action_conf = { + CONF_ID: char_conf[CONF_ID], + CONF_VALUE: char_conf[CONF_VALUE], + } + value_action = await ble_server_characteristic_set_value(action_conf, char_conf[CONF_VALUE_ACTION_ID], cg.TemplateArguments(None), {}) + cg.add(value_action.play()) for descriptor_conf in char_conf[CONF_DESCRIPTORS]: - max_length = descriptor_conf[CONF_MAX_LENGTH] - # If max_length is 0, calculate the optimal length based on the value - if max_length == 0: - max_length = len(parse_value(descriptor_conf[CONF_VALUE])) + descriptor_value, max_length = parse_descriptor_value(descriptor_conf[CONF_VALUE]) desc_var = cg.new_Pvariable( descriptor_conf[CONF_ID], parse_uuid(descriptor_conf[CONF_UUID]), max_length, ) if CONF_VALUE in descriptor_conf: - cg.add(desc_var.set_value(parse_value(descriptor_conf[CONF_VALUE]))) + cg.add(desc_var.set_value(descriptor_value)) cg.add(var.enqueue_start_service(service_var)) cg.add_define("USE_ESP32_BLE_SERVER") if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) +async def parse_characteristic_value(value, args): + if isinstance(value, cv.Lambda): + return await cg.templatable(value, args, cg.std_vector.template(cg.uint8), cg.std_vector.template(cg.uint8)) + if isinstance(value, list): + return cg.std_vector.template(cg.uint8)(value) + # Transform the value into a vector of bytes + exp_value = cg.RawExpression(f'to_vector({value})') + try: + bool_value = cv.boolean(value) + return cg.RawExpression(f'to_vector({"true" if bool_value else "false"})') + except cv.Invalid: + pass + try: + int_ = cv.uint64_t(value) + return cg.RawExpression(f'to_vector({int_})') + except cv.Invalid: + pass + try: + float_ = cv.float_(value) + return cg.RawExpression(f'to_vector({float_})') + except cv.Invalid: + pass + try: + string_ = cv.string(value) + return cg.RawExpression(f'to_vector("{string_}")') + except cv.Invalid: + pass + raise cv.Invalid(f"Invalid value {value}") + + @automation.register_action( - "ble_server.characteristic_set_value", + "ble_server.characteristic.set_value", BLECharacteristicSetValueAction, cv.Schema( { cv.Required(CONF_ID): cv.use_id(BLECharacteristic), - cv.Required(CONF_VALUE): cv.templatable(cv.string), + cv.Required(CONF_VALUE): CHARACTERISTIC_VALUE_SCHEMA, } ), ) async def ble_server_characteristic_set_value(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_VALUE], args, cg.std_string) - cg.add(var.set_value(template_)) + value = await parse_characteristic_value(config[CONF_VALUE], args) + cg.add(var.set_value(value)) + return var + + +@automation.register_action( + "ble_server.characteristic.notify", + BLECharacteristicNotifyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(BLECharacteristic), + } + ), +) +async def ble_server_characteristic_notify(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + # Check if the NOTIFY property is set from the global configuration + assert _ble_server_config is not None + for service_config in _ble_server_config[CONF_SERVICES]: + for char_conf in service_config[CONF_CHARACTERISTICS]: + if char_conf[CONF_ID] == config[CONF_ID]: + if not char_conf[CONF_NOTIFY]: + raise cv.Invalid(f'Characteristic "{char_conf[CONF_ID]}" does not have the NOTIFY property set') + break + var = cg.new_Pvariable(action_id, template_arg, paren) return var diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 92a85bdd25..dd2f198374 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -37,52 +37,9 @@ void BLECharacteristic::set_value(std::vector value) { this->value_ = std::move(value); xSemaphoreGive(this->set_value_lock_); } -void BLECharacteristic::set_value(const std::string &value) { - this->set_value(std::vector(value.begin(), value.end())); -} void BLECharacteristic::set_value(const uint8_t *data, size_t length) { this->set_value(std::vector(data, data + length)); } -void BLECharacteristic::set_value(uint8_t &data) { - uint8_t temp[1]; - temp[0] = data; - this->set_value(temp, 1); -} -void BLECharacteristic::set_value(uint16_t &data) { - uint8_t temp[2]; - temp[0] = data; - temp[1] = data >> 8; - this->set_value(temp, 2); -} -void BLECharacteristic::set_value(uint32_t &data) { - uint8_t temp[4]; - temp[0] = data; - temp[1] = data >> 8; - temp[2] = data >> 16; - temp[3] = data >> 24; - this->set_value(temp, 4); -} -void BLECharacteristic::set_value(int &data) { - uint8_t temp[4]; - temp[0] = data; - temp[1] = data >> 8; - temp[2] = data >> 16; - temp[3] = data >> 24; - this->set_value(temp, 4); -} -void BLECharacteristic::set_value(float &data) { - float temp = data; - this->set_value((uint8_t *) &temp, 4); -} -void BLECharacteristic::set_value(double &data) { - double temp = data; - this->set_value((uint8_t *) &temp, 8); -} -void BLECharacteristic::set_value(bool &data) { - uint8_t temp[1]; - temp[0] = data; - this->set_value(temp, 1); -} void BLECharacteristic::notify(bool require_ack) { if (require_ack) { @@ -223,6 +180,8 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt if (!param->read.need_rsp) break; // For some reason you can request a read but not want a response + this->EventEmitter::emit(BLECharacteristicEvt::EmptyEvt::ON_READ); + uint16_t max_offset = 22; esp_gatt_rsp_t response; @@ -289,8 +248,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt } if (!param->write.is_prep) { - if (this->on_write_) - this->on_write_(this->value_); + this->EventEmitter>::emit(BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_); } break; @@ -301,8 +259,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt break; this->write_event_ = false; if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { - if (this->on_write_) - this->on_write_(this->value_); + this->EventEmitter>::emit(BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_); } esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr); diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index a43ba05ac0..b613ddd155 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -2,6 +2,8 @@ #include "ble_descriptor.h" #include "esphome/components/esp32_ble/ble_uuid.h" +#include "esphome/core/event_emitter.h" +#include "esphome/core/helpers.h" #include @@ -22,21 +24,31 @@ using namespace esp32_ble; class BLEService; -class BLECharacteristic { +namespace BLECharacteristicEvt { + enum VectorEvt { + ON_WRITE, + }; + + enum EmptyEvt { + ON_READ, + }; +} + +class BLECharacteristic : public EventEmitter>, public EventEmitter { public: BLECharacteristic(ESPBTUUID uuid, uint32_t properties); ~BLECharacteristic(); void set_value(const uint8_t *data, size_t length); void set_value(std::vector value); - void set_value(const std::string &value); - void set_value(uint8_t &data); - void set_value(uint16_t &data); - void set_value(uint32_t &data); - void set_value(int &data); - void set_value(float &data); - void set_value(double &data); - void set_value(bool &data); + void set_value(const std::string &value) { this->set_value(to_vector(value)); } + void set_value(uint8_t data) { this->set_value(to_vector(data)); } + void set_value(uint16_t data) { this->set_value(to_vector(data)); } + void set_value(uint32_t data) { this->set_value(to_vector(data)); } + void set_value(int data) { this->set_value(to_vector(data)); } + void set_value(float data) { this->set_value(to_vector(data)); } + void set_value(double data) { this->set_value(to_vector(data)); } + void set_value(bool data) { this->set_value(to_vector(data)); } void set_broadcast_property(bool value); void set_indicate_property(bool value); @@ -51,8 +63,6 @@ class BLECharacteristic { void do_create(BLEService *service); void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); - void on_write(const std::function &)> &&func) { this->on_write_ = func; } - void add_descriptor(BLEDescriptor *descriptor); void remove_descriptor(BLEDescriptor *descriptor); @@ -83,8 +93,6 @@ class BLECharacteristic { std::vector descriptors_; - std::function &)> on_write_; - esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; enum State : uint8_t { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index 6ab4cde075..a82464130c 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -39,47 +39,7 @@ void BLEDescriptor::do_create(BLECharacteristic *characteristic) { } void BLEDescriptor::set_value(std::vector value) { this->set_value(value.data(), value.size()); } -void BLEDescriptor::set_value(const std::string &value) { this->set_value((uint8_t *) value.data(), value.length()); } -void BLEDescriptor::set_value(uint8_t &data) { - uint8_t temp[1]; - temp[0] = data; - this->set_value(temp, 1); -} -void BLEDescriptor::set_value(uint16_t &data) { - uint8_t temp[2]; - temp[0] = data; - temp[1] = data >> 8; - this->set_value(temp, 2); -} -void BLEDescriptor::set_value(uint32_t &data) { - uint8_t temp[4]; - temp[0] = data; - temp[1] = data >> 8; - temp[2] = data >> 16; - temp[3] = data >> 24; - this->set_value(temp, 4); -} -void BLEDescriptor::set_value(int &data) { - uint8_t temp[4]; - temp[0] = data; - temp[1] = data >> 8; - temp[2] = data >> 16; - temp[3] = data >> 24; - this->set_value(temp, 4); -} -void BLEDescriptor::set_value(float &data) { - float temp = data; - this->set_value((uint8_t *) &temp, 4); -} -void BLEDescriptor::set_value(double &data) { - double temp = data; - this->set_value((uint8_t *) &temp, 8); -} -void BLEDescriptor::set_value(bool &data) { - uint8_t temp[1]; - temp[0] = data; - this->set_value(temp, 1); -} + void BLEDescriptor::set_value(const uint8_t *data, size_t length) { if (length > this->value_.attr_max_len) { ESP_LOGE(TAG, "Size %d too large, must be no bigger than %d", length, this->value_.attr_max_len); diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index d60721e3cf..f294eeaa3c 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/esp32_ble/ble_uuid.h" +#include "esphome/core/helpers.h" #ifdef USE_ESP32 @@ -21,15 +22,15 @@ class BLEDescriptor { void do_create(BLECharacteristic *characteristic); void set_value(const uint8_t *data, size_t length); - void set_value(const std::string &value); void set_value(std::vector value); - void set_value(uint8_t &data); - void set_value(uint16_t &data); - void set_value(uint32_t &data); - void set_value(int &data); - void set_value(float &data); - void set_value(double &data); - void set_value(bool &data); + void set_value(const std::string &value) { this->set_value(to_vector(value)); } + void set_value(uint8_t data) { this->set_value(to_vector(data)); } + void set_value(uint16_t data) { this->set_value(to_vector(data)); } + void set_value(uint32_t data) { this->set_value(to_vector(data)); } + void set_value(int data) { this->set_value(to_vector(data)); } + void set_value(float data) { this->set_value(to_vector(data)); } + void set_value(double data) { this->set_value(to_vector(data)); } + void set_value(bool data) { this->set_value(to_vector(data)); } void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 897d775da7..5c227abc25 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -17,15 +17,6 @@ namespace esphome { namespace esp32_ble_server { -Trigger *BLEServerAutomationInterface::create_on_write_trigger(BLECharacteristic *characteristic) { - Trigger *on_write_trigger = new Trigger(); // NOLINT(cppcoreguidelines-owning-memory) - characteristic->on_write([on_write_trigger](const std::vector &data) { - std::string value(data.begin(), data.end()); - on_write_trigger->trigger(value); - }); - return on_write_trigger; -} - static const char *const TAG = "esp32_ble_server"; static const uint16_t DEVICE_INFORMATION_SERVICE_UUID = 0x180A; diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index fb42e0e4aa..5923b112d2 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -26,21 +26,6 @@ namespace esp32_ble_server { using namespace esp32_ble; -class BLEServerAutomationInterface { - public: - static Trigger *create_on_write_trigger(BLECharacteristic *characteristic); - - template class BLECharacteristicSetValueAction : public Action { - public: - BLECharacteristicSetValueAction(BLECharacteristic *characteristic) : parent_(characteristic) {} - TEMPLATABLE_VALUE(std::string, value) - void play(Ts... x) override { this->parent_->set_value(this->value_.value(x...)); } - - protected: - BLECharacteristic *parent_; - }; -}; - class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented { public: void setup() override; diff --git a/esphome/components/esp32_ble_server/ble_server_automations.cpp b/esphome/components/esp32_ble_server/ble_server_automations.cpp new file mode 100644 index 0000000000..8a70da5933 --- /dev/null +++ b/esphome/components/esp32_ble_server/ble_server_automations.cpp @@ -0,0 +1,33 @@ +#include "ble_server_automations.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_server { +// Interface to interact with ESPHome automations and triggers +namespace esp32_ble_server_automations { + +using namespace esp32_ble; + +Trigger> *BLETriggers::create_on_write_trigger(BLECharacteristic *characteristic) { + Trigger> *on_write_trigger = new Trigger>(); // NOLINT(cppcoreguidelines-owning-memory) + characteristic->EventEmitter>::on(BLECharacteristicEvt::VectorEvt::ON_WRITE, [on_write_trigger](const std::vector &data) { + on_write_trigger->trigger(data); + }); + return on_write_trigger; +} + +void BLECharacteristicSetValueActionManager::set_listener(BLECharacteristic *characteristic, EventEmitterListenerID listener_id) { + // Check if there is already a listener for this characteristic + if (this->listeners_.find(characteristic) != this->listeners_.end()) { + // Remove the previous listener + characteristic->EventEmitter::off(BLECharacteristicEvt::EmptyEvt::ON_READ, this->listeners_[characteristic]); + } + this->listeners_[characteristic] = listener_id; +} + +} // namespace esp32_ble_server_automations +} // namespace esp32_ble_server +} // namespace esphome + +#endif diff --git a/esphome/components/esp32_ble_server/ble_server_automations.h b/esphome/components/esp32_ble_server/ble_server_automations.h new file mode 100644 index 0000000000..c4f37118cb --- /dev/null +++ b/esphome/components/esp32_ble_server/ble_server_automations.h @@ -0,0 +1,77 @@ +#pragma once + +#include "ble_characteristic.h" + +#include "esphome/core/event_emitter.h" +#include "esphome/core/automation.h" + +#include +#include + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_server { +// Interface to interact with ESPHome actions and triggers +namespace esp32_ble_server_automations { + +using namespace esp32_ble; + +class BLETriggers { + public: + static Trigger> *create_on_write_trigger(BLECharacteristic *characteristic); +}; + +// Class to make sure only one BLECharacteristicSetValueAction is active at a time +class BLECharacteristicSetValueActionManager { + public: + // Singleton pattern + static BLECharacteristicSetValueActionManager *get_instance() { + static BLECharacteristicSetValueActionManager instance; + return &instance; + } + void set_listener(BLECharacteristic *characteristic, EventEmitterListenerID listener_id); + EventEmitterListenerID get_listener(BLECharacteristic *characteristic) { return this->listeners_[characteristic]; } + + private: + std::unordered_map listeners_; +}; + +template class BLECharacteristicSetValueAction : public Action { + public: + BLECharacteristicSetValueAction(BLECharacteristic *characteristic) : parent_(characteristic) {} + TEMPLATABLE_VALUE(std::vector, value) + void play(Ts... x) override { + // If the listener is already set, do nothing + if (BLECharacteristicSetValueActionManager::get_instance()->get_listener(this->parent_) == this->listener_id_) + return; + // Set initial value + this->parent_->set_value(this->value_.value(x...)); + // Set the listener for read events + this->listener_id_ = this->parent_->EventEmitter::on(BLECharacteristicEvt::EmptyEvt::ON_READ, [this, x...](void) { + // Set the value of the characteristic every time it is read + this->parent_->set_value(this->value_.value(x...)); + }); + // Set the listener in the global manager so only one BLECharacteristicSetValueAction is set for each characteristic + BLECharacteristicSetValueActionManager::get_instance()->set_listener(this->parent_, this->listener_id_); + } + + protected: + BLECharacteristic *parent_; + EventEmitterListenerID listener_id_; +}; + +template class BLECharacteristicNotifyAction : public Action { + public: + BLECharacteristicNotifyAction(BLECharacteristic *characteristic) : parent_(characteristic) {} + void play(Ts... x) override { this->parent_->notify(); } + + protected: + BLECharacteristic *parent_; +}; + +} // namespace esp32_ble_server_automations +} // namespace esp32_ble_server +} // namespace esphome + +#endif diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 6626f3042f..9b9caa68da 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -40,7 +40,7 @@ void ESP32ImprovComponent::setup_characteristics() { this->error_->add_descriptor(error_descriptor); this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); - this->rpc_->on_write([this](const std::vector &data) { + this->rpc_->EventEmitter>::on(BLECharacteristicEvt::VectorEvt::ON_WRITE, [this](const std::vector &data) { if (!data.empty()) { this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); } diff --git a/esphome/core/event_emitter.h b/esphome/core/event_emitter.h new file mode 100644 index 0000000000..e515f44e11 --- /dev/null +++ b/esphome/core/event_emitter.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace esphome { + +using EventEmitterListenerID = uint32_t; + +// EventEmitter class that can emit events with a specific name (usually an enum) and a list of arguments. +// Supports multiple listeners for each event. +template +class EventEmitter { + public: + EventEmitterListenerID on(EvtNames event, std::function listener) { + listeners_[event].emplace_back(++current_id_, [listener](Args... args) { + listener(args...); + }); + return current_id_; +} + + void off(EvtNames event, EventEmitterListenerID id) { + if (this->listeners_.count(event) == 0) + return; + auto &vec = this->listeners_[event]; + vec.erase(std::remove_if(vec.begin(), vec.end(), [id](const std::pair> &pair) { + return pair.first == id; + }), vec.end()); + } + + protected: + void emit(EvtNames event, Args... args) { + if (listeners_.count(event) == 0) + return; + for (const auto &listener : listeners_[event]) { + listener.second(args...); + } + } + + private: + std::map>>> listeners_; + EventEmitterListenerID current_id_ = 0; +}; + +} // namespace esphome diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index dee771d4e9..43c6b885de 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -534,6 +534,32 @@ std::vector base64_decode(const std::string &encoded_string) { return ret; } +std::vector to_vector(bool value) { return {value ? (uint8_t)1 : (uint8_t)0}; } +std::vector to_vector(uint8_t value) { return {value}; } +std::vector to_vector(uint16_t value) { + return {uint8_t(value >> 8), uint8_t(value & 0xFF)}; +} +std::vector to_vector(uint32_t value) { + return {uint8_t(value >> 24), uint8_t((value >> 16) & 0xFF), uint8_t((value >> 8) & 0xFF), uint8_t(value & 0xFF)}; +} +std::vector to_vector(uint64_t value) { + return {uint8_t(value >> 56), uint8_t((value >> 48) & 0xFF), uint8_t((value >> 40) & 0xFF), + uint8_t((value >> 32) & 0xFF), uint8_t((value >> 24) & 0xFF), uint8_t((value >> 16) & 0xFF), + uint8_t((value >> 8) & 0xFF), uint8_t(value & 0xFF)}; +} +std::vector to_vector(int value) { + return to_vector(static_cast(value)); +} +std::vector to_vector(float value) { + return to_vector(*reinterpret_cast(&value)); +} +std::vector to_vector(double value) { + return to_vector(*reinterpret_cast(&value)); +} +std::vector to_vector(const std::string &value) { + return std::vector(value.begin(), value.end()); +} + // Colors float gamma_correct(float value, float gamma) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 4af840f77b..f7a1229523 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -441,6 +441,17 @@ std::string base64_encode(const std::vector &buf); std::vector base64_decode(const std::string &encoded_string); size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len); +/// Create a byte vector multiple types of values. +std::vector to_vector(bool value); +std::vector to_vector(uint8_t value); +std::vector to_vector(uint16_t value); +std::vector to_vector(uint32_t value); +std::vector to_vector(uint64_t value); +std::vector to_vector(int value); +std::vector to_vector(float value); +std::vector to_vector(double value); +std::vector to_vector(const std::string &value); + ///@} /// @name Colors diff --git a/tests/components/esp32_ble_server/common.yaml b/tests/components/esp32_ble_server/common.yaml index a980fe106b..3615546056 100644 --- a/tests/components/esp32_ble_server/common.yaml +++ b/tests/components/esp32_ble_server/common.yaml @@ -8,26 +8,29 @@ esp32_ble_server: num_handles: 14 advertise: false characteristics: - - uuid: cad48e28-7fbe-41cf-bae9-d77a6c233423 - properties: - - read + - id: test_notify_characteristic + uuid: cad48e28-7fbe-41cf-bae9-d77a6c233423 + read: true + notify: true value: [0, 1, 2] + descriptors: + - uuid: cad48e28-7fbe-41cf-bae9-d77a6c111111 + value: 123.1 - uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc42d advertise: false characteristics: - id: test_change_characteristic uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc11d - properties: - - read + read: true value: "Initial" - uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc12d - properties: - - read - - write + write: true on_write: then: - lambda: |- - ESP_LOGD("BLE", "Received: %s", x.c_str()); - - ble_server.characteristic_set_value: + ESP_LOGD("BLE", "Received: %s", std::string(x.begin(), x.end()).c_str()); + - ble_server.characteristic.set_value: id: test_change_characteristic - value: !lambda 'return "Echo " + x + "";' + value: !lambda 'return {0x00, 0x01, 0x02};' + - ble_server.characteristic.notify: + id: test_notify_characteristic