1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-26 07:02:21 +01:00

Merge branch 'core_api_no_allocate' into integration

This commit is contained in:
J. Nick Koston
2025-09-23 11:30:08 -05:00
15 changed files with 263 additions and 130 deletions

View File

@@ -15,7 +15,10 @@ from esphome.const import (
CONF_TYPE_ID, CONF_TYPE_ID,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.core import ID
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType
from esphome.util import Registry from esphome.util import Registry
@@ -49,11 +52,11 @@ def maybe_conf(conf, *validators):
return validate return validate
def register_action(name, action_type, schema): def register_action(name: str, action_type: MockObjClass, schema: cv.Schema):
return ACTION_REGISTRY.register(name, action_type, schema) return ACTION_REGISTRY.register(name, action_type, schema)
def register_condition(name, condition_type, schema): def register_condition(name: str, condition_type: MockObjClass, schema: cv.Schema):
return CONDITION_REGISTRY.register(name, condition_type, schema) return CONDITION_REGISTRY.register(name, condition_type, schema)
@@ -164,43 +167,78 @@ XorCondition = cg.esphome_ns.class_("XorCondition", Condition)
@register_condition("and", AndCondition, validate_condition_list) @register_condition("and", AndCondition, validate_condition_list)
async def and_condition_to_code(config, condition_id, template_arg, args): async def and_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("or", OrCondition, validate_condition_list) @register_condition("or", OrCondition, validate_condition_list)
async def or_condition_to_code(config, condition_id, template_arg, args): async def or_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("all", AndCondition, validate_condition_list) @register_condition("all", AndCondition, validate_condition_list)
async def all_condition_to_code(config, condition_id, template_arg, args): async def all_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("any", OrCondition, validate_condition_list) @register_condition("any", OrCondition, validate_condition_list)
async def any_condition_to_code(config, condition_id, template_arg, args): async def any_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("not", NotCondition, validate_potentially_and_condition) @register_condition("not", NotCondition, validate_potentially_and_condition)
async def not_condition_to_code(config, condition_id, template_arg, args): async def not_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
condition = await build_condition(config, template_arg, args) condition = await build_condition(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, condition) return cg.new_Pvariable(condition_id, template_arg, condition)
@register_condition("xor", XorCondition, validate_condition_list) @register_condition("xor", XorCondition, validate_condition_list)
async def xor_condition_to_code(config, condition_id, template_arg, args): async def xor_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("lambda", LambdaCondition, cv.returning_lambda) @register_condition("lambda", LambdaCondition, cv.returning_lambda)
async def lambda_condition_to_code(config, condition_id, template_arg, args): async def lambda_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=bool) lambda_ = await cg.process_lambda(config, args, return_type=bool)
return cg.new_Pvariable(condition_id, template_arg, lambda_) return cg.new_Pvariable(condition_id, template_arg, lambda_)
@@ -217,7 +255,12 @@ async def lambda_condition_to_code(config, condition_id, template_arg, args):
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
) )
async def for_condition_to_code(config, condition_id, template_arg, args): async def for_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
condition = await build_condition( condition = await build_condition(
config[CONF_CONDITION], cg.TemplateArguments(), [] config[CONF_CONDITION], cg.TemplateArguments(), []
) )
@@ -231,7 +274,12 @@ async def for_condition_to_code(config, condition_id, template_arg, args):
@register_action( @register_action(
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds) "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
) )
async def delay_action_to_code(config, action_id, template_arg, args): async def delay_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_component(var, {}) await cg.register_component(var, {})
template_ = await cg.templatable(config, args, cg.uint32) template_ = await cg.templatable(config, args, cg.uint32)
@@ -256,10 +304,15 @@ async def delay_action_to_code(config, action_id, template_arg, args):
cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL), cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
), ),
) )
async def if_action_to_code(config, action_id, template_arg, args): async def if_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION)) cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
conditions = await build_condition(config[cond_conf], template_arg, args) condition = await build_condition(config[cond_conf], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions) var = cg.new_Pvariable(action_id, template_arg, condition)
if CONF_THEN in config: if CONF_THEN in config:
actions = await build_action_list(config[CONF_THEN], template_arg, args) actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions)) cg.add(var.add_then(actions))
@@ -279,9 +332,14 @@ async def if_action_to_code(config, action_id, template_arg, args):
} }
), ),
) )
async def while_action_to_code(config, action_id, template_arg, args): async def while_action_to_code(
conditions = await build_condition(config[CONF_CONDITION], template_arg, args) config: ConfigType,
var = cg.new_Pvariable(action_id, template_arg, conditions) action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
condition = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, condition)
actions = await build_action_list(config[CONF_THEN], template_arg, args) actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions)) cg.add(var.add_then(actions))
return var return var
@@ -297,7 +355,12 @@ async def while_action_to_code(config, action_id, template_arg, args):
} }
), ),
) )
async def repeat_action_to_code(config, action_id, template_arg, args): async def repeat_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
cg.add(var.set_count(count_template)) cg.add(var.set_count(count_template))
@@ -320,9 +383,14 @@ _validate_wait_until = cv.maybe_simple_value(
@register_action("wait_until", WaitUntilAction, _validate_wait_until) @register_action("wait_until", WaitUntilAction, _validate_wait_until)
async def wait_until_action_to_code(config, action_id, template_arg, args): async def wait_until_action_to_code(
conditions = await build_condition(config[CONF_CONDITION], template_arg, args) config: ConfigType,
var = cg.new_Pvariable(action_id, template_arg, conditions) action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
condition = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, condition)
if CONF_TIMEOUT in config: if CONF_TIMEOUT in config:
template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32) template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
cg.add(var.set_timeout_value(template_)) cg.add(var.set_timeout_value(template_))
@@ -331,7 +399,12 @@ async def wait_until_action_to_code(config, action_id, template_arg, args):
@register_action("lambda", LambdaAction, cv.lambda_) @register_action("lambda", LambdaAction, cv.lambda_)
async def lambda_action_to_code(config, action_id, template_arg, args): async def lambda_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=cg.void) lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return cg.new_Pvariable(action_id, template_arg, lambda_)
@@ -345,7 +418,12 @@ async def lambda_action_to_code(config, action_id, template_arg, args):
} }
), ),
) )
async def component_update_action_to_code(config, action_id, template_arg, args): async def component_update_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
comp = await cg.get_variable(config[CONF_ID]) comp = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, comp) return cg.new_Pvariable(action_id, template_arg, comp)
@@ -359,7 +437,12 @@ async def component_update_action_to_code(config, action_id, template_arg, args)
} }
), ),
) )
async def component_suspend_action_to_code(config, action_id, template_arg, args): async def component_suspend_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
comp = await cg.get_variable(config[CONF_ID]) comp = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, comp) return cg.new_Pvariable(action_id, template_arg, comp)
@@ -376,7 +459,12 @@ async def component_suspend_action_to_code(config, action_id, template_arg, args
} }
), ),
) )
async def component_resume_action_to_code(config, action_id, template_arg, args): async def component_resume_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
comp = await cg.get_variable(config[CONF_ID]) comp = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, comp) var = cg.new_Pvariable(action_id, template_arg, comp)
if CONF_UPDATE_INTERVAL in config: if CONF_UPDATE_INTERVAL in config:
@@ -385,7 +473,9 @@ async def component_resume_action_to_code(config, action_id, template_arg, args)
return var return var
async def build_action(full_config, template_arg, args): async def build_action(
full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
) -> MockObj:
registry_entry, config = cg.extract_registry_entry_config( registry_entry, config = cg.extract_registry_entry_config(
ACTION_REGISTRY, full_config ACTION_REGISTRY, full_config
) )
@@ -394,15 +484,19 @@ async def build_action(full_config, template_arg, args):
return await builder(config, action_id, template_arg, args) return await builder(config, action_id, template_arg, args)
async def build_action_list(config, templ, arg_type): async def build_action_list(
actions = [] config: list[ConfigType], templ: cg.TemplateArguments, arg_type: TemplateArgsType
) -> list[MockObj]:
actions: list[MockObj] = []
for conf in config: for conf in config:
action = await build_action(conf, templ, arg_type) action = await build_action(conf, templ, arg_type)
actions.append(action) actions.append(action)
return actions return actions
async def build_condition(full_config, template_arg, args): async def build_condition(
full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
) -> MockObj:
registry_entry, config = cg.extract_registry_entry_config( registry_entry, config = cg.extract_registry_entry_config(
CONDITION_REGISTRY, full_config CONDITION_REGISTRY, full_config
) )
@@ -411,15 +505,19 @@ async def build_condition(full_config, template_arg, args):
return await builder(config, action_id, template_arg, args) return await builder(config, action_id, template_arg, args)
async def build_condition_list(config, templ, args): async def build_condition_list(
conditions = [] config: ConfigType, templ: cg.TemplateArguments, args: TemplateArgsType
) -> list[MockObj]:
conditions: list[MockObj] = []
for conf in config: for conf in config:
condition = await build_condition(conf, templ, args) condition = await build_condition(conf, templ, args)
conditions.append(condition) conditions.append(condition)
return conditions return conditions
async def build_automation(trigger, args, config): async def build_automation(
trigger: MockObj, args: TemplateArgsType, config: ConfigType
) -> MockObj:
arg_types = [arg[0] for arg in args] arg_types = [arg[0] for arg in args]
templ = cg.TemplateArguments(*arg_types) templ = cg.TemplateArguments(*arg_types)
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger) obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)

View File

@@ -102,7 +102,7 @@ message HelloRequest {
// For example "Home Assistant" // For example "Home Assistant"
// Not strictly necessary to send but nice for debugging // Not strictly necessary to send but nice for debugging
// purposes. // purposes.
string client_info = 1; string client_info = 1 [(pointer_to_buffer) = true];
uint32 api_version_major = 2; uint32 api_version_major = 2;
uint32 api_version_minor = 3; uint32 api_version_minor = 3;
} }
@@ -139,7 +139,7 @@ message AuthenticationRequest {
option (ifdef) = "USE_API_PASSWORD"; option (ifdef) = "USE_API_PASSWORD";
// The password to log in with // The password to log in with
string password = 1; string password = 1 [(pointer_to_buffer) = true];
} }
// Confirmation of successful connection. After this the connection is available for all traffic. // Confirmation of successful connection. After this the connection is available for all traffic.
@@ -824,7 +824,7 @@ message GetTimeResponse {
option (no_delay) = true; option (no_delay) = true;
fixed32 epoch_seconds = 1; fixed32 epoch_seconds = 1;
string timezone = 2; string timezone = 2 [(pointer_to_buffer) = true];
} }
// ==================== USER-DEFINES SERVICES ==================== // ==================== USER-DEFINES SERVICES ====================
@@ -1571,7 +1571,7 @@ message BluetoothGATTWriteRequest {
uint32 handle = 2; uint32 handle = 2;
bool response = 3; bool response = 3;
bytes data = 4 [(pointer_to_buffer) = true]; bytes data = 4;
} }
message BluetoothGATTReadDescriptorRequest { message BluetoothGATTReadDescriptorRequest {
@@ -1591,7 +1591,7 @@ message BluetoothGATTWriteDescriptorRequest {
uint64 address = 1; uint64 address = 1;
uint32 handle = 2; uint32 handle = 2;
bytes data = 3 [(pointer_to_buffer) = true]; bytes data = 3;
} }
message BluetoothGATTNotifyRequest { message BluetoothGATTNotifyRequest {

View File

@@ -1078,8 +1078,14 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
if (homeassistant::global_homeassistant_time != nullptr) { if (homeassistant::global_homeassistant_time != nullptr) {
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds); homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
#ifdef USE_TIME_TIMEZONE #ifdef USE_TIME_TIMEZONE
if (!value.timezone.empty() && value.timezone != homeassistant::global_homeassistant_time->get_timezone()) { if (value.timezone_len > 0) {
homeassistant::global_homeassistant_time->set_timezone(value.timezone); const std::string &current_tz = homeassistant::global_homeassistant_time->get_timezone();
// Compare without allocating a string
if (current_tz.length() != value.timezone_len ||
memcmp(current_tz.c_str(), value.timezone, value.timezone_len) != 0) {
homeassistant::global_homeassistant_time->set_timezone(
std::string(reinterpret_cast<const char *>(value.timezone), value.timezone_len));
}
} }
#endif #endif
} }
@@ -1374,7 +1380,7 @@ void APIConnection::complete_authentication_() {
} }
bool APIConnection::send_hello_response(const HelloRequest &msg) { bool APIConnection::send_hello_response(const HelloRequest &msg) {
this->client_info_.name = msg.client_info; this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
this->client_info_.peername = this->helper_->getpeername(); this->client_info_.peername = this->helper_->getpeername();
this->client_api_version_major_ = msg.api_version_major; this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor; this->client_api_version_minor_ = msg.api_version_minor;
@@ -1402,7 +1408,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) { bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
AuthenticationResponse resp; AuthenticationResponse resp;
// bool invalid_password = 1; // bool invalid_password = 1;
resp.invalid_password = !this->parent_->check_password(msg.password); resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
if (!resp.invalid_password) { if (!resp.invalid_password) {
this->complete_authentication_(); this->complete_authentication_();
} }

View File

@@ -22,9 +22,12 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
} }
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->client_info = value.as_string(); // Use raw data directly to avoid allocation
this->client_info = value.data();
this->client_info_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
@@ -45,9 +48,12 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->password = value.as_string(); // Use raw data directly to avoid allocation
this->password = value.data();
this->password_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
@@ -917,9 +923,12 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
#endif #endif
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 2: case 2: {
this->timezone = value.as_string(); // Use raw data directly to avoid allocation
this->timezone = value.data();
this->timezone_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
@@ -2028,12 +2037,9 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val
} }
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 4: { case 4:
// Use raw data directly to avoid allocation this->data = value.as_string();
this->data = value.data();
this->data_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
@@ -2067,12 +2073,9 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto
} }
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 3: { case 3:
// Use raw data directly to avoid allocation this->data = value.as_string();
this->data = value.data();
this->data_len = value.size();
break; break;
}
default: default:
return false; return false;
} }

View File

@@ -330,11 +330,12 @@ class CommandProtoMessage : public ProtoDecodableMessage {
class HelloRequest final : public ProtoDecodableMessage { class HelloRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 1; static constexpr uint8_t MESSAGE_TYPE = 1;
static constexpr uint8_t ESTIMATED_SIZE = 17; static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "hello_request"; } const char *message_name() const override { return "hello_request"; }
#endif #endif
std::string client_info{}; const uint8_t *client_info{nullptr};
uint16_t client_info_len{0};
uint32_t api_version_major{0}; uint32_t api_version_major{0};
uint32_t api_version_minor{0}; uint32_t api_version_minor{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@@ -370,11 +371,12 @@ class HelloResponse final : public ProtoMessage {
class AuthenticationRequest final : public ProtoDecodableMessage { class AuthenticationRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 3; static constexpr uint8_t MESSAGE_TYPE = 3;
static constexpr uint8_t ESTIMATED_SIZE = 9; static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_request"; } const char *message_name() const override { return "authentication_request"; }
#endif #endif
std::string password{}; const uint8_t *password{nullptr};
uint16_t password_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1188,12 +1190,13 @@ class GetTimeRequest final : public ProtoMessage {
class GetTimeResponse final : public ProtoDecodableMessage { class GetTimeResponse final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 37; static constexpr uint8_t MESSAGE_TYPE = 37;
static constexpr uint8_t ESTIMATED_SIZE = 14; static constexpr uint8_t ESTIMATED_SIZE = 24;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "get_time_response"; } const char *message_name() const override { return "get_time_response"; }
#endif #endif
uint32_t epoch_seconds{0}; uint32_t epoch_seconds{0};
std::string timezone{}; const uint8_t *timezone{nullptr};
uint16_t timezone_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1985,15 +1988,14 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 75; static constexpr uint8_t MESSAGE_TYPE = 75;
static constexpr uint8_t ESTIMATED_SIZE = 29; static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_write_request"; } const char *message_name() const override { return "bluetooth_gatt_write_request"; }
#endif #endif
uint64_t address{0}; uint64_t address{0};
uint32_t handle{0}; uint32_t handle{0};
bool response{false}; bool response{false};
const uint8_t *data{nullptr}; std::string data{};
uint16_t data_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2021,14 +2023,13 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 77; static constexpr uint8_t MESSAGE_TYPE = 77;
static constexpr uint8_t ESTIMATED_SIZE = 27; static constexpr uint8_t ESTIMATED_SIZE = 17;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; }
#endif #endif
uint64_t address{0}; uint64_t address{0};
uint32_t handle{0}; uint32_t handle{0};
const uint8_t *data{nullptr}; std::string data{};
uint16_t data_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif

View File

@@ -670,7 +670,9 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
void HelloRequest::dump_to(std::string &out) const { void HelloRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloRequest"); MessageDumpHelper helper(out, "HelloRequest");
dump_field(out, "client_info", this->client_info); out.append(" client_info: ");
out.append(format_hex_pretty(this->client_info, this->client_info_len));
out.append("\n");
dump_field(out, "api_version_major", this->api_version_major); dump_field(out, "api_version_major", this->api_version_major);
dump_field(out, "api_version_minor", this->api_version_minor); dump_field(out, "api_version_minor", this->api_version_minor);
} }
@@ -682,7 +684,12 @@ void HelloResponse::dump_to(std::string &out) const {
dump_field(out, "name", this->name_ref_); dump_field(out, "name", this->name_ref_);
} }
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
void AuthenticationRequest::dump_to(std::string &out) const { dump_field(out, "password", this->password); } void AuthenticationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationRequest");
out.append(" password: ");
out.append(format_hex_pretty(this->password, this->password_len));
out.append("\n");
}
void AuthenticationResponse::dump_to(std::string &out) const { void AuthenticationResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationResponse"); MessageDumpHelper helper(out, "AuthenticationResponse");
dump_field(out, "invalid_password", this->invalid_password); dump_field(out, "invalid_password", this->invalid_password);
@@ -1136,7 +1143,9 @@ void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeReques
void GetTimeResponse::dump_to(std::string &out) const { void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse"); MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds); dump_field(out, "epoch_seconds", this->epoch_seconds);
dump_field(out, "timezone", this->timezone); out.append(" timezone: ");
out.append(format_hex_pretty(this->timezone, this->timezone_len));
out.append("\n");
} }
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::dump_to(std::string &out) const { void ListEntitiesServicesArgument::dump_to(std::string &out) const {
@@ -1649,7 +1658,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
dump_field(out, "handle", this->handle); dump_field(out, "handle", this->handle);
dump_field(out, "response", this->response); dump_field(out, "response", this->response);
out.append(" data: "); out.append(" data: ");
out.append(format_hex_pretty(this->data, this->data_len)); out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
out.append("\n"); out.append("\n");
} }
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
@@ -1662,7 +1671,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
dump_field(out, "address", this->address); dump_field(out, "address", this->address);
dump_field(out, "handle", this->handle); dump_field(out, "handle", this->handle);
out.append(" data: "); out.append(" data: ");
out.append(format_hex_pretty(this->data, this->data_len)); out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
out.append("\n"); out.append("\n");
} }
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {

View File

@@ -217,12 +217,12 @@ void APIServer::dump_config() {
} }
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
bool APIServer::check_password(const std::string &password) const { bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
// depend only on input password length // depend only on input password length
const char *a = this->password_.c_str(); const char *a = this->password_.c_str();
uint32_t len_a = this->password_.length(); uint32_t len_a = this->password_.length();
const char *b = password.c_str(); const char *b = reinterpret_cast<const char *>(password_data);
uint32_t len_b = password.length(); uint32_t len_b = password_len;
// disable optimization with volatile // disable optimization with volatile
volatile uint32_t length = len_b; volatile uint32_t length = len_b;
@@ -245,6 +245,7 @@ bool APIServer::check_password(const std::string &password) const {
return result == 0; return result == 0;
} }
#endif #endif
void APIServer::handle_disconnect(APIConnection *conn) {} void APIServer::handle_disconnect(APIConnection *conn) {}

View File

@@ -37,7 +37,7 @@ class APIServer : public Component, public Controller {
void on_shutdown() override; void on_shutdown() override;
bool teardown() override; bool teardown() override;
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
bool check_password(const std::string &password) const; bool check_password(const uint8_t *password_data, size_t password_len) const;
void set_password(const std::string &password); void set_password(const std::string &password);
#endif #endif
void set_port(uint16_t port); void set_port(uint16_t port);

View File

@@ -514,8 +514,7 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
return this->check_and_log_error_("esp_ble_gattc_read_char", err); return this->check_and_log_error_("esp_ble_gattc_read_char", err);
} }
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8_t *data, size_t length, esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
bool response) {
if (!this->connected()) { if (!this->connected()) {
this->log_gatt_not_connected_("write", "characteristic"); this->log_gatt_not_connected_("write", "characteristic");
return ESP_GATT_NOT_CONNECTED; return ESP_GATT_NOT_CONNECTED;
@@ -523,11 +522,8 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
handle); handle);
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
// const_cast is safe here and was previously hidden by a C-style cast
esp_err_t err = esp_err_t err =
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data), esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
return this->check_and_log_error_("esp_ble_gattc_write_char", err); return this->check_and_log_error_("esp_ble_gattc_write_char", err);
} }
@@ -544,7 +540,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err); return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
} }
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response) { esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
if (!this->connected()) { if (!this->connected()) {
this->log_gatt_not_connected_("write", "descriptor"); this->log_gatt_not_connected_("write", "descriptor");
return ESP_GATT_NOT_CONNECTED; return ESP_GATT_NOT_CONNECTED;
@@ -552,11 +548,8 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
handle); handle);
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
// const_cast is safe here and was previously hidden by a C-style cast
esp_err_t err = esp_ble_gattc_write_char_descr( esp_err_t err = esp_ble_gattc_write_char_descr(
this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data), this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err); return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
} }

View File

@@ -18,9 +18,9 @@ class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
esp_err_t read_characteristic(uint16_t handle); esp_err_t read_characteristic(uint16_t handle);
esp_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t length, bool response); esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
esp_err_t read_descriptor(uint16_t handle); esp_err_t read_descriptor(uint16_t handle);
esp_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response); esp_err_t write_descriptor(uint16_t handle, const std::string &data, bool response);
esp_err_t notify_characteristic(uint16_t handle, bool enable); esp_err_t notify_characteristic(uint16_t handle, bool enable);

View File

@@ -305,7 +305,7 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
return; return;
} }
auto err = connection->write_characteristic(msg.handle, msg.data, msg.data_len, msg.response); auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
if (err != ESP_OK) { if (err != ESP_OK) {
this->send_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }
@@ -331,7 +331,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
return; return;
} }
auto err = connection->write_descriptor(msg.handle, msg.data, msg.data_len, true); auto err = connection->write_descriptor(msg.handle, msg.data, true);
if (err != ESP_OK) { if (err != ESP_OK) {
this->send_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }

View File

@@ -1,5 +1,5 @@
import abc import abc
from collections.abc import Callable, Sequence from collections.abc import Callable
import inspect import inspect
import math import math
import re import re
@@ -13,7 +13,6 @@ from esphome.core import (
HexInt, HexInt,
Lambda, Lambda,
Library, Library,
TimePeriod,
TimePeriodMicroseconds, TimePeriodMicroseconds,
TimePeriodMilliseconds, TimePeriodMilliseconds,
TimePeriodMinutes, TimePeriodMinutes,
@@ -21,35 +20,11 @@ from esphome.core import (
TimePeriodSeconds, TimePeriodSeconds,
) )
from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last
from esphome.types import Expression, SafeExpType, TemplateArgsType
from esphome.util import OrderedDict from esphome.util import OrderedDict
from esphome.yaml_util import ESPHomeDataBase from esphome.yaml_util import ESPHomeDataBase
class Expression(abc.ABC):
__slots__ = ()
@abc.abstractmethod
def __str__(self):
"""
Convert expression into C++ code
"""
SafeExpType = (
Expression
| bool
| str
| str
| int
| float
| TimePeriod
| type[bool]
| type[int]
| type[float]
| Sequence[Any]
)
class RawExpression(Expression): class RawExpression(Expression):
__slots__ = ("text",) __slots__ = ("text",)
@@ -575,7 +550,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
return obj return obj
def new_Pvariable(id_: ID, *args: SafeExpType) -> Pvariable: def new_Pvariable(id_: ID, *args: SafeExpType) -> "MockObj":
"""Declare a new pointer variable in the code generation by calling it's constructor """Declare a new pointer variable in the code generation by calling it's constructor
with the given arguments. with the given arguments.
@@ -681,7 +656,7 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]:
async def process_lambda( async def process_lambda(
value: Lambda, value: Lambda,
parameters: list[tuple[SafeExpType, str]], parameters: TemplateArgsType,
capture: str = "=", capture: str = "=",
return_type: SafeExpType = None, return_type: SafeExpType = None,
) -> LambdaExpression | None: ) -> LambdaExpression | None:

View File

@@ -1,8 +1,10 @@
"""This helper module tracks commonly used types in the esphome python codebase.""" """This helper module tracks commonly used types in the esphome python codebase."""
from typing import TypedDict import abc
from collections.abc import Sequence
from typing import Any, TypedDict
from esphome.core import ID, EsphomeCore, Lambda from esphome.core import ID, EsphomeCore, Lambda, TimePeriod
ConfigFragmentType = ( ConfigFragmentType = (
str str
@@ -20,6 +22,32 @@ CoreType = EsphomeCore
ConfigPathType = str | int ConfigPathType = str | int
class Expression(abc.ABC):
__slots__ = ()
@abc.abstractmethod
def __str__(self):
"""
Convert expression into C++ code
"""
SafeExpType = (
Expression
| bool
| str
| int
| float
| TimePeriod
| type[bool]
| type[int]
| type[float]
| Sequence[Any]
)
TemplateArgsType = list[tuple[SafeExpType, str]]
class EntityMetadata(TypedDict): class EntityMetadata(TypedDict):
"""Metadata stored for each entity to help with duplicate detection.""" """Metadata stored for each entity to help with duplicate detection."""

View File

@@ -1,19 +1,30 @@
import collections import collections
from collections.abc import Callable
import io import io
import logging import logging
from pathlib import Path from pathlib import Path
import re import re
import subprocess import subprocess
import sys import sys
from typing import Any from typing import TYPE_CHECKING, Any
from esphome import const from esphome import const
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
if TYPE_CHECKING:
from esphome.config_validation import Schema
from esphome.cpp_generator import MockObjClass
class RegistryEntry: class RegistryEntry:
def __init__(self, name, fun, type_id, schema): def __init__(
self,
name: str,
fun: Callable[..., Any],
type_id: "MockObjClass",
schema: "Schema",
):
self.name = name self.name = name
self.fun = fun self.fun = fun
self.type_id = type_id self.type_id = type_id
@@ -38,8 +49,8 @@ class Registry(dict[str, RegistryEntry]):
self.base_schema = base_schema or {} self.base_schema = base_schema or {}
self.type_id_key = type_id_key self.type_id_key = type_id_key
def register(self, name, type_id, schema): def register(self, name: str, type_id: "MockObjClass", schema: "Schema"):
def decorator(fun): def decorator(fun: Callable[..., Any]):
self[name] = RegistryEntry(name, fun, type_id, schema) self[name] = RegistryEntry(name, fun, type_id, schema)
return fun return fun
@@ -47,8 +58,8 @@ class Registry(dict[str, RegistryEntry]):
class SimpleRegistry(dict): class SimpleRegistry(dict):
def register(self, name, data): def register(self, name: str, data: Any):
def decorator(fun): def decorator(fun: Callable[..., Any]):
self[name] = (fun, data) self[name] = (fun, data)
return fun return fun

View File

@@ -373,6 +373,14 @@ def create_field_type_info(
# Traditional fixed array approach with copy # Traditional fixed array approach with copy
return FixedArrayBytesType(field, fixed_size) return FixedArrayBytesType(field, fixed_size)
# Check for pointer_to_buffer option on string fields
if field.type == 9:
has_pointer_to_buffer = get_field_opt(field, pb.pointer_to_buffer, False)
if has_pointer_to_buffer:
# Zero-copy pointer approach for strings
return PointerToBytesBufferType(field, None)
# Special handling for bytes fields # Special handling for bytes fields
if field.type == 12: if field.type == 12:
return BytesType(field, needs_decode, needs_encode) return BytesType(field, needs_decode, needs_encode)