/
/
/
1"""Helpers for interpreting Home Assistant entities as player controls."""
2
3from __future__ import annotations
4
5import re
6from typing import TYPE_CHECKING, NamedTuple
7
8from .constants import MediaPlayerEntityFeature, parse_supported_features
9
10if TYPE_CHECKING:
11 import logging
12
13 from hass_client.models import State
14
15# Home Assistant entity IDs are a domain and an object ID, both lowercase, joined by a dot
16ENTITY_ID_PATTERN = re.compile(r"^[a-z0-9_]+\.[a-z0-9_]+$")
17
18
19class ControlCapabilities(NamedTuple):
20 """The player control roles a Home Assistant entity can serve."""
21
22 power: bool = False
23 volume: bool = False
24 mute: bool = False
25
26
27def is_entity_id(value: str) -> bool:
28 """
29 Return whether the given value has the shape of a Home Assistant entity ID.
30
31 :param value: The value to inspect.
32 """
33 return bool(ENTITY_ID_PATTERN.match(value))
34
35
36def get_control_capabilities(state: State, logger: logging.Logger) -> ControlCapabilities:
37 """
38 Return the player control roles the given Home Assistant entity can serve.
39
40 :param state: The current state of the entity to inspect.
41 :param logger: Logger to report an unparsable supported_features attribute on.
42 :return: The supported roles; all False when the entity is unusable as a player control.
43 """
44 entity_platform = state["entity_id"].split(".")[0]
45 if entity_platform in ("switch", "input_boolean"):
46 # simple on/off controls are suitable as power and mute controls
47 return ControlCapabilities(power=True, mute=True)
48 if entity_platform in ("number", "input_number"):
49 # number and input_number are very similar, both are suitable for volume control
50 return ControlCapabilities(volume=True)
51 # media player can be used as control, depending on features
52 if entity_platform != "media_player":
53 return ControlCapabilities()
54 if "mass_player_type" in state["attributes"]:
55 # filter out mass players
56 return ControlCapabilities()
57 supported_features = parse_supported_features(
58 state["attributes"].get("supported_features"),
59 state["entity_id"],
60 logger,
61 )
62 return ControlCapabilities(
63 power=(
64 MediaPlayerEntityFeature.TURN_ON in supported_features
65 and MediaPlayerEntityFeature.TURN_OFF in supported_features
66 ),
67 volume=MediaPlayerEntityFeature.VOLUME_SET in supported_features,
68 mute=MediaPlayerEntityFeature.VOLUME_MUTE in supported_features,
69 )
70
71
72def get_control_name(entity_id: str, state: State | None) -> str:
73 """
74 Return the human readable name to present a Home Assistant entity control under.
75
76 :param entity_id: The entity the control is based on.
77 :param state: The entity's current state, if known.
78 """
79 if state and (friendly_name := state["attributes"].get("friendly_name")):
80 return f"{friendly_name} ({entity_id})"
81 return entity_id
82