Add type and endianness, remove byte_length

This commit is contained in:
Rapsssito 2024-11-15 12:08:05 +01:00
parent 116d1e68a7
commit 41a4242aad
2 changed files with 136 additions and 165 deletions

View file

@ -1,3 +1,4 @@
import encodings
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble from esphome.components import esp32_ble
@ -11,6 +12,7 @@ from esphome.const import (
CONF_ON_CONNECT, CONF_ON_CONNECT,
CONF_ON_DISCONNECT, CONF_ON_DISCONNECT,
CONF_SERVICES, CONF_SERVICES,
CONF_TYPE,
CONF_UUID, CONF_UUID,
CONF_VALUE, CONF_VALUE,
) )
@ -20,27 +22,28 @@ AUTO_LOAD = ["esp32_ble", "bytebuffer"]
CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CONF_BYTE_LENGTH = "byte_length" CONF_ADVERTISE = "advertise"
CONF_BROADCAST = "broadcast"
CONF_CHARACTERISTICS = "characteristics"
CONF_DESCRIPTION = "description"
CONF_DESCRIPTORS = "descriptors"
CONF_ENDIANNES = "endianness"
CONF_INDICATE = "indicate"
CONF_MANUFACTURER = "manufacturer" CONF_MANUFACTURER = "manufacturer"
CONF_MANUFACTURER_DATA = "manufacturer_data" CONF_MANUFACTURER_DATA = "manufacturer_data"
CONF_ADVERTISE = "advertise"
CONF_ON_WRITE = "on_write" CONF_ON_WRITE = "on_write"
CONF_CHARACTERISTICS = "characteristics"
CONF_READ = "read" CONF_READ = "read"
CONF_WRITE = "write"
CONF_BROADCAST = "broadcast"
CONF_INDICATE = "indicate"
CONF_WRITE_NO_RESPONSE = "write_no_response"
CONF_DESCRIPTORS = "descriptors"
CONF_STRING_ENCODING = "string_encoding" CONF_STRING_ENCODING = "string_encoding"
CONF_DESCRIPTION = "description" CONF_WRITE = "write"
CONF_WRITE_NO_RESPONSE = "write_no_response"
CONF_CHAR_VALUE_ACTION_ID_ = "char_value_action_id_" # Internal configuration keys
CONF_VALUE_BUFFER_ = "value_buffer_"
CONF_CUD_ID_ = "cud_id_"
CONF_CUD_VALUE_BUFFER_ = "cud_value_buffer_"
CONF_CCCD_ID_ = "cccd_id_" CONF_CCCD_ID_ = "cccd_id_"
CONF_CCCD_VALUE_BUFFER_ = "cccd_value_buffer_" CONF_CCCD_VALUE_BUFFER_ = "cccd_value_buffer_"
CONF_CHAR_VALUE_ACTION_ID_ = "char_value_action_id_"
CONF_CUD_ID_ = "cud_id_"
CONF_CUD_VALUE_BUFFER_ = "cud_value_buffer_"
CONF_VALUE_BUFFER_ = "value_buffer_"
# Core key to store the global configuration # Core key to store the global configuration
_KEY_NOTIFY_REQUIRED = "esp32_ble_server_notify_required" _KEY_NOTIFY_REQUIRED = "esp32_ble_server_notify_required"
@ -68,6 +71,7 @@ BLECharacteristicNotifyAction = esp32_ble_server_automations_ns.class_(
"BLECharacteristicNotifyAction", automation.Action "BLECharacteristicNotifyAction", automation.Action
) )
bytebuffer_ns = cg.esphome_ns.namespace("bytebuffer") bytebuffer_ns = cg.esphome_ns.namespace("bytebuffer")
Endianness_ns = bytebuffer_ns.namespace("Endian")
ByteBuffer_ns = bytebuffer_ns.namespace("ByteBuffer") ByteBuffer_ns = bytebuffer_ns.namespace("ByteBuffer")
ByteBuffer = bytebuffer_ns.class_("ByteBuffer") ByteBuffer = bytebuffer_ns.class_("ByteBuffer")
@ -108,18 +112,6 @@ def validate_notify_action(action_char_id):
return action_char_id return action_char_id
def validate_descritor_value_length(descriptor_conf):
# Check if the value length is specified for the descriptor if the value is a templatable
if (
cg.is_template(descriptor_conf[CONF_VALUE])
and CONF_BYTE_LENGTH not in descriptor_conf
):
raise cv.Invalid(
f"Descriptor {descriptor_conf[CONF_UUID]} is a templatable value, so the {CONF_BYTE_LENGTH} property must be set"
)
return descriptor_conf
def create_description_cud(char_config): def create_description_cud(char_config):
if CONF_DESCRIPTION not in char_config: if CONF_DESCRIPTION not in char_config:
return char_config return char_config
@ -137,8 +129,6 @@ def create_description_cud(char_config):
CONF_READ: True, CONF_READ: True,
CONF_WRITE: False, CONF_WRITE: False,
CONF_VALUE: char_config[CONF_DESCRIPTION], CONF_VALUE: char_config[CONF_DESCRIPTION],
CONF_STRING_ENCODING: char_config[CONF_STRING_ENCODING],
CONF_VALUE_BUFFER_: char_config[CONF_CUD_VALUE_BUFFER_],
} }
) )
return char_config return char_config
@ -163,9 +153,12 @@ def create_notify_cccd(char_config):
CONF_UUID: 0x2902, CONF_UUID: 0x2902,
CONF_READ: True, CONF_READ: True,
CONF_WRITE: True, CONF_WRITE: True,
CONF_VALUE: [0, 0], CONF_VALUE: VALUE_SCHEMA(
CONF_STRING_ENCODING: char_config[CONF_STRING_ENCODING], {
CONF_VALUE_BUFFER_: char_config[CONF_CCCD_VALUE_BUFFER_], CONF_VALUE: "{0, 0}",
CONF_TYPE: "std::vector<uint8_t>",
}
),
} }
) )
return char_config return char_config
@ -189,23 +182,51 @@ def final_validate_config(config):
return config return config
VALUE_SCHEMA = cv.Any( def validate_value_type(value_config):
cv.boolean, # If the value is a not a templatable, the type must be set
cv.uint8_t, if not cg.is_template(value_config[CONF_VALUE]):
cv.uint16_t, if CONF_TYPE not in value_config:
cv.uint32_t, raise cv.Invalid(
cv.int_, f"Value {value_config[CONF_VALUE]} is not templatable, so the {CONF_TYPE} property must be set"
cv.float_,
cv.templatable(cv.All(cv.ensure_list(cv.uint8_t), cv.Length(min=1))),
cv.string,
) )
return value_config
VALUE_EXTRAS_SCHEMA = cv.Schema(
VALUE_SCHEMA = cv.Schema(
{ {
cv.Optional(CONF_STRING_ENCODING, default="utf-8"): cv.string, cv.Required(CONF_VALUE): cv.Any(
cv.Optional(CONF_BYTE_LENGTH): cv.uint16_t, cv.string_strict,
cv.GenerateID(CONF_VALUE_BUFFER_): cv.declare_id(ByteBuffer), cv.templatable(cv.All(cv.ensure_list(cv.uint8_t), cv.Length(min=1))),
),
cv.Optional(CONF_TYPE): cv.string_strict,
cv.Optional(CONF_STRING_ENCODING, default="utf-8"): cv.Any(
*(
list(encodings.aliases.aliases.keys())
+ [
"utf-8",
"utf8",
"latin-1",
"latin1",
"iso-8859-1",
"iso8859-1",
"ascii",
"us-ascii",
"utf-16",
"utf16",
"utf-32",
"utf32",
]
) # Common encodings
),
cv.Optional(CONF_ENDIANNES, default="LITTLE"): cv.enum(
{
"LITTLE": Endianness_ns.LITTLE,
"BIG": Endianness_ns.BIG,
} }
),
cv.GenerateID(CONF_VALUE_BUFFER_): cv.declare_id(ByteBuffer),
},
extra_schemas=[validate_value_type],
) )
DESCRIPTOR_SCHEMA = cv.Schema( DESCRIPTOR_SCHEMA = cv.Schema(
@ -217,11 +238,10 @@ DESCRIPTOR_SCHEMA = cv.Schema(
cv.Optional(CONF_ON_WRITE): automation.validate_automation(single=True), cv.Optional(CONF_ON_WRITE): automation.validate_automation(single=True),
cv.Required(CONF_VALUE): VALUE_SCHEMA, cv.Required(CONF_VALUE): VALUE_SCHEMA,
}, },
extra_schemas=[validate_descritor_value_length, validate_desc_on_write], extra_schemas=[validate_desc_on_write],
).extend(VALUE_EXTRAS_SCHEMA) )
SERVICE_CHARACTERISTIC_SCHEMA = ( SERVICE_CHARACTERISTIC_SCHEMA = cv.Schema(
cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(BLECharacteristic), cv.GenerateID(): cv.declare_id(BLECharacteristic),
cv.Required(CONF_UUID): cv.Any(bt_uuid, cv.hex_uint32_t), cv.Required(CONF_UUID): cv.Any(bt_uuid, cv.hex_uint32_t),
@ -229,11 +249,9 @@ SERVICE_CHARACTERISTIC_SCHEMA = (
cv.GenerateID(CONF_CHAR_VALUE_ACTION_ID_): cv.declare_id( cv.GenerateID(CONF_CHAR_VALUE_ACTION_ID_): cv.declare_id(
BLECharacteristicSetValueAction BLECharacteristicSetValueAction
), ),
cv.Optional(CONF_DESCRIPTORS, default=[]): cv.ensure_list( cv.Optional(CONF_DESCRIPTORS, default=[]): cv.ensure_list(DESCRIPTOR_SCHEMA),
DESCRIPTOR_SCHEMA
),
cv.Optional(CONF_ON_WRITE): automation.validate_automation(single=True), cv.Optional(CONF_ON_WRITE): automation.validate_automation(single=True),
cv.Optional(CONF_DESCRIPTION): cv.string, cv.Optional(CONF_DESCRIPTION): VALUE_SCHEMA,
cv.GenerateID(CONF_CUD_ID_): cv.declare_id(BLEDescriptor), cv.GenerateID(CONF_CUD_ID_): cv.declare_id(BLEDescriptor),
cv.GenerateID(CONF_CUD_VALUE_BUFFER_): cv.declare_id(ByteBuffer), cv.GenerateID(CONF_CUD_VALUE_BUFFER_): cv.declare_id(ByteBuffer),
cv.GenerateID(CONF_CCCD_ID_): cv.declare_id(BLEDescriptor), cv.GenerateID(CONF_CCCD_ID_): cv.declare_id(BLEDescriptor),
@ -244,10 +262,7 @@ SERVICE_CHARACTERISTIC_SCHEMA = (
create_description_cud, create_description_cud,
create_notify_cccd, create_notify_cccd,
], ],
) ).extend({cv.Optional(k, default=False): cv.boolean for k in PROPERTY_MAP})
.extend({cv.Optional(k, default=False): cv.boolean for k in PROPERTY_MAP})
.extend(VALUE_EXTRAS_SCHEMA)
)
SERVICE_SCHEMA = cv.Schema( SERVICE_SCHEMA = cv.Schema(
{ {
@ -291,67 +306,27 @@ def parse_uuid(uuid):
return ESPBTUUID_ns.from_raw(uuid) return ESPBTUUID_ns.from_raw(uuid)
def bytebuffer_parser_(value, str_encoding): def native_value_parser_(value, type_, str_encoding):
for val_method, casting in zip( if type_ == "encoded_string":
(
cv.boolean,
cv.uint8_t,
cv.uint16_t,
cv.uint32_t,
cv.int_,
cv.float_,
cv.string,
cv.All(cv.ensure_list(cv.uint8_t), cv.Length(min=1)),
),
(
cg.bool_,
cg.uint8,
cg.uint16,
cg.uint32,
cg.int_,
cg.float_,
None,
cg.std_vector.template(cg.uint8),
),
):
try:
val = val_method(value)
if val_method == cv.string:
# Convert to a list of bytes using encoding # Convert to a list of bytes using encoding
val = cg.std_vector.template(cg.uint8)(list(val.encode(str_encoding))) val = cg.std_vector.template(cg.uint8)(list(value.encode(str_encoding)))
else: else:
val = casting(val) val = cg.RawExpression(f"{type_}({value})")
return val, val_method return val
except cv.Invalid:
pass
raise cv.Invalid(f"Could not find type for value: {value}")
async def parse_value(value, str_encoding, buffer_id, args, byte_length=None): async def parse_value(value_config, args):
value = value_config[CONF_VALUE]
if isinstance(value, cv.Lambda): if isinstance(value, cv.Lambda):
return await cg.templatable( return await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
value,
args,
cg.std_vector.template(cg.uint8),
)
val, val_method = bytebuffer_parser_(value, str_encoding) buffer_id = value_config[CONF_VALUE_BUFFER_]
if byte_length is None: val = native_value_parser_(
# If no byte length is specified, use the default length value, value_config[CONF_TYPE], value_config[CONF_STRING_ENCODING]
buffer_var = cg.variable(buffer_id, ByteBuffer_ns.wrap(val)) )
else: buffer_var = cg.variable(
put_method_dict = { buffer_id, ByteBuffer_ns.wrap(val, value_config[CONF_ENDIANNES])
cv.boolean: "put_bool", )
cv.uint8_t: "put_uint8",
cv.uint16_t: "put_uint16",
cv.uint32_t: "put_uint32",
cv.int_: "put_int",
cv.float_: "put_float",
}
# Create a buffer with the specified length and add the value
put_method = put_method_dict.get(val_method, "put_vector")
buffer_var = cg.variable(buffer_id, ByteBuffer(byte_length))
cg.add(getattr(buffer_var, put_method)(val))
return buffer_var return buffer_var
@ -365,17 +340,12 @@ def calculate_num_handles(service_config):
async def to_code_descriptor(descriptor_conf, char_var): async def to_code_descriptor(descriptor_conf, char_var):
value = await parse_value( # TODO: Where to compute descriptor templatables?
descriptor_conf[CONF_VALUE], value = await parse_value(descriptor_conf[CONF_VALUE], {})
descriptor_conf[CONF_STRING_ENCODING],
descriptor_conf[CONF_VALUE_BUFFER_],
{},
descriptor_conf.get(CONF_BYTE_LENGTH, None),
)
desc_var = cg.new_Pvariable( desc_var = cg.new_Pvariable(
descriptor_conf[CONF_ID], descriptor_conf[CONF_ID],
parse_uuid(descriptor_conf[CONF_UUID]), parse_uuid(descriptor_conf[CONF_UUID]),
value.get_capacity(), value.get_capacity(), # TODO: FIX this does not work for templatables
descriptor_conf[CONF_READ], descriptor_conf[CONF_READ],
descriptor_conf[CONF_WRITE], descriptor_conf[CONF_WRITE],
) )
@ -409,9 +379,6 @@ async def to_code_characteristic(service_var, char_conf):
action_conf = { action_conf = {
CONF_ID: char_conf[CONF_ID], CONF_ID: char_conf[CONF_ID],
CONF_VALUE: char_conf[CONF_VALUE], CONF_VALUE: char_conf[CONF_VALUE],
CONF_BYTE_LENGTH: char_conf.get(CONF_BYTE_LENGTH, None),
CONF_STRING_ENCODING: char_conf[CONF_STRING_ENCODING],
CONF_VALUE_BUFFER_: char_conf[CONF_VALUE_BUFFER_],
} }
value_action = await ble_server_characteristic_set_value( value_action = await ble_server_characteristic_set_value(
action_conf, action_conf,
@ -477,18 +444,12 @@ async def to_code(config):
cv.Required(CONF_ID): cv.use_id(BLECharacteristic), cv.Required(CONF_ID): cv.use_id(BLECharacteristic),
cv.Required(CONF_VALUE): VALUE_SCHEMA, cv.Required(CONF_VALUE): VALUE_SCHEMA,
} }
).extend(VALUE_EXTRAS_SCHEMA), ),
) )
async def ble_server_characteristic_set_value(config, action_id, template_arg, args): async def ble_server_characteristic_set_value(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
value = await parse_value( value = await parse_value(config[CONF_VALUE], args)
config[CONF_VALUE],
config[CONF_STRING_ENCODING],
config[CONF_VALUE_BUFFER_],
args,
config.get(CONF_BYTE_LENGTH, None),
)
cg.add(var.set_buffer(value)) cg.add(var.set_buffer(value))
return var return var

View file

@ -14,26 +14,38 @@ esp32_ble_server:
advertise: false advertise: false
characteristics: characteristics:
- id: test_notify_characteristic - id: test_notify_characteristic
description: "Notify characteristic" description:
value: "Notify characteristic"
type: "encoded_string"
uuid: cad48e28-7fbe-41cf-bae9-d77a6c233423 uuid: cad48e28-7fbe-41cf-bae9-d77a6c233423
read: true read: true
notify: true notify: true
value: [9, 9, 9] value:
value: "{9, 9, 9}"
type: std::vector<uint8_t>
descriptors: descriptors:
- uuid: cad48e28-7fbe-41cf-bae9-d77a6c111111 - uuid: cad48e28-7fbe-41cf-bae9-d77a6c111111
value: 123.1 value:
byte_length: 100 value: "123.1"
type: float
endianness: BIG
- uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc42d - uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc42d
advertise: false advertise: false
characteristics: characteristics:
- id: test_change_characteristic - id: test_change_characteristic
uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc11c uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc11c
read: true read: true
value:
value: "Initial" value: "Initial"
description: "Change characteristic" type: "encoded_string"
description:
value: "Change characteristic"
type: "encoded_string"
descriptors: descriptors:
- uuid: 0x2312 - uuid: 0x2312
value: 0x12 value:
value: "0x12"
type: uint16_t
on_write: on_write:
- lambda: |- - lambda: |-
ESP_LOGD("BLE", "Descriptor received: %s from %d", std::string(x.begin(), x.end()).c_str(), id); ESP_LOGD("BLE", "Descriptor received: %s from %d", std::string(x.begin(), x.end()).c_str(), id);
@ -45,9 +57,7 @@ esp32_ble_server:
ESP_LOGD("BLE", "Characteristic received: %s from %d", std::string(x.begin(), x.end()).c_str(), id); ESP_LOGD("BLE", "Characteristic received: %s from %d", std::string(x.begin(), x.end()).c_str(), id);
- ble_server.characteristic.set_value: - ble_server.characteristic.set_value:
id: test_change_characteristic id: test_change_characteristic
value:
value: !lambda 'return bytebuffer::ByteBuffer::wrap({0x00, 0x01, 0x02}).get_data();' value: !lambda 'return bytebuffer::ByteBuffer::wrap({0x00, 0x01, 0x02}).get_data();'
- ble_server.characteristic.set_value:
id: test_change_characteristic
value: [1, 2, 3, 4]
- ble_server.characteristic.notify: - ble_server.characteristic.notify:
id: test_notify_characteristic id: test_notify_characteristic