mirror of
https://github.com/esphome/esphome.git
synced 2026-01-11 12:40:50 -07:00
146 lines
4.6 KiB
Python
146 lines
4.6 KiB
Python
"""Utilities for computing entity object_id in integration tests.
|
|
|
|
This module contains the algorithm that aioesphomeapi will use to compute
|
|
object_id client-side from API data.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from esphome.helpers import fnv1_hash_object_id, sanitize, snake_case
|
|
|
|
if TYPE_CHECKING:
|
|
from aioesphomeapi import DeviceInfo, EntityInfo
|
|
|
|
|
|
def compute_object_id(name: str) -> str:
|
|
"""Compute object_id from name using snake_case + sanitize."""
|
|
return sanitize(snake_case(name))
|
|
|
|
|
|
def infer_name_add_mac_suffix(device_info: DeviceInfo) -> bool:
|
|
"""Infer name_add_mac_suffix from device name ending with MAC suffix."""
|
|
mac_suffix = device_info.mac_address.replace(":", "")[-6:].lower()
|
|
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.
|
|
|
|
Args:
|
|
entity: The entity to compute object_id for
|
|
device_info: Device info from the API
|
|
device_id_to_name: Mapping of device_id to device name for sub-devices
|
|
|
|
Returns:
|
|
The computed object_id string
|
|
"""
|
|
name_for_id = _get_name_for_object_id(entity, device_info, device_id_to_name)
|
|
return compute_object_id(name_for_id)
|
|
|
|
|
|
def compute_entity_hash(
|
|
entity: EntityInfo,
|
|
device_info: DeviceInfo,
|
|
device_id_to_name: dict[int, str],
|
|
) -> int:
|
|
"""Compute expected object_id hash for an entity.
|
|
|
|
Args:
|
|
entity: The entity to compute hash for
|
|
device_info: Device info from the API
|
|
device_id_to_name: Mapping of device_id to device name for sub-devices
|
|
|
|
Returns:
|
|
The computed FNV-1 hash
|
|
"""
|
|
name_for_id = _get_name_for_object_id(entity, device_info, device_id_to_name)
|
|
return fnv1_hash_object_id(name_for_id)
|
|
|
|
|
|
def verify_entity_object_id(
|
|
entity: EntityInfo,
|
|
device_info: DeviceInfo,
|
|
device_id_to_name: dict[int, str],
|
|
) -> None:
|
|
"""Verify an entity's object_id and hash match the expected values.
|
|
|
|
Args:
|
|
entity: The entity to verify
|
|
device_info: Device info from the API
|
|
device_id_to_name: Mapping of device_id to device name for sub-devices
|
|
|
|
Raises:
|
|
AssertionError: If object_id or hash doesn't match expected value
|
|
"""
|
|
expected_object_id = compute_entity_object_id(
|
|
entity, device_info, device_id_to_name
|
|
)
|
|
assert entity.object_id == expected_object_id, (
|
|
f"object_id mismatch for entity '{entity.name}': "
|
|
f"expected '{expected_object_id}', got '{entity.object_id}'"
|
|
)
|
|
|
|
expected_hash = compute_entity_hash(entity, device_info, device_id_to_name)
|
|
assert entity.key == expected_hash, (
|
|
f"hash mismatch for entity '{entity.name}': "
|
|
f"expected {expected_hash:#x}, got {entity.key:#x}"
|
|
)
|
|
|
|
|
|
def verify_all_entities(
|
|
entities: list[EntityInfo],
|
|
device_info: DeviceInfo,
|
|
) -> None:
|
|
"""Verify all entities have correct object_id and hash values.
|
|
|
|
Args:
|
|
entities: List of entities to verify
|
|
device_info: Device info from the API
|
|
|
|
Raises:
|
|
AssertionError: If any entity's object_id or hash doesn't match
|
|
"""
|
|
# Build device_id -> name lookup from sub-devices
|
|
device_id_to_name = {d.device_id: d.name for d in device_info.devices}
|
|
|
|
for entity in entities:
|
|
verify_entity_object_id(entity, device_info, device_id_to_name)
|