Compare commits

...

73 Commits

Author SHA1 Message Date
J. Nick Koston
ac64dbfb8d encode perf 2025-07-11 07:18:15 -10:00
J. Nick Koston
9e78b5b06f preen 2025-07-11 06:55:04 -10:00
J. Nick Koston
be7550f5da preen 2025-07-11 06:45:49 -10:00
J. Nick Koston
bde3d025dc preen 2025-07-11 06:41:20 -10:00
J. Nick Koston
eb7f982b5b preen 2025-07-11 06:07:22 -10:00
J. Nick Koston
c2657c1896 reduce 2025-07-11 06:05:26 -10:00
J. Nick Koston
037eff53b1 revert 2025-07-11 06:00:57 -10:00
J. Nick Koston
a03a748a56 revert 2025-07-11 05:55:42 -10:00
J. Nick Koston
d567533161 no template 2025-07-11 05:45:49 -10:00
J. Nick Koston
28620190f0 revert wire type storage 2025-07-11 05:44:17 -10:00
J. Nick Koston
984c24a1b5 revert binary search, get wire type 2025-07-11 05:38:37 -10:00
J. Nick Koston
f2037aadc5 tweak 2025-07-10 22:45:00 -10:00
J. Nick Koston
00e39958b8 preen 2025-07-10 22:11:26 -10:00
J. Nick Koston
3570b888d2 opt 2025-07-10 22:06:31 -10:00
J. Nick Koston
ac0f279e7e cleanup 2025-07-10 21:21:05 -10:00
J. Nick Koston
701babbab4 cleanup 2025-07-10 21:19:36 -10:00
J. Nick Koston
ce350642e4 cleanup 2025-07-10 21:18:04 -10:00
J. Nick Koston
6ff19992da cleanup 2025-07-10 21:11:37 -10:00
J. Nick Koston
58ff3a8abd cleanup 2025-07-10 21:07:20 -10:00
J. Nick Koston
60babfc356 preen 2025-07-10 20:33:01 -10:00
J. Nick Koston
5630720715 preen 2025-07-10 20:25:02 -10:00
J. Nick Koston
0d5b353cdf preen 2025-07-10 16:30:43 -10:00
J. Nick Koston
c745140835 preen 2025-07-10 15:56:03 -10:00
J. Nick Koston
084ecf93a7 preen 2025-07-10 15:51:13 -10:00
J. Nick Koston
e4ea6c5eee preen 2025-07-10 15:42:48 -10:00
J. Nick Koston
3a94361c64 preen 2025-07-10 15:33:28 -10:00
J. Nick Koston
4c188fb4d3 fixes 2025-07-10 15:06:20 -10:00
J. Nick Koston
949249cac3 cleanup 2025-07-10 14:12:57 -10:00
J. Nick Koston
2cb0418c95 tweak 2025-07-10 12:41:09 -10:00
J. Nick Koston
d27a938f74 cleanup 2025-07-10 12:33:33 -10:00
J. Nick Koston
5372bcb2b8 cleanup 2025-07-10 12:21:57 -10:00
J. Nick Koston
dd35038771 preen 2025-07-10 11:58:47 -10:00
J. Nick Koston
ae945c9a96 fixes 2025-07-10 11:54:56 -10:00
J. Nick Koston
b3bb059e42 less bad 2025-07-10 11:06:08 -10:00
J. Nick Koston
9a9aebe8b4 v3 2025-07-10 09:06:57 -10:00
J. Nick Koston
0b2a889d0e remove v1 2025-07-10 08:15:48 -10:00
J. Nick Koston
0c8693b8c6 less bad 2025-07-10 07:48:08 -10:00
J. Nick Koston
e24d4450ac working but horrid 2025-07-10 07:33:23 -10:00
J. Nick Koston
1818a56096 dry 2025-07-10 06:12:52 -10:00
J. Nick Koston
27b0c99580 fixes 2025-07-10 06:05:40 -10:00
J. Nick Koston
22ac43e7eb fixes 2025-07-10 05:57:54 -10:00
J. Nick Koston
835c819e02 fix repeat 2025-07-10 05:38:36 -10:00
J. Nick Koston
79dbeef0c9 fixes 2025-07-10 05:12:49 -10:00
J. Nick Koston
6b0391a1f9 fixes 2025-07-10 05:09:57 -10:00
J. Nick Koston
ab4a21e84d fix 2025-07-09 22:20:49 -10:00
J. Nick Koston
df72954c08 fix 2025-07-09 22:13:25 -10:00
J. Nick Koston
44da11c66e fix 2025-07-09 22:09:07 -10:00
J. Nick Koston
e437f41072 fix 2025-07-09 22:04:26 -10:00
J. Nick Koston
23ed9db40f fix 2025-07-09 21:58:14 -10:00
J. Nick Koston
921974ec23 tweak 2025-07-09 21:50:57 -10:00
J. Nick Koston
95afae4830 weak 2025-07-09 17:38:27 -10:00
J. Nick Koston
163a5747f0 Merge branch 'memory' into api_flash_memory 2025-07-09 17:33:51 -10:00
J. Nick Koston
78a82ed46e Reduce API Flash 2025-07-09 17:31:02 -10:00
J. Nick Koston
33fb4d5d42 fixes 2025-07-09 16:27:40 -10:00
J. Nick Koston
7f7623cc8d Merge remote-tracking branch 'upstream/dev' into memory 2025-07-09 16:16:01 -10:00
J. Nick Koston
f2e914fb94 Merge remote-tracking branch 'upstream/dev' into memory 2025-07-09 15:20:45 -10:00
J. Nick Koston
1a0943c960 add component symbols 2025-07-09 10:00:20 -10:00
J. Nick Koston
073590124d Merge remote-tracking branch 'upstream/dev' into memory 2025-07-09 09:37:48 -10:00
J. Nick Koston
62c7f14d9a Merge remote-tracking branch 'upstream/dev' into memory 2025-07-07 12:08:50 -05:00
J. Nick Koston
b1553807f7 wip 2025-07-02 09:14:26 -05:00
J. Nick Koston
797d4929ab wip 2025-07-02 09:05:28 -05:00
J. Nick Koston
ba5bb9dfa7 wip 2025-07-02 09:02:06 -05:00
J. Nick Koston
dd49d832c4 wip 2025-07-02 08:56:13 -05:00
J. Nick Koston
5004f44f65 wip 2025-07-02 08:42:17 -05:00
J. Nick Koston
bc9c4a8b8e wip 2025-07-02 08:35:42 -05:00
J. Nick Koston
6f05ee7427 wip 2025-07-02 08:25:41 -05:00
J. Nick Koston
f3523a96c9 wip 2025-07-02 08:24:15 -05:00
J. Nick Koston
06957d9895 wip 2025-07-02 08:21:36 -05:00
J. Nick Koston
1f361b07d1 wip 2025-07-02 08:16:35 -05:00
J. Nick Koston
40d9c0a3db wip 2025-07-02 07:58:35 -05:00
J. Nick Koston
548cd39496 wip 2025-07-02 07:51:04 -05:00
J. Nick Koston
85049611c3 wip 2025-07-02 07:48:55 -05:00
J. Nick Koston
b8a75bc925 analyze_memory 2025-07-02 07:30:17 -05:00
14 changed files with 5223 additions and 7744 deletions

View File

@@ -458,6 +458,13 @@ def command_vscode(args):
def command_compile(args, config):
# Set memory analysis options in config
if args.analyze_memory:
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
if args.memory_report:
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
@@ -837,6 +844,17 @@ def parse_args(argv):
help="Only generate source code, do not compile.",
action="store_true",
)
parser_compile.add_argument(
"--analyze-memory",
help="Analyze and display memory usage by component after compilation.",
action="store_true",
)
parser_compile.add_argument(
"--memory-report",
help="Save memory analysis report to a file (supports .json or .txt).",
type=str,
metavar="FILE",
)
parser_upload = subparsers.add_parser(
"upload",

1600
esphome/analyze_memory.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -193,7 +193,8 @@ void APIConnection::loop() {
// 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
ESP_LOGW(TAG, "Buffer full, ping queued");
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE);
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE,
PingRequest::ESTIMATED_SIZE);
this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
}
}
@@ -265,7 +266,7 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
// 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, uint16_t message_type, APIConnection *conn,
uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// If in log-only mode, just log and return
@@ -316,7 +317,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes
#ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
BinarySensorStateResponse::MESSAGE_TYPE);
BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -343,7 +344,8 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
#ifdef USE_COVER
bool APIConnection::send_cover_state(cover::Cover *cover) {
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE,
CoverStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -400,7 +402,8 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
#ifdef USE_FAN
bool APIConnection::send_fan_state(fan::Fan *fan) {
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE,
FanStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -455,7 +458,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
#ifdef USE_LIGHT
bool APIConnection::send_light_state(light::LightState *light) {
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE,
LightStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -543,7 +547,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
#ifdef USE_SENSOR
bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE,
SensorStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -575,7 +580,8 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
#ifdef USE_SWITCH
bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE,
SwitchStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -611,7 +617,7 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
#ifdef USE_TEXT_SENSOR
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
TextSensorStateResponse::MESSAGE_TYPE);
TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -638,7 +644,8 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
#ifdef USE_CLIMATE
bool APIConnection::send_climate_state(climate::Climate *climate) {
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE,
ClimateStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -734,7 +741,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
#ifdef USE_NUMBER
bool APIConnection::send_number_state(number::Number *number) {
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE,
NumberStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -770,7 +778,8 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
#ifdef USE_DATETIME_DATE
bool APIConnection::send_date_state(datetime::DateEntity *date) {
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE,
DateStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -800,7 +809,8 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
#ifdef USE_DATETIME_TIME
bool APIConnection::send_time_state(datetime::TimeEntity *time) {
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE,
TimeStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -831,7 +841,7 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
#ifdef USE_DATETIME_DATETIME
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
DateTimeStateResponse::MESSAGE_TYPE);
DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -862,7 +872,8 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
#ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text) {
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE,
TextStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -896,7 +907,8 @@ void APIConnection::text_command(const TextCommandRequest &msg) {
#ifdef USE_SELECT
bool APIConnection::send_select_state(select::Select *select) {
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE,
SelectStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -944,7 +956,8 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg
#ifdef USE_LOCK
bool APIConnection::send_lock_state(lock::Lock *a_lock) {
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE,
LockStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -986,7 +999,8 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
#ifdef USE_VALVE
bool APIConnection::send_valve_state(valve::Valve *valve) {
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE,
ValveStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -1023,7 +1037,7 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) {
#ifdef USE_MEDIA_PLAYER
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
MediaPlayerStateResponse::MESSAGE_TYPE);
MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -1262,7 +1276,8 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon
#ifdef USE_ALARM_CONTROL_PANEL
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
AlarmControlPanelStateResponse::MESSAGE_TYPE);
AlarmControlPanelStateResponse::MESSAGE_TYPE,
AlarmControlPanelStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
uint32_t remaining_size, bool is_single) {
@@ -1316,7 +1331,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE);
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
EventResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) {
@@ -1341,7 +1357,8 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
#ifdef USE_UPDATE
bool APIConnection::send_update_state(update::UpdateEntity *update) {
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE);
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
UpdateStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
@@ -1535,6 +1552,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
}
}
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
ESP_LOGD(TAG, "execute_service called with key: %u, args count: %zu", msg.key, msg.args.size());
bool found = false;
for (auto *service : this->parent_->get_user_services()) {
if (service->execute_service(msg)) {
@@ -1588,7 +1606,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
}
return false;
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) {
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
return false;
}
@@ -1622,7 +1640,8 @@ void APIConnection::on_fatal_error() {
this->flags_.remove = true;
}
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type,
uint8_t estimated_size) {
// Check if we already have a message of this type for this entity
// This provides deduplication per entity/message_type combination
// O(n) but optimized for RAM and not performance.
@@ -1632,17 +1651,19 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
item.creator.cleanup(message_type);
// Move assign the new creator
item.creator = std::move(creator);
item.estimated_size = estimated_size; // Update estimated size too
return;
}
}
// No existing item found, add new one
items.emplace_back(entity, std::move(creator), message_type);
items.emplace_back(entity, std::move(creator), message_type, estimated_size);
}
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
uint8_t estimated_size) {
// Insert at front for high priority messages (no deduplication check)
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type));
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type, estimated_size));
}
bool APIConnection::schedule_batch_() {
@@ -1710,11 +1731,11 @@ void APIConnection::process_batch_() {
// Initialize buffer and tracking variables
this->parent_->get_shared_buffer_ref().clear();
// Pre-calculate exact buffer size needed based on message types
// Pre-calculate exact buffer size needed based on stored estimated sizes
uint32_t total_estimated_size = 0;
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
const auto &item = this->deferred_batch_[i];
total_estimated_size += get_estimated_message_size(item.message_type);
total_estimated_size += item.estimated_size;
}
// Calculate total overhead for all messages
@@ -1808,7 +1829,7 @@ void APIConnection::process_batch_() {
}
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single, uint16_t message_type) const {
bool is_single, uint8_t message_type) const {
#ifdef USE_EVENT
// Special case: EventResponse uses string pointer
if (message_type == EventResponse::MESSAGE_TYPE) {
@@ -1839,149 +1860,6 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
// Use generated ESTIMATED_SIZE constants from each message type
switch (message_type) {
#ifdef USE_BINARY_SENSOR
case BinarySensorStateResponse::MESSAGE_TYPE:
return BinarySensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesBinarySensorResponse::MESSAGE_TYPE:
return ListEntitiesBinarySensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SENSOR
case SensorStateResponse::MESSAGE_TYPE:
return SensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesSensorResponse::MESSAGE_TYPE:
return ListEntitiesSensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SWITCH
case SwitchStateResponse::MESSAGE_TYPE:
return SwitchStateResponse::ESTIMATED_SIZE;
case ListEntitiesSwitchResponse::MESSAGE_TYPE:
return ListEntitiesSwitchResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_TEXT_SENSOR
case TextSensorStateResponse::MESSAGE_TYPE:
return TextSensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesTextSensorResponse::MESSAGE_TYPE:
return ListEntitiesTextSensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_NUMBER
case NumberStateResponse::MESSAGE_TYPE:
return NumberStateResponse::ESTIMATED_SIZE;
case ListEntitiesNumberResponse::MESSAGE_TYPE:
return ListEntitiesNumberResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_TEXT
case TextStateResponse::MESSAGE_TYPE:
return TextStateResponse::ESTIMATED_SIZE;
case ListEntitiesTextResponse::MESSAGE_TYPE:
return ListEntitiesTextResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SELECT
case SelectStateResponse::MESSAGE_TYPE:
return SelectStateResponse::ESTIMATED_SIZE;
case ListEntitiesSelectResponse::MESSAGE_TYPE:
return ListEntitiesSelectResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_LOCK
case LockStateResponse::MESSAGE_TYPE:
return LockStateResponse::ESTIMATED_SIZE;
case ListEntitiesLockResponse::MESSAGE_TYPE:
return ListEntitiesLockResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_EVENT
case EventResponse::MESSAGE_TYPE:
return EventResponse::ESTIMATED_SIZE;
case ListEntitiesEventResponse::MESSAGE_TYPE:
return ListEntitiesEventResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_COVER
case CoverStateResponse::MESSAGE_TYPE:
return CoverStateResponse::ESTIMATED_SIZE;
case ListEntitiesCoverResponse::MESSAGE_TYPE:
return ListEntitiesCoverResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_FAN
case FanStateResponse::MESSAGE_TYPE:
return FanStateResponse::ESTIMATED_SIZE;
case ListEntitiesFanResponse::MESSAGE_TYPE:
return ListEntitiesFanResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_LIGHT
case LightStateResponse::MESSAGE_TYPE:
return LightStateResponse::ESTIMATED_SIZE;
case ListEntitiesLightResponse::MESSAGE_TYPE:
return ListEntitiesLightResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_CLIMATE
case ClimateStateResponse::MESSAGE_TYPE:
return ClimateStateResponse::ESTIMATED_SIZE;
case ListEntitiesClimateResponse::MESSAGE_TYPE:
return ListEntitiesClimateResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_ESP32_CAMERA
case ListEntitiesCameraResponse::MESSAGE_TYPE:
return ListEntitiesCameraResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_BUTTON
case ListEntitiesButtonResponse::MESSAGE_TYPE:
return ListEntitiesButtonResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_MEDIA_PLAYER
case MediaPlayerStateResponse::MESSAGE_TYPE:
return MediaPlayerStateResponse::ESTIMATED_SIZE;
case ListEntitiesMediaPlayerResponse::MESSAGE_TYPE:
return ListEntitiesMediaPlayerResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
case AlarmControlPanelStateResponse::MESSAGE_TYPE:
return AlarmControlPanelStateResponse::ESTIMATED_SIZE;
case ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE:
return ListEntitiesAlarmControlPanelResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_DATE
case DateStateResponse::MESSAGE_TYPE:
return DateStateResponse::ESTIMATED_SIZE;
case ListEntitiesDateResponse::MESSAGE_TYPE:
return ListEntitiesDateResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_TIME
case TimeStateResponse::MESSAGE_TYPE:
return TimeStateResponse::ESTIMATED_SIZE;
case ListEntitiesTimeResponse::MESSAGE_TYPE:
return ListEntitiesTimeResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_DATETIME
case DateTimeStateResponse::MESSAGE_TYPE:
return DateTimeStateResponse::ESTIMATED_SIZE;
case ListEntitiesDateTimeResponse::MESSAGE_TYPE:
return ListEntitiesDateTimeResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_VALVE
case ValveStateResponse::MESSAGE_TYPE:
return ValveStateResponse::ESTIMATED_SIZE;
case ListEntitiesValveResponse::MESSAGE_TYPE:
return ListEntitiesValveResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_UPDATE
case UpdateStateResponse::MESSAGE_TYPE:
return UpdateStateResponse::ESTIMATED_SIZE;
case ListEntitiesUpdateResponse::MESSAGE_TYPE:
return ListEntitiesUpdateResponse::ESTIMATED_SIZE;
#endif
case ListEntitiesServicesResponse::MESSAGE_TYPE:
return ListEntitiesServicesResponse::ESTIMATED_SIZE;
case ListEntitiesDoneResponse::MESSAGE_TYPE:
return ListEntitiesDoneResponse::ESTIMATED_SIZE;
case DisconnectRequest::MESSAGE_TYPE:
return DisconnectRequest::ESTIMATED_SIZE;
default:
// Fallback for unknown message types
return 24;
}
}
} // namespace api
} // namespace esphome
#endif

View File

@@ -33,7 +33,7 @@ class APIConnection : public APIServerConnection {
bool send_list_info_done() {
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done,
ListEntitiesDoneResponse::MESSAGE_TYPE);
ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE);
}
#ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
@@ -256,7 +256,7 @@ class APIConnection : public APIServerConnection {
}
bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) override;
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
std::string get_client_combined_info() const {
if (this->client_info_ == this->client_peername_) {
@@ -298,7 +298,7 @@ class APIConnection : public APIServerConnection {
}
// Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn,
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
#ifdef USE_VOICE_ASSISTANT
@@ -443,9 +443,6 @@ class APIConnection : public APIServerConnection {
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
// Helper function to get estimated message size for buffer pre-allocation
static uint16_t get_estimated_message_size(uint16_t message_type);
// Batch message method for ping requests
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
@@ -505,10 +502,10 @@ class APIConnection : public APIServerConnection {
// Call operator - uses message_type to determine union type
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint16_t message_type) const;
uint8_t message_type) const;
// Manual cleanup method - must be called before destruction for string types
void cleanup(uint16_t message_type) {
void cleanup(uint8_t message_type) {
#ifdef USE_EVENT
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
delete data_.string_ptr;
@@ -529,11 +526,12 @@ class APIConnection : public APIServerConnection {
struct BatchItem {
EntityBase *entity; // Entity pointer
MessageCreator creator; // Function that creates the message when needed
uint16_t message_type; // Message type for overhead calculation
uint8_t message_type; // Message type for overhead calculation (max 255)
uint8_t estimated_size; // Estimated message size (max 255 bytes)
// Constructor for creating BatchItem
BatchItem(EntityBase *entity, MessageCreator creator, uint16_t message_type)
: entity(entity), creator(std::move(creator)), message_type(message_type) {}
BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size)
: entity(entity), creator(std::move(creator)), message_type(message_type), estimated_size(estimated_size) {}
};
std::vector<BatchItem> items;
@@ -559,9 +557,9 @@ class APIConnection : public APIServerConnection {
}
// Add item to the batch
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Clear all items with proper cleanup
void clear() {
@@ -641,7 +639,7 @@ class APIConnection : public APIServerConnection {
#ifdef HAS_PROTO_MESSAGE_DUMP
// Helper to log a proto message from a MessageCreator object
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint16_t message_type) {
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) {
this->flags_.log_only_mode = true;
creator(entity, this, MAX_PACKET_SIZE, true, message_type);
this->flags_.log_only_mode = false;
@@ -654,7 +652,8 @@ class APIConnection : public APIServerConnection {
#endif
// Helper method to send a message either immediately or via batching
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint16_t message_type) {
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
uint8_t estimated_size) {
// Try to send immediately if:
// 1. We should try to send immediately (should_try_send_immediately = true)
// 2. Batch delay is 0 (user has opted in to immediate sending)
@@ -675,23 +674,25 @@ class APIConnection : public APIServerConnection {
}
// Fall back to scheduled batching
return this->schedule_message_(entity, creator, message_type);
return this->schedule_message_(entity, creator, message_type, estimated_size);
}
// Helper function to schedule a deferred message with known message type
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
this->deferred_batch_.add_item(entity, std::move(creator), message_type);
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size);
return this->schedule_batch_();
}
// Overload for function pointers (for info messages and current state reads)
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
return schedule_message_(entity, MessageCreator(function_ptr), message_type);
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
uint8_t estimated_size) {
return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size);
}
// Helper function to schedule a high priority message at the front of the batch
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type);
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
uint8_t estimated_size) {
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
return this->schedule_batch_();
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -128,20 +128,21 @@ class ProtoSize {
* All add_*_field methods follow these common patterns:
*
* @param total_size Reference to the total message size to update
* @param field_id_size Pre-calculated size of the field ID in bytes
* @param precalced_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
* 3. Add the precalced_field_id_size + calculated value size to total_size
*/
/**
* @brief Calculates and adds the size of an int32 field to the total message size
*/
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
static inline void add_int32_field(uint32_t &total_size, uint32_t precalced_field_id_size, int32_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
@@ -150,17 +151,17 @@ class ProtoSize {
// Calculate and directly add to total_size
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
total_size += precalced_field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
total_size += precalced_field_id_size + varint(static_cast<uint32_t>(value));
}
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size
*/
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value,
static inline void add_uint32_field(uint32_t &total_size, uint32_t precalced_field_id_size, uint32_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
@@ -168,20 +169,21 @@ class ProtoSize {
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
total_size += precalced_field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size
*/
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value, bool force = false) {
static inline void add_bool_field(uint32_t &total_size, uint32_t precalced_field_id_size, bool value,
bool force = false) {
// Skip calculation if value is false and not forced
if (!value && !force) {
return; // No need to update total_size
}
// Boolean fields always use 1 byte when true
total_size += field_id_size + 1;
total_size += precalced_field_id_size + 1;
}
/**
@@ -193,7 +195,7 @@ class ProtoSize {
* @param is_nonzero Whether the value is non-zero
*/
template<uint32_t NumBytes>
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero,
static inline void add_fixed_field(uint32_t &total_size, uint32_t precalced_field_id_size, bool is_nonzero,
bool force = false) {
// Skip calculation if value is zero and not forced
if (!is_nonzero && !force) {
@@ -201,7 +203,7 @@ class ProtoSize {
}
// Fixed fields always take exactly NumBytes
total_size += field_id_size + NumBytes;
total_size += precalced_field_id_size + NumBytes;
}
/**
@@ -209,14 +211,15 @@ class ProtoSize {
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, bool force = false) {
static inline void add_enum_field(uint32_t &total_size, uint32_t precalced_field_id_size, uint32_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
total_size += precalced_field_id_size + varint(value);
}
/**
@@ -224,7 +227,8 @@ class ProtoSize {
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
static inline void add_sint32_field(uint32_t &total_size, uint32_t precalced_field_id_size, int32_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
@@ -232,26 +236,27 @@ class ProtoSize {
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
total_size += precalced_field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size
*/
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
static inline void add_int64_field(uint32_t &total_size, uint32_t precalced_field_id_size, int64_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
total_size += precalced_field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size
*/
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value,
static inline void add_uint64_field(uint32_t &total_size, uint32_t precalced_field_id_size, uint64_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
@@ -259,7 +264,7 @@ class ProtoSize {
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
total_size += precalced_field_id_size + varint(value);
}
/**
@@ -267,7 +272,8 @@ class ProtoSize {
*
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
static inline void add_sint64_field(uint32_t &total_size, uint32_t precalced_field_id_size, int64_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
@@ -275,13 +281,13 @@ class ProtoSize {
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
total_size += field_id_size + varint(zigzag);
total_size += precalced_field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a string/bytes field to the total message size
*/
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str,
static inline void add_string_field(uint32_t &total_size, uint32_t precalced_field_id_size, const std::string &str,
bool force = false) {
// Skip calculation if string is empty and not forced
if (str.empty() && !force) {
@@ -290,7 +296,7 @@ class ProtoSize {
// Calculate and directly add to total_size
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
total_size += precalced_field_id_size + varint(str_size) + str_size;
}
/**
@@ -301,7 +307,7 @@ class ProtoSize {
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size,
static inline void add_message_field(uint32_t &total_size, uint32_t precalced_field_id_size, uint32_t nested_size,
bool force = false) {
// Skip calculation if nested message is empty and not forced
if (nested_size == 0 && !force) {
@@ -310,7 +316,7 @@ class ProtoSize {
// Calculate and directly add to total_size
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
total_size += precalced_field_id_size + varint(nested_size) + nested_size;
}
/**
@@ -322,13 +328,13 @@ class ProtoSize {
*
* @param message The nested message object
*/
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message,
bool force = false) {
static inline void add_message_object(uint32_t &total_size, uint32_t precalced_field_id_size,
const ProtoMessage &message, bool force = false) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
// Use the base implementation with the calculated nested_size
add_message_field(total_size, field_id_size, nested_size, force);
add_message_field(total_size, precalced_field_id_size, nested_size, force);
}
/**
@@ -341,7 +347,7 @@ class ProtoSize {
* @param messages Vector of message objects
*/
template<typename MessageType>
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
static inline void add_repeated_message(uint32_t &total_size, uint32_t precalced_field_id_size,
const std::vector<MessageType> &messages) {
// Skip if the vector is empty
if (messages.empty()) {
@@ -350,7 +356,7 @@ class ProtoSize {
// For repeated fields, always use force=true
for (const auto &message : messages) {
add_message_object(total_size, field_id_size, message, true);
add_message_object(total_size, precalced_field_id_size, message, true);
}
}
};

View File

@@ -475,7 +475,8 @@ void APIServer::on_shutdown() {
if (!c->send_message(DisconnectRequest())) {
// 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, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE);
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE,
DisconnectRequest::ESTIMATED_SIZE);
}
}
}

View File

@@ -14,7 +14,7 @@ class APIConnection;
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
ResponseType::MESSAGE_TYPE); \
ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
}
class ListEntitiesIterator : public ComponentIterator {

View File

@@ -2,84 +2,14 @@
#include <cinttypes>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "api_pb2_size.h"
namespace esphome {
namespace api {
static const char *const TAG = "api.proto";
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
uint32_t i = 0;
bool error = false;
while (i < length) {
uint32_t consumed;
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
break;
}
uint32_t field_type = (res->as_uint32()) & 0b111;
uint32_t field_id = (res->as_uint32()) >> 3;
i += consumed;
switch (field_type) {
case 0: { // VarInt
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
error = true;
break;
}
if (!this->decode_varint(field_id, *res)) {
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
}
i += consumed;
break;
}
case 2: { // Length-delimited
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
error = true;
break;
}
uint32_t field_length = res->as_uint32();
i += consumed;
if (field_length > length - i) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
error = true;
break;
}
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
}
i += field_length;
break;
}
case 5: { // 32-bit
if (length - i < 4) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
error = true;
break;
}
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
}
i += 4;
break;
}
default:
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
error = true;
break;
}
if (error) {
break;
}
}
}
// Message handler registries are defined in api_pb2.cpp
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string ProtoMessage::dump() const {
@@ -89,5 +19,554 @@ std::string ProtoMessage::dump() const {
}
#endif
// Helper to get vector size for any repeated field type
static inline size_t get_vector_size(ProtoFieldType type, const void *field_addr) {
switch (type) {
case ProtoFieldType::TYPE_BOOL:
return static_cast<const std::vector<bool> *>(field_addr)->size();
case ProtoFieldType::TYPE_INT32:
case ProtoFieldType::TYPE_SINT32:
case ProtoFieldType::TYPE_SFIXED32:
return static_cast<const std::vector<int32_t> *>(field_addr)->size();
case ProtoFieldType::TYPE_UINT32:
case ProtoFieldType::TYPE_ENUM:
case ProtoFieldType::TYPE_FIXED32:
return static_cast<const std::vector<uint32_t> *>(field_addr)->size();
case ProtoFieldType::TYPE_INT64:
case ProtoFieldType::TYPE_SINT64:
return static_cast<const std::vector<int64_t> *>(field_addr)->size();
case ProtoFieldType::TYPE_UINT64:
return static_cast<const std::vector<uint64_t> *>(field_addr)->size();
case ProtoFieldType::TYPE_FLOAT:
return static_cast<const std::vector<float> *>(field_addr)->size();
case ProtoFieldType::TYPE_STRING:
case ProtoFieldType::TYPE_BYTES:
return static_cast<const std::vector<std::string> *>(field_addr)->size();
default:
return 0;
}
}
// Helper to get a pointer to the nth element in a vector (const version)
static inline const void *get_vector_element(ProtoFieldType type, const void *field_addr, size_t index) {
switch (type) {
case ProtoFieldType::TYPE_BOOL: {
// std::vector<bool> is special - we need to handle it differently
static bool temp_bool;
temp_bool = (*static_cast<const std::vector<bool> *>(field_addr))[index];
return &temp_bool;
}
case ProtoFieldType::TYPE_INT32:
case ProtoFieldType::TYPE_SINT32:
case ProtoFieldType::TYPE_SFIXED32:
return &(*static_cast<const std::vector<int32_t> *>(field_addr))[index];
case ProtoFieldType::TYPE_UINT32:
case ProtoFieldType::TYPE_ENUM:
case ProtoFieldType::TYPE_FIXED32:
return &(*static_cast<const std::vector<uint32_t> *>(field_addr))[index];
case ProtoFieldType::TYPE_INT64:
case ProtoFieldType::TYPE_SINT64:
return &(*static_cast<const std::vector<int64_t> *>(field_addr))[index];
case ProtoFieldType::TYPE_UINT64:
return &(*static_cast<const std::vector<uint64_t> *>(field_addr))[index];
case ProtoFieldType::TYPE_FLOAT:
return &(*static_cast<const std::vector<float> *>(field_addr))[index];
case ProtoFieldType::TYPE_STRING:
case ProtoFieldType::TYPE_BYTES:
return &(*static_cast<const std::vector<std::string> *>(field_addr))[index];
default:
return nullptr;
}
}
// Unified encode function that works for both single fields and repeated fields
static inline void encode_field(ProtoWriteBuffer &buffer, ProtoFieldType type, uint8_t field_num,
const void *field_addr, bool force) {
switch (type) {
case ProtoFieldType::TYPE_BOOL:
buffer.encode_bool(field_num, *static_cast<const bool *>(field_addr), force);
break;
case ProtoFieldType::TYPE_INT32:
buffer.encode_int32(field_num, *static_cast<const int32_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_UINT32:
case ProtoFieldType::TYPE_ENUM:
buffer.encode_uint32(field_num, *static_cast<const uint32_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_INT64:
buffer.encode_int64(field_num, *static_cast<const int64_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_UINT64:
buffer.encode_uint64(field_num, *static_cast<const uint64_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_SINT32:
buffer.encode_sint32(field_num, *static_cast<const int32_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_SINT64:
buffer.encode_sint64(field_num, *static_cast<const int64_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_STRING:
buffer.encode_string(field_num, *static_cast<const std::string *>(field_addr), force);
break;
case ProtoFieldType::TYPE_BYTES: {
const auto *str = static_cast<const std::string *>(field_addr);
buffer.encode_bytes(field_num, reinterpret_cast<const uint8_t *>(str->data()), str->size(), force);
break;
}
case ProtoFieldType::TYPE_FLOAT:
buffer.encode_float(field_num, *static_cast<const float *>(field_addr), force);
break;
case ProtoFieldType::TYPE_FIXED32:
buffer.encode_fixed32(field_num, *static_cast<const uint32_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_SFIXED32:
buffer.encode_sfixed32(field_num, *static_cast<const int32_t *>(field_addr), force);
break;
default:
break;
}
}
// Unified size calculation
static inline void calculate_field_size(uint32_t &total_size, ProtoFieldType type, uint8_t precalc_size,
const void *field_addr, bool force) {
switch (type) {
case ProtoFieldType::TYPE_BOOL:
ProtoSize::add_bool_field(total_size, precalc_size, *static_cast<const bool *>(field_addr), force);
break;
case ProtoFieldType::TYPE_INT32:
ProtoSize::add_int32_field(total_size, precalc_size, *static_cast<const int32_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_UINT32:
case ProtoFieldType::TYPE_ENUM:
ProtoSize::add_uint32_field(total_size, precalc_size, *static_cast<const uint32_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_INT64:
ProtoSize::add_int64_field(total_size, precalc_size, *static_cast<const int64_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_UINT64:
ProtoSize::add_uint64_field(total_size, precalc_size, *static_cast<const uint64_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_SINT32:
ProtoSize::add_sint32_field(total_size, precalc_size, *static_cast<const int32_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_SINT64:
ProtoSize::add_sint64_field(total_size, precalc_size, *static_cast<const int64_t *>(field_addr), force);
break;
case ProtoFieldType::TYPE_STRING:
case ProtoFieldType::TYPE_BYTES:
ProtoSize::add_string_field(total_size, precalc_size, *static_cast<const std::string *>(field_addr), force);
break;
case ProtoFieldType::TYPE_FLOAT:
case ProtoFieldType::TYPE_FIXED32:
case ProtoFieldType::TYPE_SFIXED32: {
// All 32-bit fixed types use the same size calculation
// Check if the 4-byte value is non-zero by treating as uint32_t
uint32_t val = *static_cast<const uint32_t *>(field_addr);
ProtoSize::add_fixed_field<4>(total_size, precalc_size, val != 0, force);
break;
}
default:
break;
}
}
// Decode varint for single fields
static inline bool decode_varint_field(ProtoFieldType type, void *field_addr, const ProtoVarInt &value) {
switch (type) {
case ProtoFieldType::TYPE_BOOL:
*static_cast<bool *>(field_addr) = value.as_bool();
return true;
case ProtoFieldType::TYPE_INT32:
*static_cast<int32_t *>(field_addr) = value.as_int32();
return true;
case ProtoFieldType::TYPE_UINT32:
case ProtoFieldType::TYPE_ENUM:
*static_cast<uint32_t *>(field_addr) = value.as_uint32();
return true;
case ProtoFieldType::TYPE_INT64:
*static_cast<int64_t *>(field_addr) = value.as_int64();
return true;
case ProtoFieldType::TYPE_UINT64:
*static_cast<uint64_t *>(field_addr) = value.as_uint64();
return true;
case ProtoFieldType::TYPE_SINT32:
*static_cast<int32_t *>(field_addr) = value.as_sint32();
return true;
case ProtoFieldType::TYPE_SINT64:
*static_cast<int64_t *>(field_addr) = value.as_sint64();
return true;
default:
return false;
}
}
// Decode varint for repeated fields
static inline bool decode_repeated_varint_field(ProtoFieldType type, void *field_addr, const ProtoVarInt &value) {
switch (type) {
case ProtoFieldType::TYPE_BOOL:
static_cast<std::vector<bool> *>(field_addr)->push_back(value.as_bool());
return true;
case ProtoFieldType::TYPE_INT32:
static_cast<std::vector<int32_t> *>(field_addr)->push_back(value.as_int32());
return true;
case ProtoFieldType::TYPE_UINT32:
case ProtoFieldType::TYPE_ENUM:
static_cast<std::vector<uint32_t> *>(field_addr)->push_back(value.as_uint32());
return true;
case ProtoFieldType::TYPE_INT64:
static_cast<std::vector<int64_t> *>(field_addr)->push_back(value.as_int64());
return true;
case ProtoFieldType::TYPE_UINT64:
static_cast<std::vector<uint64_t> *>(field_addr)->push_back(value.as_uint64());
return true;
case ProtoFieldType::TYPE_SINT32:
static_cast<std::vector<int32_t> *>(field_addr)->push_back(value.as_sint32());
return true;
case ProtoFieldType::TYPE_SINT64:
static_cast<std::vector<int64_t> *>(field_addr)->push_back(value.as_sint64());
return true;
default:
return false;
}
}
// Decode 32-bit for single fields
static inline bool decode_32bit_field(ProtoFieldType type, void *field_addr, const Proto32Bit &value) {
switch (type) {
case ProtoFieldType::TYPE_FLOAT:
*static_cast<float *>(field_addr) = value.as_float();
return true;
case ProtoFieldType::TYPE_FIXED32:
*static_cast<uint32_t *>(field_addr) = value.as_fixed32();
return true;
case ProtoFieldType::TYPE_SFIXED32:
*static_cast<int32_t *>(field_addr) = value.as_sfixed32();
return true;
default:
return false;
}
}
// Decode 32-bit for repeated fields
static inline bool decode_repeated_32bit_field(ProtoFieldType type, void *field_addr, const Proto32Bit &value) {
switch (type) {
case ProtoFieldType::TYPE_FLOAT:
static_cast<std::vector<float> *>(field_addr)->push_back(value.as_float());
return true;
case ProtoFieldType::TYPE_FIXED32:
static_cast<std::vector<uint32_t> *>(field_addr)->push_back(value.as_fixed32());
return true;
case ProtoFieldType::TYPE_SFIXED32:
static_cast<std::vector<int32_t> *>(field_addr)->push_back(value.as_sfixed32());
return true;
default:
return false;
}
}
// Decode length-delimited for single fields
static inline bool decode_length_field(ProtoFieldType type, void *field_addr, const ProtoLengthDelimited &value,
uint8_t message_type_id) {
switch (type) {
case ProtoFieldType::TYPE_STRING:
case ProtoFieldType::TYPE_BYTES:
*static_cast<std::string *>(field_addr) = value.as_string();
return true;
case ProtoFieldType::TYPE_MESSAGE:
if (message_type_id < MESSAGE_HANDLER_COUNT && MESSAGE_HANDLERS[message_type_id].decode != nullptr) {
return MESSAGE_HANDLERS[message_type_id].decode(field_addr, value);
}
return false;
default:
return false;
}
}
// Decode length-delimited for repeated fields
static inline bool decode_repeated_length_field(ProtoFieldType type, void *field_addr,
const ProtoLengthDelimited &value, uint8_t message_type_id) {
switch (type) {
case ProtoFieldType::TYPE_STRING:
case ProtoFieldType::TYPE_BYTES:
static_cast<std::vector<std::string> *>(field_addr)->push_back(value.as_string());
return true;
case ProtoFieldType::TYPE_MESSAGE:
if (message_type_id < REPEATED_MESSAGE_HANDLER_COUNT &&
REPEATED_MESSAGE_HANDLERS[message_type_id].decode != nullptr) {
return REPEATED_MESSAGE_HANDLERS[message_type_id].decode(field_addr, value);
}
return false;
default:
return false;
}
}
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
uint8_t *base = reinterpret_cast<uint8_t *>(this);
// Get metadata
const FieldMeta *fields = get_field_metadata();
uint8_t field_count = get_field_count();
const RepeatedFieldMeta *repeated_fields = get_repeated_field_metadata();
uint8_t repeated_count = get_repeated_field_count();
// Handle empty message
if (field_count == 0 && repeated_count == 0) {
if (length > 0) {
ESP_LOGW(TAG, "Received data for message with no fields");
}
return;
}
uint32_t i = 0;
while (i < length) {
// Parse field tag
uint32_t consumed;
auto tag_res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!tag_res.has_value()) {
ESP_LOGV(TAG, "Invalid field tag at position %u", i);
break;
}
uint32_t tag = tag_res->as_uint32();
uint32_t field_id = tag >> 3;
uint32_t wire_type = tag & 0x07;
i += consumed;
bool decoded = false;
switch (wire_type) {
case 0: { // Varint
auto value_res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!value_res.has_value()) {
ESP_LOGV(TAG, "Invalid varint at position %u", i);
return;
}
ProtoVarInt value = *value_res;
// Try regular fields first using binary search
if (const FieldMeta *field = find_field_binary(fields, field_count, field_id)) {
decoded = decode_varint_field(field->get_type(), base + field->get_offset(), value);
}
// If not found, try repeated fields (linear search - usually only 1-2 fields)
if (!decoded) {
for (uint8_t j = 0; j < repeated_count; j++) {
if (repeated_fields[j].field_num == field_id) {
decoded = decode_repeated_varint_field(repeated_fields[j].get_type(),
base + repeated_fields[j].get_offset(), value);
break;
}
}
}
i += consumed;
break;
}
case 2: { // Length-delimited
auto len_res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!len_res.has_value()) {
ESP_LOGV(TAG, "Invalid length at position %u", i);
return;
}
uint32_t field_length = len_res->as_uint32();
i += consumed;
if (i + field_length > length) {
ESP_LOGV(TAG, "Length-delimited field exceeds buffer at position %u", i);
return;
}
ProtoLengthDelimited value(&buffer[i], field_length);
// Try regular fields first using binary search
if (const FieldMeta *field = find_field_binary(fields, field_count, field_id)) {
decoded =
decode_length_field(field->get_type(), base + field->get_offset(), value, field->get_message_type_id());
}
// If not found, try repeated fields (linear search - usually only 1-2 fields)
if (!decoded) {
for (uint8_t j = 0; j < repeated_count; j++) {
if (repeated_fields[j].field_num == field_id) {
decoded =
decode_repeated_length_field(repeated_fields[j].get_type(), base + repeated_fields[j].get_offset(),
value, repeated_fields[j].get_message_type_id());
break;
}
}
}
i += field_length;
break;
}
case 5: { // 32-bit
if (i + 4 > length) {
ESP_LOGV(TAG, "32-bit field exceeds buffer at position %u", i);
return;
}
uint32_t raw = 0;
raw |= uint32_t(buffer[i]) << 0;
raw |= uint32_t(buffer[i + 1]) << 8;
raw |= uint32_t(buffer[i + 2]) << 16;
raw |= uint32_t(buffer[i + 3]) << 24;
Proto32Bit value(raw);
// Try regular fields first using binary search
if (const FieldMeta *field = find_field_binary(fields, field_count, field_id)) {
decoded = decode_32bit_field(field->get_type(), base + field->get_offset(), value);
}
// If not found, try repeated fields (linear search - usually only 1-2 fields)
if (!decoded) {
for (uint8_t j = 0; j < repeated_count; j++) {
if (repeated_fields[j].field_num == field_id) {
decoded = decode_repeated_32bit_field(repeated_fields[j].get_type(),
base + repeated_fields[j].get_offset(), value);
break;
}
}
}
i += 4;
break;
}
default:
ESP_LOGV(TAG, "Unknown wire type %u at position %u", wire_type, i);
return;
}
if (!decoded) {
ESP_LOGV(TAG, "Skipping unknown field %u with wire type %u", field_id, wire_type);
}
}
}
void ProtoMessage::encode(ProtoWriteBuffer buffer) const {
const uint8_t *base = reinterpret_cast<const uint8_t *>(this);
// Encode regular fields
uint8_t field_count = get_field_count();
if (field_count) {
const FieldMeta *fields = get_field_metadata();
for (uint8_t i = 0; i < field_count; i++) {
const void *field_addr = base + fields[i].get_offset();
if (fields[i].get_type() == ProtoFieldType::TYPE_MESSAGE) {
uint8_t handler_id = fields[i].get_message_type_id();
if (handler_id < MESSAGE_HANDLER_COUNT && MESSAGE_HANDLERS[handler_id].encode != nullptr) {
MESSAGE_HANDLERS[handler_id].encode(buffer, field_addr, fields[i].field_num);
}
} else {
encode_field(buffer, fields[i].get_type(), fields[i].field_num, field_addr, false);
}
}
}
// Encode repeated fields - reuse the same encode_field function!
uint8_t repeated_count = get_repeated_field_count();
if (repeated_count == 0) {
return; // No repeated fields to process
}
const RepeatedFieldMeta *repeated_fields = get_repeated_field_metadata();
for (uint8_t i = 0; i < repeated_count; i++) {
const void *field_addr = base + repeated_fields[i].get_offset();
if (repeated_fields[i].get_type() == ProtoFieldType::TYPE_MESSAGE) {
uint8_t handler_id = repeated_fields[i].get_message_type_id();
if (handler_id < REPEATED_MESSAGE_HANDLER_COUNT && REPEATED_MESSAGE_HANDLERS[handler_id].encode != nullptr) {
REPEATED_MESSAGE_HANDLERS[handler_id].encode(buffer, field_addr, repeated_fields[i].field_num);
}
} else {
// Early exit for empty vectors
size_t count = get_vector_size(repeated_fields[i].get_type(), field_addr);
if (count == 0) {
continue;
}
// Iterate through the vector and encode each element using the same function!
for (size_t j = 0; j < count; j++) {
const void *element = get_vector_element(repeated_fields[i].get_type(), field_addr, j);
if (element != nullptr) {
encode_field(buffer, repeated_fields[i].get_type(), repeated_fields[i].field_num, element, true);
}
}
}
}
}
void ProtoMessage::calculate_size(uint32_t &total_size) const {
const uint8_t *base = reinterpret_cast<const uint8_t *>(this);
// Calculate size for regular fields
uint8_t field_count = get_field_count();
if (field_count) {
const FieldMeta *fields = get_field_metadata();
for (uint8_t i = 0; i < field_count; i++) {
const void *field_addr = base + fields[i].get_offset();
if (fields[i].get_type() == ProtoFieldType::TYPE_MESSAGE) {
uint8_t handler_id = fields[i].get_message_type_id();
if (handler_id < MESSAGE_HANDLER_COUNT && MESSAGE_HANDLERS[handler_id].size != nullptr) {
MESSAGE_HANDLERS[handler_id].size(total_size, field_addr, fields[i].get_precalced_size(), false);
}
} else {
calculate_field_size(total_size, fields[i].get_type(), fields[i].get_precalced_size(), field_addr, false);
}
}
}
// Calculate size for repeated fields - reuse the same calculate_field_size function!
uint8_t repeated_count = get_repeated_field_count();
if (repeated_count == 0) {
return; // No repeated fields to process
}
const RepeatedFieldMeta *repeated_fields = get_repeated_field_metadata();
for (uint8_t i = 0; i < repeated_count; i++) {
const void *field_addr = base + repeated_fields[i].get_offset();
if (repeated_fields[i].get_type() == ProtoFieldType::TYPE_MESSAGE) {
uint8_t handler_id = repeated_fields[i].get_message_type_id();
if (handler_id < REPEATED_MESSAGE_HANDLER_COUNT && REPEATED_MESSAGE_HANDLERS[handler_id].size != nullptr) {
REPEATED_MESSAGE_HANDLERS[handler_id].size(total_size, field_addr, repeated_fields[i].get_precalced_size());
}
} else {
// Special optimization for fixed-size types
ProtoFieldType type = repeated_fields[i].get_type();
size_t count = get_vector_size(type, field_addr);
// Early exit for empty vectors
if (count == 0) {
continue;
}
// For fixed-size types, we can calculate size more efficiently
if (type == ProtoFieldType::TYPE_FIXED32 || type == ProtoFieldType::TYPE_SFIXED32 ||
type == ProtoFieldType::TYPE_FLOAT) {
total_size += count * (repeated_fields[i].get_precalced_size() + 4);
} else if (type == ProtoFieldType::TYPE_BOOL) {
// Booleans are always 1 byte when encoded
total_size += count * (repeated_fields[i].get_precalced_size() + 1);
} else {
// For variable-size types, calculate each element
for (size_t j = 0; j < count; j++) {
const void *element = get_vector_element(type, field_addr, j);
if (element != nullptr) {
calculate_field_size(total_size, type, repeated_fields[i].get_precalced_size(), element, true);
}
}
}
}
}
}
} // namespace api
} // namespace esphome

View File

@@ -13,6 +13,64 @@
namespace esphome {
namespace api {
// Forward declarations
class ProtoWriteBuffer;
// Enum for all supported field types
enum class ProtoFieldType : uint8_t {
// Varint types (wire type 0)
TYPE_BOOL = 0,
TYPE_INT32 = 1,
TYPE_UINT32 = 2,
TYPE_INT64 = 3,
TYPE_UINT64 = 4,
TYPE_SINT32 = 5,
TYPE_SINT64 = 6,
TYPE_ENUM = 7,
// Length-delimited types (wire type 2)
TYPE_STRING = 8,
TYPE_BYTES = 9,
TYPE_MESSAGE = 10,
// 32-bit types (wire type 5)
TYPE_FLOAT = 11,
TYPE_FIXED32 = 12,
TYPE_SFIXED32 = 13,
};
// Helper to get wire type from field type
inline constexpr uint8_t get_wire_type(ProtoFieldType type) {
switch (type) {
case ProtoFieldType::TYPE_BOOL:
case ProtoFieldType::TYPE_INT32:
case ProtoFieldType::TYPE_UINT32:
case ProtoFieldType::TYPE_INT64:
case ProtoFieldType::TYPE_UINT64:
case ProtoFieldType::TYPE_SINT32:
case ProtoFieldType::TYPE_SINT64:
case ProtoFieldType::TYPE_ENUM:
return 0; // varint
case ProtoFieldType::TYPE_STRING:
case ProtoFieldType::TYPE_BYTES:
case ProtoFieldType::TYPE_MESSAGE:
return 2; // length-delimited
case ProtoFieldType::TYPE_FLOAT:
case ProtoFieldType::TYPE_FIXED32:
case ProtoFieldType::TYPE_SFIXED32:
return 5; // 32-bit
}
return 0;
}
// Macro to calculate field offset without triggering -Winvalid-offsetof
// This uses the same approach as offsetof but with explicit reinterpret_cast
#define PROTO_FIELD_OFFSET(Type, Member) \
static_cast<uint16_t>(reinterpret_cast<size_t>(&reinterpret_cast<Type *>(16)->Member) - \
16) /* NOLINT(bugprone-macro-parentheses) */
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
class ProtoVarInt {
public:
@@ -55,20 +113,20 @@ class ProtoVarInt {
return {}; // Incomplete or invalid varint
}
uint16_t as_uint16() const { return this->value_; }
uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; }
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
int32_t as_int32() const {
inline uint16_t as_uint16() const { return this->value_; }
inline uint32_t as_uint32() const { return this->value_; }
inline uint64_t as_uint64() const { return this->value_; }
inline bool as_bool() const { return this->value_; }
template<typename T> inline T as_enum() const { return static_cast<T>(this->as_uint32()); }
inline int32_t as_int32() const {
// Not ZigZag encoded
return static_cast<int32_t>(this->as_int64());
}
int64_t as_int64() const {
inline int64_t as_int64() const {
// Not ZigZag encoded
return static_cast<int64_t>(this->value_);
}
int32_t as_sint32() const {
inline int32_t as_sint32() const {
// with ZigZag encoding
if (this->value_ & 1) {
return static_cast<int32_t>(~(this->value_ >> 1));
@@ -76,7 +134,7 @@ class ProtoVarInt {
return static_cast<int32_t>(this->value_ >> 1);
}
}
int64_t as_sint64() const {
inline int64_t as_sint64() const {
// with ZigZag encoding
if (this->value_ & 1) {
return static_cast<int64_t>(~(this->value_ >> 1));
@@ -118,6 +176,18 @@ class ProtoVarInt {
out.push_back(val);
return;
}
// Optimize for 2-byte varints (0x80-0x3FFF)
if (val <= 0x3FFF) {
out.push_back((val & 0x7F) | 0x80);
out.push_back(val >> 7);
return;
}
// For 3+ byte varints, write first 2 bytes directly
out.push_back((val & 0x7F) | 0x80);
out.push_back(((val >> 7) & 0x7F) | 0x80);
val >>= 14;
// Continue with remaining bytes
while (val) {
uint8_t temp = val & 0x7F;
val >>= 7;
@@ -136,8 +206,10 @@ class ProtoVarInt {
class ProtoLengthDelimited {
public:
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
template<class C> C as_message() const {
inline std::string as_string() const {
return std::string(reinterpret_cast<const char *>(this->value_), this->length_);
}
template<class C> inline C as_message() const {
auto msg = C();
msg.decode(this->value_, this->length_);
return msg;
@@ -151,9 +223,9 @@ class ProtoLengthDelimited {
class Proto32Bit {
public:
explicit Proto32Bit(uint32_t value) : value_(value) {}
uint32_t as_fixed32() const { return this->value_; }
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
float as_float() const {
inline uint32_t as_fixed32() const { return this->value_; }
inline int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
inline float as_float() const {
union {
uint32_t raw;
float value;
@@ -166,30 +238,116 @@ class Proto32Bit {
const uint32_t value_;
};
class Proto64Bit {
public:
explicit Proto64Bit(uint64_t value) : value_(value) {}
uint64_t as_fixed64() const { return this->value_; }
int64_t as_sfixed64() const { return static_cast<int64_t>(this->value_); }
double as_double() const {
union {
uint64_t raw;
double value;
} s{};
s.raw = this->value_;
return s.value;
// Function pointer types used by V2 structures
using EncodeFunc = void (*)(ProtoWriteBuffer &, const void *field_ptr, uint8_t field_num);
using SizeFunc = void (*)(uint32_t &total_size, const void *field_ptr, uint8_t precalced_field_id_size, bool force);
using DecodeLengthFunc = bool (*)(void *field_ptr, ProtoLengthDelimited value);
// Function pointer types for repeated fields used by V2 structures
using RepeatedEncodeFunc = void (*)(ProtoWriteBuffer &, const void *field_ptr, uint8_t field_num);
using RepeatedSizeFunc = void (*)(uint32_t &total_size, const void *field_ptr, uint8_t precalced_field_id_size);
using RepeatedDecodeLengthFunc = bool (*)(void *field_ptr, ProtoLengthDelimited value);
// Message handler registry entry
struct MessageHandler {
EncodeFunc encode;
SizeFunc size;
DecodeLengthFunc decode;
};
// Repeated message handler registry entry
struct RepeatedMessageHandler {
RepeatedEncodeFunc encode;
RepeatedSizeFunc size;
RepeatedDecodeLengthFunc decode;
};
// Global message handler registries (defined in api_pb2.cpp)
extern const MessageHandler MESSAGE_HANDLERS[];
extern const uint8_t MESSAGE_HANDLER_COUNT;
extern const RepeatedMessageHandler REPEATED_MESSAGE_HANDLERS[];
extern const uint8_t REPEATED_MESSAGE_HANDLER_COUNT;
// Optimized metadata structure (4 bytes - no padding on 32-bit architectures)
struct FieldMeta {
uint8_t field_num; // Protobuf field number (1-255)
uint8_t type_and_size; // bits 0-4: ProtoFieldType, bits 5-6: precalced_field_id_size-1, bit 7: reserved
union {
uint16_t offset; // For non-message types: offset in class (0-65535)
struct {
uint8_t offset_low; // For TYPE_MESSAGE: low byte of offset (bits 0-7)
uint8_t message_type_id; // For TYPE_MESSAGE: bits 0-1: offset high (bits 8-9), bits 2-7: handler index (0-63)
};
};
// Helper methods
inline ProtoFieldType get_type() const { return static_cast<ProtoFieldType>(type_and_size & 0x1F); }
inline uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; }
inline uint16_t get_offset() const {
if (get_type() == ProtoFieldType::TYPE_MESSAGE) {
// Reconstruct full offset from packed fields (10-bit offset)
// Bits 0-7 from offset_low, bits 8-9 from lower 2 bits of message_type_id
return static_cast<uint16_t>(offset_low) | (static_cast<uint16_t>(message_type_id & 0x03) << 8);
}
return offset;
}
inline uint8_t get_message_type_id() const { return message_type_id >> 2; } // Upper 6 bits for type ID (0-63)
};
// Optimized repeated field metadata (4 bytes - no padding on 32-bit architectures)
struct RepeatedFieldMeta {
uint8_t field_num; // Protobuf field number (1-255)
uint8_t type_and_size; // bits 0-4: ProtoFieldType, bits 5-6: precalced_field_id_size-1, bit 7: reserved
union {
uint16_t offset; // For non-message types: offset in class (0-65535)
struct {
uint8_t offset_low; // For TYPE_MESSAGE: low byte of offset (bits 0-7)
uint8_t message_type_id; // For TYPE_MESSAGE: bits 0-1: offset high (bits 8-9), bits 2-7: handler index (0-63)
};
};
// Helper methods
inline ProtoFieldType get_type() const { return static_cast<ProtoFieldType>(type_and_size & 0x1F); }
inline uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; }
inline uint16_t get_offset() const {
if (get_type() == ProtoFieldType::TYPE_MESSAGE) {
// Reconstruct full offset from packed fields (10-bit offset)
// Bits 0-7 from offset_low, bits 8-9 from lower 2 bits of message_type_id
return static_cast<uint16_t>(offset_low) | (static_cast<uint16_t>(message_type_id & 0x03) << 8);
}
return offset;
}
inline uint8_t get_message_type_id() const { return message_type_id >> 2; } // Upper 6 bits for type ID (0-63)
};
// Binary search for field lookup - optimized for performance
inline const FieldMeta *find_field_binary(const FieldMeta *fields, uint8_t count, uint8_t field_id) {
uint8_t left = 0;
uint8_t right = count;
while (left < right) {
uint8_t mid = (left + right) / 2;
uint8_t mid_field = fields[mid].field_num;
if (mid_field < field_id) {
left = mid + 1;
} else if (mid_field > field_id) {
right = mid;
} else {
// Found field_id
return &fields[mid];
}
}
protected:
const uint64_t value_;
};
return nullptr; // Field not found
}
class ProtoWriteBuffer {
public:
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
void write(uint8_t value) { this->buffer_->push_back(value); }
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
inline void write(uint8_t value) { this->buffer_->push_back(value); }
inline void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
inline void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
/**
* Encode a field key (tag/wire type combination).
*
@@ -202,11 +360,11 @@ class ProtoWriteBuffer {
*
* Following https://protobuf.dev/programming-guides/encoding/#structure
*/
void encode_field_raw(uint32_t field_id, uint32_t type) {
inline void encode_field_raw(uint32_t field_id, uint32_t type) {
uint32_t val = (field_id << 3) | (type & 0b111);
this->encode_varint_raw(val);
}
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
inline void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
if (len == 0 && !force)
return;
@@ -215,25 +373,25 @@ class ProtoWriteBuffer {
auto *data = reinterpret_cast<const uint8_t *>(string);
this->buffer_->insert(this->buffer_->end(), data, data + len);
}
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
inline void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
this->encode_string(field_id, value.data(), value.size(), force);
}
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
inline void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
}
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
inline void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 0); // type 0: Varint - uint32
this->encode_varint_raw(value);
}
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
inline void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 0); // type 0: Varint - uint64
this->encode_varint_raw(ProtoVarInt(value));
}
void encode_bool(uint32_t field_id, bool value, bool force = false) {
inline void encode_bool(uint32_t field_id, bool value, bool force = false) {
if (!value && !force)
return;
this->encode_field_raw(field_id, 0); // type 0: Varint - bool
@@ -244,29 +402,15 @@ class ProtoWriteBuffer {
return;
this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32
this->write((value >> 0) & 0xFF);
this->write((value >> 8) & 0xFF);
this->write((value >> 16) & 0xFF);
this->write((value >> 24) & 0xFF);
// Reserve space and write all 4 bytes at once for better performance
size_t pos = this->buffer_->size();
this->buffer_->resize(pos + 4);
(*this->buffer_)[pos] = (value >> 0) & 0xFF;
(*this->buffer_)[pos + 1] = (value >> 8) & 0xFF;
(*this->buffer_)[pos + 2] = (value >> 16) & 0xFF;
(*this->buffer_)[pos + 3] = (value >> 24) & 0xFF;
}
void encode_fixed64(uint32_t field_id, uint64_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 1); // type 1: 64-bit fixed64
this->write((value >> 0) & 0xFF);
this->write((value >> 8) & 0xFF);
this->write((value >> 16) & 0xFF);
this->write((value >> 24) & 0xFF);
this->write((value >> 32) & 0xFF);
this->write((value >> 40) & 0xFF);
this->write((value >> 48) & 0xFF);
this->write((value >> 56) & 0xFF);
}
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
}
void encode_float(uint32_t field_id, float value, bool force = false) {
inline void encode_float(uint32_t field_id, float value, bool force = false) {
if (value == 0.0f && !force)
return;
@@ -277,7 +421,7 @@ class ProtoWriteBuffer {
val.value = value;
this->encode_fixed32(field_id, val.raw);
}
void encode_int32(uint32_t field_id, int32_t value, bool force = false) {
inline void encode_int32(uint32_t field_id, int32_t value, bool force = false) {
if (value < 0) {
// negative int32 is always 10 byte long
this->encode_int64(field_id, value, force);
@@ -285,10 +429,10 @@ class ProtoWriteBuffer {
}
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
}
void encode_int64(uint32_t field_id, int64_t value, bool force = false) {
inline void encode_int64(uint32_t field_id, int64_t value, bool force = false) {
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
}
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
inline void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
uint32_t uvalue;
if (value < 0) {
uvalue = ~(value << 1);
@@ -297,7 +441,7 @@ class ProtoWriteBuffer {
}
this->encode_uint32(field_id, uvalue, force);
}
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
inline void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
uint64_t uvalue;
if (value < 0) {
uvalue = ~(value << 1);
@@ -306,6 +450,11 @@ class ProtoWriteBuffer {
}
this->encode_uint64(field_id, uvalue, force);
}
inline void encode_sfixed32(uint32_t field_id, int32_t value, bool force = false) {
if (!force && value == 0)
return;
this->encode_fixed32(field_id, static_cast<uint32_t>(value), force);
}
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
size_t begin = this->buffer_->size();
@@ -327,11 +476,18 @@ class ProtoWriteBuffer {
class ProtoMessage {
public:
virtual ~ProtoMessage() = default;
// Default implementation for messages with no fields
virtual void encode(ProtoWriteBuffer buffer) const {}
// Metadata getters - must be implemented by derived classes
virtual const FieldMeta *get_field_metadata() const { return nullptr; }
virtual uint8_t get_field_count() const { return 0; }
virtual const RepeatedFieldMeta *get_repeated_field_metadata() const { return nullptr; }
virtual uint8_t get_repeated_field_count() const { return 0; }
// Encode/decode/calculate_size using V2 metadata (will check for V3 first)
void encode(ProtoWriteBuffer buffer) const;
void decode(const uint8_t *buffer, size_t length);
// Default implementation for messages with no fields
virtual void calculate_size(uint32_t &total_size) const {}
void calculate_size(uint32_t &total_size) const;
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const;
virtual void dump_to(std::string &out) const = 0;
@@ -339,10 +495,6 @@ class ProtoMessage {
#endif
protected:
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; }
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
};
template<typename T> const char *proto_enum_to_string(T value);
@@ -363,11 +515,11 @@ class ProtoService {
* @return A ProtoWriteBuffer object with the reserved size.
*/
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
// Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint16_t message_type) {
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
uint32_t msg_size = 0;
msg.calculate_size(msg_size);
@@ -401,6 +553,17 @@ class ProtoService {
return true;
}
};
template<typename MessageType>
void encode_repeated_message_field(ProtoWriteBuffer &buffer, const void *field_ptr, uint8_t field_num);
template<typename MessageType>
void size_repeated_message_field(uint32_t &total_size, const void *field_ptr, uint8_t precalced_field_id_size);
template<typename MessageType>
void encode_message_field(ProtoWriteBuffer &buffer, const void *field_ptr, uint8_t field_num);
template<typename MessageType>
void size_message_field(uint32_t &total_size, const void *field_ptr, uint8_t precalced_field_id_size, bool force);
} // namespace api
} // namespace esphome

View File

@@ -0,0 +1,53 @@
#pragma once
#include "proto.h"
#include "api_pb2_size.h"
namespace esphome {
namespace api {
// Template functions for message field handling (regular fields)
template<typename MessageType>
inline void encode_message_field(ProtoWriteBuffer &buffer, const void *field_ptr, uint8_t field_num) {
const MessageType *msg = static_cast<const MessageType *>(field_ptr);
buffer.encode_message<MessageType>(field_num, *msg);
}
template<typename MessageType>
inline void size_message_field(uint32_t &total_size, const void *field_ptr, uint8_t precalced_field_id_size,
bool force) {
const MessageType *msg = static_cast<const MessageType *>(field_ptr);
ProtoSize::add_message_object(total_size, precalced_field_id_size, *msg, force);
}
template<typename MessageType> inline bool decode_message_field(void *field_ptr, ProtoLengthDelimited value) {
MessageType *msg = static_cast<MessageType *>(field_ptr);
*msg = value.as_message<MessageType>();
return true;
}
// Template repeated field functions (must be in header for instantiation)
template<typename MessageType>
inline void encode_repeated_message_field(ProtoWriteBuffer &buffer, const void *field_ptr, uint8_t field_num) {
const auto *vec = static_cast<const std::vector<MessageType> *>(field_ptr);
for (const auto &msg : *vec) {
buffer.encode_message<MessageType>(field_num, msg, true);
}
}
// Template size functions for repeated fields (must be in header for instantiation)
template<typename MessageType>
inline void size_repeated_message_field(uint32_t &total_size, const void *field_ptr, uint8_t precalced_field_id_size) {
const auto *vec = static_cast<const std::vector<MessageType> *>(field_ptr);
ProtoSize::add_repeated_message<MessageType>(total_size, precalced_field_id_size, *vec);
}
// Template decode functions for repeated fields
template<typename MessageType> inline bool decode_repeated_message_field(void *field_ptr, ProtoLengthDelimited value) {
auto *vec = static_cast<std::vector<MessageType> *>(field_ptr);
vec->push_back(value.as_message<MessageType>());
return true;
}
} // namespace api
} // namespace esphome

View File

@@ -5,6 +5,7 @@ import os
from pathlib import Path
import re
import subprocess
from typing import Any
from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE
from esphome.core import CORE, EsphomeError
@@ -104,7 +105,16 @@ def run_compile(config, verbose):
args = []
if CONF_COMPILE_PROCESS_LIMIT in config[CONF_ESPHOME]:
args += [f"-j{config[CONF_ESPHOME][CONF_COMPILE_PROCESS_LIMIT]}"]
return run_platformio_cli_run(config, verbose, *args)
result = run_platformio_cli_run(config, verbose, *args)
# Run memory analysis if enabled
if config.get(CONF_ESPHOME, {}).get("analyze_memory", False):
try:
analyze_memory_usage(config)
except Exception as e:
_LOGGER.warning("Failed to analyze memory usage: %s", e)
return result
def _run_idedata(config):
@@ -331,3 +341,93 @@ class IDEData:
return f"{self.cc_path[:-7]}addr2line.exe"
return f"{self.cc_path[:-3]}addr2line"
@property
def objdump_path(self) -> str:
# replace gcc at end with objdump
# Windows
if self.cc_path.endswith(".exe"):
return f"{self.cc_path[:-7]}objdump.exe"
return f"{self.cc_path[:-3]}objdump"
@property
def readelf_path(self) -> str:
# replace gcc at end with readelf
# Windows
if self.cc_path.endswith(".exe"):
return f"{self.cc_path[:-7]}readelf.exe"
return f"{self.cc_path[:-3]}readelf"
def analyze_memory_usage(config: dict[str, Any]) -> None:
"""Analyze memory usage by component after compilation."""
# Lazy import to avoid overhead when not needed
from esphome.analyze_memory import MemoryAnalyzer
idedata = get_idedata(config)
# Get paths to tools
elf_path = idedata.firmware_elf_path
objdump_path = idedata.objdump_path
readelf_path = idedata.readelf_path
# Debug logging
_LOGGER.debug("ELF path from idedata: %s", elf_path)
# Check if file exists
if not Path(elf_path).exists():
# Try alternate path
alt_path = Path(CORE.relative_build_path(".pioenvs", CORE.name, "firmware.elf"))
if alt_path.exists():
elf_path = str(alt_path)
_LOGGER.debug("Using alternate ELF path: %s", elf_path)
else:
_LOGGER.warning("ELF file not found at %s or %s", elf_path, alt_path)
return
# Extract external components from config
external_components = set()
# Get the list of built-in ESPHome components
from esphome.analyze_memory import get_esphome_components
builtin_components = get_esphome_components()
# Special non-component keys that appear in configs
NON_COMPONENT_KEYS = {
CONF_ESPHOME,
"substitutions",
"packages",
"globals",
"<<",
}
# Check all top-level keys in config
for key in config:
if key not in builtin_components and key not in NON_COMPONENT_KEYS:
# This is an external component
external_components.add(key)
_LOGGER.debug("Detected external components: %s", external_components)
# Create analyzer and run analysis
analyzer = MemoryAnalyzer(elf_path, objdump_path, readelf_path, external_components)
analyzer.analyze()
# Generate and print report
report = analyzer.generate_report()
_LOGGER.info("\n%s", report)
# Optionally save to file
if config.get(CONF_ESPHOME, {}).get("memory_report_file"):
report_file = Path(config[CONF_ESPHOME]["memory_report_file"])
if report_file.suffix == ".json":
report_file.write_text(analyzer.to_json())
_LOGGER.info("Memory report saved to %s", report_file)
else:
report_file.write_text(report)
_LOGGER.info("Memory report saved to %s", report_file)

File diff suppressed because it is too large Load Diff