Merge branch 'integration' of https://github.com/esphome/esphome into integration

This commit is contained in:
J. Nick Koston
2026-03-04 10:36:46 -10:00
66 changed files with 2514 additions and 1826 deletions
+4 -3
View File
@@ -23,6 +23,7 @@ import esphome.codegen as cg
from esphome.config import iter_component_configs, read_config, strip_default_ids
from esphome.const import (
ALLOWED_NAME_CHARS,
ARGUMENT_HELP_DEVICE,
CONF_API,
CONF_BAUD_RATE,
CONF_BROKER,
@@ -1367,7 +1368,7 @@ def parse_args(argv):
parser_upload.add_argument(
"--device",
action="append",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
help=ARGUMENT_HELP_DEVICE,
)
parser_upload.add_argument(
"--upload_speed",
@@ -1390,7 +1391,7 @@ def parse_args(argv):
parser_logs.add_argument(
"--device",
action="append",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
help=ARGUMENT_HELP_DEVICE,
)
parser_logs.add_argument(
"--reset",
@@ -1420,7 +1421,7 @@ def parse_args(argv):
parser_run.add_argument(
"--device",
action="append",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
help=ARGUMENT_HELP_DEVICE,
)
parser_run.add_argument(
"--upload_speed",
+140 -139
View File
@@ -114,9 +114,10 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
}
#elif defined(USE_API_PLAINTEXT)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
this->helper_ = std::unique_ptr<APIPlaintextFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#elif defined(USE_API_NOISE)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
this->helper_ =
std::unique_ptr<APINoiseFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
#else
#error "No frame helper defined"
#endif
@@ -274,7 +275,7 @@ void APIConnection::check_keepalive_(uint32_t now) {
// Only send ping if we're not disconnecting
ESP_LOGVV(TAG, "Sending keepalive PING");
PingRequest req;
this->flags_.sent_ping = this->send_message(req, PingRequest::MESSAGE_TYPE);
this->flags_.sent_ping = this->send_message(req);
if (!this->flags_.sent_ping) {
// If we can't send the ping request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority
@@ -335,7 +336,7 @@ bool APIConnection::send_disconnect_response_() {
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected"));
this->flags_.next_close = true;
DisconnectResponse resp;
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
return this->send_message(resp);
}
void APIConnection::on_disconnect_response() {
// Don't close socket here, let APIServer::loop() do it
@@ -343,61 +344,19 @@ void APIConnection::on_disconnect_response() {
this->flags_.remove = true;
}
// Encodes a message to the buffer and returns the total number of bytes used,
// including header and footer overhead. Returns 0 if the message doesn't fit.
uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// If in log-only mode, just log and return
if (conn->flags_.log_only_mode) {
DumpBuffer dump_buf;
conn->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
return 1; // Return non-zero to indicate "success" for logging
}
uint16_t APIConnection::fill_and_encode_entity_state(EntityBase *entity, StateResponseProtoMessage &msg,
CalculateSizeFn size_fn, MessageEncodeFn encode_fn,
APIConnection *conn, uint32_t remaining_size) {
msg.key = entity->get_object_id_hash();
#ifdef USE_DEVICES
msg.device_id = entity->get_device_id();
#endif
// Calculate size
uint32_t calculated_size = msg.calculated_size();
// Cache frame sizes to avoid repeated virtual calls
const uint8_t header_padding = conn->helper_->frame_header_padding();
const uint8_t footer_size = conn->helper_->frame_footer_size();
// Calculate total size with padding for buffer allocation
size_t total_calculated_size = calculated_size + header_padding + footer_size;
// Check if it fits
if (total_calculated_size > remaining_size) {
return 0; // Doesn't fit
}
// Get buffer size after allocation (which includes header padding)
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
if (conn->flags_.batch_first_message) {
// First message - buffer already prepared by caller, just clear flag
conn->flags_.batch_first_message = false;
} else {
// Batch message second or later
// Add padding for previous message footer + this message header
size_t current_size = shared_buf.size();
shared_buf.reserve(current_size + total_calculated_size);
shared_buf.resize(current_size + footer_size + header_padding);
}
// Pre-resize buffer to include payload, then encode through raw pointer
size_t write_start = shared_buf.size();
shared_buf.resize(write_start + calculated_size);
ProtoWriteBuffer buffer{&shared_buf, write_start};
msg.encode(buffer);
// Return total size (header + payload + footer)
return static_cast<uint16_t>(header_padding + calculated_size + footer_size);
return encode_to_buffer(size_fn(&msg), encode_fn, &msg, conn, remaining_size);
}
uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResponseProtoMessage &msg,
uint8_t message_type, APIConnection *conn,
uint32_t remaining_size) {
CalculateSizeFn size_fn, MessageEncodeFn encode_fn,
APIConnection *conn, uint32_t remaining_size) {
// Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash();
@@ -405,7 +364,7 @@ uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResp
// For older clients, we must send object_id for backward compatibility
// See: https://github.com/esphome/backlog/issues/76
// TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then
// Buffer must remain in scope until encode_message_to_buffer is called
// Buffer must remain in scope until encode_to_buffer is called
char object_id_buf[OBJECT_ID_MAX_LEN];
if (!conn->client_supports_api_version(1, 14)) {
msg.object_id = entity->get_object_id_to(object_id_buf);
@@ -425,16 +384,17 @@ uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResp
#ifdef USE_DEVICES
msg.device_id = entity->get_device_id();
#endif
return encode_message_to_buffer(msg, message_type, conn, remaining_size);
return encode_to_buffer(size_fn(&msg), encode_fn, &msg, conn, remaining_size);
}
uint16_t APIConnection::fill_and_encode_entity_info_with_device_class(EntityBase *entity, InfoResponseProtoMessage &msg,
StringRef &device_class_field,
uint8_t message_type, APIConnection *conn,
CalculateSizeFn size_fn,
MessageEncodeFn encode_fn, APIConnection *conn,
uint32_t remaining_size) {
char dc_buf[MAX_DEVICE_CLASS_LENGTH];
device_class_field = StringRef(entity->get_device_class_to(dc_buf));
return fill_and_encode_entity_info(entity, msg, message_type, conn, remaining_size);
return fill_and_encode_entity_info(entity, msg, size_fn, encode_fn, conn, remaining_size);
}
#ifdef USE_BINARY_SENSOR
@@ -448,16 +408,14 @@ uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConn
BinarySensorStateResponse resp;
resp.state = binary_sensor->state;
resp.missing_state = !binary_sensor->has_state();
return fill_and_encode_entity_state(binary_sensor, resp, BinarySensorStateResponse::MESSAGE_TYPE, conn,
remaining_size);
return fill_and_encode_entity_state(binary_sensor, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
ListEntitiesBinarySensorResponse msg;
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
return fill_and_encode_entity_info_with_device_class(
binary_sensor, msg, msg.device_class, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(binary_sensor, msg, msg.device_class, conn, remaining_size);
}
#endif
@@ -473,7 +431,7 @@ uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *
if (traits.get_supports_tilt())
msg.tilt = cover->tilt;
msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
return fill_and_encode_entity_state(cover, msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(cover, msg, conn, remaining_size);
}
uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *cover = static_cast<cover::Cover *>(entity);
@@ -483,8 +441,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
msg.supports_position = traits.get_supports_position();
msg.supports_tilt = traits.get_supports_tilt();
msg.supports_stop = traits.get_supports_stop();
return fill_and_encode_entity_info_with_device_class(cover, msg, msg.device_class,
ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(cover, msg, msg.device_class, conn, remaining_size);
}
void APIConnection::on_cover_command_request(const CoverCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
@@ -516,7 +473,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
msg.direction = static_cast<enums::FanDirection>(fan->direction);
if (traits.supports_preset_modes() && fan->has_preset_mode())
msg.preset_mode = fan->get_preset_mode();
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(fan, msg, conn, remaining_size);
}
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *fan = static_cast<fan::Fan *>(entity);
@@ -527,7 +484,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
msg.supports_direction = traits.supports_direction();
msg.supported_speed_count = traits.supported_speed_count();
msg.supported_preset_modes = &traits.supported_preset_modes();
return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(fan, msg, conn, remaining_size);
}
void APIConnection::on_fan_command_request(const FanCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
@@ -570,7 +527,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
if (light->supports_effects()) {
resp.effect = light->get_effect_name();
}
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(light, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *light = static_cast<light::LightState *>(entity);
@@ -595,7 +552,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
}
}
msg.effects = &effects_list;
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(light, msg, conn, remaining_size);
}
void APIConnection::on_light_command_request(const LightCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
@@ -640,7 +597,7 @@ uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection
SensorStateResponse resp;
resp.state = sensor->state;
resp.missing_state = !sensor->has_state();
return fill_and_encode_entity_state(sensor, resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(sensor, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
@@ -650,8 +607,7 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
msg.accuracy_decimals = sensor->get_accuracy_decimals();
msg.force_update = sensor->get_force_update();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
return fill_and_encode_entity_info_with_device_class(sensor, msg, msg.device_class,
ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(sensor, msg, msg.device_class, conn, remaining_size);
}
#endif
@@ -664,15 +620,14 @@ uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection
auto *a_switch = static_cast<switch_::Switch *>(entity);
SwitchStateResponse resp;
resp.state = a_switch->state;
return fill_and_encode_entity_state(a_switch, resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(a_switch, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *a_switch = static_cast<switch_::Switch *>(entity);
ListEntitiesSwitchResponse msg;
msg.assumed_state = a_switch->assumed_state();
return fill_and_encode_entity_info_with_device_class(a_switch, msg, msg.device_class,
ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(a_switch, msg, msg.device_class, conn, remaining_size);
}
void APIConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
@@ -696,13 +651,12 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec
TextSensorStateResponse resp;
resp.state = StringRef(text_sensor->state);
resp.missing_state = !text_sensor->has_state();
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(text_sensor, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
ListEntitiesTextSensorResponse msg;
return fill_and_encode_entity_info_with_device_class(
text_sensor, msg, msg.device_class, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(text_sensor, msg, msg.device_class, conn, remaining_size);
}
#endif
@@ -742,7 +696,7 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
resp.current_humidity = climate->current_humidity;
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY))
resp.target_humidity = climate->target_humidity;
return fill_and_encode_entity_state(climate, resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(climate, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *climate = static_cast<climate::Climate *>(entity);
@@ -769,7 +723,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supported_presets = &traits.get_supported_presets();
msg.supported_custom_presets = &traits.get_supported_custom_presets();
msg.supported_swing_modes = &traits.get_supported_swing_modes();
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(climate, msg, conn, remaining_size);
}
void APIConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
@@ -807,7 +761,7 @@ uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection
NumberStateResponse resp;
resp.state = number->state;
resp.missing_state = !number->has_state();
return fill_and_encode_entity_state(number, resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(number, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
@@ -818,8 +772,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
msg.step = number->traits.get_step();
return fill_and_encode_entity_info_with_device_class(number, msg, msg.device_class,
ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(number, msg, msg.device_class, conn, remaining_size);
}
void APIConnection::on_number_command_request(const NumberCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
@@ -839,12 +792,12 @@ uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *c
resp.year = date->year;
resp.month = date->month;
resp.day = date->day;
return fill_and_encode_entity_state(date, resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(date, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *date = static_cast<datetime::DateEntity *>(entity);
ListEntitiesDateResponse msg;
return fill_and_encode_entity_info(date, msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(date, msg, conn, remaining_size);
}
void APIConnection::on_date_command_request(const DateCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
@@ -864,12 +817,12 @@ uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *c
resp.hour = time->hour;
resp.minute = time->minute;
resp.second = time->second;
return fill_and_encode_entity_state(time, resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(time, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *time = static_cast<datetime::TimeEntity *>(entity);
ListEntitiesTimeResponse msg;
return fill_and_encode_entity_info(time, msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(time, msg, conn, remaining_size);
}
void APIConnection::on_time_command_request(const TimeCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
@@ -891,12 +844,12 @@ uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnectio
ESPTime state = datetime->state_as_esptime();
resp.epoch_seconds = state.timestamp;
}
return fill_and_encode_entity_state(datetime, resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(datetime, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
ListEntitiesDateTimeResponse msg;
return fill_and_encode_entity_info(datetime, msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(datetime, msg, conn, remaining_size);
}
void APIConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
@@ -915,7 +868,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c
TextStateResponse resp;
resp.state = StringRef(text->state);
resp.missing_state = !text->has_state();
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(text, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
@@ -925,7 +878,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
msg.min_length = text->traits.get_min_length();
msg.max_length = text->traits.get_max_length();
msg.pattern = text->traits.get_pattern_ref();
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(text, msg, conn, remaining_size);
}
void APIConnection::on_text_command_request(const TextCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
@@ -944,14 +897,14 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
SelectStateResponse resp;
resp.state = select->current_option();
resp.missing_state = !select->has_state();
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(select, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *select = static_cast<select::Select *>(entity);
ListEntitiesSelectResponse msg;
msg.options = &select->traits.get_options();
return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(select, msg, conn, remaining_size);
}
void APIConnection::on_select_command_request(const SelectCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
@@ -964,8 +917,7 @@ void APIConnection::on_select_command_request(const SelectCommandRequest &msg) {
uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *button = static_cast<button::Button *>(entity);
ListEntitiesButtonResponse msg;
return fill_and_encode_entity_info_with_device_class(button, msg, msg.device_class,
ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(button, msg, msg.device_class, conn, remaining_size);
}
void esphome::api::APIConnection::on_button_command_request(const ButtonCommandRequest &msg) {
ENTITY_COMMAND_GET(button::Button, button, button)
@@ -982,7 +934,7 @@ uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *c
auto *a_lock = static_cast<lock::Lock *>(entity);
LockStateResponse resp;
resp.state = static_cast<enums::LockState>(a_lock->state);
return fill_and_encode_entity_state(a_lock, resp, LockStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(a_lock, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
@@ -991,7 +943,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co
msg.assumed_state = a_lock->traits.get_assumed_state();
msg.supports_open = a_lock->traits.get_supports_open();
msg.requires_code = a_lock->traits.get_requires_code();
return fill_and_encode_entity_info(a_lock, msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(a_lock, msg, conn, remaining_size);
}
void APIConnection::on_lock_command_request(const LockCommandRequest &msg) {
ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
@@ -1019,7 +971,7 @@ uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *
ValveStateResponse resp;
resp.position = valve->position;
resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
return fill_and_encode_entity_state(valve, resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(valve, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *valve = static_cast<valve::Valve *>(entity);
@@ -1028,8 +980,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position();
msg.supports_stop = traits.get_supports_stop();
return fill_and_encode_entity_info_with_device_class(valve, msg, msg.device_class,
ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(valve, msg, msg.device_class, conn, remaining_size);
}
void APIConnection::on_valve_command_request(const ValveCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
@@ -1055,7 +1006,7 @@ uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConne
resp.state = static_cast<enums::MediaPlayerState>(report_state);
resp.volume = media_player->volume;
resp.muted = media_player->is_muted();
return fill_and_encode_entity_state(media_player, resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(media_player, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
@@ -1072,8 +1023,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
media_format.sample_bytes = supported_format.sample_bytes;
}
return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn,
remaining_size);
return fill_and_encode_entity_info(media_player, msg, conn, remaining_size);
}
void APIConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
@@ -1114,7 +1064,7 @@ void APIConnection::try_send_camera_image_() {
msg.device_id = camera::Camera::instance()->get_device_id();
#endif
if (!this->send_message_impl(msg, CameraImageResponse::MESSAGE_TYPE)) {
if (!this->send_message(msg)) {
return; // Send failed, try again later
}
this->image_reader_->consume_data(to_send);
@@ -1140,7 +1090,7 @@ void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image)
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *camera = static_cast<camera::Camera *>(entity);
ListEntitiesCameraResponse msg;
return fill_and_encode_entity_info(camera, msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(camera, msg, conn, remaining_size);
}
void APIConnection::on_camera_image_request(const CameraImageRequest &msg) {
if (camera::Camera::instance() == nullptr)
@@ -1295,7 +1245,7 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno
bool APIConnection::send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg) {
VoiceAssistantConfigurationResponse resp;
if (!this->check_voice_assistant_api_connection_()) {
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
return this->send_message(resp);
}
auto &config = voice_assistant::global_voice_assistant->get_configuration();
@@ -1327,7 +1277,7 @@ bool APIConnection::send_voice_assistant_get_configuration_response_(const Voice
resp.active_wake_words = &config.active_wake_words;
resp.max_active_wake_words = config.max_active_wake_words;
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
return this->send_message(resp);
}
void APIConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
if (!this->send_voice_assistant_get_configuration_response_(msg)) {
@@ -1362,8 +1312,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, A
auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
AlarmControlPanelStateResponse resp;
resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
return fill_and_encode_entity_state(a_alarm_control_panel, resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn,
remaining_size);
return fill_and_encode_entity_state(a_alarm_control_panel, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn,
uint32_t remaining_size) {
@@ -1372,8 +1321,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP
msg.supported_features = a_alarm_control_panel->get_supported_features();
msg.requires_code = a_alarm_control_panel->get_requires_code();
msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
return fill_and_encode_entity_info(a_alarm_control_panel, msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE,
conn, remaining_size);
return fill_and_encode_entity_info(a_alarm_control_panel, msg, conn, remaining_size);
}
void APIConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
@@ -1420,7 +1368,7 @@ uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConne
resp.target_temperature_high = wh->get_target_temperature_high();
resp.state = wh->get_state();
return fill_and_encode_entity_state(wh, resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(wh, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
@@ -1431,7 +1379,7 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec
msg.target_temperature_step = traits.get_target_temperature_step();
msg.supported_modes = &traits.get_supported_modes();
msg.supported_features = traits.get_feature_flags();
return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(wh, msg, conn, remaining_size);
}
void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
@@ -1467,15 +1415,14 @@ uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef e
uint32_t remaining_size) {
EventResponse resp;
resp.event_type = event_type;
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(event, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *event = static_cast<event::Event *>(entity);
ListEntitiesEventResponse msg;
msg.event_types = &event->get_event_types();
return fill_and_encode_entity_info_with_device_class(event, msg, msg.device_class,
ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(event, msg, msg.device_class, conn, remaining_size);
}
#endif
@@ -1492,9 +1439,7 @@ void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRF
#endif
}
void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) {
this->send_message(msg, InfraredRFReceiveEvent::MESSAGE_TYPE);
}
void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) { this->send_message(msg); }
#endif
#ifdef USE_INFRARED
@@ -1502,7 +1447,7 @@ uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection
auto *infrared = static_cast<infrared::Infrared *>(entity);
ListEntitiesInfraredResponse msg;
msg.capabilities = infrared->get_capability_flags();
return fill_and_encode_entity_info(infrared, msg, ListEntitiesInfraredResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(infrared, msg, conn, remaining_size);
}
#endif
@@ -1526,13 +1471,12 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
resp.release_summary = StringRef(update->update_info.summary);
resp.release_url = StringRef(update->update_info.release_url);
}
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(update, resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *update = static_cast<update::UpdateEntity *>(entity);
ListEntitiesUpdateResponse msg;
return fill_and_encode_entity_info_with_device_class(update, msg, msg.device_class,
ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info_with_device_class(update, msg, msg.device_class, conn, remaining_size);
}
void APIConnection::on_update_command_request(const UpdateCommandRequest &msg) {
ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
@@ -1558,7 +1502,7 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
SubscribeLogsResponse msg;
msg.level = static_cast<enums::LogLevel>(level);
msg.set_message(reinterpret_cast<const uint8_t *>(line), message_len);
return this->send_message_impl(msg, SubscribeLogsResponse::MESSAGE_TYPE);
return this->send_message(msg);
}
void APIConnection::complete_authentication_() {
@@ -1615,12 +1559,12 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
// Auto-authenticate - password auth was removed in ESPHome 2026.1.0
this->complete_authentication_();
return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
return this->send_message(resp);
}
bool APIConnection::send_ping_response_() {
PingResponse resp;
return this->send_message(resp, PingResponse::MESSAGE_TYPE);
return this->send_message(resp);
}
bool APIConnection::send_device_info_response_() {
@@ -1744,7 +1688,7 @@ bool APIConnection::send_device_info_response_() {
}
#endif
return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE);
return this->send_message(resp);
}
void APIConnection::on_hello_request(const HelloRequest &msg) {
if (!this->send_hello_response_(msg)) {
@@ -1844,7 +1788,7 @@ void APIConnection::send_execute_service_response(uint32_t call_id, bool success
resp.call_id = call_id;
resp.success = success;
resp.error_message = error_message;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
this->send_message(resp);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
@@ -1855,7 +1799,7 @@ void APIConnection::send_execute_service_response(uint32_t call_id, bool success
resp.error_message = error_message;
resp.response_data = response_data;
resp.response_data_len = response_data_len;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
this->send_message(resp);
}
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -1894,7 +1838,7 @@ bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptio
resp.success = true;
}
return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
return this->send_message(resp);
}
void APIConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
if (!this->send_noise_encryption_set_key_response_(msg)) {
@@ -1923,16 +1867,73 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
}
return false;
}
bool APIConnection::send_message_impl(const ProtoMessage &msg, uint8_t message_type) {
uint32_t payload_size = msg.calculated_size();
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
bool APIConnection::send_message_(uint32_t payload_size, uint8_t message_type, MessageEncodeFn encode_fn,
const void *msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// Skip dump for log messages (recursive logging risk) and camera frames (high-frequency noise)
if (message_type != SubscribeLogsResponse::MESSAGE_TYPE
#ifdef USE_CAMERA
&& message_type != CameraImageResponse::MESSAGE_TYPE
#endif
) {
auto *proto_msg = static_cast<const ProtoMessage *>(msg);
DumpBuffer dump_buf;
this->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf));
}
#endif
auto &shared_buf = this->parent_->get_shared_buffer_ref();
this->prepare_first_message_buffer(shared_buf, payload_size);
size_t write_start = shared_buf.size();
shared_buf.resize(write_start + payload_size);
ProtoWriteBuffer buffer{&shared_buf, write_start};
msg.encode(buffer);
encode_fn(msg, buffer);
return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type);
}
// Encodes a message to the buffer and returns the total number of bytes used,
// including header and footer overhead. Returns 0 if the message doesn't fit.
uint16_t APIConnection::encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg,
APIConnection *conn, uint32_t remaining_size) {
#ifdef HAS_PROTO_MESSAGE_DUMP
if (conn->flags_.log_only_mode) {
auto *proto_msg = static_cast<const ProtoMessage *>(msg);
DumpBuffer dump_buf;
conn->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf));
return 1;
}
#endif
// Cache frame sizes to avoid repeated virtual calls
const uint8_t header_padding = conn->helper_->frame_header_padding();
const uint8_t footer_size = conn->helper_->frame_footer_size();
// Calculate total size with padding for buffer allocation
size_t total_calculated_size = calculated_size + header_padding + footer_size;
// Check if it fits
if (total_calculated_size > remaining_size)
return 0; // Doesn't fit
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
if (conn->flags_.batch_first_message) {
// First message - buffer already prepared by caller, just clear flag
conn->flags_.batch_first_message = false;
} else {
// Batch message second or later
// Add padding for previous message footer + this message header
size_t current_size = shared_buf.size();
shared_buf.reserve(current_size + total_calculated_size);
shared_buf.resize(current_size + footer_size + header_padding);
}
// Pre-resize buffer to include payload, then encode through raw pointer
size_t write_start = shared_buf.size();
shared_buf.resize(write_start + calculated_size);
ProtoWriteBuffer buffer{&shared_buf, write_start};
encode_fn(msg, buffer);
// Return total size (header + payload + footer)
return static_cast<uint16_t>(header_padding + calculated_size + footer_size);
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
@@ -2291,17 +2292,17 @@ uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item,
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
ListEntitiesDoneResponse resp;
return encode_message_to_buffer(resp, ListEntitiesDoneResponse::MESSAGE_TYPE, conn, remaining_size);
return encode_message_to_buffer(resp, conn, remaining_size);
}
uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
DisconnectRequest req;
return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size);
return encode_message_to_buffer(req, conn, remaining_size);
}
uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
PingRequest req;
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size);
return encode_message_to_buffer(req, conn, remaining_size);
}
#ifdef USE_API_HOMEASSISTANT_STATES
@@ -2320,7 +2321,7 @@ void APIConnection::process_state_subscriptions_() {
resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef("");
resp.once = it.once;
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
if (this->send_message(resp)) {
this->state_subs_at_++;
}
}
+85 -21
View File
@@ -3,6 +3,12 @@
#include "esphome/core/defines.h"
#ifdef USE_API
#include "api_frame_helper.h"
#ifdef USE_API_NOISE
#include "api_frame_helper_noise.h"
#endif
#ifdef USE_API_PLAINTEXT
#include "api_frame_helper_plaintext.h"
#endif
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "api_server.h"
@@ -123,7 +129,7 @@ class APIConnection final : public APIServerConnectionBase {
void send_homeassistant_action(const HomeassistantActionRequest &call) {
if (!this->flags_.service_call_subscription)
return;
this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
this->send_message(call);
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
@@ -147,7 +153,7 @@ class APIConnection final : public APIServerConnectionBase {
#ifdef USE_HOMEASSISTANT_TIME
void send_time_request() {
GetTimeRequest req;
this->send_message(req, GetTimeRequest::MESSAGE_TYPE);
this->send_message(req);
}
#endif
@@ -257,7 +263,19 @@ class APIConnection final : public APIServerConnectionBase {
void on_fatal_error() override;
void on_no_setup_connection() override;
bool send_message_impl(const ProtoMessage &msg, uint8_t message_type) override;
// Function pointer type for type-erased message encoding
using MessageEncodeFn = void (*)(const void *, ProtoWriteBuffer &);
// Function pointer type for type-erased size calculation
using CalculateSizeFn = uint32_t (*)(const void *);
template<typename T> bool send_message(const T &msg) {
if constexpr (T::ESTIMATED_SIZE == 0) {
return this->send_message_(0, T::MESSAGE_TYPE, &encode_msg_noop, &msg);
} else {
return this->send_message_(msg.calculate_size(), T::MESSAGE_TYPE, &proto_encode_msg<T>, &msg);
}
}
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
shared_buf.clear();
@@ -312,28 +330,68 @@ class APIConnection final : public APIServerConnectionBase {
void process_state_subscriptions_();
#endif
// Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size);
// Helper to fill entity state base and encode message
static uint16_t fill_and_encode_entity_state(EntityBase *entity, StateResponseProtoMessage &msg, uint8_t message_type,
APIConnection *conn, uint32_t remaining_size) {
msg.key = entity->get_object_id_hash();
#ifdef USE_DEVICES
msg.device_id = entity->get_device_id();
#endif
return encode_message_to_buffer(msg, message_type, conn, remaining_size);
// Size thunk — converts void* back to concrete type for direct calculate_size() call
template<typename T> static uint32_t calc_size(const void *msg) {
return static_cast<const T *>(msg)->calculate_size();
}
// Helper to fill entity info base and encode message
static uint16_t fill_and_encode_entity_info(EntityBase *entity, InfoResponseProtoMessage &msg, uint8_t message_type,
APIConnection *conn, uint32_t remaining_size);
// Shared no-op encode thunk for empty messages (ESTIMATED_SIZE == 0)
static void encode_msg_noop(const void *, ProtoWriteBuffer &) {}
// Wrapper for entity types that have a device_class field
// Non-template buffer management for send_message
bool send_message_(uint32_t payload_size, uint8_t message_type, MessageEncodeFn encode_fn, const void *msg);
// Non-template buffer management for batch encoding
static uint16_t encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg,
APIConnection *conn, uint32_t remaining_size);
// Thin template wrapper — computes size, delegates buffer work to non-template helper
template<typename T> static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) {
if constexpr (T::ESTIMATED_SIZE == 0) {
return encode_to_buffer(0, &encode_msg_noop, &msg, conn, remaining_size);
} else {
return encode_to_buffer(msg.calculate_size(), &proto_encode_msg<T>, &msg, conn, remaining_size);
}
}
// Non-template core — fills state fields and encodes
static uint16_t fill_and_encode_entity_state(EntityBase *entity, StateResponseProtoMessage &msg,
CalculateSizeFn size_fn, MessageEncodeFn encode_fn, APIConnection *conn,
uint32_t remaining_size);
// Thin template wrapper
template<typename T>
static uint16_t fill_and_encode_entity_state(EntityBase *entity, T &msg, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_state(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
}
// Non-template core — fills info fields, allocates buffers, and encodes
static uint16_t fill_and_encode_entity_info(EntityBase *entity, InfoResponseProtoMessage &msg,
CalculateSizeFn size_fn, MessageEncodeFn encode_fn, APIConnection *conn,
uint32_t remaining_size);
// Thin template wrapper
template<typename T>
static uint16_t fill_and_encode_entity_info(EntityBase *entity, T &msg, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_info(entity, msg, &calc_size<T>, &proto_encode_msg<T>, conn, remaining_size);
}
// Non-template core — fills device_class, then delegates to fill_and_encode_entity_info
static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, InfoResponseProtoMessage &msg,
StringRef &device_class_field, uint8_t message_type,
APIConnection *conn, uint32_t remaining_size);
StringRef &device_class_field, CalculateSizeFn size_fn,
MessageEncodeFn encode_fn, APIConnection *conn,
uint32_t remaining_size);
// Thin template wrapper
template<typename T>
static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, T &msg,
StringRef &device_class_field, APIConnection *conn,
uint32_t remaining_size) {
return fill_and_encode_entity_info_with_device_class(entity, msg, device_class_field, &calc_size<T>,
&proto_encode_msg<T>, conn, remaining_size);
}
#ifdef USE_VOICE_ASSISTANT
// Helper to check voice assistant validity and connection ownership
@@ -466,7 +524,13 @@ class APIConnection final : public APIServerConnectionBase {
// === Optimal member ordering for 32-bit systems ===
// Group 1: Pointers (4 bytes each on 32-bit)
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
std::unique_ptr<APIFrameHelper> helper_;
#elif defined(USE_API_NOISE)
std::unique_ptr<APINoiseFrameHelper> helper_;
#elif defined(USE_API_PLAINTEXT)
std::unique_ptr<APIPlaintextFrameHelper> helper_;
#endif
APIServer *parent_;
// Group 2: Iterator union (saves ~16 bytes vs separate iterators)
File diff suppressed because it is too large Load Diff
+176 -176
View File
@@ -388,8 +388,8 @@ class HelloResponse final : public ProtoMessage {
uint32_t api_version_minor{0};
StringRef server_info{};
StringRef name{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -453,8 +453,8 @@ class AreaInfo final : public ProtoMessage {
public:
uint32_t area_id{0};
StringRef name{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -468,8 +468,8 @@ class DeviceInfo final : public ProtoMessage {
uint32_t device_id{0};
StringRef name{};
uint32_t area_id{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -533,8 +533,8 @@ class DeviceInfoResponse final : public ProtoMessage {
#ifdef USE_ZWAVE_PROXY
uint32_t zwave_home_id{0};
#endif
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -564,8 +564,8 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
#endif
StringRef device_class{};
bool is_status_binary_sensor{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -581,8 +581,8 @@ class BinarySensorStateResponse final : public StateResponseProtoMessage {
#endif
bool state{false};
bool missing_state{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -603,8 +603,8 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage {
bool supports_tilt{false};
StringRef device_class{};
bool supports_stop{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -621,8 +621,8 @@ class CoverStateResponse final : public StateResponseProtoMessage {
float position{0.0f};
float tilt{0.0f};
enums::CoverOperation current_operation{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -663,8 +663,8 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage {
bool supports_direction{false};
int32_t supported_speed_count{0};
const std::vector<const char *> *supported_preset_modes{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -683,8 +683,8 @@ class FanStateResponse final : public StateResponseProtoMessage {
enums::FanDirection direction{};
int32_t speed_level{0};
StringRef preset_mode{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -730,8 +730,8 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage {
float min_mireds{0.0f};
float max_mireds{0.0f};
const FixedVector<const char *> *effects{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -757,8 +757,8 @@ class LightStateResponse final : public StateResponseProtoMessage {
float cold_white{0.0f};
float warm_white{0.0f};
StringRef effect{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -821,8 +821,8 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage {
bool force_update{false};
StringRef device_class{};
enums::SensorStateClass state_class{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -838,8 +838,8 @@ class SensorStateResponse final : public StateResponseProtoMessage {
#endif
float state{0.0f};
bool missing_state{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -857,8 +857,8 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage {
#endif
bool assumed_state{false};
StringRef device_class{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -873,8 +873,8 @@ class SwitchStateResponse final : public StateResponseProtoMessage {
const char *message_name() const override { return "switch_state_response"; }
#endif
bool state{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -907,8 +907,8 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_text_sensor_response"; }
#endif
StringRef device_class{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -924,8 +924,8 @@ class TextSensorStateResponse final : public StateResponseProtoMessage {
#endif
StringRef state{};
bool missing_state{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -963,8 +963,8 @@ class SubscribeLogsResponse final : public ProtoMessage {
this->message_ptr_ = data;
this->message_len_ = len;
}
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -996,8 +996,8 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage {
const char *message_name() const override { return "noise_encryption_set_key_response"; }
#endif
bool success{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1010,8 +1010,8 @@ class HomeassistantServiceMap final : public ProtoMessage {
public:
StringRef key{};
StringRef value{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1039,8 +1039,8 @@ class HomeassistantActionRequest final : public ProtoMessage {
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
StringRef response_template{};
#endif
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1083,8 +1083,8 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
StringRef entity_id{};
StringRef attribute{};
bool once{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1173,8 +1173,8 @@ class ListEntitiesServicesArgument final : public ProtoMessage {
public:
StringRef name{};
enums::ServiceArgType type{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1192,8 +1192,8 @@ class ListEntitiesServicesResponse final : public ProtoMessage {
uint32_t key{0};
FixedVector<ListEntitiesServicesArgument> args{};
enums::SupportsResponseType supports_response{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1262,8 +1262,8 @@ class ExecuteServiceResponse final : public ProtoMessage {
const uint8_t *response_data{nullptr};
uint16_t response_data_len{0};
#endif
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1279,8 +1279,8 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_camera_response"; }
#endif
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1301,8 +1301,8 @@ class CameraImageResponse final : public StateResponseProtoMessage {
this->data_len_ = len;
}
bool done{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1352,8 +1352,8 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
float visual_min_humidity{0.0f};
float visual_max_humidity{0.0f};
uint32_t feature_flags{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1380,8 +1380,8 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
StringRef custom_preset{};
float current_humidity{0.0f};
float target_humidity{0.0f};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1438,8 +1438,8 @@ class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
float target_temperature_step{0.0f};
const water_heater::WaterHeaterModeMask *supported_modes{};
uint32_t supported_features{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1459,8 +1459,8 @@ class WaterHeaterStateResponse final : public StateResponseProtoMessage {
uint32_t state{0};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1503,8 +1503,8 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
StringRef unit_of_measurement{};
enums::NumberMode mode{};
StringRef device_class{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1520,8 +1520,8 @@ class NumberStateResponse final : public StateResponseProtoMessage {
#endif
float state{0.0f};
bool missing_state{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1554,8 +1554,8 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_select_response"; }
#endif
const FixedVector<const char *> *options{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1571,8 +1571,8 @@ class SelectStateResponse final : public StateResponseProtoMessage {
#endif
StringRef state{};
bool missing_state{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1608,8 +1608,8 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
const FixedVector<const char *> *tones{};
bool supports_duration{false};
bool supports_volume{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1624,8 +1624,8 @@ class SirenStateResponse final : public StateResponseProtoMessage {
const char *message_name() const override { return "siren_state_response"; }
#endif
bool state{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1669,8 +1669,8 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage {
bool supports_open{false};
bool requires_code{false};
StringRef code_format{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1685,8 +1685,8 @@ class LockStateResponse final : public StateResponseProtoMessage {
const char *message_name() const override { return "lock_state_response"; }
#endif
enums::LockState state{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1722,8 +1722,8 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_button_response"; }
#endif
StringRef device_class{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1754,8 +1754,8 @@ class MediaPlayerSupportedFormat final : public ProtoMessage {
uint32_t num_channels{0};
enums::MediaPlayerFormatPurpose purpose{};
uint32_t sample_bytes{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1772,8 +1772,8 @@ class ListEntitiesMediaPlayerResponse final : public InfoResponseProtoMessage {
bool supports_pause{false};
std::vector<MediaPlayerSupportedFormat> supported_formats{};
uint32_t feature_flags{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1790,8 +1790,8 @@ class MediaPlayerStateResponse final : public StateResponseProtoMessage {
enums::MediaPlayerState state{};
float volume{0.0f};
bool muted{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1846,8 +1846,8 @@ class BluetoothLERawAdvertisement final : public ProtoMessage {
uint32_t address_type{0};
uint8_t data[62]{};
uint8_t data_len{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1863,8 +1863,8 @@ class BluetoothLERawAdvertisementsResponse final : public ProtoMessage {
#endif
std::array<BluetoothLERawAdvertisement, BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE> advertisements{};
uint16_t advertisements_len{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1900,8 +1900,8 @@ class BluetoothDeviceConnectionResponse final : public ProtoMessage {
bool connected{false};
uint32_t mtu{0};
int32_t error{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1928,8 +1928,8 @@ class BluetoothGATTDescriptor final : public ProtoMessage {
std::array<uint64_t, 2> uuid{};
uint32_t handle{0};
uint32_t short_uuid{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1943,8 +1943,8 @@ class BluetoothGATTCharacteristic final : public ProtoMessage {
uint32_t properties{0};
FixedVector<BluetoothGATTDescriptor> descriptors{};
uint32_t short_uuid{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1957,8 +1957,8 @@ class BluetoothGATTService final : public ProtoMessage {
uint32_t handle{0};
FixedVector<BluetoothGATTCharacteristic> characteristics{};
uint32_t short_uuid{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1974,8 +1974,8 @@ class BluetoothGATTGetServicesResponse final : public ProtoMessage {
#endif
uint64_t address{0};
std::vector<BluetoothGATTService> services{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1990,8 +1990,8 @@ class BluetoothGATTGetServicesDoneResponse final : public ProtoMessage {
const char *message_name() const override { return "bluetooth_gatt_get_services_done_response"; }
#endif
uint64_t address{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2029,8 +2029,8 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
this->data_ptr_ = data;
this->data_len_ = len;
}
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2124,8 +2124,8 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
this->data_ptr_ = data;
this->data_len_ = len;
}
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2142,8 +2142,8 @@ class BluetoothConnectionsFreeResponse final : public ProtoMessage {
uint32_t free{0};
uint32_t limit{0};
std::array<uint64_t, BLUETOOTH_PROXY_MAX_CONNECTIONS> allocated{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2160,8 +2160,8 @@ class BluetoothGATTErrorResponse final : public ProtoMessage {
uint64_t address{0};
uint32_t handle{0};
int32_t error{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2177,8 +2177,8 @@ class BluetoothGATTWriteResponse final : public ProtoMessage {
#endif
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2194,8 +2194,8 @@ class BluetoothGATTNotifyResponse final : public ProtoMessage {
#endif
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2212,8 +2212,8 @@ class BluetoothDevicePairingResponse final : public ProtoMessage {
uint64_t address{0};
bool paired{false};
int32_t error{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2230,8 +2230,8 @@ class BluetoothDeviceUnpairingResponse final : public ProtoMessage {
uint64_t address{0};
bool success{false};
int32_t error{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2248,8 +2248,8 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
uint64_t address{0};
bool success{false};
int32_t error{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2266,8 +2266,8 @@ class BluetoothScannerStateResponse final : public ProtoMessage {
enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{};
enums::BluetoothScannerMode configured_mode{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2312,8 +2312,8 @@ class VoiceAssistantAudioSettings final : public ProtoMessage {
uint32_t noise_suppression_level{0};
uint32_t auto_gain{0};
float volume_multiplier{0.0f};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2332,8 +2332,8 @@ class VoiceAssistantRequest final : public ProtoMessage {
uint32_t flags{0};
VoiceAssistantAudioSettings audio_settings{};
StringRef wake_word_phrase{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2394,8 +2394,8 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage {
const uint8_t *data{nullptr};
uint16_t data_len{0};
bool end{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2452,8 +2452,8 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage {
const char *message_name() const override { return "voice_assistant_announce_finished"; }
#endif
bool success{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2465,8 +2465,8 @@ class VoiceAssistantWakeWord final : public ProtoMessage {
StringRef id{};
StringRef wake_word{};
std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2515,8 +2515,8 @@ class VoiceAssistantConfigurationResponse final : public ProtoMessage {
std::vector<VoiceAssistantWakeWord> available_wake_words{};
const std::vector<std::string> *active_wake_words{};
uint32_t max_active_wake_words{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2550,8 +2550,8 @@ class ListEntitiesAlarmControlPanelResponse final : public InfoResponseProtoMess
uint32_t supported_features{0};
bool requires_code{false};
bool requires_code_to_arm{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2566,8 +2566,8 @@ class AlarmControlPanelStateResponse final : public StateResponseProtoMessage {
const char *message_name() const override { return "alarm_control_panel_state_response"; }
#endif
enums::AlarmControlPanelState state{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2605,8 +2605,8 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage {
uint32_t max_length{0};
StringRef pattern{};
enums::TextMode mode{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2622,8 +2622,8 @@ class TextStateResponse final : public StateResponseProtoMessage {
#endif
StringRef state{};
bool missing_state{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2656,8 +2656,8 @@ class ListEntitiesDateResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_date_response"; }
#endif
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2675,8 +2675,8 @@ class DateStateResponse final : public StateResponseProtoMessage {
uint32_t year{0};
uint32_t month{0};
uint32_t day{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2710,8 +2710,8 @@ class ListEntitiesTimeResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_time_response"; }
#endif
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2729,8 +2729,8 @@ class TimeStateResponse final : public StateResponseProtoMessage {
uint32_t hour{0};
uint32_t minute{0};
uint32_t second{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2766,8 +2766,8 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
#endif
StringRef device_class{};
const FixedVector<const char *> *event_types{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2782,8 +2782,8 @@ class EventResponse final : public StateResponseProtoMessage {
const char *message_name() const override { return "event_response"; }
#endif
StringRef event_type{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2803,8 +2803,8 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage {
bool assumed_state{false};
bool supports_position{false};
bool supports_stop{false};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2820,8 +2820,8 @@ class ValveStateResponse final : public StateResponseProtoMessage {
#endif
float position{0.0f};
enums::ValveOperation current_operation{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2855,8 +2855,8 @@ class ListEntitiesDateTimeResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_date_time_response"; }
#endif
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2872,8 +2872,8 @@ class DateTimeStateResponse final : public StateResponseProtoMessage {
#endif
bool missing_state{false};
uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2906,8 +2906,8 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_update_response"; }
#endif
StringRef device_class{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2930,8 +2930,8 @@ class UpdateStateResponse final : public StateResponseProtoMessage {
StringRef title{};
StringRef release_summary{};
StringRef release_url{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2965,8 +2965,8 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage {
#endif
const uint8_t *data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -2984,8 +2984,8 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
enums::ZWaveProxyRequestType type{};
const uint8_t *data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -3004,8 +3004,8 @@ class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_infrared_response"; }
#endif
uint32_t capabilities{0};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -3051,8 +3051,8 @@ class InfraredRFReceiveEvent final : public ProtoMessage {
#endif
uint32_t key{0};
const std::vector<int32_t> *timings{};
void encode(ProtoWriteBuffer &buffer) const override;
void calculate_size(ProtoSize &size) const override;
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
-8
View File
@@ -19,14 +19,6 @@ class APIServerConnectionBase : public ProtoService {
public:
#endif
bool send_message(const ProtoMessage &msg, uint8_t message_type) {
#ifdef HAS_PROTO_MESSAGE_DUMP
DumpBuffer dump_buf;
this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
#endif
return this->send_message_impl(msg, message_type);
}
virtual void on_hello_request(const HelloRequest &value){};
virtual void on_disconnect_request(){};
+4 -4
View File
@@ -359,11 +359,11 @@ void APIServer::on_update(update::UpdateEntity *obj) {
#endif
#ifdef USE_ZWAVE_PROXY
void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) {
void APIServer::on_zwave_proxy_request(const ZWaveProxyRequest &msg) {
// We could add code to manage a second subscription type, but, since this message type is
// very infrequent and small, we simply send it to all clients
for (auto &c : this->clients_)
c->send_message(msg, api::ZWaveProxyRequest::MESSAGE_TYPE);
c->send_message(msg);
}
#endif
@@ -531,7 +531,7 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString
this->set_noise_psk(active_psk);
for (auto &c : this->clients_) {
DisconnectRequest req;
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
c->send_message(req);
}
});
}
@@ -631,7 +631,7 @@ void APIServer::on_shutdown() {
// Send disconnect requests to all connected clients
for (auto &c : this->clients_) {
DisconnectRequest req;
if (!c->send_message(req, DisconnectRequest::MESSAGE_TYPE)) {
if (!c->send_message(req)) {
// If we can't send the disconnect request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority
c->schedule_message_front_(nullptr, DisconnectRequest::MESSAGE_TYPE, DisconnectRequest::ESTIMATED_SIZE);
+1 -1
View File
@@ -179,7 +179,7 @@ class APIServer : public Component,
void on_update(update::UpdateEntity *obj) override;
#endif
#ifdef USE_ZWAVE_PROXY
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
void on_zwave_proxy_request(const ZWaveProxyRequest &msg);
#endif
#ifdef USE_IR_RF
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
+1 -1
View File
@@ -94,7 +94,7 @@ ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(clie
#ifdef USE_API_USER_DEFINED_ACTIONS
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto resp = service->encode_list_service_response();
return this->client_->send_message(resp, ListEntitiesServicesResponse::MESSAGE_TYPE);
return this->client_->send_message(resp);
}
#endif
+74 -347
View File
@@ -364,7 +364,11 @@ class ProtoWriteBuffer {
/// Encode a packed repeated sint32 field (zero-copy from vector)
void encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values);
/// Encode a nested message field (force=true for repeated, false for singular)
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = true);
/// Templated so concrete message type is preserved for direct encode/calculate_size calls.
template<typename T> void encode_message(uint32_t field_id, const T &value, bool force = true);
// Non-template core for encode_message — all buffer work happens here
void encode_message(uint32_t field_id, uint32_t msg_length_bytes, const void *value,
void (*encode_fn)(const void *, ProtoWriteBuffer &), bool force);
std::vector<uint8_t> *get_buffer() const { return buffer_; }
protected:
@@ -452,20 +456,20 @@ class DumpBuffer {
class ProtoMessage {
public:
// Default implementation for messages with no fields
virtual void encode(ProtoWriteBuffer &buffer) const {}
// Default implementation for messages with no fields
virtual void calculate_size(ProtoSize &size) const {}
// Convenience: calculate and return size directly (defined after ProtoSize)
uint32_t calculated_size() const;
// Non-virtual defaults for messages with no fields.
// Concrete message classes hide these with their own implementations.
// All call sites use templates to preserve the concrete type, so virtual
// dispatch is not needed. This eliminates per-message vtable entries for
// encode/calculate_size, saving ~1.3 KB of flash across all message types.
void encode(ProtoWriteBuffer &buffer) const {}
uint32_t calculate_size() const { return 0; }
#ifdef HAS_PROTO_MESSAGE_DUMP
virtual const char *dump_to(DumpBuffer &out) const = 0;
virtual const char *message_name() const { return "unknown"; }
#endif
protected:
// Non-virtual: messages are never deleted polymorphically.
// Protected prevents accidental `delete base_ptr` (compile error).
// Non-virtual destructor is protected to prevent polymorphic deletion.
~ProtoMessage() = default;
};
@@ -494,32 +498,7 @@ class ProtoDecodableMessage : public ProtoMessage {
};
class ProtoSize {
private:
uint32_t total_size_ = 0;
public:
/**
* @brief ProtoSize class for Protocol Buffer serialization size calculation
*
* This class provides methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. The class now uses an
* object-based approach to reduce parameter passing overhead while keeping
* varint calculation methods static for external use.
*
* Implements Protocol Buffer encoding size calculation according to:
* https://protobuf.dev/programming-guides/encoding/
*
* Key features:
* - Object-based approach reduces flash usage by eliminating parameter passing
* - Early-return optimization for zero/default values
* - Static varint methods for external callers
* - Specialized handling for different field types according to protobuf spec
*/
ProtoSize() = default;
uint32_t get_size() const { return total_size_; }
/**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
*
@@ -616,320 +595,77 @@ class ProtoSize {
return varint(tag);
}
/**
* @brief Common parameters for all add_*_field methods
*
* All add_*_field methods follow these common patterns:
* * @param field_id_size Pre-calculated size of the field ID in bytes
* @param value The value to calculate size for (type varies)
* @param force Whether to calculate size even if the value is default/zero/empty
*
* Each method follows this implementation pattern:
* 1. Skip calculation if value is default (0, false, empty) and not forced
* 2. Calculate the size based on the field's encoding rules
* 3. Add the field_id_size + calculated value size to total_size
*/
/**
* @brief Calculates and adds the size of an int32 field to the total message size
*/
inline void add_int32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
add_int32_force(field_id_size, value);
}
// Static methods that RETURN size contribution (no ProtoSize object needed).
// Used by generated calculate_size() methods to accumulate into a plain uint32_t register.
static constexpr uint32_t calc_int32(uint32_t field_id_size, int32_t value) {
return value ? field_id_size + (value < 0 ? 10 : varint(static_cast<uint32_t>(value))) : 0;
}
/**
* @brief Calculates and adds the size of an int32 field to the total message size (force version)
*/
inline void add_int32_force(uint32_t field_id_size, int32_t value) {
// Always calculate size when forced
// Negative values are encoded as 10-byte varints in protobuf
total_size_ += field_id_size + (value < 0 ? 10 : varint(static_cast<uint32_t>(value)));
static constexpr uint32_t calc_int32_force(uint32_t field_id_size, int32_t value) {
return field_id_size + (value < 0 ? 10 : varint(static_cast<uint32_t>(value)));
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size
*/
inline void add_uint32(uint32_t field_id_size, uint32_t value) {
if (value != 0) {
add_uint32_force(field_id_size, value);
}
static constexpr uint32_t calc_uint32(uint32_t field_id_size, uint32_t value) {
return value ? field_id_size + varint(value) : 0;
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size (force version)
*/
inline void add_uint32_force(uint32_t field_id_size, uint32_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
static constexpr uint32_t calc_uint32_force(uint32_t field_id_size, uint32_t value) {
return field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size
*/
inline void add_bool(uint32_t field_id_size, bool value) {
if (value) {
// Boolean fields always use 1 byte when true
total_size_ += field_id_size + 1;
}
static constexpr uint32_t calc_bool(uint32_t field_id_size, bool value) { return value ? field_id_size + 1 : 0; }
static constexpr uint32_t calc_bool_force(uint32_t field_id_size) { return field_id_size + 1; }
static constexpr uint32_t calc_float(uint32_t field_id_size, float value) {
return value != 0.0f ? field_id_size + 4 : 0;
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size (force version)
*/
inline void add_bool_force(uint32_t field_id_size, bool value) {
// Always calculate size when force is true
// Boolean fields always use 1 byte
total_size_ += field_id_size + 1;
static constexpr uint32_t calc_fixed32(uint32_t field_id_size, uint32_t value) {
return value ? field_id_size + 4 : 0;
}
/**
* @brief Calculates and adds the size of a float field to the total message size
*/
inline void add_float(uint32_t field_id_size, float value) {
if (value != 0.0f) {
total_size_ += field_id_size + 4;
}
static constexpr uint32_t calc_sfixed32(uint32_t field_id_size, int32_t value) {
return value ? field_id_size + 4 : 0;
}
// NOTE: add_double_field removed - wire type 1 (64-bit: double) not supported
// to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of a fixed32 field to the total message size
*/
inline void add_fixed32(uint32_t field_id_size, uint32_t value) {
if (value != 0) {
total_size_ += field_id_size + 4;
}
static constexpr uint32_t calc_sint32(uint32_t field_id_size, int32_t value) {
return value ? field_id_size + varint(encode_zigzag32(value)) : 0;
}
// NOTE: add_fixed64_field removed - wire type 1 (64-bit: fixed64) not supported
// to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of a sfixed32 field to the total message size
*/
inline void add_sfixed32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
total_size_ += field_id_size + 4;
}
static constexpr uint32_t calc_sint32_force(uint32_t field_id_size, int32_t value) {
return field_id_size + varint(encode_zigzag32(value));
}
// NOTE: add_sfixed64_field removed - wire type 1 (64-bit: sfixed64) not supported
// to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of a sint32 field to the total message size
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
inline void add_sint32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
add_sint32_force(field_id_size, value);
}
static constexpr uint32_t calc_int64(uint32_t field_id_size, int64_t value) {
return value ? field_id_size + varint(value) : 0;
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size (force version)
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
// Always calculate size when force is true
// ZigZag encoding for sint32
total_size_ += field_id_size + varint(encode_zigzag32(value));
static constexpr uint32_t calc_int64_force(uint32_t field_id_size, int64_t value) {
return field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size
*/
inline void add_int64(uint32_t field_id_size, int64_t value) {
if (value != 0) {
add_int64_force(field_id_size, value);
}
static constexpr uint32_t calc_uint64(uint32_t field_id_size, uint64_t value) {
return value ? field_id_size + varint(value) : 0;
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size (force version)
*/
inline void add_int64_force(uint32_t field_id_size, int64_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
static constexpr uint32_t calc_uint64_force(uint32_t field_id_size, uint64_t value) {
return field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size
*/
inline void add_uint64(uint32_t field_id_size, uint64_t value) {
if (value != 0) {
add_uint64_force(field_id_size, value);
}
static constexpr uint32_t calc_length(uint32_t field_id_size, size_t len) {
return len ? field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len) : 0;
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size (force version)
*/
inline void add_uint64_force(uint32_t field_id_size, uint64_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
static constexpr uint32_t calc_length_force(uint32_t field_id_size, size_t len) {
return field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
}
// NOTE: sint64 support functions (add_sint64_field, add_sint64_field_force) removed
// sint64 type is not supported by ESPHome API to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size
*/
inline void add_length(uint32_t field_id_size, size_t len) {
if (len != 0) {
add_length_force(field_id_size, len);
}
static constexpr uint32_t calc_sint64(uint32_t field_id_size, int64_t value) {
return value ? field_id_size + varint(encode_zigzag64(value)) : 0;
}
/**
* @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size (repeated
* field version)
*/
inline void add_length_force(uint32_t field_id_size, size_t len) {
// Always calculate size when force is true
// Field ID + length varint + data bytes
total_size_ += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
static constexpr uint32_t calc_sint64_force(uint32_t field_id_size, int64_t value) {
return field_id_size + varint(encode_zigzag64(value));
}
/**
* @brief Adds a pre-calculated size directly to the total
*
* This is used when we can calculate the total size by multiplying the number
* of elements by the bytes per element (for repeated fixed-size types like float, fixed32, etc.)
*
* @param size The pre-calculated total size to add
*/
inline void add_precalculated_size(uint32_t size) { total_size_ += size; }
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
* This helper function directly updates the total_size reference if the nested size
* is greater than zero.
*
* @param nested_size The pre-calculated size of the nested message
*/
inline void add_message_field(uint32_t field_id_size, uint32_t nested_size) {
if (nested_size != 0) {
add_message_field_force(field_id_size, nested_size);
}
static constexpr uint32_t calc_fixed64(uint32_t field_id_size, uint64_t value) {
return value ? field_id_size + 8 : 0;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (force version)
*
* @param nested_size The pre-calculated size of the nested message
*/
inline void add_message_field_force(uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size when force is true
// Field ID + length varint + nested message content
total_size_ += field_id_size + varint(nested_size) + nested_size;
static constexpr uint32_t calc_sfixed64(uint32_t field_id_size, int64_t value) {
return value ? field_id_size + 8 : 0;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
* This version takes a ProtoMessage object, calculates its size internally,
* and updates the total_size reference. This eliminates the need for a temporary variable
* at the call site.
*
* @param message The nested message object
*/
inline void add_message_object(uint32_t field_id_size, const ProtoMessage &message) {
// Calculate nested message size by creating a temporary ProtoSize
ProtoSize nested_calc;
message.calculate_size(nested_calc);
uint32_t nested_size = nested_calc.get_size();
// Use the base implementation with the calculated nested_size
add_message_field(field_id_size, nested_size);
static constexpr uint32_t calc_message(uint32_t field_id_size, uint32_t nested_size) {
return nested_size ? field_id_size + varint(nested_size) + nested_size : 0;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (force version)
*
* @param message The nested message object
*/
inline void add_message_object_force(uint32_t field_id_size, const ProtoMessage &message) {
// Calculate nested message size by creating a temporary ProtoSize
ProtoSize nested_calc;
message.calculate_size(nested_calc);
uint32_t nested_size = nested_calc.get_size();
// Use the base implementation with the calculated nested_size
add_message_field_force(field_id_size, nested_size);
}
/**
* @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
*
* This helper processes a vector of message objects, calculating the size for each message
* and adding it to the total size.
*
* @tparam MessageType The type of the nested messages in the vector
* @param messages Vector of message objects
*/
template<typename MessageType>
inline void add_repeated_message(uint32_t field_id_size, const std::vector<MessageType> &messages) {
// Skip if the vector is empty
if (!messages.empty()) {
// Use the force version for all messages in the repeated field
for (const auto &message : messages) {
add_message_object_force(field_id_size, message);
}
}
}
/**
* @brief Calculates and adds the sizes of all messages in a repeated field to the total message size (FixedVector
* version)
*
* @tparam MessageType The type of the nested messages in the FixedVector
* @param messages FixedVector of message objects
*/
template<typename MessageType>
inline void add_repeated_message(uint32_t field_id_size, const FixedVector<MessageType> &messages) {
// Skip if the fixed vector is empty
if (!messages.empty()) {
// Use the force version for all messages in the repeated field
for (const auto &message : messages) {
add_message_object_force(field_id_size, message);
}
}
}
/**
* @brief Calculate size of a packed repeated sint32 field
*/
inline void add_packed_sint32(uint32_t field_id_size, const std::vector<int32_t> &values) {
if (values.empty())
return;
size_t packed_size = 0;
for (int value : values) {
packed_size += varint(encode_zigzag32(value));
}
// field_id + length varint + packed data
total_size_ += field_id_size + varint(static_cast<uint32_t>(packed_size)) + static_cast<uint32_t>(packed_size);
static constexpr uint32_t calc_message_force(uint32_t field_id_size, uint32_t nested_size) {
return field_id_size + varint(nested_size) + nested_size;
}
};
// Implementation of methods that depend on ProtoSize being fully defined
inline uint32_t ProtoMessage::calculated_size() const {
ProtoSize size;
this->calculate_size(size);
return size.get_size();
}
// Implementation of encode_packed_sint32 - must be after ProtoSize is defined
inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values) {
if (values.empty())
@@ -949,31 +685,30 @@ inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std:
}
}
// Implementation of encode_message - must be after ProtoMessage is defined
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
// Calculate the message size first
ProtoSize msg_size;
value.calculate_size(msg_size);
uint32_t msg_length_bytes = msg_size.get_size();
// Encode thunk — converts void* back to concrete type for direct encode() call
template<typename T> void proto_encode_msg(const void *msg, ProtoWriteBuffer &buf) {
static_cast<const T *>(msg)->encode(buf);
}
// Skip empty singular messages (matches add_message_field which skips when nested_size == 0)
// Repeated messages (force=true) are always encoded since an empty item is meaningful
// Implementation of encode_message - must be after ProtoMessage is defined
template<typename T> inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const T &value, bool force) {
this->encode_message(field_id, value.calculate_size(), &value, &proto_encode_msg<T>, force);
}
// Non-template core for encode_message
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, uint32_t msg_length_bytes, const void *value,
void (*encode_fn)(const void *, ProtoWriteBuffer &), bool force) {
if (msg_length_bytes == 0 && !force)
return;
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Write the length varint directly through pos_
this->encode_field_raw(field_id, 2);
this->encode_varint_raw(msg_length_bytes);
// Encode nested message - pos_ advances directly through the reference
#ifdef ESPHOME_DEBUG_API
uint8_t *start = this->pos_;
value.encode(*this);
encode_fn(value, *this);
if (static_cast<uint32_t>(this->pos_ - start) != msg_length_bytes)
this->debug_check_encode_size_(field_id, msg_length_bytes, this->pos_ - start);
#else
value.encode(*this);
encode_fn(value, *this);
#endif
}
@@ -993,14 +728,6 @@ class ProtoService {
virtual void on_no_setup_connection() = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0;
/**
* Send a protobuf message by calculating its size, allocating a buffer, encoding, and sending.
* This is the implementation method - callers should use send_message() which adds logging.
* @param msg The protobuf message to send.
* @param message_type The message type identifier.
* @return True if the message was sent successfully, false otherwise.
*/
virtual bool send_message_impl(const ProtoMessage &msg, uint8_t message_type) = 0;
// Authentication helper methods
inline bool check_connection_setup_() {
@@ -183,10 +183,7 @@ void BluetoothConnection::send_service_for_discovery_() {
static constexpr size_t MAX_PACKET_SIZE = 1360;
// Keep running total of actual message size
size_t current_size = 0;
api::ProtoSize size;
resp.calculate_size(size);
current_size = size.get_size();
size_t current_size = resp.calculate_size();
while (this->send_service_ < this->service_count_) {
esp_gattc_service_elem_t service_result;
@@ -302,9 +299,7 @@ void BluetoothConnection::send_service_for_discovery_() {
} // end if (total_char_count > 0)
// Calculate the actual size of just this service
api::ProtoSize service_sizer;
service_resp.calculate_size(service_sizer);
size_t service_size = service_sizer.get_size() + 1; // +1 for field tag
size_t service_size = service_resp.calculate_size() + 1; // +1 for field tag
// Check if adding this service would exceed the limit
if (current_size + service_size > MAX_PACKET_SIZE) {
@@ -333,7 +328,7 @@ void BluetoothConnection::send_service_for_discovery_() {
}
// Send the message with dynamically batched services
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
api_conn->send_message(resp);
}
void BluetoothConnection::log_connection_error_(const char *operation, esp_gatt_status_t status) {
@@ -419,7 +414,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
resp.address = this->address_;
resp.handle = param->read.handle;
resp.set_data(param->read.value, param->read.value_len);
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTReadResponse::MESSAGE_TYPE);
this->proxy_->get_api_connection()->send_message(resp);
break;
}
case ESP_GATTC_WRITE_CHAR_EVT:
@@ -432,7 +427,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
api::BluetoothGATTWriteResponse resp;
resp.address = this->address_;
resp.handle = param->write.handle;
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTWriteResponse::MESSAGE_TYPE);
this->proxy_->get_api_connection()->send_message(resp);
break;
}
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
@@ -445,7 +440,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
api::BluetoothGATTNotifyResponse resp;
resp.address = this->address_;
resp.handle = param->unreg_for_notify.handle;
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTNotifyResponse::MESSAGE_TYPE);
this->proxy_->get_api_connection()->send_message(resp);
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
@@ -458,7 +453,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
api::BluetoothGATTNotifyResponse resp;
resp.address = this->address_;
resp.handle = param->reg_for_notify.handle;
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTNotifyResponse::MESSAGE_TYPE);
this->proxy_->get_api_connection()->send_message(resp);
break;
}
case ESP_GATTC_NOTIFY_EVT: {
@@ -468,7 +463,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
resp.address = this->address_;
resp.handle = param->notify.handle;
resp.set_data(param->notify.value, param->notify.value_len);
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTNotifyDataResponse::MESSAGE_TYPE);
this->proxy_->get_api_connection()->send_message(resp);
break;
}
default:
@@ -44,7 +44,7 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
resp.configured_mode = this->configured_scan_active_
? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
this->api_connection_->send_message(resp);
}
void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state) {
@@ -112,7 +112,7 @@ void BluetoothProxy::flush_pending_advertisements() {
return;
// Send the message
this->api_connection_->send_message(this->response_, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
this->api_connection_->send_message(this->response_);
ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
@@ -269,7 +269,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
call.success = ret == ESP_OK;
call.error = ret;
this->api_connection_->send_message(call, api::BluetoothDeviceClearCacheResponse::MESSAGE_TYPE);
this->api_connection_->send_message(call);
break;
}
@@ -389,7 +389,7 @@ void BluetoothProxy::send_device_connection(uint64_t address, bool connected, ui
call.connected = connected;
call.mtu = mtu;
call.error = error;
this->api_connection_->send_message(call, api::BluetoothDeviceConnectionResponse::MESSAGE_TYPE);
this->api_connection_->send_message(call);
}
void BluetoothProxy::send_connections_free() {
if (this->api_connection_ != nullptr) {
@@ -398,7 +398,7 @@ void BluetoothProxy::send_connections_free() {
}
void BluetoothProxy::send_connections_free(api::APIConnection *api_connection) {
api_connection->send_message(this->connections_free_response_, api::BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
api_connection->send_message(this->connections_free_response_);
}
void BluetoothProxy::send_gatt_services_done(uint64_t address) {
@@ -406,7 +406,7 @@ void BluetoothProxy::send_gatt_services_done(uint64_t address) {
return;
api::BluetoothGATTGetServicesDoneResponse call;
call.address = address;
this->api_connection_->send_message(call, api::BluetoothGATTGetServicesDoneResponse::MESSAGE_TYPE);
this->api_connection_->send_message(call);
}
void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
@@ -416,7 +416,7 @@ void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_
call.address = address;
call.handle = handle;
call.error = error;
this->api_connection_->send_message(call, api::BluetoothGATTWriteResponse::MESSAGE_TYPE);
this->api_connection_->send_message(call);
}
void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
@@ -425,7 +425,7 @@ void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_
call.paired = paired;
call.error = error;
this->api_connection_->send_message(call, api::BluetoothDevicePairingResponse::MESSAGE_TYPE);
this->api_connection_->send_message(call);
}
void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
@@ -434,7 +434,7 @@ void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_e
call.success = success;
call.error = error;
this->api_connection_->send_message(call, api::BluetoothDeviceUnpairingResponse::MESSAGE_TYPE);
this->api_connection_->send_message(call);
}
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
+12 -2
View File
@@ -2,7 +2,7 @@
#include "esphome/core/defines.h"
#ifdef USE_OTA
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_factory.h"
#include "esphome/components/socket/socket.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -86,7 +86,17 @@ class ESPHomeOTAComponent final : public ota::OTAComponent {
socket::ListenSocket *server_{nullptr};
std::unique_ptr<socket::Socket> client_;
std::unique_ptr<ota::OTABackend> backend_;
#ifdef USE_ESP8266
std::unique_ptr<ota::ESP8266OTABackend> backend_;
#elif defined(USE_ESP32)
std::unique_ptr<ota::IDFOTABackend> backend_;
#elif defined(USE_RP2040)
std::unique_ptr<ota::ArduinoRP2040OTABackend> backend_;
#elif defined(USE_LIBRETINY)
std::unique_ptr<ota::ArduinoLibreTinyOTABackend> backend_;
#elif defined(USE_HOST)
std::unique_ptr<ota::HostOTABackend> backend_;
#endif
uint32_t client_connect_time_{0};
uint16_t port_;
@@ -361,7 +361,7 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui
}
}
uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer) {
uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> &data_buffer) {
while (this->available())
this->read();
this->write((uint8_t) (START_CODE >> 8));
@@ -372,12 +372,12 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
this->write(this->address_[3]);
this->write(COMMAND);
uint16_t wire_length = p_data_buffer->size() + 2;
uint16_t wire_length = data_buffer.size() + 2;
this->write((uint8_t) (wire_length >> 8));
this->write((uint8_t) (wire_length & 0xFF));
uint16_t sum = (wire_length >> 8) + (wire_length & 0xFF) + COMMAND;
for (auto data : *p_data_buffer) {
for (auto data : data_buffer) {
this->write(data);
sum += data;
}
@@ -385,7 +385,7 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
this->write((uint8_t) (sum >> 8));
this->write((uint8_t) (sum & 0xFF));
p_data_buffer->clear();
data_buffer.clear();
uint8_t byte;
uint16_t idx = 0, length = 0;
@@ -431,9 +431,9 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
length |= byte;
break;
default:
p_data_buffer->push_back(byte);
data_buffer.push_back(byte);
if ((idx - 8) == length) {
switch ((*p_data_buffer)[0]) {
switch (data_buffer[0]) {
case OK:
case NO_FINGER:
case IMAGE_FAIL:
@@ -453,25 +453,26 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
ESP_LOGE(TAG, "Reader failed to process request");
break;
default:
ESP_LOGE(TAG, "Unknown response received from reader: 0x%.2X", (*p_data_buffer)[0]);
ESP_LOGE(TAG, "Unknown response received from reader: 0x%.2X", data_buffer[0]);
break;
}
this->last_transfer_ms_ = millis();
return (*p_data_buffer)[0];
return data_buffer[0];
}
break;
}
idx++;
}
ESP_LOGE(TAG, "No response received from reader");
(*p_data_buffer)[0] = TIMEOUT;
data_buffer.clear();
data_buffer.push_back(TIMEOUT);
this->last_transfer_ms_ = millis();
return TIMEOUT;
}
uint8_t FingerprintGrowComponent::send_command_() {
this->sensor_wakeup_();
return this->transfer_(&this->data_);
return this->transfer_(this->data_);
}
void FingerprintGrowComponent::sensor_wakeup_() {
@@ -517,7 +518,7 @@ void FingerprintGrowComponent::sensor_wakeup_() {
std::vector<uint8_t> buffer = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16),
(uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)};
if (this->transfer_(&buffer) != OK) {
if (this->transfer_(buffer) != OK) {
ESP_LOGE(TAG, "Wrong password");
}
}
@@ -169,7 +169,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
bool set_password_();
bool get_parameters_();
void get_fingerprint_count_();
uint8_t transfer_(std::vector<uint8_t> *p_data_buffer);
uint8_t transfer_(std::vector<uint8_t> &data_buffer);
uint8_t send_command_();
void sensor_wakeup_();
void sensor_sleep_();
@@ -190,7 +190,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
bool is_sensor_awake_ = false;
uint32_t last_transfer_ms_ = 0;
uint32_t last_aura_led_control_ = 0;
uint16_t last_aura_led_duration_ = 0;
uint32_t last_aura_led_duration_ = 0;
uint16_t system_identifier_code_ = 0;
uint32_t idle_period_to_sleep_ms_ = UINT32_MAX;
sensor::Sensor *fingerprint_count_sensor_{nullptr};
@@ -8,10 +8,6 @@
#include "esphome/components/md5/md5.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_esp8266.h"
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
#include "esphome/components/ota/ota_backend_esp_idf.h"
namespace esphome {
namespace http_request {
@@ -69,7 +65,7 @@ void OtaHttpRequestComponent::flash() {
}
}
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend,
void OtaHttpRequestComponent::cleanup_(decltype(ota::make_ota_backend()) backend,
const std::shared_ptr<HttpContainer> &container) {
if (this->update_started_) {
ESP_LOGV(TAG, "Aborting OTA backend");
@@ -1,6 +1,6 @@
#pragma once
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_factory.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
@@ -39,7 +39,7 @@ class OtaHttpRequestComponent final : public ota::OTAComponent, public Parented<
void flash();
protected:
void cleanup_(std::unique_ptr<ota::OTABackend> backend, const std::shared_ptr<HttpContainer> &container);
void cleanup_(decltype(ota::make_ota_backend()) backend, const std::shared_ptr<HttpContainer> &container);
uint8_t do_ota_();
std::string get_url_with_auth_(const std::string &url);
bool http_get_md5_();
@@ -32,6 +32,7 @@ class IntegrationSensor : public sensor::Sensor, public Component {
void set_method(IntegrationMethod method) { method_ = method; }
void set_restore(bool restore) { restore_ = restore; }
void reset() { this->publish_and_save_(0.0f); }
void set_value(float value) { this->publish_and_save_(value); }
protected:
void process_sensor_value_(float value);
@@ -71,14 +72,16 @@ class IntegrationSensor : public sensor::Sensor, public Component {
float last_value_{0.0f};
};
template<typename... Ts> class ResetAction : public Action<Ts...> {
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<IntegrationSensor> {
public:
explicit ResetAction(IntegrationSensor *parent) : parent_(parent) {}
void play(const Ts &...x) override { this->parent_->reset(); }
};
protected:
IntegrationSensor *parent_;
template<typename... Ts> class SetValueAction : public Action<Ts...>, public Parented<IntegrationSensor> {
public:
TEMPLATABLE_VALUE(float, value)
void play(const Ts &...x) override { this->parent_->set_value(this->value_.value(x...)); }
};
} // namespace integration
+23 -2
View File
@@ -9,6 +9,7 @@ from esphome.const import (
CONF_RESTORE,
CONF_SENSOR,
CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE,
)
from esphome.core.entity_helpers import inherit_property_from
@@ -17,6 +18,7 @@ IntegrationSensor = integration_ns.class_(
"IntegrationSensor", sensor.Sensor, cg.Component
)
ResetAction = integration_ns.class_("ResetAction", automation.Action)
SetValueAction = integration_ns.class_("SetValueAction", automation.Action)
IntegrationSensorTime = integration_ns.enum("IntegrationSensorTime")
INTEGRATION_TIMES = {
@@ -111,5 +113,24 @@ async def to_code(config):
),
)
async def sensor_integration_reset_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"sensor.integration.set_value",
SetValueAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(IntegrationSensor),
cv.Required(CONF_VALUE): cv.templatable(cv.float_),
}
),
)
async def sensor_integration_set_value_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_VALUE], args, float)
cg.add(var.set_value(template_))
return var
+15 -10
View File
@@ -460,6 +460,10 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
uint8_t index = 6; // Start at presence byte position
uint16_t range;
const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]);
if (len < static_cast<int>(index + 1 + sizeof(range) + elements * sizeof(this->gate_energy_[0]))) {
ESP_LOGW(TAG, "Energy frame too short: %d bytes", len);
return;
}
this->set_presence_(buffer[index]);
index++;
memcpy(&range, &buffer[index], sizeof(range));
@@ -471,8 +475,11 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
}
if (this->current_operating_mode == OP_CALIBRATE_MODE) {
this->update_radar_data(gate_energy_, sample_number_counter);
this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++;
this->update_radar_data(gate_energy_, this->sample_number_counter);
this->sample_number_counter++;
if (this->sample_number_counter >= CALIBRATE_SAMPLES) {
this->sample_number_counter = 0;
}
}
// Resonable refresh rate for home assistant database size health
@@ -503,22 +510,20 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
char *endptr{nullptr};
char outbuf[bufsize]{0};
while (true) {
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
if (pos >= 2 && inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
this->set_presence_(false);
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
} else if (pos >= 1 && inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
this->set_presence_(true);
}
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
if (index < bufsize - 1) {
outbuf[index++] = inbuf[pos];
pos++;
}
}
if (pos < len - 1) {
pos++;
} else {
if (pos < len - 1) {
pos++;
} else {
break;
}
break;
}
}
outbuf[index] = '\0';
@@ -67,11 +67,13 @@ class MediaSource {
/// @brief Start playing the given URI
/// Sources should validate the URI and state, returning false if the source is busy.
/// The orchestrator is responsible for stopping active sources before starting a new one.
/// @note Must only be called from the main loop.
/// @param uri URI to play; e.g., "http://stream_url"
/// @return true if playback started successfully, false otherwise
virtual bool play_uri(const std::string &uri) = 0;
/// @brief Handle playback commands (pause, stop, next, etc.)
/// @brief Handle playback commands; e.g., pause, stop, next, etc.
/// @note Must only be called from the main loop.
/// @param command Command to execute
virtual void handle_command(MediaSourceCommand command) = 0;
@@ -81,7 +83,8 @@ class MediaSource {
// === State Access ===
/// @brief Get current playback state (must only be called from the main loop)
/// @brief Get current playback state
/// @note Must only be called from the main loop.
/// @return Current state of this source
MediaSourceState get_state() const { return this->state_; }
@@ -136,9 +139,10 @@ class MediaSource {
virtual void notify_audio_played(uint32_t frames, int64_t timestamp) {}
protected:
/// @brief Update state and notify listener (must only be called from the main loop)
/// @brief Update state and notify listener
/// This is the only way to change state_, ensuring listener notifications always fire.
/// Sources running FreeRTOS tasks should signal via event groups and call this from loop().
/// @note Must only be called from the main loop.
/// @param state New state to set
void set_state_(MediaSourceState state) {
if (this->state_ != state) {
@@ -438,24 +438,14 @@ void MixerSpeaker::loop() {
// Handle pending start request
if (event_group_bits & MIXER_TASK_COMMAND_START) {
// Only start the task if it's fully stopped and cleaned up
if (!this->status_has_error() && (this->task_handle_ == nullptr) && (this->task_stack_buffer_ == nullptr)) {
esp_err_t err = this->start_task_();
switch (err) {
case ESP_OK:
xEventGroupClearBits(this->event_group_, MIXER_TASK_COMMAND_START);
break;
case ESP_ERR_NO_MEM:
ESP_LOGE(TAG, "Failed to start; retrying in 1 second");
this->status_momentary_error("memory-failure", 1000);
return;
case ESP_ERR_INVALID_STATE:
ESP_LOGE(TAG, "Failed to start; retrying in 1 second");
this->status_momentary_error("task-failure", 1000);
return;
default:
ESP_LOGE(TAG, "Failed to start; retrying in 1 second");
this->status_momentary_error("failure", 1000);
return;
if (!this->status_has_error() && !this->task_.is_created()) {
if (this->task_.create(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this, MIXER_TASK_PRIORITY,
this->task_stack_in_psram_)) {
xEventGroupClearBits(this->event_group_, MIXER_TASK_COMMAND_START);
} else {
ESP_LOGE(TAG, "Failed to start; retrying in 1 second");
this->status_momentary_error("failure", 1000);
return;
}
}
}
@@ -478,13 +468,12 @@ void MixerSpeaker::loop() {
xEventGroupClearBits(this->event_group_, MIXER_TASK_STATE_STOPPING);
}
if (event_group_bits & MIXER_TASK_STATE_STOPPED) {
if (this->delete_task_() == ESP_OK) {
ESP_LOGD(TAG, "Stopped");
xEventGroupClearBits(this->event_group_, MIXER_TASK_ALL_BITS);
}
this->task_.deallocate();
ESP_LOGD(TAG, "Stopped");
xEventGroupClearBits(this->event_group_, MIXER_TASK_ALL_BITS);
}
if (this->task_handle_ != nullptr) {
if (this->task_.is_created()) {
// If the mixer task is running, check if all source speakers are stopped
bool all_stopped = true;
@@ -497,7 +486,7 @@ void MixerSpeaker::loop() {
// Send stop command signal to the mixer task since no source speakers are active
xEventGroupSetBits(this->event_group_, MIXER_TASK_COMMAND_STOP);
}
} else if (this->task_stack_buffer_ == nullptr) {
} else {
// Task is fully stopped and cleaned up, check if we can disable loop
event_group_bits = xEventGroupGetBits(this->event_group_);
if (event_group_bits == 0) {
@@ -538,60 +527,6 @@ esp_err_t MixerSpeaker::start(audio::AudioStreamInfo &stream_info) {
return ESP_OK;
}
esp_err_t MixerSpeaker::start_task_() {
if (this->task_stack_buffer_ == nullptr) {
if (this->task_stack_in_psram_) {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
} else {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
}
}
if (this->task_stack_buffer_ == nullptr) {
return ESP_ERR_NO_MEM;
}
if (this->task_handle_ == nullptr) {
this->task_handle_ = xTaskCreateStatic(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this,
MIXER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_);
}
if (this->task_handle_ == nullptr) {
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
esp_err_t MixerSpeaker::delete_task_() {
if (this->task_handle_ != nullptr) {
// Delete the task
vTaskDelete(this->task_handle_);
this->task_handle_ = nullptr;
}
if ((this->task_handle_ == nullptr) && (this->task_stack_buffer_ != nullptr)) {
// Deallocate the task stack buffer
if (this->task_stack_in_psram_) {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
} else {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
}
this->task_stack_buffer_ = nullptr;
}
if ((this->task_handle_ != nullptr) || (this->task_stack_buffer_ != nullptr)) {
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
void MixerSpeaker::copy_frames(const int16_t *input_buffer, audio::AudioStreamInfo input_stream_info,
int16_t *output_buffer, audio::AudioStreamInfo output_stream_info,
uint32_t frames_to_transfer) {
@@ -8,8 +8,8 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/static_task.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <atomic>
@@ -143,8 +143,6 @@ class MixerSpeaker : public Component {
/// @param stream_info The calling source speaker's audio stream information
/// @return ESP_ERR_NOT_SUPPORTED if the incoming stream is incompatible due to unsupported bits per sample
/// ESP_ERR_INVALID_ARG if the incoming stream is incompatible to be mixed with the other input audio stream
/// ESP_ERR_NO_MEM if there isn't enough memory for the task's stack
/// ESP_ERR_INVALID_STATE if the task fails to start
/// ESP_OK if the incoming stream is compatible and the mixer task starts
esp_err_t start(audio::AudioStreamInfo &stream_info);
@@ -188,16 +186,6 @@ class MixerSpeaker : public Component {
static void audio_mixer_task(void *params);
/// @brief Starts the mixer task after allocating memory for the task stack.
/// @return ESP_ERR_NO_MEM if there isn't enough memory for the task's stack
/// ESP_ERR_INVALID_STATE if the task didn't start
/// ESP_OK if successful
esp_err_t start_task_();
/// @brief If the task is stopped, it sets the task handle to the nullptr and deallocates its stack
/// @return ESP_OK if the task was stopped, ESP_ERR_INVALID_STATE otherwise.
esp_err_t delete_task_();
EventGroupHandle_t event_group_{nullptr};
FixedVector<SourceSpeaker *> source_speakers_;
@@ -207,9 +195,7 @@ class MixerSpeaker : public Component {
bool queue_mode_;
bool task_stack_in_psram_{false};
TaskHandle_t task_handle_{nullptr};
StaticTask_t task_stack_;
StackType_t *task_stack_buffer_{nullptr};
StaticTask task_;
optional<audio::AudioStreamInfo> audio_stream_info_;
-13
View File
@@ -49,17 +49,6 @@ enum OTAState {
OTA_ERROR,
};
class OTABackend {
public:
virtual ~OTABackend() = default;
virtual OTAResponseTypes begin(size_t image_size) = 0;
virtual void set_update_md5(const char *md5) = 0;
virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0;
virtual OTAResponseTypes end() = 0;
virtual void abort() = 0;
virtual bool supports_compression() = 0;
};
/** Listener interface for OTA state changes.
*
* Components can implement this interface to receive OTA state updates
@@ -130,7 +119,5 @@ OTAGlobalCallback *get_global_ota_callback();
// - notify_state_deferred_() when in separate task (e.g., web_server OTA)
// This ensures proper listener execution in all contexts.
#endif
std::unique_ptr<ota::OTABackend> make_ota_backend();
} // namespace ota
} // namespace esphome
@@ -12,7 +12,7 @@ namespace ota {
static const char *const TAG = "ota.arduino_libretiny";
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoLibreTinyOTABackend>(); }
std::unique_ptr<ArduinoLibreTinyOTABackend> make_ota_backend() { return make_unique<ArduinoLibreTinyOTABackend>(); }
OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
// Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA
@@ -7,19 +7,21 @@
namespace esphome {
namespace ota {
class ArduinoLibreTinyOTABackend final : public OTABackend {
class ArduinoLibreTinyOTABackend final {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;
OTAResponseTypes write(uint8_t *data, size_t len) override;
OTAResponseTypes end() override;
void abort() override;
bool supports_compression() override { return false; }
OTAResponseTypes begin(size_t image_size);
void set_update_md5(const char *md5);
OTAResponseTypes write(uint8_t *data, size_t len);
OTAResponseTypes end();
void abort();
bool supports_compression() { return false; }
private:
bool md5_set_{false};
};
std::unique_ptr<ArduinoLibreTinyOTABackend> make_ota_backend();
} // namespace ota
} // namespace esphome
@@ -14,7 +14,7 @@ namespace ota {
static const char *const TAG = "ota.arduino_rp2040";
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoRP2040OTABackend>(); }
std::unique_ptr<ArduinoRP2040OTABackend> make_ota_backend() { return make_unique<ArduinoRP2040OTABackend>(); }
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
// OTA size of 0 is not currently handled, but
@@ -9,19 +9,21 @@
namespace esphome {
namespace ota {
class ArduinoRP2040OTABackend final : public OTABackend {
class ArduinoRP2040OTABackend final {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;
OTAResponseTypes write(uint8_t *data, size_t len) override;
OTAResponseTypes end() override;
void abort() override;
bool supports_compression() override { return false; }
OTAResponseTypes begin(size_t image_size);
void set_update_md5(const char *md5);
OTAResponseTypes write(uint8_t *data, size_t len);
OTAResponseTypes end();
void abort();
bool supports_compression() { return false; }
private:
bool md5_set_{false};
};
std::unique_ptr<ArduinoRP2040OTABackend> make_ota_backend();
} // namespace ota
} // namespace esphome
@@ -48,7 +48,7 @@ namespace esphome::ota {
static const char *const TAG = "ota.esp8266";
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ESP8266OTABackend>(); }
std::unique_ptr<ESP8266OTABackend> make_ota_backend() { return make_unique<ESP8266OTABackend>(); }
OTAResponseTypes ESP8266OTABackend::begin(size_t image_size) {
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
+9 -7
View File
@@ -12,15 +12,15 @@ namespace esphome::ota {
/// OTA backend for ESP8266 using native SDK functions.
/// This implementation bypasses the Arduino Updater library to save ~228 bytes of RAM
/// by not having a global Update object in .bss.
class ESP8266OTABackend final : public OTABackend {
class ESP8266OTABackend final {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;
OTAResponseTypes write(uint8_t *data, size_t len) override;
OTAResponseTypes end() override;
void abort() override;
OTAResponseTypes begin(size_t image_size);
void set_update_md5(const char *md5);
OTAResponseTypes write(uint8_t *data, size_t len);
OTAResponseTypes end();
void abort();
// Compression supported in all ESP8266 Arduino versions ESPHome supports (>= 2.7.0)
bool supports_compression() override { return true; }
bool supports_compression() { return true; }
protected:
/// Erase flash sector if current address is at sector boundary
@@ -54,5 +54,7 @@ class ESP8266OTABackend final : public OTABackend {
bool md5_set_{false};
};
std::unique_ptr<ESP8266OTABackend> make_ota_backend();
} // namespace esphome::ota
#endif // USE_ESP8266
@@ -11,7 +11,7 @@
namespace esphome {
namespace ota {
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::IDFOTABackend>(); }
std::unique_ptr<IDFOTABackend> make_ota_backend() { return make_unique<IDFOTABackend>(); }
OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
#ifdef USE_OTA_ROLLBACK
+9 -7
View File
@@ -10,14 +10,14 @@
namespace esphome {
namespace ota {
class IDFOTABackend final : public OTABackend {
class IDFOTABackend final {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;
OTAResponseTypes write(uint8_t *data, size_t len) override;
OTAResponseTypes end() override;
void abort() override;
bool supports_compression() override { return false; }
OTAResponseTypes begin(size_t image_size);
void set_update_md5(const char *md5);
OTAResponseTypes write(uint8_t *data, size_t len);
OTAResponseTypes end();
void abort();
bool supports_compression() { return false; }
private:
esp_ota_handle_t update_handle_{0};
@@ -27,6 +27,8 @@ class IDFOTABackend final : public OTABackend {
bool md5_set_{false};
};
std::unique_ptr<IDFOTABackend> make_ota_backend();
} // namespace ota
} // namespace esphome
#endif // USE_ESP32
@@ -0,0 +1,17 @@
#pragma once
#include "ota_backend.h"
#ifdef USE_ESP8266
#include "ota_backend_esp8266.h"
#elif defined(USE_ESP32)
#include "ota_backend_esp_idf.h"
#elif defined(USE_RP2040)
#include "ota_backend_arduino_rp2040.h"
#elif defined(USE_LIBRETINY)
#include "ota_backend_arduino_libretiny.h"
#elif defined(USE_HOST)
#include "ota_backend_host.h"
#endif
namespace esphome::ota {} // namespace esphome::ota
+1 -1
View File
@@ -8,7 +8,7 @@ namespace esphome::ota {
// Stub implementation - OTA is not supported on host platform.
// All methods return error codes to allow compilation of configs with OTA triggers.
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::HostOTABackend>(); }
std::unique_ptr<HostOTABackend> make_ota_backend() { return make_unique<HostOTABackend>(); }
OTAResponseTypes HostOTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UPDATE_PREPARE; }
+9 -7
View File
@@ -7,15 +7,17 @@ namespace esphome::ota {
/// Stub OTA backend for host platform - allows compilation but does not implement OTA.
/// All operations return error codes immediately. This enables configurations with
/// OTA triggers to compile for host platform during development.
class HostOTABackend final : public OTABackend {
class HostOTABackend final {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;
OTAResponseTypes write(uint8_t *data, size_t len) override;
OTAResponseTypes end() override;
void abort() override;
bool supports_compression() override { return false; }
OTAResponseTypes begin(size_t image_size);
void set_update_md5(const char *md5);
OTAResponseTypes write(uint8_t *data, size_t len);
OTAResponseTypes end();
void abort();
bool supports_compression() { return false; }
};
std::unique_ptr<HostOTABackend> make_ota_backend();
} // namespace esphome::ota
#endif
@@ -99,7 +99,7 @@ bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6
uint8_t &message_start_index) {
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
if (!(page_3_to_6.size() > p4_offset + 5)) {
if (!(page_3_to_6.size() > p4_offset + 6)) {
return false;
}
@@ -134,7 +134,7 @@ bool PN532::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *
} else {
encoded.insert(encoded.begin() + 1, 0xFF);
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
encoded.insert(encoded.begin() + 2, message_length & 0xFF);
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
}
encoded.push_back(0xFE);
+3 -3
View File
@@ -562,9 +562,9 @@ optional<size_t> PN7150::find_tag_uid_(const nfc::NfcTagUid &uid) {
}
void PN7150::purge_old_tags_() {
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
if (millis() - this->discovered_endpoint_[i].last_seen > this->tag_ttl_) {
this->erase_tag_(i);
for (size_t i = this->discovered_endpoint_.size(); i > 0; i--) {
if (millis() - this->discovered_endpoint_[i - 1].last_seen > this->tag_ttl_) {
this->erase_tag_(i - 1);
}
}
}
@@ -100,7 +100,7 @@ uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_
uint8_t &message_start_index) {
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
if (!(page_3_to_6.size() > p4_offset + 5)) {
if (!(page_3_to_6.size() > p4_offset + 6)) {
return nfc::STATUS_FAILED;
}
@@ -135,7 +135,7 @@ uint8_t PN7150::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::sha
} else {
encoded.insert(encoded.begin() + 1, 0xFF);
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
encoded.insert(encoded.begin() + 2, message_length & 0xFF);
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
}
encoded.push_back(0xFE);
+3 -3
View File
@@ -589,9 +589,9 @@ optional<size_t> PN7160::find_tag_uid_(const nfc::NfcTagUid &uid) {
}
void PN7160::purge_old_tags_() {
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
if (millis() - this->discovered_endpoint_[i].last_seen > this->tag_ttl_) {
this->erase_tag_(i);
for (size_t i = this->discovered_endpoint_.size(); i > 0; i--) {
if (millis() - this->discovered_endpoint_[i - 1].last_seen > this->tag_ttl_) {
this->erase_tag_(i - 1);
}
}
}
@@ -100,7 +100,7 @@ uint8_t PN7160::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_
uint8_t &message_start_index) {
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
if (!(page_3_to_6.size() > p4_offset + 5)) {
if (!(page_3_to_6.size() > p4_offset + 6)) {
return nfc::STATUS_FAILED;
}
@@ -135,7 +135,7 @@ uint8_t PN7160::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::sha
} else {
encoded.insert(encoded.begin() + 1, 0xFF);
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
encoded.insert(encoded.begin() + 2, message_length & 0xFF);
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
}
encoded.push_back(0xFE);
@@ -44,9 +44,13 @@ bool ProntoData::operator==(const ProntoData &rhs) const {
std::vector<uint16_t> data1 = encode_pronto(data);
std::vector<uint16_t> data2 = encode_pronto(rhs.data);
if (data1.size() != data2.size() || data1.empty()) {
return false;
}
uint32_t total_diff = 0;
// Don't need to check the last one, it's the large gap at the end.
for (std::vector<uint16_t>::size_type i = 0; i < data1.size() - 1; ++i) {
for (size_t i = 0; i < data1.size() - 1; ++i) {
int diff = data2[i] - data1[i];
diff *= diff;
if (rhs.delta == -1 && diff > 9)
@@ -106,7 +106,7 @@ void RemoteReceiverComponent::setup() {
this->store_.filter_symbols = this->filter_symbols_;
this->store_.receive_size = this->receive_symbols_ * sizeof(rmt_symbol_word_t);
this->store_.buffer_size = std::max((event_size + this->store_.receive_size) * 2, this->buffer_size_);
this->store_.buffer = new uint8_t[this->buffer_size_];
this->store_.buffer = new uint8_t[this->store_.buffer_size];
error = rmt_receive(this->channel_, (uint8_t *) this->store_.buffer + event_size, this->store_.receive_size,
&this->store_.config);
if (error != ESP_OK) {
@@ -147,7 +147,7 @@ void ResamplerSpeaker::loop() {
xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::STATE_STOPPING);
}
if (event_group_bits & ResamplingEventGroupBits::STATE_STOPPED) {
this->delete_task_();
this->task_.deallocate();
ESP_LOGD(TAG, "Stopped");
xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ALL_BITS);
}
@@ -190,7 +190,7 @@ void ResamplerSpeaker::loop() {
this->output_speaker_->stop();
}
if (this->output_speaker_->is_stopped() && (this->task_handle_ == nullptr)) {
if (this->output_speaker_->is_stopped() && !this->task_.is_created()) {
// Only transition to stopped state once the output speaker and resampler task are fully stopped
this->waiting_for_output_ = false;
this->state_ = speaker::STATE_STOPPED;
@@ -209,9 +209,6 @@ void ResamplerSpeaker::loop() {
void ResamplerSpeaker::set_start_error_(esp_err_t err) {
switch (err) {
case ESP_ERR_INVALID_STATE:
this->status_set_error(LOG_STR("Task failed to start"));
break;
case ESP_ERR_NO_MEM:
this->status_set_error(LOG_STR("Not enough memory"));
break;
@@ -267,36 +264,12 @@ esp_err_t ResamplerSpeaker::start_() {
if (this->requires_resampling_()) {
// Start the resampler task to handle converting sample rates
return this->start_task_();
}
return ESP_OK;
}
esp_err_t ResamplerSpeaker::start_task_() {
if (this->task_stack_buffer_ == nullptr) {
if (this->task_stack_in_psram_) {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
} else {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
if (!this->task_.create(resample_task, "resampler", TASK_STACK_SIZE, (void *) this, RESAMPLER_TASK_PRIORITY,
this->task_stack_in_psram_)) {
return ESP_ERR_NO_MEM;
}
}
if (this->task_stack_buffer_ == nullptr) {
return ESP_ERR_NO_MEM;
}
if (this->task_handle_ == nullptr) {
this->task_handle_ = xTaskCreateStatic(resample_task, "resampler", TASK_STACK_SIZE, (void *) this,
RESAMPLER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_);
}
if (this->task_handle_ == nullptr) {
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
@@ -305,33 +278,12 @@ void ResamplerSpeaker::stop() { this->send_command_(ResamplingEventGroupBits::CO
void ResamplerSpeaker::enter_stopping_state_() {
this->state_ = speaker::STATE_STOPPING;
this->state_start_ms_ = App.get_loop_component_start_time();
if (this->task_handle_ != nullptr) {
if (this->task_.is_created()) {
xEventGroupSetBits(this->event_group_, ResamplingEventGroupBits::TASK_COMMAND_STOP);
}
this->output_speaker_->stop();
}
void ResamplerSpeaker::delete_task_() {
if (this->task_handle_ != nullptr) {
// Delete the suspended task
vTaskDelete(this->task_handle_);
this->task_handle_ = nullptr;
}
if (this->task_stack_buffer_ != nullptr) {
// Deallocate the task stack buffer
if (this->task_stack_in_psram_) {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
} else {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
}
this->task_stack_buffer_ = nullptr;
}
}
void ResamplerSpeaker::finish() { this->send_command_(ResamplingEventGroupBits::COMMAND_FINISH); }
bool ResamplerSpeaker::has_buffered_data() const {
@@ -7,8 +7,8 @@
#include "esphome/components/speaker/speaker.h"
#include "esphome/core/component.h"
#include "esphome/core/static_task.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
namespace esphome {
@@ -57,15 +57,9 @@ class ResamplerSpeaker : public Component, public speaker::Speaker {
protected:
/// @brief Starts the output speaker after setting the resampled stream info. If resampling is required, it starts the
/// task.
/// @return ESP_OK if resampling is required
/// return value of start_task_() if resampling is required
esp_err_t start_();
/// @brief Starts the resampler task after allocating the task stack
/// @return ESP_OK if successful,
/// ESP_ERR_NO_MEM if the task stack couldn't be allocated
/// ESP_ERR_INVALID_STATE if the task wasn't created
esp_err_t start_task_();
/// ESP_ERR_NO_MEM if the resampler task couldn't be created
esp_err_t start_();
/// @brief Transitions to STATE_STOPPING, records the stopping timestamp, sends the task stop command if the task is
/// running, and stops the output speaker.
@@ -74,9 +68,6 @@ class ResamplerSpeaker : public Component, public speaker::Speaker {
/// @brief Sets the appropriate status error based on the start failure reason.
void set_start_error_(esp_err_t err);
/// @brief Deletes the resampler task if suspended, deallocates the task stack, and resets the related pointers.
void delete_task_();
/// @brief Sends a command via event group bits, enables the loop, and optionally wakes the main loop.
void send_command_(uint32_t command_bit, bool wake_loop = false);
@@ -92,9 +83,7 @@ class ResamplerSpeaker : public Component, public speaker::Speaker {
bool task_stack_in_psram_{false};
bool waiting_for_output_{false};
TaskHandle_t task_handle_{nullptr};
StaticTask_t task_stack_;
StackType_t *task_stack_buffer_{nullptr};
StaticTask task_;
audio::AudioStreamInfo target_stream_info_;
@@ -13,12 +13,12 @@ RuntimeStatsCollector::RuntimeStatsCollector() : log_interval_(60000), next_log_
global_runtime_stats = this;
}
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time) {
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_us, uint32_t current_time) {
if (component == nullptr)
return;
// Record stats using component pointer as key
this->component_stats_[component].record_time(duration_ms);
this->component_stats_[component].record_time(duration_us);
if (this->next_log_time_ == 0) {
this->next_log_time_ = current_time + this->log_interval_;
@@ -58,15 +58,16 @@ void RuntimeStatsCollector::log_stats_() {
// Sort by period runtime (descending)
std::sort(sorted, sorted + count, [this](Component *a, Component *b) {
return this->component_stats_[a].get_period_time_ms() > this->component_stats_[b].get_period_time_ms();
return this->component_stats_[a].get_period_time_us() > this->component_stats_[b].get_period_time_us();
});
// Log top components by period runtime
for (size_t i = 0; i < count; i++) {
const auto &stats = this->component_stats_[sorted[i]];
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_period_count(), stats.get_period_avg_time_ms(),
stats.get_period_max_time_ms(), stats.get_period_time_ms());
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.3fms, max=%.2fms, total=%.1fms",
LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_period_count(),
stats.get_period_avg_time_us() / 1000.0f, stats.get_period_max_time_us() / 1000.0f,
stats.get_period_time_us() / 1000.0f);
}
// Log total stats since boot (only for active components - idle ones haven't changed)
@@ -74,14 +75,15 @@ void RuntimeStatsCollector::log_stats_() {
// Re-sort by total runtime for all-time stats
std::sort(sorted, sorted + count, [this](Component *a, Component *b) {
return this->component_stats_[a].get_total_time_ms() > this->component_stats_[b].get_total_time_ms();
return this->component_stats_[a].get_total_time_us() > this->component_stats_[b].get_total_time_us();
});
for (size_t i = 0; i < count; i++) {
const auto &stats = this->component_stats_[sorted[i]];
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_total_count(), stats.get_total_avg_time_ms(),
stats.get_total_max_time_ms(), stats.get_total_time_ms());
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.3fms, max=%.2fms, total=%.1fms",
LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_total_count(),
stats.get_total_avg_time_us() / 1000.0f, stats.get_total_max_time_us() / 1000.0f,
stats.get_total_time_us() / 1000.0);
}
}
@@ -22,58 +22,58 @@ class ComponentRuntimeStats {
public:
ComponentRuntimeStats()
: period_count_(0),
period_time_ms_(0),
period_max_time_ms_(0),
period_time_us_(0),
period_max_time_us_(0),
total_count_(0),
total_time_ms_(0),
total_max_time_ms_(0) {}
total_time_us_(0),
total_max_time_us_(0) {}
void record_time(uint32_t duration_ms) {
void record_time(uint32_t duration_us) {
// Update period counters
this->period_count_++;
this->period_time_ms_ += duration_ms;
if (duration_ms > this->period_max_time_ms_)
this->period_max_time_ms_ = duration_ms;
this->period_time_us_ += duration_us;
if (duration_us > this->period_max_time_us_)
this->period_max_time_us_ = duration_us;
// Update total counters
// Update total counters (uint64_t to avoid overflow — uint32_t would overflow after ~10 hours)
this->total_count_++;
this->total_time_ms_ += duration_ms;
if (duration_ms > this->total_max_time_ms_)
this->total_max_time_ms_ = duration_ms;
this->total_time_us_ += duration_us;
if (duration_us > this->total_max_time_us_)
this->total_max_time_us_ = duration_us;
}
void reset_period_stats() {
this->period_count_ = 0;
this->period_time_ms_ = 0;
this->period_max_time_ms_ = 0;
this->period_time_us_ = 0;
this->period_max_time_us_ = 0;
}
// Period stats (reset each logging interval)
uint32_t get_period_count() const { return this->period_count_; }
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
float get_period_avg_time_ms() const {
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
uint32_t get_period_time_us() const { return this->period_time_us_; }
uint32_t get_period_max_time_us() const { return this->period_max_time_us_; }
float get_period_avg_time_us() const {
return this->period_count_ > 0 ? this->period_time_us_ / static_cast<float>(this->period_count_) : 0.0f;
}
// Total stats (persistent until reboot)
// Total stats (persistent until reboot, uint64_t to avoid overflow)
uint32_t get_total_count() const { return this->total_count_; }
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
float get_total_avg_time_ms() const {
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
uint64_t get_total_time_us() const { return this->total_time_us_; }
uint32_t get_total_max_time_us() const { return this->total_max_time_us_; }
float get_total_avg_time_us() const {
return this->total_count_ > 0 ? this->total_time_us_ / static_cast<float>(this->total_count_) : 0.0f;
}
protected:
// Period stats (reset each logging interval)
uint32_t period_count_;
uint32_t period_time_ms_;
uint32_t period_max_time_ms_;
uint32_t period_time_us_;
uint32_t period_max_time_us_;
// Total stats (persistent until reboot)
uint32_t total_count_;
uint32_t total_time_ms_;
uint32_t total_max_time_ms_;
uint64_t total_time_us_;
uint32_t total_max_time_us_;
};
class RuntimeStatsCollector {
@@ -83,7 +83,7 @@ class RuntimeStatsCollector {
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
uint32_t get_log_interval() const { return this->log_interval_; }
void record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time);
void record_component_time(Component *component, uint32_t duration_us, uint32_t current_time);
// Process any pending stats printing (should be called after component loop)
void process_pending_stats(uint32_t current_time);
@@ -87,20 +87,20 @@ void AudioPipeline::set_pause_state(bool pause_state) {
}
void AudioPipeline::suspend_tasks() {
if (this->read_task_handle_ != nullptr) {
vTaskSuspend(this->read_task_handle_);
if (this->read_task_.is_created()) {
vTaskSuspend(this->read_task_.get_handle());
}
if (this->decode_task_handle_ != nullptr) {
vTaskSuspend(this->decode_task_handle_);
if (this->decode_task_.is_created()) {
vTaskSuspend(this->decode_task_.get_handle());
}
}
void AudioPipeline::resume_tasks() {
if (this->read_task_handle_ != nullptr) {
vTaskResume(this->read_task_handle_);
if (this->read_task_.is_created()) {
vTaskResume(this->read_task_.get_handle());
}
if (this->decode_task_handle_ != nullptr) {
vTaskResume(this->decode_task_handle_);
if (this->decode_task_.is_created()) {
vTaskResume(this->decode_task_.get_handle());
}
}
@@ -159,7 +159,7 @@ AudioPipelineState AudioPipeline::process_state() {
// Init command pending
if (!(event_bits & EventGroupBits::PIPELINE_COMMAND_STOP)) {
// Only start if there is no pending stop command
if ((this->read_task_handle_ == nullptr) || (this->decode_task_handle_ == nullptr)) {
if (!this->read_task_.is_created() || !this->decode_task_.is_created()) {
// At least one task isn't running
this->start_tasks_();
}
@@ -202,8 +202,9 @@ AudioPipelineState AudioPipeline::process_state() {
if (!this->is_playing_) {
// The tasks have been stopped for two ``process_state`` calls in a row, so delete the tasks
if ((this->read_task_handle_ != nullptr) || (this->decode_task_handle_ != nullptr)) {
this->delete_tasks_();
if (this->read_task_.is_created() || this->decode_task_.is_created()) {
this->read_task_.deallocate();
this->decode_task_.deallocate();
if (this->hard_stop_) {
// Stop command was sent, so immediately end the playback
this->speaker_->stop();
@@ -234,7 +235,7 @@ AudioPipelineState AudioPipeline::process_state() {
}
}
if ((this->read_task_handle_ == nullptr) && (this->decode_task_handle_ == nullptr)) {
if (!this->read_task_.is_created() && !this->decode_task_.is_created()) {
// No tasks are running, so the pipeline is stopped.
xEventGroupClearBits(this->event_group_, EventGroupBits::PIPELINE_COMMAND_STOP);
return AudioPipelineState::STOPPED;
@@ -262,94 +263,25 @@ esp_err_t AudioPipeline::allocate_communications_() {
}
esp_err_t AudioPipeline::start_tasks_() {
if (this->read_task_handle_ == nullptr) {
if (this->read_task_stack_buffer_ == nullptr) {
// Reader task uses the AudioReader class which uses esp_http_client. This crashes on IDF 5.4 if the task stack is
// in PSRAM. As a workaround, always allocate the read task in internal memory.
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
this->read_task_stack_buffer_ = stack_allocator.allocate(READ_TASK_STACK_SIZE);
}
if (this->read_task_stack_buffer_ == nullptr) {
if (!this->read_task_.is_created()) {
// Reader task uses the AudioReader class which uses esp_http_client. This crashes on IDF 5.4 if the task stack is
// in PSRAM. As a workaround, always allocate the read task in internal memory.
if (!this->read_task_.create(read_task, (this->base_name_ + "_read").c_str(), READ_TASK_STACK_SIZE, (void *) this,
this->priority_, false)) {
return ESP_ERR_NO_MEM;
}
if (this->read_task_handle_ == nullptr) {
this->read_task_handle_ =
xTaskCreateStatic(read_task, (this->base_name_ + "_read").c_str(), READ_TASK_STACK_SIZE, (void *) this,
this->priority_, this->read_task_stack_buffer_, &this->read_task_stack_);
}
if (this->read_task_handle_ == nullptr) {
return ESP_ERR_INVALID_STATE;
}
}
if (this->decode_task_handle_ == nullptr) {
if (this->decode_task_stack_buffer_ == nullptr) {
if (this->task_stack_in_psram_) {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
this->decode_task_stack_buffer_ = stack_allocator.allocate(DECODE_TASK_STACK_SIZE);
} else {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
this->decode_task_stack_buffer_ = stack_allocator.allocate(DECODE_TASK_STACK_SIZE);
}
}
if (this->decode_task_stack_buffer_ == nullptr) {
if (!this->decode_task_.is_created()) {
if (!this->decode_task_.create(decode_task, (this->base_name_ + "_decode").c_str(), DECODE_TASK_STACK_SIZE,
(void *) this, this->priority_, this->task_stack_in_psram_)) {
return ESP_ERR_NO_MEM;
}
if (this->decode_task_handle_ == nullptr) {
this->decode_task_handle_ =
xTaskCreateStatic(decode_task, (this->base_name_ + "_decode").c_str(), DECODE_TASK_STACK_SIZE, (void *) this,
this->priority_, this->decode_task_stack_buffer_, &this->decode_task_stack_);
}
if (this->decode_task_handle_ == nullptr) {
return ESP_ERR_INVALID_STATE;
}
}
return ESP_OK;
}
void AudioPipeline::delete_tasks_() {
if (this->read_task_handle_ != nullptr) {
vTaskDelete(this->read_task_handle_);
if (this->read_task_stack_buffer_ != nullptr) {
if (this->task_stack_in_psram_) {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
stack_allocator.deallocate(this->read_task_stack_buffer_, READ_TASK_STACK_SIZE);
} else {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
stack_allocator.deallocate(this->read_task_stack_buffer_, READ_TASK_STACK_SIZE);
}
this->read_task_stack_buffer_ = nullptr;
this->read_task_handle_ = nullptr;
}
}
if (this->decode_task_handle_ != nullptr) {
vTaskDelete(this->decode_task_handle_);
if (this->decode_task_stack_buffer_ != nullptr) {
if (this->task_stack_in_psram_) {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
stack_allocator.deallocate(this->decode_task_stack_buffer_, DECODE_TASK_STACK_SIZE);
} else {
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
stack_allocator.deallocate(this->decode_task_stack_buffer_, DECODE_TASK_STACK_SIZE);
}
this->decode_task_stack_buffer_ = nullptr;
this->decode_task_handle_ = nullptr;
}
}
}
void AudioPipeline::read_task(void *params) {
AudioPipeline *this_pipeline = (AudioPipeline *) params;
@@ -8,10 +8,10 @@
#include "esphome/components/speaker/speaker.h"
#include "esphome/core/ring_buffer.h"
#include "esphome/core/static_task.h"
#include "esp_err.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/queue.h>
@@ -104,9 +104,6 @@ class AudioPipeline {
/// @return ESP_OK if successful or an appropriate error if not
esp_err_t start_tasks_();
/// @brief Resets the task related pointers and deallocates their stacks.
void delete_tasks_();
std::string base_name_;
UBaseType_t priority_;
@@ -143,15 +140,11 @@ class AudioPipeline {
// Handles reading the media file from flash or a url
static void read_task(void *params);
TaskHandle_t read_task_handle_{nullptr};
StaticTask_t read_task_stack_;
StackType_t *read_task_stack_buffer_{nullptr};
StaticTask read_task_;
// Decodes the media file into PCM audio
static void decode_task(void *params);
TaskHandle_t decode_task_handle_{nullptr};
StaticTask_t decode_task_stack_;
StackType_t *decode_task_stack_buffer_{nullptr};
StaticTask decode_task_;
};
} // namespace speaker
+2 -2
View File
@@ -186,7 +186,7 @@ void SX127x::configure_fsk_ook_() {
} else {
this->write_register_(REG_PREAMBLE_DETECT, PREAMBLE_DETECTOR_OFF);
}
this->write_register_(REG_PREAMBLE_SIZE_MSB, this->preamble_size_ >> 16);
this->write_register_(REG_PREAMBLE_SIZE_MSB, this->preamble_size_ >> 8);
this->write_register_(REG_PREAMBLE_SIZE_LSB, this->preamble_size_ & 0xFF);
// config sync generation and setup ook threshold
@@ -214,7 +214,7 @@ void SX127x::configure_lora_() {
// config preamble
if (this->preamble_size_ >= 6) {
this->write_register_(REG_PREAMBLE_LEN_MSB, this->preamble_size_ >> 16);
this->write_register_(REG_PREAMBLE_LEN_MSB, this->preamble_size_ >> 8);
this->write_register_(REG_PREAMBLE_LEN_LSB, this->preamble_size_ & 0xFF);
}
@@ -251,8 +251,7 @@ void VoiceAssistant::loop() {
}
#endif
if (this->api_client_ == nullptr ||
!this->api_client_->send_message(msg, api::VoiceAssistantRequest::MESSAGE_TYPE)) {
if (this->api_client_ == nullptr || !this->api_client_->send_message(msg)) {
ESP_LOGW(TAG, "Could not request start");
this->error_trigger_.trigger("not-connected", "Could not request start");
this->continuous_ = false;
@@ -275,7 +274,7 @@ void VoiceAssistant::loop() {
api::VoiceAssistantAudio msg;
msg.data = this->send_buffer_;
msg.data_len = read_bytes;
this->api_client_->send_message(msg, api::VoiceAssistantAudio::MESSAGE_TYPE);
this->api_client_->send_message(msg);
} else {
if (!this->udp_socket_running_) {
if (!this->start_udp_socket_()) {
@@ -354,7 +353,7 @@ void VoiceAssistant::loop() {
api::VoiceAssistantAnnounceFinished msg;
msg.success = true;
this->api_client_->send_message(msg, api::VoiceAssistantAnnounceFinished::MESSAGE_TYPE);
this->api_client_->send_message(msg);
break;
}
}
@@ -612,7 +611,7 @@ void VoiceAssistant::signal_stop_() {
ESP_LOGD(TAG, "Signaling stop");
api::VoiceAssistantRequest msg;
msg.start = false;
this->api_client_->send_message(msg, api::VoiceAssistantRequest::MESSAGE_TYPE);
this->api_client_->send_message(msg);
}
void VoiceAssistant::start_playback_timeout_() {
@@ -622,7 +621,7 @@ void VoiceAssistant::start_playback_timeout_() {
api::VoiceAssistantAnnounceFinished msg;
msg.success = true;
this->api_client_->send_message(msg, api::VoiceAssistantAnnounceFinished::MESSAGE_TYPE);
this->api_client_->send_message(msg);
});
}
@@ -1,7 +1,7 @@
#include "ota_web_server.h"
#ifdef USE_WEBSERVER_OTA
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_factory.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
@@ -71,7 +71,7 @@ class OTARequestHandler : public AsyncWebHandler {
bool ota_success_{false};
private:
std::unique_ptr<ota::OTABackend> ota_backend_{nullptr};
decltype(ota::make_ota_backend()) ota_backend_{nullptr};
};
void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
+10 -3
View File
@@ -725,6 +725,7 @@ void WiFiComponent::restart_adapter() {
void WiFiComponent::loop() {
this->wifi_loop_();
const uint32_t now = App.get_loop_component_start_time();
this->update_connected_state_();
if (this->has_sta()) {
#if defined(USE_WIFI_CONNECT_TRIGGER) || defined(USE_WIFI_DISCONNECT_TRIGGER)
@@ -776,7 +777,7 @@ void WiFiComponent::loop() {
}
case WIFI_COMPONENT_STATE_STA_CONNECTED: {
if (!this->is_connected()) {
if (!this->is_connected_()) {
ESP_LOGW(TAG, "Connection lost; reconnecting");
this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTING;
this->retry_connect();
@@ -2119,15 +2120,21 @@ bool WiFiComponent::can_proceed() {
if (!this->has_sta() || this->state_ == WIFI_COMPONENT_STATE_DISABLED || this->ap_setup_) {
return true;
}
return this->is_connected();
return this->is_connected_();
}
#endif
void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
bool WiFiComponent::is_connected() const {
bool WiFiComponent::is_connected_() const {
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED &&
this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_;
}
void WiFiComponent::update_connected_state_() {
bool connected = this->is_connected_();
if (connected != this->connected_) {
this->connected_ = connected;
}
}
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) {
this->power_save_ = power_save;
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
+4 -1
View File
@@ -443,7 +443,7 @@ class WiFiComponent : public Component {
void set_reboot_timeout(uint32_t reboot_timeout);
bool is_connected() const;
bool is_connected() const { return this->connected_; }
void set_power_save_mode(WiFiPowerSaveMode power_save);
void set_min_auth_mode(WifiMinAuthMode min_auth_mode) { min_auth_mode_ = min_auth_mode; }
@@ -678,6 +678,8 @@ class WiFiComponent : public Component {
bool wifi_sta_connect_(const WiFiAP &ap);
void wifi_pre_setup_();
WiFiSTAConnectStatus wifi_sta_connect_status_() const;
bool is_connected_() const;
void update_connected_state_();
bool wifi_scan_start_(bool passive);
#ifdef USE_WIFI_AP
@@ -854,6 +856,7 @@ class WiFiComponent : public Component {
bool has_completed_scan_after_captive_portal_start_{
false}; // Tracks if we've completed a scan after captive portal started
bool skip_cooldown_next_cycle_{false};
bool connected_{false};
bool post_connect_roaming_{true}; // Enabled by default
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
bool is_high_performance_mode_{false};
@@ -119,7 +119,7 @@ void ZWaveProxy::process_uart_() {
// If this is a data frame, use frame length indicator + 2 (for SoF + checksum), else assume 1 for ACK/NAK/CAN
this->outgoing_proto_msg_.data_len = this->buffer_[0] == ZWAVE_FRAME_TYPE_START ? this->buffer_[1] + 2 : 1;
}
this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE);
this->api_connection_->send_message(this->outgoing_proto_msg_);
}
}
}
@@ -209,7 +209,7 @@ void ZWaveProxy::send_homeid_changed_msg_(api::APIConnection *conn) {
msg.data_len = this->home_id_.size();
if (conn != nullptr) {
// Send to specific connection
conn->send_message(msg, api::ZWaveProxyRequest::MESSAGE_TYPE);
conn->send_message(msg);
} else if (api::global_api_server != nullptr) {
// We could add code to manage a second subscription type, but, since this message is
// very infrequent and small, we simply send it to all clients
@@ -342,7 +342,7 @@ void ZWaveProxy::parse_start_(uint8_t byte) {
this->buffer_[0] = byte;
this->outgoing_proto_msg_.data = this->buffer_.data();
this->outgoing_proto_msg_.data_len = 1;
this->api_connection_->send_message(this->outgoing_proto_msg_, api::ZWaveProxyFrame::MESSAGE_TYPE);
this->api_connection_->send_message(this->outgoing_proto_msg_);
}
}
+2 -9
View File
@@ -398,17 +398,10 @@ def string_strict(value):
)
# Max device class string length (47 chars + null = 48-byte PROGMEM buffer)
# Keep in sync with MAX_DEVICE_CLASS_LENGTH in esphome/core/entity_base.h
DEVICE_CLASS_MAX_LENGTH = 47
# Max icon string length (63 chars + null = 64-byte PROGMEM buffer)
# Keep in sync with MAX_ICON_LENGTH in esphome/core/entity_base.h
ICON_MAX_LENGTH = 63
def icon(value):
"""Validate that a given config value is a valid icon."""
from esphome.core.config import ICON_MAX_LENGTH
value = string_strict(value)
if not value:
return value
+3
View File
@@ -11,6 +11,9 @@ VALID_SUBSTITUTIONS_CHARACTERS = (
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
)
# CLI Help Text Constants
ARGUMENT_HELP_DEVICE = "Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses. Use 'OTA' for resolving from MQTT, DNS or mDNS and avoiding the interactive prompt."
class Platform(StrEnum):
"""Platform identifiers for ESPHome."""
+9 -1
View File
@@ -170,6 +170,14 @@ void Application::setup() {
this->setup_wake_loop_threadsafe_();
#endif
// Ensure all active looping components are in LOOP state.
// Components after the last blocking component only got one call() during setup
// (CONSTRUCTION→SETUP) and never received the second call() (SETUP→LOOP).
// The main loop calls loop() directly, bypassing call()'s state machine.
for (uint16_t i = 0; i < this->looping_components_active_end_; i++) {
this->looping_components_[i]->set_component_state_(COMPONENT_STATE_LOOP);
}
this->schedule_dump_config();
}
void Application::loop() {
@@ -190,7 +198,7 @@ void Application::loop() {
{
this->set_current_component(component);
WarnIfComponentBlockingGuard guard{component, last_op_end_time};
component->call();
component->loop();
// Use the finish method to get the current time as the end time
last_op_end_time = guard.finish();
}
+5 -2
View File
@@ -542,9 +542,12 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
uint32_t curr_time = millis();
uint32_t blocking_time = curr_time - this->started_;
#ifdef USE_RUNTIME_STATS
// Record component runtime stats
// Use micros() for accurate sub-millisecond timing. millis() has insufficient
// resolution — most components complete in microseconds but millis() only has
// 1ms granularity, so results were essentially random noise.
if (global_runtime_stats != nullptr) {
global_runtime_stats->record_component_time(this->component_, blocking_time, curr_time);
uint32_t duration_us = micros() - this->started_us_;
global_runtime_stats->record_component_time(this->component_, duration_us, curr_time);
}
#endif
if (blocking_time > WARN_IF_BLOCKING_OVER_MS) {
+15 -1
View File
@@ -563,10 +563,21 @@ class PollingComponent : public Component {
uint32_t update_interval_;
};
#ifdef USE_RUNTIME_STATS
uint32_t micros(); // Forward declare for inline constructor
#endif
class WarnIfComponentBlockingGuard {
public:
WarnIfComponentBlockingGuard(Component *component, uint32_t start_time)
: started_(start_time), component_(component) {}
: started_(start_time),
component_(component)
#ifdef USE_RUNTIME_STATS
,
started_us_(micros())
#endif
{
}
// Finish the timing operation and return the current time
uint32_t finish();
@@ -576,6 +587,9 @@ class WarnIfComponentBlockingGuard {
protected:
uint32_t started_;
Component *component_;
#ifdef USE_RUNTIME_STATS
uint32_t started_us_;
#endif
};
// Function to clear setup priority overrides after all components are set up
+8
View File
@@ -188,6 +188,14 @@ else:
# Keep in sync with ESPHOME_FRIENDLY_NAME_MAX_LEN in esphome/core/entity_base.h
FRIENDLY_NAME_MAX_LEN = 120
# Max device class string length (47 chars + null = 48-byte PROGMEM buffer)
# Keep in sync with MAX_DEVICE_CLASS_LENGTH in esphome/core/entity_base.h
DEVICE_CLASS_MAX_LENGTH = 47
# Max icon string length (63 chars + null = 64-byte PROGMEM buffer)
# Keep in sync with MAX_ICON_LENGTH in esphome/core/entity_base.h
ICON_MAX_LENGTH = 63
AREA_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(Area),
+5 -4
View File
@@ -17,6 +17,7 @@ from esphome.const import (
CONF_UNIT_OF_MEASUREMENT,
)
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
from esphome.core.config import DEVICE_CLASS_MAX_LENGTH, ICON_MAX_LENGTH
from esphome.cpp_generator import MockObj, RawStatement, add, get_variable
import esphome.final_validate as fv
from esphome.helpers import cpp_string_escape, fnv1_hash_object_id, sanitize, snake_case
@@ -180,9 +181,9 @@ def _register_string(
def register_device_class(value: str) -> int:
"""Register a device_class string and return its 1-based index."""
if value and len(value) > cv.DEVICE_CLASS_MAX_LENGTH:
if value and len(value) > DEVICE_CLASS_MAX_LENGTH:
raise ValueError(
f"Device class string too long ({len(value)} chars, max {cv.DEVICE_CLASS_MAX_LENGTH}): '{value}'"
f"Device class string too long ({len(value)} chars, max {DEVICE_CLASS_MAX_LENGTH}): '{value}'"
)
return _register_string(
value, _get_pool().device_classes, _MAX_DEVICE_CLASSES, "device_class"
@@ -196,9 +197,9 @@ def register_unit_of_measurement(value: str) -> int:
def register_icon(value: str) -> int:
"""Register an icon string and return its 1-based index."""
if value and len(value) > cv.ICON_MAX_LENGTH:
if value and len(value) > ICON_MAX_LENGTH:
raise ValueError(
f"Icon string too long ({len(value)} chars, max {cv.ICON_MAX_LENGTH}): '{value}'"
f"Icon string too long ({len(value)} chars, max {ICON_MAX_LENGTH}): '{value}'"
)
return _register_string(value, _get_pool().icons, _MAX_ICONS, "icon")
+54 -53
View File
@@ -270,18 +270,21 @@ class TypeInfo(ABC):
def _get_simple_size_calculation(
self, name: str, force: bool, base_method: str, value_expr: str = None
) -> str:
"""Helper for simple size calculations.
"""Helper for simple size calculations using static ProtoSize methods.
Args:
name: Field name
force: Whether this is for a repeated field
base_method: Base method name (e.g., "add_int32")
base_method: Base method name (e.g., "int32")
value_expr: Optional value expression (defaults to name)
"""
field_id_size = self.calculate_field_id_size()
method = f"{base_method}_force" if force else base_method
method = f"calc_{base_method}_force" if force else f"calc_{base_method}"
# calc_bool_force only takes field_id_size (no value needed - bool is always 1 byte)
if base_method == "bool" and force:
return f"size += ProtoSize::{method}({field_id_size});"
value = value_expr or name
return f"size.{method}({field_id_size}, {value});"
return f"size += ProtoSize::{method}({field_id_size}, {value});"
@abstractmethod
def get_size_calculation(self, name: str, force: bool = False) -> str:
@@ -410,7 +413,7 @@ class DoubleType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"size.add_double({field_id_size}, {name});"
return f"size += ProtoSize::calc_fixed64({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@@ -434,7 +437,7 @@ class FloatType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"size.add_float({field_id_size}, {name});"
return f"size += ProtoSize::calc_float({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@@ -457,7 +460,7 @@ class Int64Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_int64")
return self._get_simple_size_calculation(name, force, "int64")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -477,7 +480,7 @@ class UInt64Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_uint64")
return self._get_simple_size_calculation(name, force, "uint64")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -497,7 +500,7 @@ class Int32Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_int32")
return self._get_simple_size_calculation(name, force, "int32")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -518,7 +521,7 @@ class Fixed64Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"size.add_fixed64({field_id_size}, {name});"
return f"size += ProtoSize::calc_fixed64({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@@ -542,7 +545,7 @@ class Fixed32Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"size.add_fixed32({field_id_size}, {name});"
return f"size += ProtoSize::calc_fixed32({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@@ -563,7 +566,7 @@ class BoolType(TypeInfo):
return f"out.append(YESNO({name}));"
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_bool")
return self._get_simple_size_calculation(name, force, "bool")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 1 # field ID + 1 byte
@@ -647,18 +650,18 @@ class StringType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
# For SOURCE_CLIENT only messages, use the string field directly
if not self._needs_encode:
return self._get_simple_size_calculation(name, force, "add_length")
return self._get_simple_size_calculation(name, force, "length")
# Check if this is being called from a repeated field context
# In that case, 'name' will be 'it' and we need to use the repeated version
if name == "it":
# For repeated fields, we need to use add_length_force which includes field ID
# For repeated fields, we need to use length_force which includes field ID
field_id_size = self.calculate_field_id_size()
return f"size.add_length_force({field_id_size}, it.size());"
return f"size += ProtoSize::calc_length_force({field_id_size}, it.size());"
# For messages that need encoding, use the StringRef size
field_id_size = self.calculate_field_id_size()
return f"size.add_length({field_id_size}, this->{self.field_name}_ref_.size());"
return f"size += ProtoSize::calc_length({field_id_size}, this->{self.field_name}_ref_.size());"
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
@@ -721,7 +724,9 @@ class MessageType(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_message_object")
field_id_size = self.calculate_field_id_size()
method = "calc_message_force" if force else "calc_message"
return f"size += ProtoSize::{method}({field_id_size}, {name}.calculate_size());"
def get_estimated_size(self) -> int:
# For message types, we can't easily estimate the submessage size without
@@ -822,7 +827,7 @@ class BytesType(TypeInfo):
)
def get_size_calculation(self, name: str, force: bool = False) -> str:
return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len_);"
return f"size += ProtoSize::calc_length({self.calculate_field_id_size()}, this->{self.field_name}_len_);"
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes
@@ -897,7 +902,7 @@ class PointerToBytesBufferType(PointerToBufferTypeBase):
)
def get_size_calculation(self, name: str, force: bool = False) -> str:
return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len);"
return f"size += ProtoSize::calc_length({self.calculate_field_id_size()}, this->{self.field_name}_len);"
class PointerToStringBufferType(PointerToBufferTypeBase):
@@ -939,7 +944,7 @@ class PointerToStringBufferType(PointerToBufferTypeBase):
return f'dump_field(out, "{self.name}", this->{self.field_name});'
def get_size_calculation(self, name: str, force: bool = False) -> str:
return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}.size());"
return f"size += ProtoSize::calc_length({self.calculate_field_id_size()}, this->{self.field_name}.size());"
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
@@ -1103,9 +1108,9 @@ class FixedArrayBytesType(TypeInfo):
if force:
# For repeated fields, always calculate size (no zero check)
return f"size.add_length_force({field_id_size}, {length_field});"
# For non-repeated fields, add_length already checks for zero
return f"size.add_length({field_id_size}, {length_field});"
return f"size += ProtoSize::calc_length_force({field_id_size}, {length_field});"
# For non-repeated fields, length already checks for zero
return f"size += ProtoSize::calc_length({field_id_size}, {length_field});"
def get_estimated_size(self) -> int:
# Estimate based on typical BLE advertisement size
@@ -1132,7 +1137,7 @@ class UInt32Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_uint32")
return self._get_simple_size_calculation(name, force, "uint32")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -1168,7 +1173,7 @@ class EnumType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(
name, force, "add_uint32", f"static_cast<uint32_t>({name})"
name, force, "uint32", f"static_cast<uint32_t>({name})"
)
def get_estimated_size(self) -> int:
@@ -1190,7 +1195,7 @@ class SFixed32Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"size.add_sfixed32({field_id_size}, {name});"
return f"size += ProtoSize::calc_sfixed32({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@@ -1214,7 +1219,7 @@ class SFixed64Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"size.add_sfixed64({field_id_size}, {name});"
return f"size += ProtoSize::calc_sfixed64({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@@ -1237,7 +1242,7 @@ class SInt32Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_sint32")
return self._get_simple_size_calculation(name, force, "sint32")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -1257,7 +1262,7 @@ class SInt64Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_sint64")
return self._get_simple_size_calculation(name, force, "sint64")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -1694,11 +1699,17 @@ class RepeatedTypeInfo(TypeInfo):
# For repeated fields, we always need to pass force=True to the underlying type's calculation
# This is because the encode method always sets force=true for repeated fields
# Handle message types separately as they use a dedicated helper
# Handle message types separately - generate inline loop
if isinstance(self._ti, MessageType):
field_id_size = self._ti.calculate_field_id_size()
container = f"*{name}" if self._use_pointer else name
return f"size.add_repeated_message({field_id_size}, {container});"
container_ref = f"*{name}" if self._use_pointer else name
empty_check = f"{name}->empty()" if self._use_pointer else f"{name}.empty()"
o = f"if (!{empty_check}) {{\n"
o += f" for (const auto &it : {container_ref}) {{\n"
o += f" size += ProtoSize::calc_message_force({field_id_size}, it.calculate_size());\n"
o += " }\n"
o += "}"
return o
# For non-message types, generate size calculation with iteration
container_ref = f"*{name}" if self._use_pointer else name
@@ -1713,14 +1724,14 @@ class RepeatedTypeInfo(TypeInfo):
field_id_size = self._ti.calculate_field_id_size()
bytes_per_element = field_id_size + num_bytes
size_expr = f"{name}->size()" if self._use_pointer else f"{name}.size()"
o += f" size.add_precalculated_size({size_expr} * {bytes_per_element});\n"
o += f" size += {size_expr} * {bytes_per_element};\n"
else:
# Other types need the actual value
# Special handling for const char* elements
if self._use_pointer and "const char" in self._container_no_template:
field_id_size = self.calculate_field_id_size()
o += f" for (const char *it : {container_ref}) {{\n"
o += f" size.add_length_force({field_id_size}, strlen(it));\n"
o += f" size += ProtoSize::calc_length_force({field_id_size}, strlen(it));\n"
else:
auto_ref = "" if self._ti_is_bool else "&"
o += f" for (const auto {auto_ref}it : {container_ref}) {{\n"
@@ -2233,23 +2244,19 @@ def build_message_type(
o += indent("\n".join(encode)) + "\n"
o += "}\n"
cpp += o
prot = "void encode(ProtoWriteBuffer &buffer) const override;"
prot = "void encode(ProtoWriteBuffer &buffer) const;"
public_content.append(prot)
# If no fields to encode or message doesn't need encoding, the default implementation in ProtoMessage will be used
# Add calculate_size method only if this message needs encoding and has fields
if needs_encode and size_calc:
o = f"void {desc.name}::calculate_size(ProtoSize &size) const {{"
# For a single field, just inline it for simplicity
if len(size_calc) == 1 and len(size_calc[0]) + len(o) + 3 < 120:
o += f" {size_calc[0]} }}\n"
else:
# For multiple fields
o += "\n"
o += indent("\n".join(size_calc)) + "\n"
o += "}\n"
o = f"uint32_t {desc.name}::calculate_size() const {{\n"
o += " uint32_t size = 0;\n"
o += indent("\n".join(size_calc)) + "\n"
o += " return size;\n"
o += "}\n"
cpp += o
prot = "void calculate_size(ProtoSize &size) const override;"
prot = "uint32_t calculate_size() const;"
public_content.append(prot)
# If no fields to calculate size for or message doesn't need encoding, the default implementation in ProtoMessage will be used
@@ -2933,14 +2940,8 @@ static const char *const TAG = "api.service";
hpp += " public:\n"
hpp += "#endif\n\n"
# Add non-template send_message method
hpp += " bool send_message(const ProtoMessage &msg, uint8_t message_type) {\n"
hpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
hpp += " DumpBuffer dump_buf;\n"
hpp += " this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));\n"
hpp += "#endif\n"
hpp += " return this->send_message_impl(msg, message_type);\n"
hpp += " }\n\n"
# send_message is now a template on APIConnection directly
# No non-template send_message method needed here
# Add logging helper method implementations to cpp
cpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
@@ -1,9 +1,19 @@
esphome:
on_boot:
then:
- sensor.integration.reset:
id: integration_sensor
- sensor.integration.set_value:
id: integration_sensor
value: 100.0
sensor:
- platform: adc
id: my_sensor
pin: ${pin}
attenuation: 12db
- platform: integration
id: integration_sensor
sensor: my_sensor
name: Integration Sensor
time_unit: s
@@ -0,0 +1,171 @@
esphome:
name: uart-mock-ld2412-test
host:
api:
logger:
level: VERBOSE
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
# Dummy uart entry to satisfy ld2412's DEPENDENCIES = ["uart"]
# The actual UART bus used is the uart_mock component below
uart:
baud_rate: 115200
port: /dev/null
uart_mock:
id: mock_uart
baud_rate: 256000
injections:
# Phase 1 (t=100ms): Valid LD2412 normal mode data frame - happy path
# The buffer is clean at this point, so this frame should parse correctly.
# Moving target: 100cm, energy 50
# Still target: 120cm, energy 25
# Target state: 0x03 (moving + still)
# detection_distance = 100 (LD2412 computes from moving target when MOVE_BITMASK set)
#
# Frame layout (24 bytes):
# [0-3] F4 F3 F2 F1 = data frame header
# [4-5] 0D 00 = length 13
# [6] 02 = data type (normal)
# [7] AA = data header marker
# [8] 03 = target states (moving+still)
# [9-10] 64 00 = moving distance 100 (0x0064)
# [11] 32 = moving energy 50
# [12-13] 78 00 = still distance 120 (0x0078)
# [14] 19 = still energy 25
# [15-16] 64 00 = detect distance bytes (ignored by LD2412 code)
# [17] 00 = padding
# [18] 55 = data footer marker
# [19] 00 = CRC/check
# [20-23] F8 F7 F6 F5 = data frame footer
- delay: 100ms
inject_rx:
[
0xF4, 0xF3, 0xF2, 0xF1,
0x0D, 0x00,
0x02, 0xAA,
0x03,
0x64, 0x00,
0x32,
0x78, 0x00,
0x19,
0x64, 0x00,
0x00,
0x55, 0x00,
0xF8, 0xF7, 0xF6, 0xF5,
]
# Phase 2 (t=300ms): Garbage bytes
# LD2412's parser rejects bytes that don't match the frame header at
# position 0 (must start with F4 or FD), so buffer stays empty.
- delay: 200ms
inject_rx: [0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x11, 0x22]
# Phase 3 (t=400ms): Truncated frame (header + partial data, no footer)
# Starts with valid data frame header so parser accepts it.
# After this, buffer_pos_ = 8.
- delay: 100ms
inject_rx: [0xF4, 0xF3, 0xF2, 0xF1, 0x0D, 0x00, 0x02, 0xAA]
# Phase 4 (t=600ms): Overflow - inject 60 bytes of 0xFF (MAX_LINE_LENGTH=54)
# Buffer has 8 bytes from phase 3 (garbage in phase 2 was rejected).
# Overflow math: buffer_pos_ starts at 8, overflow triggers when
# buffer_pos_ reaches 53 (MAX_LINE_LENGTH - 1). Need 45 more bytes to
# fill positions 8-52, then byte 46 triggers overflow. After overflow,
# buffer_pos_ = 0 and remaining 0xFF bytes are rejected (don't match header).
- delay: 200ms
inject_rx:
[
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
]
# Phase 5 (t=700ms): Valid frame after overflow - recovery test
# Buffer was reset by overflow. This valid frame should parse correctly.
# Moving target: 50cm, energy 100
# Still target: 75cm, energy 80
# detection_distance = 50 (moving target distance, since MOVE_BITMASK set)
- delay: 100ms
inject_rx:
[
0xF4, 0xF3, 0xF2, 0xF1,
0x0D, 0x00,
0x02, 0xAA,
0x03,
0x32, 0x00,
0x64,
0x4B, 0x00,
0x50,
0x32, 0x00,
0x00,
0x55, 0x00,
0xF8, 0xF7, 0xF6, 0xF5,
]
ld2412:
id: ld2412_dev
uart_id: mock_uart
sensor:
- platform: ld2412
ld2412_id: ld2412_dev
moving_distance:
name: "Moving Distance"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
still_distance:
name: "Still Distance"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
moving_energy:
name: "Moving Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
still_energy:
name: "Still Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
detection_distance:
name: "Detection Distance"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
binary_sensor:
- platform: ld2412
ld2412_id: ld2412_dev
has_target:
name: "Has Target"
filters:
- settle: 50ms
has_moving_target:
name: "Has Moving Target"
filters:
- settle: 50ms
has_still_target:
name: "Has Still Target"
filters:
- settle: 50ms
@@ -0,0 +1,213 @@
esphome:
name: uart-mock-ld2412-eng-test
host:
api:
logger:
level: VERBOSE
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
# Dummy uart entry to satisfy ld2412's DEPENDENCIES = ["uart"]
uart:
baud_rate: 115200
port: /dev/null
uart_mock:
id: mock_uart
baud_rate: 256000
injections:
# Phase 1 (t=100ms): Valid LD2412 engineering mode data frame
#
# Engineering mode frame layout (52 bytes):
# [0-3] F4 F3 F2 F1 = data frame header
# [4-5] 2A 00 = length 42
# [6] 01 = data type (engineering mode)
# [7] AA = data header marker
# [8] 03 = target states (moving+still)
# [9-10] 1E 00 = moving distance 30 (0x001E)
# [11] 64 = moving energy 100
# [12-13] 1E 00 = still distance 30 (0x001E)
# [14] 64 = still energy 100
# [15-16] 00 00 = detection distance bytes (ignored)
# [17-30] gate moving energies (14 gates)
# [31-44] gate still energies (14 gates)
# [45] 57 = light sensor value 87
# [46] 55 = data footer marker
# [47] 00 = check
# [48-51] F8 F7 F6 F5 = data frame footer
- delay: 100ms
inject_rx:
[
0xF4, 0xF3, 0xF2, 0xF1,
0x2A, 0x00,
0x01, 0xAA,
0x03,
0x1E, 0x00,
0x64,
0x1E, 0x00,
0x64,
0x00, 0x00,
0x64, 0x41, 0x06, 0x0E, 0x2B, 0x16, 0x03, 0x03, 0x07, 0x05, 0x09, 0x08, 0x07, 0x06,
0x00, 0x00, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x50, 0x40, 0x30, 0x20, 0x10,
0x57,
0x55, 0x00,
0xF8, 0xF7, 0xF6, 0xF5,
]
# Phase 2 (t=200ms): Second engineering mode frame with different values
# Moving at 73cm, still at 30cm
- delay: 100ms
inject_rx:
[
0xF4, 0xF3, 0xF2, 0xF1,
0x2A, 0x00,
0x01, 0xAA,
0x03,
0x49, 0x00,
0x64,
0x1E, 0x00,
0x64,
0x21, 0x00,
0x11, 0x64, 0x05, 0x29, 0x39, 0x10, 0x03, 0x11, 0x0E, 0x08, 0x06, 0x04, 0x03, 0x02,
0x00, 0x00, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x50, 0x40, 0x30, 0x20, 0x10,
0x57,
0x55, 0x00,
0xF8, 0xF7, 0xF6, 0xF5,
]
# Phase 3 (t=300ms): Frame with still target at 291cm (multi-byte distance)
# This tests encode_uint16 with high byte > 0
# Target state: 0x02 (still only) -> detection_distance = still distance = 291
- delay: 100ms
inject_rx:
[
0xF4, 0xF3, 0xF2, 0xF1,
0x2A, 0x00,
0x01, 0xAA,
0x02,
0x2F, 0x00,
0x36,
0x23, 0x01,
0x64,
0x21, 0x00,
0x2F, 0x36, 0x09, 0x0D, 0x15, 0x0B, 0x06, 0x06, 0x08, 0x09, 0x08, 0x07, 0x06, 0x05,
0x00, 0x00, 0x64, 0x64, 0x64, 0x64, 0x64, 0x5A, 0x3D, 0x30, 0x20, 0x10, 0x08, 0x04,
0x57,
0x55, 0x00,
0xF8, 0xF7, 0xF6, 0xF5,
]
ld2412:
id: ld2412_dev
uart_id: mock_uart
sensor:
- platform: ld2412
ld2412_id: ld2412_dev
moving_distance:
name: "Moving Distance"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
still_distance:
name: "Still Distance"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
moving_energy:
name: "Moving Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
still_energy:
name: "Still Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
detection_distance:
name: "Detection Distance"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
light:
name: "Light"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
gate_0:
move_energy:
name: "Gate 0 Move Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
still_energy:
name: "Gate 0 Still Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
gate_1:
move_energy:
name: "Gate 1 Move Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
still_energy:
name: "Gate 1 Still Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
gate_2:
move_energy:
name: "Gate 2 Move Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
still_energy:
name: "Gate 2 Still Energy"
filters:
- timeout:
timeout: 50ms
value: last
- throttle_with_priority: 50ms
binary_sensor:
- platform: ld2412
ld2412_id: ld2412_dev
has_target:
name: "Has Target"
filters:
- settle: 50ms
has_moving_target:
name: "Has Moving Target"
filters:
- settle: 50ms
has_still_target:
name: "Has Still Target"
filters:
- settle: 50ms
+407
View File
@@ -0,0 +1,407 @@
"""Integration test for LD2412 component with mock UART.
Tests:
test_uart_mock_ld2412 (normal mode):
1. Happy path - valid data frame publishes correct sensor values
2. Garbage resilience - random bytes don't crash the component
3. Truncated frame handling - partial frame doesn't corrupt state
4. Buffer overflow recovery - overflow resets the parser
5. Post-overflow parsing - next valid frame after overflow is parsed correctly
6. TX logging - verifies LD2412 sends expected setup commands
test_uart_mock_ld2412_engineering (engineering mode):
1. Engineering mode frames with per-gate energy data and light sensor
2. Multi-byte still distance (291cm) using high byte > 0
3. Gate energy sensor values
4. Detection distance computed from target state
"""
from __future__ import annotations
import asyncio
from pathlib import Path
from aioesphomeapi import (
BinarySensorInfo,
BinarySensorState,
EntityState,
SensorInfo,
SensorState,
)
import pytest
from .state_utils import InitialStateHelper, build_key_to_entity_mapping, find_entity
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_uart_mock_ld2412(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test LD2412 data parsing with happy path, garbage, overflow, and recovery."""
# Replace external component path placeholder
external_components_path = str(
Path(__file__).parent / "fixtures" / "external_components"
)
yaml_config = yaml_config.replace(
"EXTERNAL_COMPONENT_PATH", external_components_path
)
loop = asyncio.get_running_loop()
# Track overflow warning in logs
overflow_seen = loop.create_future()
# Track TX data logged by the mock for assertions
tx_log_lines: list[str] = []
def line_callback(line: str) -> None:
if "Max command length exceeded" in line and not overflow_seen.done():
overflow_seen.set_result(True)
# Capture all TX log lines from uart_mock
if "uart_mock" in line and "TX " in line:
tx_log_lines.append(line)
# Track sensor state updates (after initial state is swallowed)
sensor_states: dict[str, list[float]] = {
"moving_distance": [],
"still_distance": [],
"moving_energy": [],
"still_energy": [],
"detection_distance": [],
}
binary_states: dict[str, list[bool]] = {
"has_target": [],
"has_moving_target": [],
"has_still_target": [],
}
# Signal when we see recovery frame values
recovery_received = loop.create_future()
def on_state(state: EntityState) -> None:
if isinstance(state, SensorState) and not state.missing_state:
sensor_name = key_to_sensor.get(state.key)
if sensor_name and sensor_name in sensor_states:
sensor_states[sensor_name].append(state.state)
# Check if this is the recovery frame (moving_distance = 50)
if (
sensor_name == "moving_distance"
and state.state == pytest.approx(50.0)
and not recovery_received.done()
):
recovery_received.set_result(True)
elif isinstance(state, BinarySensorState):
sensor_name = key_to_sensor.get(state.key)
if sensor_name and sensor_name in binary_states:
binary_states[sensor_name].append(state.state)
async with (
run_compiled(yaml_config, line_callback=line_callback),
api_client_connected() as client,
):
entities, _ = await client.list_entities_services()
# Build key mappings for all sensor types
all_names = list(sensor_states.keys()) + list(binary_states.keys())
# Sort by descending length to avoid substring collisions
# (e.g., "still_energy" matching "gate_0_still_energy")
all_names.sort(key=len, reverse=True)
key_to_sensor = build_key_to_entity_mapping(entities, all_names)
# Set up initial state helper
initial_state_helper = InitialStateHelper(entities)
client.subscribe_states(initial_state_helper.on_state_wrapper(on_state))
try:
await initial_state_helper.wait_for_initial_states()
except TimeoutError:
pytest.fail("Timeout waiting for initial states")
# Phase 1 values are in the initial states (swallowed by InitialStateHelper).
# Verify them via initial_states dict.
moving_dist_entity = find_entity(entities, "moving_distance", SensorInfo)
assert moving_dist_entity is not None
initial_moving = initial_state_helper.initial_states.get(moving_dist_entity.key)
assert initial_moving is not None and isinstance(initial_moving, SensorState)
assert initial_moving.state == pytest.approx(100.0), (
f"Initial moving distance should be 100, got {initial_moving.state}"
)
still_dist_entity = find_entity(entities, "still_distance", SensorInfo)
assert still_dist_entity is not None
initial_still = initial_state_helper.initial_states.get(still_dist_entity.key)
assert initial_still is not None and isinstance(initial_still, SensorState)
assert initial_still.state == pytest.approx(120.0), (
f"Initial still distance should be 120, got {initial_still.state}"
)
moving_energy_entity = find_entity(entities, "moving_energy", SensorInfo)
assert moving_energy_entity is not None
initial_me = initial_state_helper.initial_states.get(moving_energy_entity.key)
assert initial_me is not None and isinstance(initial_me, SensorState)
assert initial_me.state == pytest.approx(50.0), (
f"Initial moving energy should be 50, got {initial_me.state}"
)
still_energy_entity = find_entity(entities, "still_energy", SensorInfo)
assert still_energy_entity is not None
initial_se = initial_state_helper.initial_states.get(still_energy_entity.key)
assert initial_se is not None and isinstance(initial_se, SensorState)
assert initial_se.state == pytest.approx(25.0), (
f"Initial still energy should be 25, got {initial_se.state}"
)
# LD2412 detection_distance = moving_distance when MOVE_BITMASK is set
detect_dist_entity = find_entity(entities, "detection_distance", SensorInfo)
assert detect_dist_entity is not None
initial_dd = initial_state_helper.initial_states.get(detect_dist_entity.key)
assert initial_dd is not None and isinstance(initial_dd, SensorState)
assert initial_dd.state == pytest.approx(100.0), (
f"Initial detection distance should be 100, got {initial_dd.state}"
)
# Wait for the recovery frame (Phase 5) to be parsed
# This proves the component survived garbage + truncated + overflow
try:
await asyncio.wait_for(recovery_received, timeout=3.0)
except TimeoutError:
pytest.fail(
f"Timeout waiting for recovery frame. Received sensor states:\n"
f" moving_distance: {sensor_states['moving_distance']}\n"
f" still_distance: {sensor_states['still_distance']}\n"
f" moving_energy: {sensor_states['moving_energy']}\n"
f" still_energy: {sensor_states['still_energy']}\n"
f" detection_distance: {sensor_states['detection_distance']}"
)
# Verify overflow warning was logged
assert overflow_seen.done(), (
"Expected 'Max command length exceeded' warning in logs"
)
# Verify LD2412 sent setup commands (TX logging)
assert len(tx_log_lines) > 0, "Expected TX log lines from uart_mock"
tx_data = " ".join(tx_log_lines)
# Verify command frame header appears (FD:FC:FB:FA)
assert "FD:FC:FB:FA" in tx_data, (
"Expected LD2412 command frame header FD:FC:FB:FA in TX log"
)
# Verify command frame footer appears (04:03:02:01)
assert "04:03:02:01" in tx_data, (
"Expected LD2412 command frame footer 04:03:02:01 in TX log"
)
# Recovery frame values (Phase 5, after overflow)
assert len(sensor_states["moving_distance"]) >= 1, (
f"Expected recovery moving_distance, got: {sensor_states['moving_distance']}"
)
# Find the recovery value (moving_distance = 50)
recovery_values = [
v for v in sensor_states["moving_distance"] if v == pytest.approx(50.0)
]
assert len(recovery_values) >= 1, (
f"Expected moving_distance=50 in recovery, got: {sensor_states['moving_distance']}"
)
# Recovery frame: moving=50, still=75, energy=100/80, detect=50
recovery_idx = next(
i
for i, v in enumerate(sensor_states["moving_distance"])
if v == pytest.approx(50.0)
)
assert sensor_states["still_distance"][recovery_idx] == pytest.approx(75.0), (
f"Recovery still distance should be 75, got {sensor_states['still_distance'][recovery_idx]}"
)
assert sensor_states["moving_energy"][recovery_idx] == pytest.approx(100.0), (
f"Recovery moving energy should be 100, got {sensor_states['moving_energy'][recovery_idx]}"
)
assert sensor_states["still_energy"][recovery_idx] == pytest.approx(80.0), (
f"Recovery still energy should be 80, got {sensor_states['still_energy'][recovery_idx]}"
)
# LD2412 detection_distance = moving_distance when MOVE_BITMASK set
assert sensor_states["detection_distance"][recovery_idx] == pytest.approx(
50.0
), (
f"Recovery detection distance should be 50, got {sensor_states['detection_distance'][recovery_idx]}"
)
# Verify binary sensors detected targets
has_target_entity = find_entity(entities, "has_target", BinarySensorInfo)
assert has_target_entity is not None
initial_ht = initial_state_helper.initial_states.get(has_target_entity.key)
assert initial_ht is not None and isinstance(initial_ht, BinarySensorState)
assert initial_ht.state is True, "Has target should be True"
has_moving_entity = find_entity(entities, "has_moving_target", BinarySensorInfo)
assert has_moving_entity is not None
initial_hm = initial_state_helper.initial_states.get(has_moving_entity.key)
assert initial_hm is not None and isinstance(initial_hm, BinarySensorState)
assert initial_hm.state is True, "Has moving target should be True"
has_still_entity = find_entity(entities, "has_still_target", BinarySensorInfo)
assert has_still_entity is not None
initial_hs = initial_state_helper.initial_states.get(has_still_entity.key)
assert initial_hs is not None and isinstance(initial_hs, BinarySensorState)
assert initial_hs.state is True, "Has still target should be True"
@pytest.mark.asyncio
async def test_uart_mock_ld2412_engineering(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test LD2412 engineering mode with per-gate energy, light, and multi-byte distance."""
external_components_path = str(
Path(__file__).parent / "fixtures" / "external_components"
)
yaml_config = yaml_config.replace(
"EXTERNAL_COMPONENT_PATH", external_components_path
)
loop = asyncio.get_running_loop()
# Track sensor state updates (after initial state is swallowed)
sensor_states: dict[str, list[float]] = {
"moving_distance": [],
"still_distance": [],
"moving_energy": [],
"still_energy": [],
"detection_distance": [],
"light": [],
"gate_0_move_energy": [],
"gate_1_move_energy": [],
"gate_2_move_energy": [],
"gate_0_still_energy": [],
"gate_1_still_energy": [],
"gate_2_still_energy": [],
}
binary_states: dict[str, list[bool]] = {
"has_target": [],
"has_moving_target": [],
"has_still_target": [],
}
# Signal when we see Phase 3 frame values
phase3_still_received = loop.create_future()
phase3_detect_received = loop.create_future()
def on_state(state: EntityState) -> None:
if isinstance(state, SensorState) and not state.missing_state:
sensor_name = key_to_sensor.get(state.key)
if sensor_name and sensor_name in sensor_states:
sensor_states[sensor_name].append(state.state)
if (
sensor_name == "still_distance"
and state.state == pytest.approx(291.0)
and not phase3_still_received.done()
):
phase3_still_received.set_result(True)
if (
sensor_name == "detection_distance"
and state.state == pytest.approx(291.0)
and not phase3_detect_received.done()
):
phase3_detect_received.set_result(True)
elif isinstance(state, BinarySensorState):
sensor_name = key_to_sensor.get(state.key)
if sensor_name and sensor_name in binary_states:
binary_states[sensor_name].append(state.state)
async with (
run_compiled(yaml_config),
api_client_connected() as client,
):
entities, _ = await client.list_entities_services()
all_names = list(sensor_states.keys()) + list(binary_states.keys())
# Sort by descending length to avoid substring collisions
# (e.g., "still_energy" matching "gate_0_still_energy")
all_names.sort(key=len, reverse=True)
key_to_sensor = build_key_to_entity_mapping(entities, all_names)
initial_state_helper = InitialStateHelper(entities)
client.subscribe_states(initial_state_helper.on_state_wrapper(on_state))
try:
await initial_state_helper.wait_for_initial_states()
except TimeoutError:
pytest.fail("Timeout waiting for initial states")
# Phase 1 initial values (engineering mode frame):
# moving=30, energy=100, still=30, energy=100, detect=30
moving_dist_entity = find_entity(entities, "moving_distance", SensorInfo)
assert moving_dist_entity is not None
initial_moving = initial_state_helper.initial_states.get(moving_dist_entity.key)
assert initial_moving is not None and isinstance(initial_moving, SensorState)
assert initial_moving.state == pytest.approx(30.0), (
f"Initial moving distance should be 30, got {initial_moving.state}"
)
still_dist_entity = find_entity(entities, "still_distance", SensorInfo)
assert still_dist_entity is not None
initial_still = initial_state_helper.initial_states.get(still_dist_entity.key)
assert initial_still is not None and isinstance(initial_still, SensorState)
assert initial_still.state == pytest.approx(30.0), (
f"Initial still distance should be 30, got {initial_still.state}"
)
# Verify engineering mode sensors from initial state
# Gate 0 moving energy = 0x64 = 100
gate0_move_entity = find_entity(entities, "gate_0_move_energy", SensorInfo)
assert gate0_move_entity is not None
initial_g0m = initial_state_helper.initial_states.get(gate0_move_entity.key)
assert initial_g0m is not None and isinstance(initial_g0m, SensorState)
assert initial_g0m.state == pytest.approx(100.0), (
f"Gate 0 move energy should be 100, got {initial_g0m.state}"
)
# Gate 1 moving energy = 0x41 = 65
gate1_move_entity = find_entity(entities, "gate_1_move_energy", SensorInfo)
assert gate1_move_entity is not None
initial_g1m = initial_state_helper.initial_states.get(gate1_move_entity.key)
assert initial_g1m is not None and isinstance(initial_g1m, SensorState)
assert initial_g1m.state == pytest.approx(65.0), (
f"Gate 1 move energy should be 65, got {initial_g1m.state}"
)
# Light sensor = 0x57 = 87
light_entity = find_entity(entities, "light", SensorInfo)
assert light_entity is not None
initial_light = initial_state_helper.initial_states.get(light_entity.key)
assert initial_light is not None and isinstance(initial_light, SensorState)
assert initial_light.state == pytest.approx(87.0), (
f"Light sensor should be 87, got {initial_light.state}"
)
# Wait for Phase 3 frame: still_distance = 291cm (multi-byte)
try:
await asyncio.wait_for(phase3_still_received, timeout=3.0)
except TimeoutError:
pytest.fail(
f"Timeout waiting for Phase 3 still_distance. Received:\n"
f" still_distance: {sensor_states['still_distance']}\n"
f" moving_distance: {sensor_states['moving_distance']}"
)
assert pytest.approx(291.0) in sensor_states["still_distance"], (
f"Expected still_distance=291, got: {sensor_states['still_distance']}"
)
# Wait for Phase 3: detection_distance = 291 (still-only target)
# target_state=0x02 so LD2412 uses still_distance for detection_distance.
# The throttle_with_priority filter may delay this value.
try:
await asyncio.wait_for(phase3_detect_received, timeout=3.0)
except TimeoutError:
pytest.fail(
f"Timeout waiting for detection_distance=291 (still-only target). "
f"Received: {sensor_states['detection_distance']}"
)
assert pytest.approx(291.0) in sensor_states["detection_distance"], (
f"Expected detection_distance=291, got: {sensor_states['detection_distance']}"
)