From 5b3d61b4a6832a516955181c3da8a6a9158e805d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 19 Jul 2025 17:41:00 -1000 Subject: [PATCH 1/2] [api] Fix missing ifdef guards for field_ifdef fields in protobuf base classes (#9693) --- esphome/components/api/api_connection.h | 4 +++- esphome/components/api/api_pb2.h | 8 +++++++ script/api_protobuf/api_protobuf.py | 31 +++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 3873c7fcac..9ed18c24dc 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -301,8 +301,10 @@ class APIConnection : public APIServerConnection { if (entity->has_own_name()) msg.name = entity->get_name(); - // Set common EntityBase properties + // Set common EntityBase properties +#ifdef USE_ENTITY_ICON msg.icon = entity->get_icon(); +#endif msg.disabled_by_default = entity->is_disabled_by_default(); msg.entity_category = static_cast(entity->get_entity_category()); #ifdef USE_DEVICES diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 39f00b4adc..95db58aae9 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -292,9 +292,13 @@ class InfoResponseProtoMessage : public ProtoMessage { uint32_t key{0}; std::string name{}; bool disabled_by_default{false}; +#ifdef USE_ENTITY_ICON std::string icon{}; +#endif enums::EntityCategory entity_category{}; +#ifdef USE_DEVICES uint32_t device_id{0}; +#endif protected: }; @@ -303,7 +307,9 @@ class StateResponseProtoMessage : public ProtoMessage { public: ~StateResponseProtoMessage() override = default; uint32_t key{0}; +#ifdef USE_DEVICES uint32_t device_id{0}; +#endif protected: }; @@ -312,7 +318,9 @@ class CommandProtoMessage : public ProtoDecodableMessage { public: ~CommandProtoMessage() override = default; uint32_t key{0}; +#ifdef USE_DEVICES uint32_t device_id{0}; +#endif protected: }; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 4df7692167..bb0e01d171 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1491,6 +1491,28 @@ def find_common_fields( return common_fields +def get_common_field_ifdef( + field_name: str, messages: list[descriptor.DescriptorProto] +) -> str | None: + """Get the field_ifdef option if it's consistent across all messages. + + Args: + field_name: Name of the field to check + messages: List of messages that contain this field + + Returns: + The field_ifdef string if all messages have the same value, None otherwise + """ + field_ifdefs = { + get_field_opt(field, pb.field_ifdef) + for msg in messages + if (field := next((f for f in msg.field if f.name == field_name), None)) + } + + # Return the ifdef only if all messages agree on the same value + return field_ifdefs.pop() if len(field_ifdefs) == 1 else None + + def build_base_class( base_class_name: str, common_fields: list[descriptor.FieldDescriptorProto], @@ -1506,9 +1528,14 @@ def build_base_class( for field in common_fields: ti = create_field_type_info(field) + # Get field_ifdef if it's consistent across all messages + field_ifdef = get_common_field_ifdef(field.name, messages) + # Only add field declarations, not encode/decode logic - protected_content.extend(ti.protected_content) - public_content.extend(ti.public_content) + if ti.protected_content: + protected_content.extend(wrap_with_ifdef(ti.protected_content, field_ifdef)) + if ti.public_content: + public_content.extend(wrap_with_ifdef(ti.public_content, field_ifdef)) # Determine if any message using this base class needs decoding needs_decode = any( From a5ed8db5bd1f5ee25f2bf8f9b8ec134d4fa6d04e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 19 Jul 2025 22:01:22 -1000 Subject: [PATCH 2/2] [api] Fix missing ifdef guards for AreaInfo and DeviceInfo messages --- esphome/components/api/api_pb2.cpp | 4 ++++ esphome/components/api/api_pb2.h | 4 ++++ esphome/components/api/api_pb2_dump.cpp | 4 ++++ script/api_protobuf/api_protobuf.py | 21 ++++++++++++++++++--- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 437c9ece1d..8a93ff815a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -57,6 +57,7 @@ void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool void ConnectResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_bool_field(total_size, 1, this->invalid_password); } +#ifdef USE_AREAS void AreaInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->area_id); buffer.encode_string(2, this->name); @@ -65,6 +66,8 @@ void AreaInfo::calculate_size(uint32_t &total_size) const { ProtoSize::add_uint32_field(total_size, 1, this->area_id); ProtoSize::add_string_field(total_size, 1, this->name); } +#endif +#ifdef USE_DEVICES void DeviceInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->device_id); buffer.encode_string(2, this->name); @@ -75,6 +78,7 @@ void DeviceInfo::calculate_size(uint32_t &total_size) const { ProtoSize::add_string_field(total_size, 1, this->name); ProtoSize::add_uint32_field(total_size, 1, this->area_id); } +#endif void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->uses_password); buffer.encode_string(2, this->name); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 95db58aae9..66490bdcc5 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -457,6 +457,7 @@ class DeviceInfoRequest : public ProtoDecodableMessage { protected: }; +#ifdef USE_AREAS class AreaInfo : public ProtoMessage { public: uint32_t area_id{0}; @@ -469,6 +470,8 @@ class AreaInfo : public ProtoMessage { protected: }; +#endif +#ifdef USE_DEVICES class DeviceInfo : public ProtoMessage { public: uint32_t device_id{0}; @@ -482,6 +485,7 @@ class DeviceInfo : public ProtoMessage { protected: }; +#endif class DeviceInfoResponse : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 10; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index ad5a5fdcaa..20bf98bddc 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -655,6 +655,7 @@ void DisconnectResponse::dump_to(std::string &out) const { out.append("Disconnec void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); } void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); } void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } +#ifdef USE_AREAS void AreaInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("AreaInfo {\n"); @@ -668,6 +669,8 @@ void AreaInfo::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif +#ifdef USE_DEVICES void DeviceInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("DeviceInfo {\n"); @@ -686,6 +689,7 @@ void DeviceInfo::dump_to(std::string &out) const { out.append("\n"); out.append("}"); } +#endif void DeviceInfoResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("DeviceInfoResponse {\n"); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index bb0e01d171..9071d4e879 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -995,6 +995,11 @@ def build_type_usage_map( } # Analyze field usage + # Also track field_ifdef for message types + message_field_ifdefs: dict[ + str, set[str | None] + ] = {} # message_name -> set of field_ifdefs that use it + for message in file_desc.message_type: for field in message.field: type_name = field.type_name.split(".")[-1] if field.type_name else None @@ -1007,6 +1012,9 @@ def build_type_usage_map( # Track message usage elif field.type == 11: # TYPE_MESSAGE message_usage.setdefault(type_name, set()).add(message.name) + # Also track the field_ifdef if present + field_ifdef = get_field_opt(field, pb.field_ifdef) + message_field_ifdefs.setdefault(type_name, set()).add(field_ifdef) # Helper to get unique ifdef from a set of messages def get_unique_ifdef(message_names: set[str]) -> str | None: @@ -1032,9 +1040,16 @@ def build_type_usage_map( message_ifdef_map[message.name] = explicit_ifdef elif message.name in message_usage: # Inherit ifdef if all parent messages have the same one - message_ifdef_map[message.name] = get_unique_ifdef( - message_usage[message.name] - ) + if parent_ifdef := get_unique_ifdef(message_usage[message.name]): + message_ifdef_map[message.name] = parent_ifdef + elif message.name in message_field_ifdefs: + # If no parent message ifdef, check if all fields using this message have the same field_ifdef + field_ifdefs = message_field_ifdefs[message.name] - {None} + message_ifdef_map[message.name] = ( + field_ifdefs.pop() if len(field_ifdefs) == 1 else None + ) + else: + message_ifdef_map[message.name] = None else: message_ifdef_map[message.name] = None