From f8e8295088bf8bec6d3a3f8dddad14d11b8efb35 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 24 Sep 2026 16:14:25 +0100 Subject: [PATCH 1/5] [api] Decode without a vtable (#19029) --- esphome/components/api/api_pb2.cpp | 890 +++++++++++------- esphome/components/api/api_pb2.h | 294 ++++-- esphome/components/api/proto.cpp | 4 +- esphome/components/api/proto.h | 24 +- script/api_protobuf/api_protobuf.py | 61 +- .../api/test_api_protobuf_generator.py | 40 +- 6 files changed, 891 insertions(+), 422 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 5afce51074..9e4000b55b 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -7,17 +7,18 @@ namespace esphome::api { -void HelloRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void HelloRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->client_info = StringRef(value.data(), value.size()); + msg.client_info = StringRef(value.data(), value.size()); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->api_version_major = value.as_varint(); + msg.api_version_major = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->api_version_minor = value.as_varint(); + msg.api_version_minor = value.as_varint(); break; } } @@ -39,11 +40,12 @@ uint32_t HelloResponse::calc_size_msg(const void *self) { size += 2 + msg.name.size(); return size; } -void DisconnectRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void DisconnectRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->reason = static_cast(value.as_varint()); + msg.reason = static_cast(value.as_varint()); break; } } @@ -459,30 +461,31 @@ uint32_t CoverStateResponse::calc_size_msg(const void *self) { #endif return size; } -void CoverCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void CoverCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->has_position = value.as_bool(); + msg.has_position = value.as_bool(); break; case proto_tag(5, WIRE_TYPE_FIXED32): - this->position = value.as_float(); + msg.position = value.as_float(); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->has_tilt = value.as_bool(); + msg.has_tilt = value.as_bool(); break; case proto_tag(7, WIRE_TYPE_FIXED32): - this->tilt = value.as_float(); + msg.tilt = value.as_float(); break; case proto_tag(8, WIRE_TYPE_VARINT): - this->stop = value.as_bool(); + msg.stop = value.as_bool(); break; #ifdef USE_DEVICES case proto_tag(9, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -565,45 +568,46 @@ uint32_t FanStateResponse::calc_size_msg(const void *self) { #endif return size; } -void FanCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void FanCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->has_state = value.as_bool(); + msg.has_state = value.as_bool(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->state = value.as_bool(); + msg.state = value.as_bool(); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->has_oscillating = value.as_bool(); + msg.has_oscillating = value.as_bool(); break; case proto_tag(7, WIRE_TYPE_VARINT): - this->oscillating = value.as_bool(); + msg.oscillating = value.as_bool(); break; case proto_tag(8, WIRE_TYPE_VARINT): - this->has_direction = value.as_bool(); + msg.has_direction = value.as_bool(); break; case proto_tag(9, WIRE_TYPE_VARINT): - this->direction = static_cast(value.as_varint()); + msg.direction = static_cast(value.as_varint()); break; case proto_tag(10, WIRE_TYPE_VARINT): - this->has_speed_level = value.as_bool(); + msg.has_speed_level = value.as_bool(); break; case proto_tag(11, WIRE_TYPE_VARINT): - this->speed_level = static_cast(value.as_varint()); + msg.speed_level = static_cast(value.as_varint()); break; case proto_tag(12, WIRE_TYPE_VARINT): - this->has_preset_mode = value.as_bool(); + msg.has_preset_mode = value.as_bool(); break; case proto_tag(13, WIRE_TYPE_LENGTH_DELIMITED): - this->preset_mode = StringRef(value.data(), value.size()); + msg.preset_mode = StringRef(value.data(), value.size()); break; #ifdef USE_DEVICES case proto_tag(14, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -724,93 +728,94 @@ uint32_t LightStateResponse::calc_size_msg(const void *self) { #endif return size; } -void LightCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void LightCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->has_state = value.as_bool(); + msg.has_state = value.as_bool(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->state = value.as_bool(); + msg.state = value.as_bool(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->has_brightness = value.as_bool(); + msg.has_brightness = value.as_bool(); break; case proto_tag(5, WIRE_TYPE_FIXED32): - this->brightness = value.as_float(); + msg.brightness = value.as_float(); break; case proto_tag(22, WIRE_TYPE_VARINT): - this->has_color_mode = value.as_bool(); + msg.has_color_mode = value.as_bool(); break; case proto_tag(23, WIRE_TYPE_VARINT): - this->color_mode = static_cast(value.as_varint()); + msg.color_mode = static_cast(value.as_varint()); break; case proto_tag(20, WIRE_TYPE_VARINT): - this->has_color_brightness = value.as_bool(); + msg.has_color_brightness = value.as_bool(); break; case proto_tag(21, WIRE_TYPE_FIXED32): - this->color_brightness = value.as_float(); + msg.color_brightness = value.as_float(); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->has_rgb = value.as_bool(); + msg.has_rgb = value.as_bool(); break; case proto_tag(7, WIRE_TYPE_FIXED32): - this->red = value.as_float(); + msg.red = value.as_float(); break; case proto_tag(8, WIRE_TYPE_FIXED32): - this->green = value.as_float(); + msg.green = value.as_float(); break; case proto_tag(9, WIRE_TYPE_FIXED32): - this->blue = value.as_float(); + msg.blue = value.as_float(); break; case proto_tag(10, WIRE_TYPE_VARINT): - this->has_white = value.as_bool(); + msg.has_white = value.as_bool(); break; case proto_tag(11, WIRE_TYPE_FIXED32): - this->white = value.as_float(); + msg.white = value.as_float(); break; case proto_tag(12, WIRE_TYPE_VARINT): - this->has_color_temperature = value.as_bool(); + msg.has_color_temperature = value.as_bool(); break; case proto_tag(13, WIRE_TYPE_FIXED32): - this->color_temperature = value.as_float(); + msg.color_temperature = value.as_float(); break; case proto_tag(24, WIRE_TYPE_VARINT): - this->has_cold_white = value.as_bool(); + msg.has_cold_white = value.as_bool(); break; case proto_tag(25, WIRE_TYPE_FIXED32): - this->cold_white = value.as_float(); + msg.cold_white = value.as_float(); break; case proto_tag(26, WIRE_TYPE_VARINT): - this->has_warm_white = value.as_bool(); + msg.has_warm_white = value.as_bool(); break; case proto_tag(27, WIRE_TYPE_FIXED32): - this->warm_white = value.as_float(); + msg.warm_white = value.as_float(); break; case proto_tag(14, WIRE_TYPE_VARINT): - this->has_transition_length = value.as_bool(); + msg.has_transition_length = value.as_bool(); break; case proto_tag(15, WIRE_TYPE_VARINT): - this->transition_length = value.as_varint(); + msg.transition_length = value.as_varint(); break; case proto_tag(16, WIRE_TYPE_VARINT): - this->has_flash_length = value.as_bool(); + msg.has_flash_length = value.as_bool(); break; case proto_tag(17, WIRE_TYPE_VARINT): - this->flash_length = value.as_varint(); + msg.flash_length = value.as_varint(); break; case proto_tag(18, WIRE_TYPE_VARINT): - this->has_effect = value.as_bool(); + msg.has_effect = value.as_bool(); break; case proto_tag(19, WIRE_TYPE_LENGTH_DELIMITED): - this->effect = StringRef(value.data(), value.size()); + msg.effect = StringRef(value.data(), value.size()); break; #ifdef USE_DEVICES case proto_tag(28, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -945,18 +950,19 @@ uint32_t SwitchStateResponse::calc_size_msg(const void *self) { #endif return size; } -void SwitchCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SwitchCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->state = value.as_bool(); + msg.state = value.as_bool(); break; #ifdef USE_DEVICES case proto_tag(3, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -1021,14 +1027,15 @@ uint32_t TextSensorStateResponse::calc_size_msg(const void *self) { return size; } #endif -void SubscribeLogsRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SubscribeLogsRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->level = static_cast(value.as_varint()); + msg.level = static_cast(value.as_varint()); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->dump_config = value.as_bool(); + msg.dump_config = value.as_bool(); break; } } @@ -1053,12 +1060,14 @@ SubscribeLogsResponse::calc_size_msg(const void *self) { return size; } #ifdef USE_API_NOISE -void NoiseEncryptionSetKeyRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void NoiseEncryptionSetKeyRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->key = value.data(); - this->key_len = value.size(); + msg.key = value.data(); + msg.key_len = value.size(); break; } } @@ -1149,22 +1158,24 @@ uint32_t HomeassistantActionRequest::calc_size_msg(const void *self) { } #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES -void HomeassistantActionResponse::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void HomeassistantActionResponse::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->call_id = value.as_varint(); + msg.call_id = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->success = value.as_bool(); + msg.success = value.as_bool(); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - this->error_message = StringRef(value.data(), value.size()); + msg.error_message = StringRef(value.data(), value.size()); break; #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON case proto_tag(4, WIRE_TYPE_LENGTH_DELIMITED): - this->response_data = value.data(); - this->response_data_len = value.size(); + msg.response_data = value.data(); + msg.response_data_len = value.size(); break; #endif } @@ -1188,70 +1199,75 @@ uint32_t SubscribeHomeAssistantStateResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_bool(1, msg.once); return size; } -void HomeAssistantStateResponse::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void HomeAssistantStateResponse::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->entity_id = StringRef(value.data(), value.size()); + msg.entity_id = StringRef(value.data(), value.size()); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->state = StringRef(value.data(), value.size()); + msg.state = StringRef(value.data(), value.size()); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - this->attribute = StringRef(value.data(), value.size()); + msg.attribute = StringRef(value.data(), value.size()); break; } } #endif -void DSTRule::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void DSTRule::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->time_seconds = decode_zigzag32(static_cast(value.as_varint())); + msg.time_seconds = decode_zigzag32(static_cast(value.as_varint())); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->day = value.as_varint(); + msg.day = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->type = static_cast(value.as_varint()); + msg.type = static_cast(value.as_varint()); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->month = value.as_varint(); + msg.month = value.as_varint(); break; case proto_tag(5, WIRE_TYPE_VARINT): - this->week = value.as_varint(); + msg.week = value.as_varint(); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->day_of_week = value.as_varint(); + msg.day_of_week = value.as_varint(); break; } } -void ParsedTimezone::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void ParsedTimezone::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->std_offset_seconds = decode_zigzag32(static_cast(value.as_varint())); + msg.std_offset_seconds = decode_zigzag32(static_cast(value.as_varint())); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->dst_offset_seconds = decode_zigzag32(static_cast(value.as_varint())); + msg.dst_offset_seconds = decode_zigzag32(static_cast(value.as_varint())); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - value.decode_to_message(this->dst_start); + value.decode_to_message(msg.dst_start); break; case proto_tag(4, WIRE_TYPE_LENGTH_DELIMITED): - value.decode_to_message(this->dst_end); + value.decode_to_message(msg.dst_end); break; } } -void GetTimeResponse::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void GetTimeResponse::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->epoch_seconds = value.as_fixed32(); + msg.epoch_seconds = value.as_fixed32(); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - value.decode_to_message(this->parsed_timezone); - this->has_parsed_timezone = true; + value.decode_to_message(msg.parsed_timezone); + msg.has_parsed_timezone = true; break; } } @@ -1312,35 +1328,36 @@ uint32_t ListEntitiesServicesResponse::calc_size_msg(const void *self) { #endif return size; } -void ExecuteServiceArgument::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void ExecuteServiceArgument::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->bool_ = value.as_bool(); + msg.bool_ = value.as_bool(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->legacy_int = static_cast(value.as_varint()); + msg.legacy_int = static_cast(value.as_varint()); break; case proto_tag(3, WIRE_TYPE_FIXED32): - this->float_ = value.as_float(); + msg.float_ = value.as_float(); break; case proto_tag(4, WIRE_TYPE_LENGTH_DELIMITED): - this->string_ = StringRef(value.data(), value.size()); + msg.string_ = StringRef(value.data(), value.size()); break; case proto_tag(5, WIRE_TYPE_VARINT): - this->int_ = decode_zigzag32(static_cast(value.as_varint())); + msg.int_ = decode_zigzag32(static_cast(value.as_varint())); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->bool_array.push_back(value.as_bool()); + msg.bool_array.push_back(value.as_bool()); break; case proto_tag(7, WIRE_TYPE_VARINT): - this->int_array.push_back(decode_zigzag32(static_cast(value.as_varint()))); + msg.int_array.push_back(decode_zigzag32(static_cast(value.as_varint()))); break; case proto_tag(8, WIRE_TYPE_FIXED32): - this->float_array.push_back(value.as_float()); + msg.float_array.push_back(value.as_float()); break; case proto_tag(9, WIRE_TYPE_LENGTH_DELIMITED): - this->string_array.push_back(value.as_string()); + msg.string_array.push_back(value.as_string()); break; } } @@ -1353,26 +1370,27 @@ void ExecuteServiceArgument::decode(const uint8_t *buffer, size_t length) { this->float_array.init(count_float_array); uint32_t count_string_array = ProtoDecodableMessage::count_repeated_field(buffer, length, 9); this->string_array.init(count_string_array); - ProtoDecodableMessage::decode(buffer, length); + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); } -void ExecuteServiceRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void ExecuteServiceRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->args.emplace_back(); - value.decode_to_message(this->args.back()); + msg.args.emplace_back(); + value.decode_to_message(msg.args.back()); break; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES case proto_tag(3, WIRE_TYPE_VARINT): - this->call_id = value.as_varint(); + msg.call_id = value.as_varint(); break; #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES case proto_tag(4, WIRE_TYPE_VARINT): - this->return_response = value.as_bool(); + msg.return_response = value.as_bool(); break; #endif } @@ -1380,7 +1398,7 @@ void ExecuteServiceRequest::decode_field(uint32_t tag, const uint8_t *data, prot void ExecuteServiceRequest::decode(const uint8_t *buffer, size_t length) { uint32_t count_args = ProtoDecodableMessage::count_repeated_field(buffer, length, 2); this->args.init(count_args); - ProtoDecodableMessage::decode(buffer, length); + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); } #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES @@ -1462,14 +1480,15 @@ uint32_t CameraImageResponse::calc_size_msg(const void *self) { #endif return size; } -void CameraImageRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void CameraImageRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->single = value.as_bool(); + msg.single = value.as_bool(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->stream = value.as_bool(); + msg.stream = value.as_bool(); break; } } @@ -1635,75 +1654,76 @@ uint32_t ClimateStateResponse::calc_size_msg(const void *self) { #endif return size; } -void ClimateCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void ClimateCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->has_mode = value.as_bool(); + msg.has_mode = value.as_bool(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->mode = static_cast(value.as_varint()); + msg.mode = static_cast(value.as_varint()); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->has_target_temperature = value.as_bool(); + msg.has_target_temperature = value.as_bool(); break; case proto_tag(5, WIRE_TYPE_FIXED32): - this->target_temperature = value.as_float(); + msg.target_temperature = value.as_float(); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->has_target_temperature_low = value.as_bool(); + msg.has_target_temperature_low = value.as_bool(); break; case proto_tag(7, WIRE_TYPE_FIXED32): - this->target_temperature_low = value.as_float(); + msg.target_temperature_low = value.as_float(); break; case proto_tag(8, WIRE_TYPE_VARINT): - this->has_target_temperature_high = value.as_bool(); + msg.has_target_temperature_high = value.as_bool(); break; case proto_tag(9, WIRE_TYPE_FIXED32): - this->target_temperature_high = value.as_float(); + msg.target_temperature_high = value.as_float(); break; case proto_tag(12, WIRE_TYPE_VARINT): - this->has_fan_mode = value.as_bool(); + msg.has_fan_mode = value.as_bool(); break; case proto_tag(13, WIRE_TYPE_VARINT): - this->fan_mode = static_cast(value.as_varint()); + msg.fan_mode = static_cast(value.as_varint()); break; case proto_tag(14, WIRE_TYPE_VARINT): - this->has_swing_mode = value.as_bool(); + msg.has_swing_mode = value.as_bool(); break; case proto_tag(15, WIRE_TYPE_VARINT): - this->swing_mode = static_cast(value.as_varint()); + msg.swing_mode = static_cast(value.as_varint()); break; case proto_tag(16, WIRE_TYPE_VARINT): - this->has_custom_fan_mode = value.as_bool(); + msg.has_custom_fan_mode = value.as_bool(); break; case proto_tag(17, WIRE_TYPE_LENGTH_DELIMITED): - this->custom_fan_mode = StringRef(value.data(), value.size()); + msg.custom_fan_mode = StringRef(value.data(), value.size()); break; case proto_tag(18, WIRE_TYPE_VARINT): - this->has_preset = value.as_bool(); + msg.has_preset = value.as_bool(); break; case proto_tag(19, WIRE_TYPE_VARINT): - this->preset = static_cast(value.as_varint()); + msg.preset = static_cast(value.as_varint()); break; case proto_tag(20, WIRE_TYPE_VARINT): - this->has_custom_preset = value.as_bool(); + msg.has_custom_preset = value.as_bool(); break; case proto_tag(21, WIRE_TYPE_LENGTH_DELIMITED): - this->custom_preset = StringRef(value.data(), value.size()); + msg.custom_preset = StringRef(value.data(), value.size()); break; case proto_tag(22, WIRE_TYPE_VARINT): - this->has_target_humidity = value.as_bool(); + msg.has_target_humidity = value.as_bool(); break; case proto_tag(23, WIRE_TYPE_FIXED32): - this->target_humidity = value.as_float(); + msg.target_humidity = value.as_float(); break; #ifdef USE_DEVICES case proto_tag(24, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -1803,34 +1823,36 @@ uint32_t WaterHeaterStateResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_float(1, msg.target_temperature_high); return size; } -void WaterHeaterCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void WaterHeaterCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->has_fields = value.as_varint(); + msg.has_fields = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->mode = static_cast(value.as_varint()); + msg.mode = static_cast(value.as_varint()); break; case proto_tag(4, WIRE_TYPE_FIXED32): - this->target_temperature = value.as_float(); + msg.target_temperature = value.as_float(); break; #ifdef USE_DEVICES case proto_tag(5, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif case proto_tag(6, WIRE_TYPE_VARINT): - this->state = value.as_varint(); + msg.state = value.as_varint(); break; case proto_tag(7, WIRE_TYPE_FIXED32): - this->target_temperature_low = value.as_float(); + msg.target_temperature_low = value.as_float(); break; case proto_tag(8, WIRE_TYPE_FIXED32): - this->target_temperature_high = value.as_float(); + msg.target_temperature_high = value.as_float(); break; } } @@ -1910,18 +1932,19 @@ uint32_t NumberStateResponse::calc_size_msg(const void *self) { #endif return size; } -void NumberCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void NumberCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_FIXED32): - this->state = value.as_float(); + msg.state = value.as_float(); break; #ifdef USE_DEVICES case proto_tag(3, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -1990,18 +2013,19 @@ uint32_t SelectStateResponse::calc_size_msg(const void *self) { #endif return size; } -void SelectCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SelectCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->state = StringRef(value.data(), value.size()); + msg.state = StringRef(value.data(), value.size()); break; #ifdef USE_DEVICES case proto_tag(3, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -2072,39 +2096,40 @@ uint32_t SirenStateResponse::calc_size_msg(const void *self) { #endif return size; } -void SirenCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SirenCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->has_state = value.as_bool(); + msg.has_state = value.as_bool(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->state = value.as_bool(); + msg.state = value.as_bool(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->has_tone = value.as_bool(); + msg.has_tone = value.as_bool(); break; case proto_tag(5, WIRE_TYPE_LENGTH_DELIMITED): - this->tone = StringRef(value.data(), value.size()); + msg.tone = StringRef(value.data(), value.size()); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->has_duration = value.as_bool(); + msg.has_duration = value.as_bool(); break; case proto_tag(7, WIRE_TYPE_VARINT): - this->duration = value.as_varint(); + msg.duration = value.as_varint(); break; case proto_tag(8, WIRE_TYPE_VARINT): - this->has_volume = value.as_bool(); + msg.has_volume = value.as_bool(); break; case proto_tag(9, WIRE_TYPE_FIXED32): - this->volume = value.as_float(); + msg.volume = value.as_float(); break; #ifdef USE_DEVICES case proto_tag(10, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -2171,24 +2196,25 @@ uint32_t LockStateResponse::calc_size_msg(const void *self) { #endif return size; } -void LockCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void LockCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->command = static_cast(value.as_varint()); + msg.command = static_cast(value.as_varint()); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->has_code = value.as_bool(); + msg.has_code = value.as_bool(); break; case proto_tag(4, WIRE_TYPE_LENGTH_DELIMITED): - this->code = StringRef(value.data(), value.size()); + msg.code = StringRef(value.data(), value.size()); break; #ifdef USE_DEVICES case proto_tag(5, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -2229,15 +2255,16 @@ uint32_t ListEntitiesButtonResponse::calc_size_msg(const void *self) { #endif return size; } -void ButtonCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void ButtonCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; #ifdef USE_DEVICES case proto_tag(2, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -2333,51 +2360,54 @@ uint32_t MediaPlayerStateResponse::calc_size_msg(const void *self) { #endif return size; } -void MediaPlayerCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void MediaPlayerCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->has_command = value.as_bool(); + msg.has_command = value.as_bool(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->command = static_cast(value.as_varint()); + msg.command = static_cast(value.as_varint()); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->has_volume = value.as_bool(); + msg.has_volume = value.as_bool(); break; case proto_tag(5, WIRE_TYPE_FIXED32): - this->volume = value.as_float(); + msg.volume = value.as_float(); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->has_media_url = value.as_bool(); + msg.has_media_url = value.as_bool(); break; case proto_tag(7, WIRE_TYPE_LENGTH_DELIMITED): - this->media_url = StringRef(value.data(), value.size()); + msg.media_url = StringRef(value.data(), value.size()); break; case proto_tag(8, WIRE_TYPE_VARINT): - this->has_announcement = value.as_bool(); + msg.has_announcement = value.as_bool(); break; case proto_tag(9, WIRE_TYPE_VARINT): - this->announcement = value.as_bool(); + msg.announcement = value.as_bool(); break; #ifdef USE_DEVICES case proto_tag(10, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } } #endif #ifdef USE_BLUETOOTH_PROXY -void SubscribeBluetoothLEAdvertisementsRequest::decode_field(uint32_t tag, const uint8_t *data, +void SubscribeBluetoothLEAdvertisementsRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->flags = value.as_varint(); + msg.flags = value.as_varint(); break; } } @@ -2423,20 +2453,21 @@ BluetoothLERawAdvertisementsResponse::calc_size_msg(const void *self) { } #endif #ifdef USE_BLUETOOTH_PROXY_CONNECTIONS -void BluetoothDeviceRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothDeviceRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->address = value.as_varint(); + msg.address = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->request_type = static_cast(value.as_varint()); + msg.request_type = static_cast(value.as_varint()); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->has_address_type = value.as_bool(); + msg.has_address_type = value.as_bool(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->address_type = value.as_varint(); + msg.address_type = value.as_varint(); break; } } @@ -2459,11 +2490,13 @@ uint32_t BluetoothDeviceConnectionResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_int32(1, msg.error); return size; } -void BluetoothGATTGetServicesRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothGATTGetServicesRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->address = value.as_varint(); + msg.address = value.as_varint(); break; } } @@ -2585,14 +2618,16 @@ uint32_t BluetoothGATTGetServicesDoneResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_uint64(1, msg.address); return size; } -void BluetoothGATTReadRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothGATTReadRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->address = value.as_varint(); + msg.address = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->handle = value.as_varint(); + msg.handle = value.as_varint(); break; } } @@ -2612,61 +2647,69 @@ uint32_t BluetoothGATTReadResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_length(1, msg.data_len_); return size; } -void BluetoothGATTWriteRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothGATTWriteRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->address = value.as_varint(); + msg.address = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->handle = value.as_varint(); + msg.handle = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->response = value.as_bool(); + msg.response = value.as_bool(); break; case proto_tag(4, WIRE_TYPE_LENGTH_DELIMITED): - this->data = value.data(); - this->data_len = value.size(); + msg.data = value.data(); + msg.data_len = value.size(); break; } } -void BluetoothGATTReadDescriptorRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothGATTReadDescriptorRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->address = value.as_varint(); + msg.address = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->handle = value.as_varint(); + msg.handle = value.as_varint(); break; } } -void BluetoothGATTWriteDescriptorRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothGATTWriteDescriptorRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->address = value.as_varint(); + msg.address = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->handle = value.as_varint(); + msg.handle = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - this->data = value.data(); - this->data_len = value.size(); + msg.data = value.data(); + msg.data_len = value.size(); break; } } -void BluetoothGATTNotifyRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothGATTNotifyRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->address = value.as_varint(); + msg.address = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->handle = value.as_varint(); + msg.handle = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->enable = value.as_bool(); + msg.enable = value.as_bool(); break; } } @@ -2826,24 +2869,28 @@ uint32_t BluetoothScannerStateResponse::calc_size_msg(const void *self) { size += msg.configured_mode ? 2 : 0; return size; } -void BluetoothScannerSetModeRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothScannerSetModeRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->mode = static_cast(value.as_varint()); + msg.mode = static_cast(value.as_varint()); break; } } #endif #ifdef USE_VOICE_ASSISTANT -void SubscribeVoiceAssistantRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SubscribeVoiceAssistantRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->subscribe = value.as_bool(); + msg.subscribe = value.as_bool(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->flags = value.as_varint(); + msg.flags = value.as_varint(); break; } } @@ -2885,53 +2932,58 @@ uint32_t VoiceAssistantRequest::calc_size_msg(const void *self) { size += ProtoSize::calc_length(1, msg.wake_word_phrase.size()); return size; } -void VoiceAssistantResponse::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantResponse::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->port = value.as_varint(); + msg.port = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->error = value.as_bool(); + msg.error = value.as_bool(); break; } } -void VoiceAssistantEventData::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantEventData::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->name = StringRef(value.data(), value.size()); + msg.name = StringRef(value.data(), value.size()); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->value = StringRef(value.data(), value.size()); + msg.value = StringRef(value.data(), value.size()); break; } } -void VoiceAssistantEventResponse::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantEventResponse::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->event_type = static_cast(value.as_varint()); + msg.event_type = static_cast(value.as_varint()); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->data.emplace_back(); - value.decode_to_message(this->data.back()); + msg.data.emplace_back(); + value.decode_to_message(msg.data.back()); break; } } -void VoiceAssistantAudio::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantAudio::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->data = value.data(); - this->data_len = value.size(); + msg.data = value.data(); + msg.data_len = value.size(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->end = value.as_bool(); + msg.end = value.as_bool(); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - this->data2 = value.data(); - this->data2_len = value.size(); + msg.data2 = value.data(); + msg.data2_len = value.size(); break; } } @@ -2951,43 +3003,47 @@ uint32_t VoiceAssistantAudio::calc_size_msg(const void *self) { size += ProtoSize::calc_length(1, msg.data2_len); return size; } -void VoiceAssistantTimerEventResponse::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantTimerEventResponse::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->event_type = static_cast(value.as_varint()); + msg.event_type = static_cast(value.as_varint()); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->timer_id = StringRef(value.data(), value.size()); + msg.timer_id = StringRef(value.data(), value.size()); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - this->name = StringRef(value.data(), value.size()); + msg.name = StringRef(value.data(), value.size()); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->total_seconds = value.as_varint(); + msg.total_seconds = value.as_varint(); break; case proto_tag(5, WIRE_TYPE_VARINT): - this->seconds_left = value.as_varint(); + msg.seconds_left = value.as_varint(); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->is_active = value.as_bool(); + msg.is_active = value.as_bool(); break; } } -void VoiceAssistantAnnounceRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantAnnounceRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->media_id = StringRef(value.data(), value.size()); + msg.media_id = StringRef(value.data(), value.size()); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->text = StringRef(value.data(), value.size()); + msg.text = StringRef(value.data(), value.size()); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - this->preannounce_media_id = StringRef(value.data(), value.size()); + msg.preannounce_media_id = StringRef(value.data(), value.size()); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->start_conversation = value.as_bool(); + msg.start_conversation = value.as_bool(); break; } } @@ -3026,38 +3082,42 @@ uint32_t VoiceAssistantWakeWord::calc_size_msg(const void *self) { } return size; } -void VoiceAssistantExternalWakeWord::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantExternalWakeWord::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->id = StringRef(value.data(), value.size()); + msg.id = StringRef(value.data(), value.size()); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->wake_word = StringRef(value.data(), value.size()); + msg.wake_word = StringRef(value.data(), value.size()); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - this->trained_languages.push_back(value.as_string()); + msg.trained_languages.push_back(value.as_string()); break; case proto_tag(4, WIRE_TYPE_LENGTH_DELIMITED): - this->model_type = StringRef(value.data(), value.size()); + msg.model_type = StringRef(value.data(), value.size()); break; case proto_tag(5, WIRE_TYPE_VARINT): - this->model_size = value.as_varint(); + msg.model_size = value.as_varint(); break; case proto_tag(6, WIRE_TYPE_LENGTH_DELIMITED): - this->model_hash = StringRef(value.data(), value.size()); + msg.model_hash = StringRef(value.data(), value.size()); break; case proto_tag(7, WIRE_TYPE_LENGTH_DELIMITED): - this->url = StringRef(value.data(), value.size()); + msg.url = StringRef(value.data(), value.size()); break; } } -void VoiceAssistantConfigurationRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantConfigurationRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->external_wake_words.emplace_back(); - value.decode_to_message(this->external_wake_words.back()); + msg.external_wake_words.emplace_back(); + value.decode_to_message(msg.external_wake_words.back()); break; } } @@ -3090,11 +3150,13 @@ uint32_t VoiceAssistantConfigurationResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_uint32(1, msg.max_active_wake_words); return size; } -void VoiceAssistantSetConfiguration::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void VoiceAssistantSetConfiguration::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->active_wake_words.push_back(value.as_string()); + msg.active_wake_words.push_back(value.as_string()); break; } } @@ -3160,21 +3222,23 @@ uint32_t AlarmControlPanelStateResponse::calc_size_msg(const void *self) { #endif return size; } -void AlarmControlPanelCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void AlarmControlPanelCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->command = static_cast(value.as_varint()); + msg.command = static_cast(value.as_varint()); break; case proto_tag(3, WIRE_TYPE_LENGTH_DELIMITED): - this->code = StringRef(value.data(), value.size()); + msg.code = StringRef(value.data(), value.size()); break; #ifdef USE_DEVICES case proto_tag(4, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -3243,18 +3307,19 @@ uint32_t TextStateResponse::calc_size_msg(const void *self) { #endif return size; } -void TextCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void TextCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->state = StringRef(value.data(), value.size()); + msg.state = StringRef(value.data(), value.size()); break; #ifdef USE_DEVICES case proto_tag(3, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -3319,24 +3384,25 @@ uint32_t DateStateResponse::calc_size_msg(const void *self) { #endif return size; } -void DateCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void DateCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->year = value.as_varint(); + msg.year = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->month = value.as_varint(); + msg.month = value.as_varint(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->day = value.as_varint(); + msg.day = value.as_varint(); break; #ifdef USE_DEVICES case proto_tag(5, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -3401,24 +3467,25 @@ uint32_t TimeStateResponse::calc_size_msg(const void *self) { #endif return size; } -void TimeCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void TimeCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->hour = value.as_varint(); + msg.hour = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->minute = value.as_varint(); + msg.minute = value.as_varint(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->second = value.as_varint(); + msg.second = value.as_varint(); break; #ifdef USE_DEVICES case proto_tag(5, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -3553,24 +3620,25 @@ uint32_t ValveStateResponse::calc_size_msg(const void *self) { #endif return size; } -void ValveCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void ValveCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->has_position = value.as_bool(); + msg.has_position = value.as_bool(); break; case proto_tag(3, WIRE_TYPE_FIXED32): - this->position = value.as_float(); + msg.position = value.as_float(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->stop = value.as_bool(); + msg.stop = value.as_bool(); break; #ifdef USE_DEVICES case proto_tag(5, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -3633,18 +3701,19 @@ uint32_t DateTimeStateResponse::calc_size_msg(const void *self) { #endif return size; } -void DateTimeCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void DateTimeCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_FIXED32): - this->epoch_seconds = value.as_fixed32(); + msg.epoch_seconds = value.as_fixed32(); break; #ifdef USE_DEVICES case proto_tag(3, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } @@ -3723,30 +3792,32 @@ uint32_t UpdateStateResponse::calc_size_msg(const void *self) { #endif return size; } -void UpdateCommandRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void UpdateCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->command = static_cast(value.as_varint()); + msg.command = static_cast(value.as_varint()); break; #ifdef USE_DEVICES case proto_tag(3, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif } } #endif #ifdef USE_ZWAVE_PROXY -void ZWaveProxyFrame::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void ZWaveProxyFrame::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED): - this->data = value.data(); - this->data_len = value.size(); + msg.data = value.data(); + msg.data_len = value.size(); break; } } @@ -3766,15 +3837,16 @@ ZWaveProxyFrame::calc_size_msg(const void *self) { size += ProtoSize::calc_length(1, msg.data_len); return size; } -void ZWaveProxyRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void ZWaveProxyRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->type = static_cast(value.as_varint()); + msg.type = static_cast(value.as_varint()); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->data = value.data(); - this->data_len = value.size(); + msg.data = value.data(); + msg.data_len = value.size(); break; } } @@ -3846,30 +3918,32 @@ uint32_t ListEntitiesInfraredResponse::calc_size_msg(const void *self) { } #endif #if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) -void InfraredRFTransmitRawTimingsRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void InfraredRFTransmitRawTimingsRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { #ifdef USE_DEVICES case proto_tag(1, WIRE_TYPE_VARINT): - this->device_id = value.as_varint(); + msg.device_id = value.as_varint(); break; #endif case proto_tag(2, WIRE_TYPE_FIXED32): - this->key = value.as_fixed32(); + msg.key = value.as_fixed32(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->carrier_frequency = value.as_varint(); + msg.carrier_frequency = value.as_varint(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->repeat_count = value.as_varint(); + msg.repeat_count = value.as_varint(); break; case proto_tag(5, WIRE_TYPE_LENGTH_DELIMITED): - this->timings_data_ = value.data(); - this->timings_length_ = value.size(); - this->timings_count_ = count_packed_varints(value.data(), value.size()); + msg.timings_data_ = value.data(); + msg.timings_length_ = value.size(); + msg.timings_count_ = count_packed_varints(value.data(), value.size()); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->modulation = value.as_varint(); + msg.modulation = value.as_varint(); break; } } @@ -3948,26 +4022,28 @@ uint32_t ListEntitiesRadioFrequencyResponse::calc_size_msg(const void *self) { } #endif #ifdef USE_SERIAL_PROXY -void SerialProxyConfigureRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SerialProxyConfigureRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->instance = value.as_varint(); + msg.instance = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->baudrate = value.as_varint(); + msg.baudrate = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->flow_control = value.as_bool(); + msg.flow_control = value.as_bool(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->parity = static_cast(value.as_varint()); + msg.parity = static_cast(value.as_varint()); break; case proto_tag(5, WIRE_TYPE_VARINT): - this->stop_bits = value.as_varint(); + msg.stop_bits = value.as_varint(); break; case proto_tag(6, WIRE_TYPE_VARINT): - this->data_size = value.as_varint(); + msg.data_size = value.as_varint(); break; } } @@ -3989,34 +4065,39 @@ SerialProxyDataReceived::calc_size_msg(const void *self) { size += ProtoSize::calc_length(1, msg.data_len_); return size; } -void SerialProxyWriteRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SerialProxyWriteRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->instance = value.as_varint(); + msg.instance = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): - this->data = value.data(); - this->data_len = value.size(); + msg.data = value.data(); + msg.data_len = value.size(); break; } } -void SerialProxySetModemPinsRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SerialProxySetModemPinsRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->instance = value.as_varint(); + msg.instance = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->line_states = value.as_varint(); + msg.line_states = value.as_varint(); break; } } -void SerialProxyGetModemPinsRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SerialProxyGetModemPinsRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->instance = value.as_varint(); + msg.instance = value.as_varint(); break; } } @@ -4037,14 +4118,15 @@ uint32_t SerialProxyGetModemPinsResponse::calc_size_msg(const void *self) { size += msg.status ? 2 : 0; return size; } -void SerialProxyRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SerialProxyRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->instance = value.as_varint(); + msg.instance = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->type = static_cast(value.as_varint()); + msg.type = static_cast(value.as_varint()); break; } } @@ -4066,36 +4148,40 @@ uint32_t SerialProxyRequestResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_length(1, msg.error_message.size()); return size; } -void SerialProxySetModeRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void SerialProxySetModeRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->instance = value.as_varint(); + msg.instance = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->mode = static_cast(value.as_varint()); + msg.mode = static_cast(value.as_varint()); break; } } #endif #ifdef USE_BLUETOOTH_PROXY_CONNECTIONS -void BluetoothSetConnectionParamsRequest::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { +void BluetoothSetConnectionParamsRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, + proto_varint_value_t scalar) { + auto &msg = *static_cast(self); const ProtoFieldValue value(data, scalar); switch (tag) { case proto_tag(1, WIRE_TYPE_VARINT): - this->address = value.as_varint(); + msg.address = value.as_varint(); break; case proto_tag(2, WIRE_TYPE_VARINT): - this->min_interval = value.as_varint(); + msg.min_interval = value.as_varint(); break; case proto_tag(3, WIRE_TYPE_VARINT): - this->max_interval = value.as_varint(); + msg.max_interval = value.as_varint(); break; case proto_tag(4, WIRE_TYPE_VARINT): - this->latency = value.as_varint(); + msg.latency = value.as_varint(); break; case proto_tag(5, WIRE_TYPE_VARINT): - this->timeout = value.as_varint(); + msg.timeout = value.as_varint(); break; } } @@ -4115,5 +4201,131 @@ uint32_t BluetoothSetConnectionParamsResponse::calc_size_msg(const void *self) { return size; } #endif +#ifndef HAS_PROTO_MESSAGE_DUMP +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#ifdef USE_COVER +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_FAN +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_LIGHT +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_SWITCH +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#ifdef USE_API_NOISE +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_API_HOMEASSISTANT_STATES +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#ifdef USE_API_USER_DEFINED_ACTIONS +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_CAMERA +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_CLIMATE +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_WATER_HEATER +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_NUMBER +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_SELECT +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_SIREN +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_LOCK +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_BUTTON +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_MEDIA_PLAYER +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_BLUETOOTH_PROXY +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_BLUETOOTH_PROXY +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_VOICE_ASSISTANT +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_ALARM_CONTROL_PANEL +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_TEXT +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_DATETIME_DATE +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_DATETIME_TIME +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_VALVE +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_DATETIME_DATETIME +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_UPDATE +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_ZWAVE_PROXY +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_SERIAL_PROXY +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index aa30a02064..5b3683a1ab 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -424,12 +424,15 @@ class HelloRequest final : public ProtoDecodableMessage { StringRef client_info{}; uint32_t api_version_major{0}; uint32_t api_version_minor{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class HelloResponse final : public ProtoMessage { public: @@ -462,6 +465,9 @@ class DisconnectRequest final : public ProtoDecodableMessage { const LogString *message_name() const override { return LOG_STR("disconnect_request"); } #endif enums::DisconnectReason reason{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -473,7 +479,7 @@ class DisconnectRequest final : public ProtoDecodableMessage { #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class DisconnectResponse final : public ProtoMessage { public: @@ -844,12 +850,15 @@ class CoverCommandRequest final : public CommandProtoMessage { bool has_tilt{false}; float tilt{0.0f}; bool stop{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_FAN @@ -918,12 +927,15 @@ class FanCommandRequest final : public CommandProtoMessage { int32_t speed_level{0}; bool has_preset_mode{false}; StringRef preset_mode{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_LIGHT @@ -1014,12 +1026,15 @@ class LightCommandRequest final : public CommandProtoMessage { uint32_t flash_length{0}; bool has_effect{false}; StringRef effect{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_SENSOR @@ -1119,12 +1134,15 @@ class SwitchCommandRequest final : public CommandProtoMessage { const LogString *message_name() const override { return LOG_STR("switch_command_request"); } #endif bool state{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_TEXT_SENSOR @@ -1179,12 +1197,15 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage { #endif enums::LogLevel level{}; bool dump_config{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class SubscribeLogsResponse final : public ProtoMessage { public: @@ -1222,12 +1243,15 @@ class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage { #endif const uint8_t *key{nullptr}; uint16_t key_len{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class NoiseEncryptionSetKeyResponse final : public ProtoMessage { public: @@ -1316,12 +1340,15 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage { const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; #endif + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_API_HOMEASSISTANT_STATES @@ -1357,12 +1384,15 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage { StringRef entity_id{}; StringRef state{}; StringRef attribute{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif class GetTimeRequest final : public ProtoMessage { @@ -1386,12 +1416,15 @@ class DSTRule final : public ProtoDecodableMessage { uint32_t month{0}; uint32_t week{0}; uint32_t day_of_week{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class ParsedTimezone final : public ProtoDecodableMessage { public: @@ -1399,12 +1432,15 @@ class ParsedTimezone final : public ProtoDecodableMessage { int32_t dst_offset_seconds{0}; DSTRule dst_start{}; DSTRule dst_end{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class GetTimeResponse final : public ProtoDecodableMessage { public: @@ -1416,12 +1452,15 @@ class GetTimeResponse final : public ProtoDecodableMessage { uint32_t epoch_seconds{0}; ParsedTimezone parsed_timezone{}; bool has_parsed_timezone{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #ifdef USE_API_USER_DEFINED_ACTIONS class ListEntitiesServicesArgument final : public ProtoMessage { @@ -1489,7 +1528,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage { #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class ExecuteServiceRequest final : public ProtoDecodableMessage { public: @@ -1512,7 +1551,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage { #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES @@ -1598,12 +1637,15 @@ class CameraImageRequest final : public ProtoDecodableMessage { #endif bool single{false}; bool stream{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_CLIMATE @@ -1704,12 +1746,15 @@ class ClimateCommandRequest final : public CommandProtoMessage { StringRef custom_preset{}; bool has_target_humidity{false}; float target_humidity{0.0f}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_WATER_HEATER @@ -1776,12 +1821,15 @@ class WaterHeaterCommandRequest final : public CommandProtoMessage { uint32_t state{0}; float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_NUMBER @@ -1839,12 +1887,15 @@ class NumberCommandRequest final : public CommandProtoMessage { const LogString *message_name() const override { return LOG_STR("number_command_request"); } #endif float state{0.0f}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_SELECT @@ -1897,12 +1948,15 @@ class SelectCommandRequest final : public CommandProtoMessage { const LogString *message_name() const override { return LOG_STR("select_command_request"); } #endif StringRef state{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_SIREN @@ -1963,12 +2017,15 @@ class SirenCommandRequest final : public CommandProtoMessage { uint32_t duration{0}; bool has_volume{false}; float volume{0.0f}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_LOCK @@ -2025,12 +2082,15 @@ class LockCommandRequest final : public CommandProtoMessage { enums::LockCommand command{}; bool has_code{false}; StringRef code{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_BUTTON @@ -2061,12 +2121,15 @@ class ButtonCommandRequest final : public CommandProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("button_command_request"); } #endif + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_MEDIA_PLAYER @@ -2147,12 +2210,15 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage { StringRef media_url{}; bool has_announcement{false}; bool announcement{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_BLUETOOTH_PROXY @@ -2164,12 +2230,15 @@ class SubscribeBluetoothLEAdvertisementsRequest final : public ProtoDecodableMes const LogString *message_name() const override { return LOG_STR("subscribe_bluetooth_le_advertisements_request"); } #endif uint32_t flags{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothLERawAdvertisement final : public ProtoMessage { public: @@ -2218,12 +2287,15 @@ class BluetoothDeviceRequest final : public ProtoDecodableMessage { enums::BluetoothDeviceRequestType request_type{}; bool has_address_type{false}; uint32_t address_type{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothDeviceConnectionResponse final : public ProtoMessage { public: @@ -2256,12 +2328,15 @@ class BluetoothGATTGetServicesRequest final : public ProtoDecodableMessage { const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_get_services_request"); } #endif uint64_t address{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothGATTDescriptor final : public ProtoMessage { public: @@ -2367,12 +2442,15 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage { #endif uint64_t address{0}; uint32_t handle{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothGATTReadResponse final : public ProtoMessage { public: @@ -2413,12 +2491,15 @@ class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { bool response{false}; const uint8_t *data{nullptr}; uint16_t data_len{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { public: @@ -2429,12 +2510,15 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { #endif uint64_t address{0}; uint32_t handle{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { public: @@ -2447,12 +2531,15 @@ class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { uint32_t handle{0}; const uint8_t *data{nullptr}; uint16_t data_len{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage { public: @@ -2464,12 +2551,15 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage { uint64_t address{0}; uint32_t handle{0}; bool enable{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothGATTNotifyDataResponse final : public ProtoMessage { public: @@ -2682,12 +2772,15 @@ class BluetoothScannerSetModeRequest final : public ProtoDecodableMessage { const LogString *message_name() const override { return LOG_STR("bluetooth_scanner_set_mode_request"); } #endif enums::BluetoothScannerMode mode{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_VOICE_ASSISTANT @@ -2700,12 +2793,15 @@ class SubscribeVoiceAssistantRequest final : public ProtoDecodableMessage { #endif bool subscribe{false}; uint32_t flags{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantAudioSettings final : public ProtoMessage { public: @@ -2757,23 +2853,29 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage { #endif uint32_t port{0}; bool error{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantEventData final : public ProtoDecodableMessage { public: StringRef name{}; StringRef value{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantEventResponse final : public ProtoDecodableMessage { public: @@ -2784,12 +2886,15 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage { #endif enums::VoiceAssistantEvent event_type{}; std::vector data{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantAudio final : public ProtoDecodableMessage { public: @@ -2803,6 +2908,9 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage { bool end{false}; const uint8_t *data2{nullptr}; uint16_t data2_len{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -2814,7 +2922,7 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage { #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage { public: @@ -2829,12 +2937,15 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage { uint32_t total_seconds{0}; uint32_t seconds_left{0}; bool is_active{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage { public: @@ -2847,12 +2958,15 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage { StringRef text{}; StringRef preannounce_media_id{}; bool start_conversation{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantAnnounceFinished final : public ProtoMessage { public: @@ -2900,12 +3014,15 @@ class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage { uint32_t model_size{0}; StringRef model_hash{}; StringRef url{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage { public: @@ -2915,12 +3032,15 @@ class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage { const LogString *message_name() const override { return LOG_STR("voice_assistant_configuration_request"); } #endif std::vector external_wake_words{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class VoiceAssistantConfigurationResponse final : public ProtoMessage { public: @@ -2952,12 +3072,15 @@ class VoiceAssistantSetConfiguration final : public ProtoDecodableMessage { const LogString *message_name() const override { return LOG_STR("voice_assistant_set_configuration"); } #endif std::vector active_wake_words{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_ALARM_CONTROL_PANEL @@ -3012,12 +3135,15 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage { #endif enums::AlarmControlPanelStateCommand command{}; StringRef code{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_TEXT @@ -3073,12 +3199,15 @@ class TextCommandRequest final : public CommandProtoMessage { const LogString *message_name() const override { return LOG_STR("text_command_request"); } #endif StringRef state{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_DATETIME_DATE @@ -3134,12 +3263,15 @@ class DateCommandRequest final : public CommandProtoMessage { uint32_t year{0}; uint32_t month{0}; uint32_t day{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_DATETIME_TIME @@ -3195,12 +3327,15 @@ class TimeCommandRequest final : public CommandProtoMessage { uint32_t hour{0}; uint32_t minute{0}; uint32_t second{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_EVENT @@ -3301,12 +3436,15 @@ class ValveCommandRequest final : public CommandProtoMessage { bool has_position{false}; float position{0.0f}; bool stop{false}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_DATETIME_DATETIME @@ -3358,12 +3496,15 @@ class DateTimeCommandRequest final : public CommandProtoMessage { const LogString *message_name() const override { return LOG_STR("date_time_command_request"); } #endif uint32_t epoch_seconds{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_UPDATE @@ -3423,12 +3564,15 @@ class UpdateCommandRequest final : public CommandProtoMessage { const LogString *message_name() const override { return LOG_STR("update_command_request"); } #endif enums::UpdateCommand command{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_ZWAVE_PROXY @@ -3441,6 +3585,9 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage { #endif const uint8_t *data{nullptr}; uint16_t data_len{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -3452,7 +3599,7 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage { #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class ZWaveProxyRequest final : public ProtoDecodableMessage { public: @@ -3464,6 +3611,9 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage { enums::ZWaveProxyRequestType type{}; const uint8_t *data{nullptr}; uint16_t data_len{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -3475,7 +3625,7 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage { #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class ZWaveProxyRequestResponse final : public ProtoMessage { public: @@ -3540,12 +3690,15 @@ class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage { uint16_t timings_length_{0}; uint16_t timings_count_{0}; uint32_t modulation{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class InfraredRFReceiveEvent final : public ProtoMessage { public: @@ -3611,12 +3764,15 @@ class SerialProxyConfigureRequest final : public ProtoDecodableMessage { enums::SerialProxyParity parity{}; uint32_t stop_bits{0}; uint32_t data_size{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class SerialProxyDataReceived final : public ProtoMessage { public: @@ -3654,12 +3810,15 @@ class SerialProxyWriteRequest final : public ProtoDecodableMessage { uint32_t instance{0}; const uint8_t *data{nullptr}; uint16_t data_len{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage { public: @@ -3670,12 +3829,15 @@ class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage { #endif uint32_t instance{0}; uint32_t line_states{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage { public: @@ -3685,12 +3847,15 @@ class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage { const LogString *message_name() const override { return LOG_STR("serial_proxy_get_modem_pins_request"); } #endif uint32_t instance{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class SerialProxyGetModemPinsResponse final : public ProtoMessage { public: @@ -3723,12 +3888,15 @@ class SerialProxyRequest final : public ProtoDecodableMessage { #endif uint32_t instance{0}; enums::SerialProxyRequestType type{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class SerialProxyRequestResponse final : public ProtoMessage { public: @@ -3762,12 +3930,15 @@ class SerialProxySetModeRequest final : public ProtoDecodableMessage { #endif uint32_t instance{0}; enums::SerialProxyMode mode{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; #endif #ifdef USE_BLUETOOTH_PROXY_CONNECTIONS @@ -3783,12 +3954,15 @@ class BluetoothSetConnectionParamsRequest final : public ProtoDecodableMessage { uint32_t max_interval{0}; uint32_t latency{0}; uint32_t timeout{0}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif protected: - void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override; + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; class BluetoothSetConnectionParamsResponse final : public ProtoMessage { public: diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 6b558f7f92..fd1a9519dc 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -216,7 +216,7 @@ void ProtoWriteBuffer::debug_check_encode_size_(uint32_t field_id, uint32_t expe #endif -void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) { +void ProtoDecodableMessage::decode_fields(void *msg, const uint8_t *buffer, size_t length, DecodeFieldFn field) { const uint8_t *ptr = buffer; const uint8_t *end = buffer + length; @@ -287,7 +287,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) { return; } } - this->decode_field(tag, data, scalar); + field(msg, tag, data, scalar); } } diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 1a5160cb82..c112b0f415 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -10,6 +10,7 @@ #include #include +#include #include #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE @@ -723,7 +724,13 @@ class ProtoMessage { // Base class for messages that support decoding class ProtoDecodableMessage : public ProtoMessage { public: - void decode(const uint8_t *buffer, size_t length); + /// Stores one decoded field into \p msg; generated per message type. \p scalar is the varint or + /// fixed32 value, or the length of the length-delimited payload at \p data. An unknown field or + /// wrong wire type matches no case and is skipped. + using DecodeFieldFn = void (*)(void *msg, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); + /// Walk \p buffer and hand every field to \p field. The generated decode() passes the message's + /// own decode_field, so decodable messages carry no vtable. + static void decode_fields(void *msg, const uint8_t *buffer, size_t length, DecodeFieldFn field); /** * Count occurrences of a repeated field in a protobuf buffer. @@ -735,14 +742,15 @@ class ProtoDecodableMessage : public ProtoMessage { * @return Number of times the field appears in the buffer */ static uint32_t count_repeated_field(const uint8_t *buffer, size_t length, uint32_t target_field_id); - - protected: - ~ProtoDecodableMessage() = default; - /// Store one decoded field; \p scalar is the varint or fixed32 value, or the length of the - /// length-delimited payload at \p data. An unknown field or wrong wire type matches no case and is skipped. - /// Three register arguments keep the decode loop free of spills. - virtual void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {} + // The destructor stays accessible on purpose: the generated messages are aggregates that brace + // initialise sub message members, which copies a base temporary. That trades away the compile time + // guard against deleting through this type; messages are stack locals and never owned through a base + // pointer. ProtoMessage keeps its guard for the dump builds. }; +#ifndef HAS_PROTO_MESSAGE_DUMP +// decode() passes decode_field explicitly, so nothing here may add a vtable +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif class ProtoSize { public: diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index bdfa58a3b3..db548ae4f6 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2536,8 +2536,8 @@ def build_message_type( # Get source direction to determine if we need decode/encode methods source = message_source_map[desc.name] - needs_decode = source in (SOURCE_BOTH, SOURCE_CLIENT) - needs_encode = source in (SOURCE_BOTH, SOURCE_SERVER) + needs_decode = message_needs_decode(source) + needs_encode = message_needs_encode(source) # Add MESSAGE_TYPE method if this is a service message if message_id is not None: @@ -2661,15 +2661,22 @@ def build_message_type( cpp = "" if decode: - o = f"void {desc.name}::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {{\n" + o = f"void {desc.name}::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {{\n" + o += f" auto &msg = *static_cast<{desc.name} *>(self);\n" o += " const ProtoFieldValue value(data, scalar);\n" o += " switch (tag) {\n" - o += indent("\n".join(decode), " ") + "\n" + o += indent("\n".join(decode), " ").replace("this->", "msg.") + "\n" o += " }\n" o += "}\n" cpp += o - prot = "void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override;" + prot = "static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar);" protected_content.insert(0, prot) + if not fixed_vector_fields: + public_content.append( + "void decode(const uint8_t *buffer, size_t length) {\n" + " ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field);\n" + "}" + ) # Generate custom decode() override for messages with FixedVector fields if fixed_vector_fields: @@ -2679,8 +2686,8 @@ def build_message_type( for field_name, field_number in fixed_vector_fields: o += f" uint32_t count_{field_name} = ProtoDecodableMessage::count_repeated_field(buffer, length, {field_number});\n" o += f" this->{field_name}.init(count_{field_name});\n" - # Call parent decode to populate the fields - o += " ProtoDecodableMessage::decode(buffer, length);\n" + # Then the shared loop fills them + o += " ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field);\n" o += "}\n" cpp += o # Generate the decode() declaration in header (public method) @@ -2839,6 +2846,23 @@ def get_field_opt( return field.options.Extensions[opt] +def message_needs_decode(source: int) -> bool: + return source in (SOURCE_BOTH, SOURCE_CLIENT) + + +def message_needs_encode(source: int) -> bool: + return source in (SOURCE_BOTH, SOURCE_SERVER) + + +def is_decodable_class(desc: descriptor.DescriptorProto, source: int) -> bool: + """Whether the generated class derives from ProtoDecodableMessage: decoded, and either on a + decodable base class or with at least one live field.""" + return message_needs_decode(source) and ( + get_base_class(desc) is not None + or any(not field.options.deprecated for field in desc.field) + ) + + def get_base_class(desc: descriptor.DescriptorProto) -> str | None: """Get the base_class option from a message descriptor.""" if not desc.options.HasExtension(pb.base_class): @@ -2940,11 +2964,11 @@ def build_base_class( # Determine if any message using this base class needs decoding/encoding needs_decode = any( - message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_CLIENT) + message_needs_decode(message_source_map.get(msg.name, SOURCE_BOTH)) for msg in messages ) needs_encode = any( - message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_SERVER) + message_needs_encode(message_source_map.get(msg.name, SOURCE_BOTH)) for msg in messages ) @@ -3378,6 +3402,7 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint # Generate message types with base class information # Simple grouping by ifdef + decodable_messages: list[tuple[str, str | None]] = [] current_ifdef = None for m in mt: @@ -3394,6 +3419,8 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint continue s, c, dc = build_message_type(m, base_class_fields, message_source_map) + if is_decodable_class(m, message_source_map[m.name]): + decodable_messages.append((m.name, message_ifdef_map.get(m.name))) msg_ifdef = message_ifdef_map.get(m.name) # Handle ifdef changes @@ -3420,6 +3447,22 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint cpp += "#endif\n" dump_cpp += "#endif\n" + # decode() passes decode_field explicitly, so without the dump virtuals no decodable message + # may carry a vtable; a build at any level below VERY_VERBOSE proves it + cpp += "#ifndef HAS_PROTO_MESSAGE_DUMP\n" + assert_ifdef = None + for name, msg_ifdef in decodable_messages: + if msg_ifdef != assert_ifdef: + if assert_ifdef is not None: + cpp += "#endif\n" + if msg_ifdef is not None: + cpp += _make_ifdef_line(msg_ifdef) + "\n" + assert_ifdef = msg_ifdef + cpp += f'static_assert(!std::is_polymorphic_v<{name}>, "decodable messages carry no vtable");\n' + if assert_ifdef is not None: + cpp += "#endif\n" + cpp += "#endif\n" + content += """\ } // namespace esphome::api diff --git a/tests/unit_tests/components/api/test_api_protobuf_generator.py b/tests/unit_tests/components/api/test_api_protobuf_generator.py index 20a6f87c45..2e4eb67a24 100644 --- a/tests/unit_tests/components/api/test_api_protobuf_generator.py +++ b/tests/unit_tests/components/api/test_api_protobuf_generator.py @@ -287,18 +287,19 @@ def test_a_fixed64_field_fails_at_generation_time() -> None: build_message_type(desc, {}, {"Wide": SOURCE_CLIENT}) -def test_message_gets_a_single_decode_field_override() -> None: - """All wire types of a decoded message land in one decode_field() switch.""" +def test_message_decodes_through_one_static_decode_field() -> None: + """All wire types of a decoded message land in one static decode_field() switch that the + inline decode() hands to the shared loop.""" desc = descriptor_pb2.DescriptorProto(name="Mixed") desc.field.add(name="name", number=1, type=STRING) desc.field.add(name="count", number=2, type=UINT32) desc.field.add(name="level", number=3, type=FLOAT) header, cpp, _ = build_message_type(desc, {}, {"Mixed": SOURCE_CLIENT}) - decl = "void decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) override;" + decl = "static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar);" assert header.count(decl) == 1 assert ( cpp.count( - "void Mixed::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {" + "void Mixed::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {" ) == 1 ) @@ -310,6 +311,37 @@ def test_message_gets_a_single_decode_field_override() -> None: (3, "WIRE_TYPE_FIXED32"), ): assert f"case proto_tag({number}, {wire_type}):" in cpp, cpp + # The static body works on the cast message, never on this + assert "auto &msg = *static_cast(self);" in cpp + assert "this->" not in cpp + assert ( + header.count( + "ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field);" + ) + == 1 + ) + + +def test_fixed_vector_message_keeps_its_own_decode() -> None: + """A message that sizes a FixedVector first decodes through its own decode(), not the inline one.""" + desc = descriptor_pb2.DescriptorProto(name="Sized") + field = desc.field.add(name="values", number=1, type=UINT32) + field.label = descriptor_pb2.FieldDescriptorProto.LABEL_REPEATED + field.options.Extensions[pb.fixed_vector] = True + header, cpp, _ = build_message_type(desc, {}, {"Sized": SOURCE_CLIENT}) + assert ( + "ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field);" + not in header + ) + assert header.count("void decode(const uint8_t *buffer, size_t length);") == 1 + assert "void Sized::decode(const uint8_t *buffer, size_t length) {" in cpp + assert "ProtoDecodableMessage::count_repeated_field(buffer, length, 1)" in cpp + assert ( + cpp.count( + "ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field);" + ) + == 1 + ) @pytest.mark.parametrize("source", [SOURCE_CLIENT, SOURCE_SERVER, SOURCE_BOTH]) From d731ddefc9ab07ea368e4ca210493f83e90ed234 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 24 Sep 2026 16:55:18 +0100 Subject: [PATCH 2/5] [core] Dispatch controller updates through generated direct calls APIServer and WebServer inherited both Component and Controller, so each on_*_update override reached through the registry needed a non-virtual thunk to adjust this to the secondary base, and every publish iterated a StaticVector of Controller pointers with an indirect call. Drop the Controller base class and the runtime list. ControllerRegistry only declares notify_*; code generation defines each one in main.cpp as direct calls on the registered controllers through the entity_types.h X-macro. A ControllerContract concept with a generated static_assert per controller reports a missing callback clearly. On an ESP32 IDF config with api, web_server and four entity types this saves 256 B of flash and 24 B of RAM. --- esphome/codegen.py | 1 - esphome/components/api/__init__.py | 5 +- esphome/components/api/api_server.cpp | 3 - esphome/components/api/api_server.h | 46 ++++++------ esphome/components/api/subscribe_state.h | 1 - .../prometheus/prometheus_handler.h | 2 +- esphome/components/web_server/__init__.py | 5 +- esphome/components/web_server/web_server.cpp | 2 - esphome/components/web_server/web_server.h | 47 ++++++------ esphome/core/__init__.py | 16 ++-- esphome/core/component_iterator.h | 2 +- esphome/core/config.py | 39 ++++++++-- esphome/core/controller.h | 19 ----- esphome/core/controller_registry.cpp | 11 --- esphome/core/controller_registry.h | 73 +++++-------------- esphome/core/defines.h | 1 - esphome/cpp_types.py | 1 - tests/components/api/__init__.py | 2 - tests/unit_tests/test_codegen.py | 1 - 19 files changed, 114 insertions(+), 163 deletions(-) delete mode 100644 esphome/core/controller.h delete mode 100644 esphome/core/controller_registry.cpp diff --git a/esphome/codegen.py b/esphome/codegen.py index 5debb52b4e..3dfec65f23 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -64,7 +64,6 @@ from esphome.cpp_types import ( # noqa: F401 Application, Component, ComponentPtr, - Controller, EntityBase, EntityCategory, ESPTime, diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 854bceecfa..2291dc54f8 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -81,7 +81,7 @@ def AUTO_LOAD(config: ConfigType) -> list[str]: api_ns = cg.esphome_ns.namespace("api") -APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller) +APIServer = api_ns.class_("APIServer", cg.Component) HomeAssistantServiceCallAction = api_ns.class_( "HomeAssistantServiceCallAction", automation.Action ) @@ -462,8 +462,7 @@ async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - # Track controller registration for StaticVector sizing - CORE.register_controller() + CORE.register_controller(var) # Request a log listener slot for API log streaming request_log_listener() diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 310cdcf67b..2a89d31a74 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -5,7 +5,6 @@ #include "api_connection.h" #include "esphome/components/network/util.h" #include "esphome/core/application.h" -#include "esphome/core/controller_registry.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -61,8 +60,6 @@ void APIServer::socket_failed_(const LogString *msg) { } void APIServer::setup() { - ControllerRegistry::register_controller(this); - #ifdef USE_API_NOISE // Always reserve the slot: flash preferences are positional on esp8266, so // a yaml key build must keep the layout of a runtime key build diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index b8a71c93e7..1461e1d702 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -14,7 +14,6 @@ #include "esphome/components/socket/socket.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" -#include "esphome/core/controller.h" #include "esphome/core/log.h" #include "esphome/core/string_ref.h" #ifdef USE_PROVISIONING @@ -49,8 +48,7 @@ struct SavedNoisePsk { bool load_saved_noise_psk(noise::psk_t &out); #endif -class APIServer final : public Component, - public Controller +class APIServer final : public Component #ifdef USE_CAMERA , public camera::CameraListener @@ -94,58 +92,58 @@ class APIServer final : public Component, void handle_disconnect(APIConnection *conn); #ifdef USE_BINARY_SENSOR - void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override; + void on_binary_sensor_update(binary_sensor::BinarySensor *obj); #endif #ifdef USE_COVER - void on_cover_update(cover::Cover *obj) override; + void on_cover_update(cover::Cover *obj); #endif #ifdef USE_FAN - void on_fan_update(fan::Fan *obj) override; + void on_fan_update(fan::Fan *obj); #endif #ifdef USE_LIGHT - void on_light_update(light::LightState *obj) override; + void on_light_update(light::LightState *obj); #endif #ifdef USE_SENSOR - void on_sensor_update(sensor::Sensor *obj) override; + void on_sensor_update(sensor::Sensor *obj); #endif #ifdef USE_SWITCH - void on_switch_update(switch_::Switch *obj) override; + void on_switch_update(switch_::Switch *obj); #endif #ifdef USE_TEXT_SENSOR - void on_text_sensor_update(text_sensor::TextSensor *obj) override; + void on_text_sensor_update(text_sensor::TextSensor *obj); #endif #ifdef USE_CLIMATE - void on_climate_update(climate::Climate *obj) override; + void on_climate_update(climate::Climate *obj); #endif #ifdef USE_NUMBER - void on_number_update(number::Number *obj) override; + void on_number_update(number::Number *obj); #endif #ifdef USE_DATETIME_DATE - void on_date_update(datetime::DateEntity *obj) override; + void on_date_update(datetime::DateEntity *obj); #endif #ifdef USE_DATETIME_TIME - void on_time_update(datetime::TimeEntity *obj) override; + void on_time_update(datetime::TimeEntity *obj); #endif #ifdef USE_DATETIME_DATETIME - void on_datetime_update(datetime::DateTimeEntity *obj) override; + void on_datetime_update(datetime::DateTimeEntity *obj); #endif #ifdef USE_TEXT - void on_text_update(text::Text *obj) override; + void on_text_update(text::Text *obj); #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj) override; + void on_select_update(select::Select *obj); #endif #ifdef USE_LOCK - void on_lock_update(lock::Lock *obj) override; + void on_lock_update(lock::Lock *obj); #endif #ifdef USE_VALVE - void on_valve_update(valve::Valve *obj) override; + void on_valve_update(valve::Valve *obj); #endif #ifdef USE_MEDIA_PLAYER - void on_media_player_update(media_player::MediaPlayer *obj) override; + void on_media_player_update(media_player::MediaPlayer *obj); #endif #ifdef USE_WATER_HEATER - void on_water_heater_update(water_heater::WaterHeater *obj) override; + void on_water_heater_update(water_heater::WaterHeater *obj); #endif #ifdef USE_API_HOMEASSISTANT_SERVICES void send_homeassistant_action(const HomeassistantActionRequest &call); @@ -188,13 +186,13 @@ class APIServer final : public Component, #endif #ifdef USE_ALARM_CONTROL_PANEL - void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; + void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj); #endif #ifdef USE_EVENT - void on_event(event::Event *obj) override; + void on_event(event::Event *obj); #endif #ifdef USE_UPDATE - void on_update(update::UpdateEntity *obj) override; + void on_update(update::UpdateEntity *obj); #endif #ifdef USE_ZWAVE_PROXY void on_zwave_proxy_request(const ZWaveProxyRequest &msg); diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 6b1ae9651d..36e96e1081 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -4,7 +4,6 @@ #ifdef USE_API #include "esphome/core/component.h" #include "esphome/core/component_iterator.h" -#include "esphome/core/controller.h" namespace esphome::api { class APIConnection; diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 5574039e41..9dcbf7e686 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -6,8 +6,8 @@ #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" -#include "esphome/core/controller.h" #include "esphome/core/entity_base.h" +#include "esphome/core/entity_includes.h" #include "esphome/core/progmem.h" #ifdef USE_CLIMATE #include "esphome/core/log.h" diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 2459163786..3ec365cf38 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -62,7 +62,7 @@ DEFAULT_PORT = 80 web_server_ns = cg.esphome_ns.namespace("web_server") -WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) +WebServer = web_server_ns.class_("WebServer", cg.Component) sorting_groups = {} @@ -378,8 +378,7 @@ async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID], paren) await cg.register_component(var, config) - # Track controller registration for StaticVector sizing - CORE.register_controller() + CORE.register_controller(var) version = config[CONF_VERSION] diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 49943f8a71..4906a2050a 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -5,7 +5,6 @@ #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" -#include "esphome/core/controller_registry.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -365,7 +364,6 @@ json::SerializationBuffer<> WebServer::get_config_json() { } void WebServer::setup() { - ControllerRegistry::register_controller(this); this->base_->init(); #ifdef USE_LOGGER diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 3e50eeeec0..27e35dfe0f 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -6,7 +6,6 @@ #include "esphome/components/web_server_base/web_server_base.h" #ifdef USE_WEBSERVER #include "esphome/core/component.h" -#include "esphome/core/controller.h" #include "esphome/core/entity_base.h" #include "esphome/core/progmem.h" #ifdef USE_LOGGER @@ -187,7 +186,7 @@ class DeferredUpdateEventSourceList final : public std::list'. void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -301,7 +300,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_SWITCH - void on_switch_update(switch_::Switch *obj) override; + void on_switch_update(switch_::Switch *obj); /// Handle a switch request under '/switch//'. void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -319,7 +318,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_BINARY_SENSOR - void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override; + void on_binary_sensor_update(binary_sensor::BinarySensor *obj); /// Handle a binary sensor request under '/binary_sensor/'. void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -329,7 +328,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_FAN - void on_fan_update(fan::Fan *obj) override; + void on_fan_update(fan::Fan *obj); /// Handle a fan request under '/fan//'. void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -339,7 +338,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_LIGHT - void on_light_update(light::LightState *obj) override; + void on_light_update(light::LightState *obj); /// Handle a light request under '/light//'. void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -349,7 +348,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_TEXT_SENSOR - void on_text_sensor_update(text_sensor::TextSensor *obj) override; + void on_text_sensor_update(text_sensor::TextSensor *obj); /// Handle a text sensor request under '/text_sensor/'. void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -359,7 +358,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_COVER - void on_cover_update(cover::Cover *obj) override; + void on_cover_update(cover::Cover *obj); /// Handle a cover request under '/cover//'. void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -369,7 +368,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_NUMBER - void on_number_update(number::Number *obj) override; + void on_number_update(number::Number *obj); /// Handle a number request under '/number/'. void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -378,7 +377,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_DATETIME_DATE - void on_date_update(datetime::DateEntity *obj) override; + void on_date_update(datetime::DateEntity *obj); /// Handle a date request under '/date/'. void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -387,7 +386,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_DATETIME_TIME - void on_time_update(datetime::TimeEntity *obj) override; + void on_time_update(datetime::TimeEntity *obj); /// Handle a time request under '/time/'. void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -396,7 +395,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_DATETIME_DATETIME - void on_datetime_update(datetime::DateTimeEntity *obj) override; + void on_datetime_update(datetime::DateTimeEntity *obj); /// Handle a datetime request under '/datetime/'. void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -405,7 +404,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_TEXT - void on_text_update(text::Text *obj) override; + void on_text_update(text::Text *obj); /// Handle a text input request under '/text/'. void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -414,7 +413,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj) override; + void on_select_update(select::Select *obj); /// Handle a select request under '/select/'. void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -423,7 +422,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_CLIMATE - void on_climate_update(climate::Climate *obj) override; + void on_climate_update(climate::Climate *obj); /// Handle a climate request under '/climate/'. void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -432,7 +431,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_LOCK - void on_lock_update(lock::Lock *obj) override; + void on_lock_update(lock::Lock *obj); /// Handle a lock request under '/lock//'. void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -442,7 +441,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_VALVE - void on_valve_update(valve::Valve *obj) override; + void on_valve_update(valve::Valve *obj); /// Handle a valve request under '/valve//'. void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -452,7 +451,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_ALARM_CONTROL_PANEL - void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; + void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj); /// Handle a alarm_control_panel request under '/alarm_control_panel/'. void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -462,7 +461,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_WATER_HEATER - void on_water_heater_update(water_heater::WaterHeater *obj) override; + void on_water_heater_update(water_heater::WaterHeater *obj); /// Handle a water_heater request under '/water_heater//'. void handle_water_heater_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -484,8 +483,12 @@ class WebServer final : public Controller, public Component, public AsyncWebHand static json::SerializationBuffer<> radio_frequency_all_json_generator(WebServer *web_server, void *source); #endif +#ifdef USE_MEDIA_PLAYER + void on_media_player_update(media_player::MediaPlayer *obj) {} +#endif + #ifdef USE_EVENT - void on_event(event::Event *obj) override; + void on_event(event::Event *obj); static json::SerializationBuffer<> event_state_json_generator(WebServer *web_server, void *source); static json::SerializationBuffer<> event_all_json_generator(WebServer *web_server, void *source); @@ -495,7 +498,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand #endif #ifdef USE_UPDATE - void on_update(update::UpdateEntity *obj) override; + void on_update(update::UpdateEntity *obj); /// Handle a update request under '/update/'. void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match); diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 5fcad90a81..7a60e8de41 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -52,8 +52,8 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) -# Key for tracking controller count in CORE.data for ControllerRegistry StaticVector sizing -KEY_CONTROLLER_REGISTRY_COUNT = "controller_registry_count" +# Key for the controllers (APIServer, WebServer) that receive entity state updates +KEY_CONTROLLER_REGISTRY_CONTROLLERS = "controller_registry_controllers" # CORE.data key for the "is_rp2040 deprecation warning already fired this # run" flag. Mirrors the ``cv.only_on_rp2040`` dedupe pattern; cleared @@ -1209,10 +1209,14 @@ class EsphomeCore: if not self.platform_counts[platform_name]: self.platform_counts[platform_name] = 1 - def register_controller(self) -> None: - """Track registration of a Controller for ControllerRegistry StaticVector sizing.""" - controller_count = self.data.setdefault(KEY_CONTROLLER_REGISTRY_COUNT, 0) - self.data[KEY_CONTROLLER_REGISTRY_COUNT] = controller_count + 1 + def register_controller(self, controller: "MockObj") -> None: + """Register a controller that receives every entity state update. + + Code generation defines the ControllerRegistry notify functions in + main.cpp as direct calls on each registered controller, so the C++ + class only needs the on_*_update methods, not a base class. + """ + self.data.setdefault(KEY_CONTROLLER_REGISTRY_CONTROLLERS, []).append(controller) @property def cpp_main_section(self): diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index fac09e9e14..3c11d4a880 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/controller.h" +#include "esphome/core/entity_includes.h" #include "esphome/core/helpers.h" #ifdef USE_CAMERA diff --git a/esphome/core/config.py b/esphome/core/config.py index 051a136aab..9a647f72dd 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -46,7 +46,7 @@ from esphome.const import ( ) from esphome.core import ( CORE, - KEY_CONTROLLER_REGISTRY_COUNT, + KEY_CONTROLLER_REGISTRY_CONTROLLERS, CoroPriority, coroutine_with_priority, ) @@ -674,12 +674,35 @@ async def _add_platform_defines() -> None: @coroutine_with_priority(CoroPriority.FINAL) -async def _add_controller_registry_define() -> None: - # Generate StaticVector size for ControllerRegistry - controller_count = CORE.data.get(KEY_CONTROLLER_REGISTRY_COUNT, 0) - if controller_count > 0: - cg.add_define("USE_CONTROLLER_REGISTRY") - cg.add_define("CONTROLLER_REGISTRY_MAX", controller_count) +async def _add_controller_registry_dispatch() -> None: + # Define ControllerRegistry::notify_*() in main.cpp as direct calls on each + # registered controller. entity_types.h expands one definition per entity + # type that is compiled in, so only the callbacks in use are emitted. + controllers = CORE.data.get(KEY_CONTROLLER_REGISTRY_CONTROLLERS) + if not controllers: + return + cg.add_define("USE_CONTROLLER_REGISTRY") + for var in controllers: + cg.add_global( + cg.RawStatement( + f"static_assert(ControllerContract>, " + f'"{var} is missing an on_*_update() callback for an entity type in this build ' + '(esphome/core/controller_registry.h)");' + ) + ) + calls = " \\\n".join(f" {var}->on_##callback(obj);" for var in controllers) + cg.add_global( + cg.RawStatement( + "#define ENTITY_TYPE_(type, singular, plural, count, upper)\n" + "#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \\\n" + " void ControllerRegistry::notify_##callback(type *obj) { \\\n" + f"{calls} \\\n" + " }\n" + '#include "esphome/core/entity_types.h"\n' + "#undef ENTITY_TYPE_\n" + "#undef ENTITY_CONTROLLER_TYPE_" + ) + ) @coroutine_with_priority(CoroPriority.FINAL) @@ -755,7 +778,7 @@ async def to_code(config: ConfigType) -> None: ) CORE.add_job(_add_platform_defines) - CORE.add_job(_add_controller_registry_define) + CORE.add_job(_add_controller_registry_dispatch) CORE.add_job(_add_looping_components) CORE.add_job(_add_automations, config) diff --git a/esphome/core/controller.h b/esphome/core/controller.h deleted file mode 100644 index 09975b465f..0000000000 --- a/esphome/core/controller.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "esphome/core/entity_includes.h" - -namespace esphome { - -class Controller { - public: -// Controller virtual methods (generated from entity_types.h) -// NOLINTBEGIN(bugprone-macro-parentheses) -#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback -#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) virtual void on_##callback(type *obj){}; -#include "esphome/core/entity_types.h" -#undef ENTITY_TYPE_ -#undef ENTITY_CONTROLLER_TYPE_ - // NOLINTEND(bugprone-macro-parentheses) -}; - -} // namespace esphome diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp deleted file mode 100644 index 907e0f923d..0000000000 --- a/esphome/core/controller_registry.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "esphome/core/controller_registry.h" - -#ifdef USE_CONTROLLER_REGISTRY - -namespace esphome { - -StaticVector ControllerRegistry::controllers; - -} // namespace esphome - -#endif // USE_CONTROLLER_REGISTRY diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index c6113116ff..4adb4f9b76 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -5,37 +5,34 @@ #ifdef USE_CONTROLLER_REGISTRY #include "esphome/core/entity_includes.h" -#include "esphome/core/helpers.h" namespace esphome { -class Controller; +/// A controller provides a plain on_*_update() member for every entity type in the build. +/// Checked by a static_assert in the generated main.cpp for each registered controller. +// NOLINTBEGIN(bugprone-macro-parentheses) +template +concept ControllerContract = requires(T &controller) { + controller; +#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback +#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \ + controller.on_##callback(static_cast(nullptr)); +#include "esphome/core/entity_types.h" +#undef ENTITY_TYPE_ +#undef ENTITY_CONTROLLER_TYPE_ +}; +// NOLINTEND(bugprone-macro-parentheses) -/** Global registry for Controllers to receive entity state updates. +/** Fan-out of entity state updates to the controllers (APIServer, WebServer). * - * This singleton registry allows Controllers (APIServer, WebServer) to receive - * entity state change notifications without storing per-entity callbacks. - * - * Instead of each entity maintaining controller callbacks (32 bytes overhead per entity), - * entities call ControllerRegistry::notify_*_update() which iterates the small list - * of registered controllers (typically 2: API and WebServer). - * - * Each notify method directly iterates controllers and calls the virtual method, - * avoiding function pointer indirection for minimal dispatch overhead. - * - * Memory savings: 32 bytes per entity (2 controllers × 16 bytes std::function overhead) - * Typical config (25 entities): ~780 bytes saved - * Large config (80 entities): ~2,540 bytes saved + * Entities call ControllerRegistry::notify_*_update() instead of holding + * per-entity controller callbacks. The notify functions are only declared here; + * code generation defines them in main.cpp as direct calls on each controller + * that registered through CORE.register_controller(), so there is no virtual + * dispatch, no controller base class and no runtime list of controllers. */ class ControllerRegistry { public: - /** Register a controller to receive entity state updates. - * - * Controllers should call this in their setup() method. - * Typically only APIServer and WebServer register. - */ - static void register_controller(Controller *controller) { controllers.push_back(controller); } - // Notify method declarations (generated from entity_types.h) // NOLINTBEGIN(bugprone-macro-parentheses) #define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback @@ -45,38 +42,8 @@ class ControllerRegistry { #undef ENTITY_TYPE_ #undef ENTITY_CONTROLLER_TYPE_ // NOLINTEND(bugprone-macro-parentheses) - - protected: - static StaticVector controllers; }; } // namespace esphome -// Include controller.h AFTER the class definition so notify methods can be -// defined inline. This is safe because controller_registry.h is only ever -// included from .cpp files, never from other headers. -#include "esphome/core/controller.h" - -namespace esphome { - -// Inline notify methods — each is a tiny loop over 1-2 controllers. -// Defining them here (rather than in controller_registry.cpp) allows the -// compiler to inline them into the single call site in each entity's -// notify_frontend_(), eliminating an unnecessary function-call frame. - -// NOLINTBEGIN(bugprone-macro-parentheses) -#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback -#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \ - inline void ControllerRegistry::notify_##callback(type *obj) { \ - for (auto *controller : controllers) { \ - controller->on_##callback(obj); \ - } \ - } -#include "esphome/core/entity_types.h" -#undef ENTITY_TYPE_ -#undef ENTITY_CONTROLLER_TYPE_ -// NOLINTEND(bugprone-macro-parentheses) - -} // namespace esphome - #endif // USE_CONTROLLER_REGISTRY diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 7bc08ea577..e1ae1d5fb6 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -629,7 +629,6 @@ #define USE_DASHBOARD_IMPORT // Default counts for static analysis -#define CONTROLLER_REGISTRY_MAX 2 #define ESPHOME_AREA_COUNT 10 #define ESPHOME_COMPONENT_COUNT 50 #define ESPHOME_DEVICE_COUNT 10 diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 45d6559b3f..04892f5fea 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -37,7 +37,6 @@ optional = global_ns.namespace("std").class_("optional") arduino_json_ns = global_ns.namespace("ArduinoJson") JsonObject = arduino_json_ns.class_("JsonObject") JsonObjectConst = arduino_json_ns.class_("JsonObjectConst") -Controller = esphome_ns.class_("Controller") GPIOPin = esphome_ns.class_("GPIOPin") InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin) gpio_ns = esphome_ns.namespace("gpio") diff --git a/tests/components/api/__init__.py b/tests/components/api/__init__.py index 2aa558726c..a5e3731c75 100644 --- a/tests/components/api/__init__.py +++ b/tests/components/api/__init__.py @@ -1,5 +1,4 @@ import esphome.codegen as cg -from esphome.core import CORE from tests.testing_helpers import ComponentManifestOverride @@ -12,6 +11,5 @@ def override_manifest(manifest: ComponentManifestOverride) -> None: cg.add_define("API_MAX_SEND_QUEUE", 8) cg.add_define("MAX_API_CONNECTIONS", 1) cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS") - CORE.register_controller() # api_server registers with the controller registry manifest.to_code = to_code_testing diff --git a/tests/unit_tests/test_codegen.py b/tests/unit_tests/test_codegen.py index 8d01fef7c2..c5c971b22d 100644 --- a/tests/unit_tests/test_codegen.py +++ b/tests/unit_tests/test_codegen.py @@ -70,7 +70,6 @@ from esphome.cpp_generator import _extract_component_ns "arduino_json_ns", "JsonObject", "JsonObjectConst", - "Controller", "GPIOPin", ), ) From 4b9906c5c9662786397185d50dbd33794b2b9dc6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 24 Sep 2026 17:03:03 +0100 Subject: [PATCH 3/5] [core] Move the controller dispatch into controller_dispatch.h Code generation now emits one function returning the registered controllers as a tuple plus an include; the header defines the ControllerRegistry notify functions with a fold expression and checks ControllerContract for every tuple element. --- esphome/core/config.py | 25 ++++-------------- esphome/core/controller_dispatch.h | 41 ++++++++++++++++++++++++++++++ esphome/writer.py | 4 +++ 3 files changed, 50 insertions(+), 20 deletions(-) create mode 100644 esphome/core/controller_dispatch.h diff --git a/esphome/core/config.py b/esphome/core/config.py index 9a647f72dd..2d27ebce6f 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -675,34 +675,19 @@ async def _add_platform_defines() -> None: @coroutine_with_priority(CoroPriority.FINAL) async def _add_controller_registry_dispatch() -> None: - # Define ControllerRegistry::notify_*() in main.cpp as direct calls on each - # registered controller. entity_types.h expands one definition per entity - # type that is compiled in, so only the callbacks in use are emitted. + # controller_dispatch.h defines ControllerRegistry::notify_*() as direct + # calls on the controllers returned by esphome_controllers(). controllers = CORE.data.get(KEY_CONTROLLER_REGISTRY_CONTROLLERS) if not controllers: return cg.add_define("USE_CONTROLLER_REGISTRY") - for var in controllers: - cg.add_global( - cg.RawStatement( - f"static_assert(ControllerContract>, " - f'"{var} is missing an on_*_update() callback for an entity type in this build ' - '(esphome/core/controller_registry.h)");' - ) - ) - calls = " \\\n".join(f" {var}->on_##callback(obj);" for var in controllers) + entries = ", ".join(str(var) for var in controllers) cg.add_global( cg.RawStatement( - "#define ENTITY_TYPE_(type, singular, plural, count, upper)\n" - "#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \\\n" - " void ControllerRegistry::notify_##callback(type *obj) { \\\n" - f"{calls} \\\n" - " }\n" - '#include "esphome/core/entity_types.h"\n' - "#undef ENTITY_TYPE_\n" - "#undef ENTITY_CONTROLLER_TYPE_" + f"static auto esphome_controllers() {{ return std::tuple{{{entries}}}; }}" ) ) + cg.add_global(cg.RawStatement('#include "esphome/core/controller_dispatch.h"')) @coroutine_with_priority(CoroPriority.FINAL) diff --git a/esphome/core/controller_dispatch.h b/esphome/core/controller_dispatch.h new file mode 100644 index 0000000000..631de50236 --- /dev/null +++ b/esphome/core/controller_dispatch.h @@ -0,0 +1,41 @@ +#pragma once + +// Included once by the generated main.cpp, after it returns the registered controllers as a tuple: +// +// static auto esphome_controllers() { return std::tuple{api_apiserver_id, web_server_webserver_id}; } +// #include "esphome/core/controller_dispatch.h" +// +// Defines ControllerRegistry::notify_*() as direct calls on those controllers. Excluded from +// esphome.h so nothing else includes it. + +#include "esphome/core/controller_registry.h" + +#ifdef USE_CONTROLLER_REGISTRY + +#include +#include + +namespace esphome { + +template constexpr bool controllers_satisfy_contract(std::tuple *) { + static_assert((ControllerContract> && ...), + "A registered controller is missing an on_*_update() callback for an entity type in this build " + "(esphome/core/controller_registry.h)"); + return true; +} +static_assert(controllers_satisfy_contract(static_cast(nullptr))); + +// NOLINTBEGIN(bugprone-macro-parentheses) +#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback +#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \ + void ControllerRegistry::notify_##callback(type *obj) { \ + std::apply([obj](auto *...controller) { (controller->on_##callback(obj), ...); }, esphome_controllers()); \ + } +#include "esphome/core/entity_types.h" +#undef ENTITY_TYPE_ +#undef ENTITY_CONTROLLER_TYPE_ +// NOLINTEND(bugprone-macro-parentheses) + +} // namespace esphome + +#endif // USE_CONTROLLER_REGISTRY diff --git a/esphome/writer.py b/esphome/writer.py index 0b9e7669ef..3a1dc3ecf4 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -211,6 +211,7 @@ VERSION_H_TARGET = "esphome/core/version.h" BUILD_INFO_DATA_H_TARGET = "esphome/core/build_info_data.h" BUILD_INFO_DATA_CPP_TARGET = "esphome/core/build_info_data.cpp" ENTITY_TYPES_H_TARGET = "esphome/core/entity_types.h" +CONTROLLER_DISPATCH_H_TARGET = "esphome/core/controller_dispatch.h" ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY @@ -243,6 +244,9 @@ def copy_src_tree(): # new component is loaded by a consumer. esphome_h_exclude = { Path(ENTITY_TYPES_H_TARGET), + Path( + CONTROLLER_DISPATCH_H_TARGET + ), # included by main.cpp once ESPHOME_CONTROLLERS is defined Path( "esphome/core/ring_buffer.h" ), # moved to components/ring_buffer/, removed in 2026.11.0 From 207edd0522f7d84aca105e35591586c19e6c6e53 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 24 Sep 2026 17:12:32 +0100 Subject: [PATCH 4/5] [core] Tidy the controller dispatch after review Move ControllerContract next to its only user in controller_dispatch.h, collapse the contract check into one static_assert, and let codegen build the tuple with ArrayInitializer after emitting the tuple include. Share one esphome.h exclusion set between the writer and the clang-tidy all-headers file so the dispatch header is skipped by both. Add unit tests for the generated dispatch. --- esphome/components/web_server/web_server.h | 2 +- esphome/core/__init__.py | 7 +--- esphome/core/config.py | 8 +++-- esphome/core/controller_dispatch.h | 39 +++++++++++++--------- esphome/core/controller_registry.h | 22 ++---------- esphome/writer.py | 27 ++++++--------- script/helpers.py | 6 ++-- tests/unit_tests/core/test_config.py | 32 +++++++++++++++++- 8 files changed, 78 insertions(+), 65 deletions(-) diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 27e35dfe0f..b776c955ff 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -484,7 +484,7 @@ class WebServer final : public Component, public AsyncWebHandler { #endif #ifdef USE_MEDIA_PLAYER - void on_media_player_update(media_player::MediaPlayer *obj) {} + void on_media_player_update(media_player::MediaPlayer *) {} #endif #ifdef USE_EVENT diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 7a60e8de41..fb75285081 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -1210,12 +1210,7 @@ class EsphomeCore: self.platform_counts[platform_name] = 1 def register_controller(self, controller: "MockObj") -> None: - """Register a controller that receives every entity state update. - - Code generation defines the ControllerRegistry notify functions in - main.cpp as direct calls on each registered controller, so the C++ - class only needs the on_*_update methods, not a base class. - """ + """Register a controller that receives every entity state update.""" self.data.setdefault(KEY_CONTROLLER_REGISTRY_CONTROLLERS, []).append(controller) @property diff --git a/esphome/core/config.py b/esphome/core/config.py index 2d27ebce6f..4d3fb83312 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -676,15 +676,17 @@ async def _add_platform_defines() -> None: @coroutine_with_priority(CoroPriority.FINAL) async def _add_controller_registry_dispatch() -> None: # controller_dispatch.h defines ControllerRegistry::notify_*() as direct - # calls on the controllers returned by esphome_controllers(). + # calls on the controllers returned by esphome_controllers(), emitted as + # static auto esphome_controllers() { return std::tuple{a, b}; } controllers = CORE.data.get(KEY_CONTROLLER_REGISTRY_CONTROLLERS) if not controllers: return cg.add_define("USE_CONTROLLER_REGISTRY") - entries = ", ".join(str(var) for var in controllers) + controllers = cg.ArrayInitializer(*controllers) + cg.add_global(cg.RawStatement("#include ")) cg.add_global( cg.RawStatement( - f"static auto esphome_controllers() {{ return std::tuple{{{entries}}}; }}" + f"static auto esphome_controllers() {{ return std::tuple{controllers}; }}" ) ) cg.add_global(cg.RawStatement('#include "esphome/core/controller_dispatch.h"')) diff --git a/esphome/core/controller_dispatch.h b/esphome/core/controller_dispatch.h index 631de50236..fc6a03556d 100644 --- a/esphome/core/controller_dispatch.h +++ b/esphome/core/controller_dispatch.h @@ -6,26 +6,36 @@ // #include "esphome/core/controller_dispatch.h" // // Defines ControllerRegistry::notify_*() as direct calls on those controllers. Excluded from -// esphome.h so nothing else includes it. - -#include "esphome/core/controller_registry.h" - -#ifdef USE_CONTROLLER_REGISTRY +// esphome.h and the clang-tidy all-headers file, so nothing else includes it. #include #include +#include "esphome/core/controller_registry.h" + namespace esphome { -template constexpr bool controllers_satisfy_contract(std::tuple *) { - static_assert((ControllerContract> && ...), - "A registered controller is missing an on_*_update() callback for an entity type in this build " - "(esphome/core/controller_registry.h)"); - return true; -} -static_assert(controllers_satisfy_contract(static_cast(nullptr))); - // NOLINTBEGIN(bugprone-macro-parentheses) + +/// A controller provides a plain on_*_update() member for every entity type in the build. +template +concept ControllerContract = requires(T &controller) { + controller; // keeps the requirement list non-empty when no entity type has a callback +#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback +#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \ + controller.on_##callback(static_cast(nullptr)); +#include "esphome/core/entity_types.h" +#undef ENTITY_TYPE_ +#undef ENTITY_CONTROLLER_TYPE_ +}; + +template constexpr bool controllers_satisfy_contract(std::tuple *) { + return (ControllerContract> && ...); +} +static_assert(controllers_satisfy_contract(static_cast(nullptr)), + "A registered controller is missing an on_*_update() callback for an entity type in this build " + "(ControllerContract in esphome/core/controller_dispatch.h)"); + #define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback #define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \ void ControllerRegistry::notify_##callback(type *obj) { \ @@ -34,8 +44,7 @@ static_assert(controllers_satisfy_contract(static_cast -concept ControllerContract = requires(T &controller) { - controller; -#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback -#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \ - controller.on_##callback(static_cast(nullptr)); -#include "esphome/core/entity_types.h" -#undef ENTITY_TYPE_ -#undef ENTITY_CONTROLLER_TYPE_ -}; -// NOLINTEND(bugprone-macro-parentheses) - /** Fan-out of entity state updates to the controllers (APIServer, WebServer). * * Entities call ControllerRegistry::notify_*_update() instead of holding - * per-entity controller callbacks. The notify functions are only declared here; - * code generation defines them in main.cpp as direct calls on each controller - * that registered through CORE.register_controller(), so there is no virtual - * dispatch, no controller base class and no runtime list of controllers. + * per-entity controller callbacks. The functions are only declared here; + * controller_dispatch.h, included by the generated main.cpp, defines them as + * direct calls on the controllers registered through CORE.register_controller(). */ class ControllerRegistry { public: diff --git a/esphome/writer.py b/esphome/writer.py index 3a1dc3ecf4..9ab5d644c5 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -211,7 +211,16 @@ VERSION_H_TARGET = "esphome/core/version.h" BUILD_INFO_DATA_H_TARGET = "esphome/core/build_info_data.h" BUILD_INFO_DATA_CPP_TARGET = "esphome/core/build_info_data.cpp" ENTITY_TYPES_H_TARGET = "esphome/core/entity_types.h" -CONTROLLER_DISPATCH_H_TARGET = "esphome/core/controller_dispatch.h" +# Headers that must not be included bare from esphome.h or the clang-tidy +# all-headers file: X-macro files, headers main.cpp includes itself, and +# deprecated headers that only resolve when their new component is loaded. +ESPHOME_H_EXCLUDE = { + Path(ENTITY_TYPES_H_TARGET), + # main.cpp includes it after defining esphome_controllers() + Path("esphome/core/controller_dispatch.h"), + # moved to components/ring_buffer/, removed in 2026.11.0 + Path("esphome/core/ring_buffer.h"), +} ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY @@ -237,23 +246,9 @@ def copy_src_tree(): source_files_l.sort() # Build #include list for esphome.h - # X-macro files are included multiple times with different macro definitions - # and must not be included bare in esphome.h - # Deprecated headers that re-export from a relocated component must not be - # auto-included, since their #include of the new path only resolves when the - # new component is loaded by a consumer. - esphome_h_exclude = { - Path(ENTITY_TYPES_H_TARGET), - Path( - CONTROLLER_DISPATCH_H_TARGET - ), # included by main.cpp once ESPHOME_CONTROLLERS is defined - Path( - "esphome/core/ring_buffer.h" - ), # moved to components/ring_buffer/, removed in 2026.11.0 - } include_l = [] for target, _ in source_files_l: - if target.suffix in HEADER_FILE_EXTENSIONS and target not in esphome_h_exclude: + if target.suffix in HEADER_FILE_EXTENSIONS and target not in ESPHOME_H_EXCLUDE: include_l.append(f'#include "{target}"') include_l.append("") include_s = "\n".join(include_l) diff --git a/script/helpers.py b/script/helpers.py index a8a237118f..f2516523c7 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -429,11 +429,9 @@ def build_all_include(header_files: list[str] | None = None) -> None: if line ] - from esphome.writer import ENTITY_TYPES_H_TARGET + from esphome.writer import ESPHOME_H_EXCLUDE - # X-macro files are included multiple times with different macro definitions - # and must not be included bare in the all-include header - exclude = {ENTITY_TYPES_H_TARGET} + exclude = {str(path) for path in ESPHOME_H_EXCLUDE} headers = [f'#include "{h}"' for h in header_files if h not in exclude] headers.sort() headers.append("") diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index 07cff003cd..0db7039668 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -9,6 +9,7 @@ from unittest.mock import MagicMock, Mock, patch import pytest from esphome import config_validation as cv, core +import esphome.codegen as cg from esphome.components.safe_mode import to_code as safe_mode_to_code from esphome.const import ( CONF_AREA, @@ -23,7 +24,7 @@ from esphome.const import ( KEY_TARGET_PLATFORM, Toolchain, ) -from esphome.core import CORE, config +from esphome.core import CORE, KEY_CONTROLLER_REGISTRY_CONTROLLERS, config from esphome.core.config import ( Area, make_app_name_cpp, @@ -455,6 +456,35 @@ async def test_add_looping_components_with_entries() -> None: assert "(1 * HasLoopOverride::value)" in text +@pytest.mark.asyncio +async def test_add_controller_registry_dispatch_without_controllers() -> None: + """Nothing is emitted when no controller registered.""" + CORE.data.pop(KEY_CONTROLLER_REGISTRY_CONTROLLERS, None) + + await config._add_controller_registry_dispatch() + + assert "USE_CONTROLLER_REGISTRY" not in {d.name for d in CORE.defines} + assert not [s for s in CORE.global_statements if "controller" in str(s)] + + +@pytest.mark.asyncio +async def test_add_controller_registry_dispatch_with_controllers() -> None: + """Registered controllers become one tuple plus the dispatch include.""" + CORE.register_controller(cg.MockObj("api_apiserver_id")) + CORE.register_controller(cg.MockObj("web_server_webserver_id")) + + await config._add_controller_registry_dispatch() + + assert "USE_CONTROLLER_REGISTRY" in {d.name for d in CORE.defines} + statements = [str(s) for s in CORE.global_statements] + assert "#include " in statements + assert ( + "static auto esphome_controllers() { return std::tuple{api_apiserver_id, web_server_webserver_id}; }" + in statements + ) + assert '#include "esphome/core/controller_dispatch.h"' in statements + + def test_valid_include_with_angle_brackets() -> None: """Test valid_include accepts angle bracket includes.""" assert valid_include("") == "" From 6174aae86032abf9d314dc6a7244521caba93c4a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 24 Sep 2026 17:19:21 +0100 Subject: [PATCH 5/5] [web_server] Say why the media player callback is a stub --- esphome/components/web_server/web_server.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index b776c955ff..e395f92b5a 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -484,6 +484,7 @@ class WebServer final : public Component, public AsyncWebHandler { #endif #ifdef USE_MEDIA_PLAYER + // Not exposed over HTTP; the stub only satisfies ControllerContract void on_media_player_update(media_player::MediaPlayer *) {} #endif