From da1955fefcdd0aab73e5445f30f0dae657d6c52a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Dec 2025 07:54:52 -1000 Subject: [PATCH 1/2] dry up tests --- tests/integration/entity_utils.py | 69 ++++++++++++++++--------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/tests/integration/entity_utils.py b/tests/integration/entity_utils.py index f0164341e3..7596983ee2 100644 --- a/tests/integration/entity_utils.py +++ b/tests/integration/entity_utils.py @@ -25,15 +25,44 @@ def infer_name_add_mac_suffix(device_info: DeviceInfo) -> bool: return device_info.name.endswith(f"-{mac_suffix}") +def _get_name_for_object_id( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> str: + """Get the name used for object_id computation. + + This is the algorithm that aioesphomeapi will use to determine which + name to use for computing object_id client-side from API data. + + Args: + entity: The entity to get name for + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Returns: + The name to use for object_id computation + """ + if entity.name: + # Named entity: use entity name + return entity.name + if entity.device_id != 0: + # Empty name on sub-device: use sub-device name + return device_id_to_name[entity.device_id] + if infer_name_add_mac_suffix(device_info) or device_info.friendly_name: + # Empty name on main device with MAC suffix or friendly_name: use friendly_name + # (even if empty - this is bug-for-bug compatibility for MAC suffix case) + return device_info.friendly_name + # Empty name on main device, no friendly_name: use device name + return device_info.name + + def compute_entity_object_id( entity: EntityInfo, device_info: DeviceInfo, device_id_to_name: dict[int, str], ) -> str: - """Compute expected object_id for an entity using the algorithm from PR summary. - - This is the algorithm that aioesphomeapi will use to compute object_id - client-side from API data. + """Compute expected object_id for an entity. Args: entity: The entity to compute object_id for @@ -43,25 +72,7 @@ def compute_entity_object_id( Returns: The computed object_id string """ - name_add_mac_suffix = infer_name_add_mac_suffix(device_info) - - if entity.name: - # Named entity: use entity name - name_for_id = entity.name - elif entity.device_id != 0: - # Empty name on sub-device: use sub-device name - name_for_id = device_id_to_name[entity.device_id] - elif name_add_mac_suffix: - # Empty name on main device with MAC suffix: use friendly_name directly - # (even if empty - this is bug-for-bug compatibility) - name_for_id = device_info.friendly_name - elif device_info.friendly_name: - # Empty name on main device with friendly_name set: use it - name_for_id = device_info.friendly_name - else: - # Empty name on main device, no friendly_name: use device name - name_for_id = device_info.name - + name_for_id = _get_name_for_object_id(entity, device_info, device_id_to_name) return compute_object_id(name_for_id) @@ -80,17 +91,7 @@ def compute_entity_hash( Returns: The computed FNV-1 hash """ - name_add_mac_suffix = infer_name_add_mac_suffix(device_info) - - if entity.name: - name_for_id = entity.name - elif entity.device_id != 0: - name_for_id = device_id_to_name[entity.device_id] - elif name_add_mac_suffix or device_info.friendly_name: - name_for_id = device_info.friendly_name - else: - name_for_id = device_info.name - + name_for_id = _get_name_for_object_id(entity, device_info, device_id_to_name) return fnv1_hash_object_id(name_for_id) From 7a091c0ac6a7769bd92dda8dcfd6574b583b5fba Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Dec 2025 15:23:32 -1000 Subject: [PATCH 2/2] [api] Remove object_id from API protocol - clients compute it from name --- esphome/components/api/api_connection.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 6363116900..0b46ed54aa 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -24,9 +24,9 @@ struct ClientInfo { // Keepalive timeout in milliseconds static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; // Maximum number of entities to process in a single batch during initial state/info sending -// This was increased from 20 to 24 after removing the unique_id field from entity info messages, +// This was increased from 24 to 34 after removing object_id from entity info messages, // which reduced message sizes allowing more entities per batch without exceeding packet limits -static constexpr size_t MAX_INITIAL_PER_BATCH = 24; +static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // Maximum number of packets to process in a single batch (platform-dependent) // This limit exists to prevent stack overflow from the PacketInfo array in process_batch_ // Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes @@ -323,10 +323,8 @@ class APIConnection final : public APIServerConnection { APIConnection *conn, uint32_t remaining_size, bool is_single) { // Set common fields that are shared by all entity types msg.key = entity->get_object_id_hash(); - // Get object_id with zero heap allocation - // Static case returns direct reference, dynamic case uses buffer - char object_id_buf[OBJECT_ID_MAX_LEN]; - msg.set_object_id(entity->get_object_id_to(object_id_buf)); + // object_id is no longer sent over the wire - clients compute it from the name + // See: https://github.com/esphome/backlog/issues/76 if (entity->has_own_name()) { msg.set_name(entity->get_name());