mirror of
https://github.com/esphome/esphome.git
synced 2024-11-28 09:44:12 +01:00
Compute characteristic value on request
This commit is contained in:
parent
55c3b66ed8
commit
4e4cdc92a1
14 changed files with 411 additions and 208 deletions
|
@ -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,20 +140,19 @@ 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":
|
||||
if char_conf[CONF_READ]:
|
||||
result = result | BLECharacteristic_ns.PROPERTY_READ
|
||||
elif prop == "WRITE":
|
||||
if char_conf[CONF_WRITE]:
|
||||
result = result | BLECharacteristic_ns.PROPERTY_WRITE
|
||||
elif prop == "NOTIFY":
|
||||
if char_conf[CONF_NOTIFY]:
|
||||
result = result | BLECharacteristic_ns.PROPERTY_NOTIFY
|
||||
elif prop == "BROADCAST":
|
||||
if char_conf[CONF_BROADCAST]:
|
||||
result = result | BLECharacteristic_ns.PROPERTY_BROADCAST
|
||||
elif prop == "INDICATE":
|
||||
if char_conf[CONF_INDICATE]:
|
||||
result = result | BLECharacteristic_ns.PROPERTY_INDICATE
|
||||
elif prop == "WRITE_NR":
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
77
esphome/components/esp32_ble_server/ble_server_automations.h
Normal file
77
esphome/components/esp32_ble_server/ble_server_automations.h
Normal 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
|
|
@ -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());
|
||||
}
|
||||
|
|
47
esphome/core/event_emitter.h
Normal file
47
esphome/core/event_emitter.h
Normal 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
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue