mirror of
https://github.com/esphome/esphome.git
synced 2025-09-08 20:55:34 +00:00
Compare commits
73 Commits
2025.8.0b4
...
api_flash_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ac64dbfb8d | ||
![]() |
9e78b5b06f | ||
![]() |
be7550f5da | ||
![]() |
bde3d025dc | ||
![]() |
eb7f982b5b | ||
![]() |
c2657c1896 | ||
![]() |
037eff53b1 | ||
![]() |
a03a748a56 | ||
![]() |
d567533161 | ||
![]() |
28620190f0 | ||
![]() |
984c24a1b5 | ||
![]() |
f2037aadc5 | ||
![]() |
00e39958b8 | ||
![]() |
3570b888d2 | ||
![]() |
ac0f279e7e | ||
![]() |
701babbab4 | ||
![]() |
ce350642e4 | ||
![]() |
6ff19992da | ||
![]() |
58ff3a8abd | ||
![]() |
60babfc356 | ||
![]() |
5630720715 | ||
![]() |
0d5b353cdf | ||
![]() |
c745140835 | ||
![]() |
084ecf93a7 | ||
![]() |
e4ea6c5eee | ||
![]() |
3a94361c64 | ||
![]() |
4c188fb4d3 | ||
![]() |
949249cac3 | ||
![]() |
2cb0418c95 | ||
![]() |
d27a938f74 | ||
![]() |
5372bcb2b8 | ||
![]() |
dd35038771 | ||
![]() |
ae945c9a96 | ||
![]() |
b3bb059e42 | ||
![]() |
9a9aebe8b4 | ||
![]() |
0b2a889d0e | ||
![]() |
0c8693b8c6 | ||
![]() |
e24d4450ac | ||
![]() |
1818a56096 | ||
![]() |
27b0c99580 | ||
![]() |
22ac43e7eb | ||
![]() |
835c819e02 | ||
![]() |
79dbeef0c9 | ||
![]() |
6b0391a1f9 | ||
![]() |
ab4a21e84d | ||
![]() |
df72954c08 | ||
![]() |
44da11c66e | ||
![]() |
e437f41072 | ||
![]() |
23ed9db40f | ||
![]() |
921974ec23 | ||
![]() |
95afae4830 | ||
![]() |
163a5747f0 | ||
![]() |
78a82ed46e | ||
![]() |
33fb4d5d42 | ||
![]() |
7f7623cc8d | ||
![]() |
f2e914fb94 | ||
![]() |
1a0943c960 | ||
![]() |
073590124d | ||
![]() |
62c7f14d9a | ||
![]() |
b1553807f7 | ||
![]() |
797d4929ab | ||
![]() |
ba5bb9dfa7 | ||
![]() |
dd49d832c4 | ||
![]() |
5004f44f65 | ||
![]() |
bc9c4a8b8e | ||
![]() |
6f05ee7427 | ||
![]() |
f3523a96c9 | ||
![]() |
06957d9895 | ||
![]() |
1f361b07d1 | ||
![]() |
40d9c0a3db | ||
![]() |
548cd39496 | ||
![]() |
85049611c3 | ||
![]() |
b8a75bc925 |
@@ -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
1600
esphome/analyze_memory.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
53
esphome/components/api/proto_templates.h
Normal file
53
esphome/components/api/proto_templates.h
Normal 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
|
@@ -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
Reference in New Issue
Block a user