Compute characteristic value on request

This commit is contained in:
Rapsssito 2024-07-12 23:30:42 +02:00
parent 55c3b66ed8
commit 4e4cdc92a1
14 changed files with 411 additions and 208 deletions

View file

@ -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

View file

@ -37,52 +37,9 @@ void BLECharacteristic::set_value(std::vector<uint8_t> value) {
this->value_ = std::move(value);
xSemaphoreGive(this->set_value_lock_);
}
void BLECharacteristic::set_value(const std::string &value) {
this->set_value(std::vector<uint8_t>(value.begin(), value.end()));
}
void BLECharacteristic::set_value(const uint8_t *data, size_t length) {
this->set_value(std::vector<uint8_t>(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<BLECharacteristicEvt::EmptyEvt>::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<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>>::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<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>>::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);

View file

@ -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 <vector>
@ -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<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>>, public EventEmitter<BLECharacteristicEvt::EmptyEvt> {
public:
BLECharacteristic(ESPBTUUID uuid, uint32_t properties);
~BLECharacteristic();
void set_value(const uint8_t *data, size_t length);
void set_value(std::vector<uint8_t> 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<void(const std::vector<uint8_t> &)> &&func) { this->on_write_ = func; }
void add_descriptor(BLEDescriptor *descriptor);
void remove_descriptor(BLEDescriptor *descriptor);
@ -83,8 +93,6 @@ class BLECharacteristic {
std::vector<BLEDescriptor *> descriptors_;
std::function<void(const std::vector<uint8_t> &)> on_write_;
esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
enum State : uint8_t {

View file

@ -39,47 +39,7 @@ void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
}
void BLEDescriptor::set_value(std::vector<uint8_t> 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);

View file

@ -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<uint8_t> 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);

View file

@ -17,15 +17,6 @@
namespace esphome {
namespace esp32_ble_server {
Trigger<std::string> *BLEServerAutomationInterface::create_on_write_trigger(BLECharacteristic *characteristic) {
Trigger<std::string> *on_write_trigger = new Trigger<std::string>(); // NOLINT(cppcoreguidelines-owning-memory)
characteristic->on_write([on_write_trigger](const std::vector<uint8_t> &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;

View file

@ -26,21 +26,6 @@ namespace esp32_ble_server {
using namespace esp32_ble;
class BLEServerAutomationInterface {
public:
static Trigger<std::string> *create_on_write_trigger(BLECharacteristic *characteristic);
template<typename... Ts> class BLECharacteristicSetValueAction : public Action<Ts...> {
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<ESP32BLE> {
public:
void setup() override;

View file

@ -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<std::vector<uint8_t>> *BLETriggers::create_on_write_trigger(BLECharacteristic *characteristic) {
Trigger<std::vector<uint8_t>> *on_write_trigger = new Trigger<std::vector<uint8_t>>(); // NOLINT(cppcoreguidelines-owning-memory)
characteristic->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>>::on(BLECharacteristicEvt::VectorEvt::ON_WRITE, [on_write_trigger](const std::vector<uint8_t> &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<BLECharacteristicEvt::EmptyEvt>::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

View file

@ -0,0 +1,77 @@
#pragma once
#include "ble_characteristic.h"
#include "esphome/core/event_emitter.h"
#include "esphome/core/automation.h"
#include <vector>
#include <unordered_map>
#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<std::vector<uint8_t>> *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<BLECharacteristic *, EventEmitterListenerID> listeners_;
};
template<typename... Ts> class BLECharacteristicSetValueAction : public Action<Ts...> {
public:
BLECharacteristicSetValueAction(BLECharacteristic *characteristic) : parent_(characteristic) {}
TEMPLATABLE_VALUE(std::vector<uint8_t>, 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<BLECharacteristicEvt::EmptyEvt>::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<typename... Ts> class BLECharacteristicNotifyAction : public Action<Ts...> {
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

View file

@ -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<uint8_t> &data) {
this->rpc_->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>>::on(BLECharacteristicEvt::VectorEvt::ON_WRITE, [this](const std::vector<uint8_t> &data) {
if (!data.empty()) {
this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
}

View file

@ -0,0 +1,47 @@
#pragma once
#include <map>
#include <vector>
#include <functional>
#include <string>
#include <algorithm>
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 <typename EvtNames, typename... Args>
class EventEmitter {
public:
EventEmitterListenerID on(EvtNames event, std::function<void(Args...)> 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<EventEmitterListenerID, std::function<void(Args...)>> &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<EvtNames, std::vector<std::pair<EventEmitterListenerID, std::function<void(Args...)>>>> listeners_;
EventEmitterListenerID current_id_ = 0;
};
} // namespace esphome

View file

@ -534,6 +534,32 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
return ret;
}
std::vector<uint8_t> to_vector(bool value) { return {value ? (uint8_t)1 : (uint8_t)0}; }
std::vector<uint8_t> to_vector(uint8_t value) { return {value}; }
std::vector<uint8_t> to_vector(uint16_t value) {
return {uint8_t(value >> 8), uint8_t(value & 0xFF)};
}
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> to_vector(int value) {
return to_vector(static_cast<uint32_t>(value));
}
std::vector<uint8_t> to_vector(float value) {
return to_vector(*reinterpret_cast<uint32_t *>(&value));
}
std::vector<uint8_t> to_vector(double value) {
return to_vector(*reinterpret_cast<uint64_t *>(&value));
}
std::vector<uint8_t> to_vector(const std::string &value) {
return std::vector<uint8_t>(value.begin(), value.end());
}
// Colors
float gamma_correct(float value, float gamma) {

View file

@ -441,6 +441,17 @@ std::string base64_encode(const std::vector<uint8_t> &buf);
std::vector<uint8_t> 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<uint8_t> to_vector(bool value);
std::vector<uint8_t> to_vector(uint8_t value);
std::vector<uint8_t> to_vector(uint16_t value);
std::vector<uint8_t> to_vector(uint32_t value);
std::vector<uint8_t> to_vector(uint64_t value);
std::vector<uint8_t> to_vector(int value);
std::vector<uint8_t> to_vector(float value);
std::vector<uint8_t> to_vector(double value);
std::vector<uint8_t> to_vector(const std::string &value);
///@}
/// @name Colors

View file

@ -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