Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support to interface with universal remote control devices."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Iterable
6 from datetime import timedelta
7 from enum import IntFlag
8 import functools as ft
9 import logging
10 from typing import Any, final
11 
12 from propcache import cached_property
13 import voluptuous as vol
14 
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.const import (
17  ATTR_COMMAND,
18  SERVICE_TOGGLE,
19  SERVICE_TURN_OFF,
20  SERVICE_TURN_ON,
21  STATE_ON,
22 )
23 from homeassistant.core import HomeAssistant
24 from homeassistant.helpers import config_validation as cv
26  DeprecatedConstantEnum,
27  all_with_deprecated_constants,
28  check_if_deprecated_constant,
29  dir_with_deprecated_constants,
30 )
31 from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
32 from homeassistant.helpers.entity_component import EntityComponent
33 from homeassistant.helpers.typing import ConfigType
34 from homeassistant.loader import bind_hass
35 from homeassistant.util.hass_dict import HassKey
36 
37 _LOGGER = logging.getLogger(__name__)
38 
39 DOMAIN = "remote"
40 DATA_COMPONENT: HassKey[EntityComponent[RemoteEntity]] = HassKey(DOMAIN)
41 ENTITY_ID_FORMAT = DOMAIN + ".{}"
42 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
43 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
44 SCAN_INTERVAL = timedelta(seconds=30)
45 
46 ATTR_ACTIVITY = "activity"
47 ATTR_ACTIVITY_LIST = "activity_list"
48 ATTR_CURRENT_ACTIVITY = "current_activity"
49 ATTR_COMMAND_TYPE = "command_type"
50 ATTR_DEVICE = "device"
51 ATTR_NUM_REPEATS = "num_repeats"
52 ATTR_DELAY_SECS = "delay_secs"
53 ATTR_HOLD_SECS = "hold_secs"
54 ATTR_ALTERNATIVE = "alternative"
55 ATTR_TIMEOUT = "timeout"
56 
57 MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
58 
59 SERVICE_SEND_COMMAND = "send_command"
60 SERVICE_LEARN_COMMAND = "learn_command"
61 SERVICE_DELETE_COMMAND = "delete_command"
62 SERVICE_SYNC = "sync"
63 
64 DEFAULT_NUM_REPEATS = 1
65 DEFAULT_DELAY_SECS = 0.4
66 DEFAULT_HOLD_SECS = 0
67 
68 
69 class RemoteEntityFeature(IntFlag):
70  """Supported features of the remote entity."""
71 
72  LEARN_COMMAND = 1
73  DELETE_COMMAND = 2
74  ACTIVITY = 4
75 
76 
77 # These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
78 # Please use the RemoteEntityFeature enum instead.
79 _DEPRECATED_SUPPORT_LEARN_COMMAND = DeprecatedConstantEnum(
80  RemoteEntityFeature.LEARN_COMMAND, "2025.1"
81 )
82 _DEPRECATED_SUPPORT_DELETE_COMMAND = DeprecatedConstantEnum(
83  RemoteEntityFeature.DELETE_COMMAND, "2025.1"
84 )
85 _DEPRECATED_SUPPORT_ACTIVITY = DeprecatedConstantEnum(
86  RemoteEntityFeature.ACTIVITY, "2025.1"
87 )
88 
89 
90 REMOTE_SERVICE_ACTIVITY_SCHEMA = cv.make_entity_service_schema(
91  {vol.Optional(ATTR_ACTIVITY): cv.string}
92 )
93 
94 
95 @bind_hass
96 def is_on(hass: HomeAssistant, entity_id: str) -> bool:
97  """Return if the remote is on based on the statemachine."""
98  return hass.states.is_state(entity_id, STATE_ON)
99 
100 
101 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
102  """Track states and offer events for remotes."""
103  component = hass.data[DATA_COMPONENT] = EntityComponent[RemoteEntity](
104  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
105  )
106  await component.async_setup(config)
107 
108  component.async_register_entity_service(
109  SERVICE_TURN_OFF, REMOTE_SERVICE_ACTIVITY_SCHEMA, "async_turn_off"
110  )
111 
112  component.async_register_entity_service(
113  SERVICE_TURN_ON, REMOTE_SERVICE_ACTIVITY_SCHEMA, "async_turn_on"
114  )
115 
116  component.async_register_entity_service(
117  SERVICE_TOGGLE, REMOTE_SERVICE_ACTIVITY_SCHEMA, "async_toggle"
118  )
119 
120  component.async_register_entity_service(
121  SERVICE_SEND_COMMAND,
122  {
123  vol.Required(ATTR_COMMAND): vol.All(cv.ensure_list, [cv.string]),
124  vol.Optional(ATTR_DEVICE): cv.string,
125  vol.Optional(
126  ATTR_NUM_REPEATS, default=DEFAULT_NUM_REPEATS
127  ): cv.positive_int,
128  vol.Optional(ATTR_DELAY_SECS): vol.Coerce(float),
129  vol.Optional(ATTR_HOLD_SECS, default=DEFAULT_HOLD_SECS): vol.Coerce(float),
130  },
131  "async_send_command",
132  )
133 
134  component.async_register_entity_service(
135  SERVICE_LEARN_COMMAND,
136  {
137  vol.Optional(ATTR_DEVICE): cv.string,
138  vol.Optional(ATTR_COMMAND): vol.All(cv.ensure_list, [cv.string]),
139  vol.Optional(ATTR_COMMAND_TYPE): cv.string,
140  vol.Optional(ATTR_ALTERNATIVE): cv.boolean,
141  vol.Optional(ATTR_TIMEOUT): cv.positive_int,
142  },
143  "async_learn_command",
144  )
145 
146  component.async_register_entity_service(
147  SERVICE_DELETE_COMMAND,
148  {
149  vol.Required(ATTR_COMMAND): vol.All(cv.ensure_list, [cv.string]),
150  vol.Optional(ATTR_DEVICE): cv.string,
151  },
152  "async_delete_command",
153  )
154 
155  return True
156 
157 
158 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
159  """Set up a config entry."""
160  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
161 
162 
163 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
164  """Unload a config entry."""
165  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
166 
167 
168 class RemoteEntityDescription(ToggleEntityDescription, frozen_or_thawed=True):
169  """A class that describes remote entities."""
170 
171 
172 CACHED_PROPERTIES_WITH_ATTR_ = {
173  "supported_features",
174  "current_activity",
175  "activity_list",
176 }
177 
178 
179 class RemoteEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
180  """Base class for remote entities."""
181 
182  entity_description: RemoteEntityDescription
183  _attr_activity_list: list[str] | None = None
184  _attr_current_activity: str | None = None
185  _attr_supported_features: RemoteEntityFeature = RemoteEntityFeature(0)
186 
187  @cached_property
188  def supported_features(self) -> RemoteEntityFeature:
189  """Flag supported features."""
190  return self._attr_supported_features
191 
192  @property
193  def supported_features_compat(self) -> RemoteEntityFeature:
194  """Return the supported features as RemoteEntityFeature.
195 
196  Remove this compatibility shim in 2025.1 or later.
197  """
198  features = self.supported_featuressupported_featuressupported_features
199  if type(features) is int: # noqa: E721
200  new_features = RemoteEntityFeature(features)
201  self._report_deprecated_supported_features_values_report_deprecated_supported_features_values(new_features)
202  return new_features
203  return features
204 
205  @cached_property
206  def current_activity(self) -> str | None:
207  """Active activity."""
208  return self._attr_current_activity
209 
210  @cached_property
211  def activity_list(self) -> list[str] | None:
212  """List of available activities."""
213  return self._attr_activity_list
214 
215  @final
216  @property
217  def state_attributes(self) -> dict[str, Any] | None:
218  """Return optional state attributes."""
219  if RemoteEntityFeature.ACTIVITY not in self.supported_features_compatsupported_features_compat:
220  return None
221 
222  return {
223  ATTR_ACTIVITY_LIST: self.activity_listactivity_list,
224  ATTR_CURRENT_ACTIVITY: self.current_activitycurrent_activity,
225  }
226 
227  def send_command(self, command: Iterable[str], **kwargs: Any) -> None:
228  """Send commands to a device."""
229  raise NotImplementedError
230 
231  async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None:
232  """Send commands to a device."""
233  await self.hasshass.async_add_executor_job(
234  ft.partial(self.send_commandsend_command, command, **kwargs)
235  )
236 
237  def learn_command(self, **kwargs: Any) -> None:
238  """Learn a command from a device."""
239  raise NotImplementedError
240 
241  async def async_learn_command(self, **kwargs: Any) -> None:
242  """Learn a command from a device."""
243  await self.hasshass.async_add_executor_job(ft.partial(self.learn_commandlearn_command, **kwargs))
244 
245  def delete_command(self, **kwargs: Any) -> None:
246  """Delete commands from the database."""
247  raise NotImplementedError
248 
249  async def async_delete_command(self, **kwargs: Any) -> None:
250  """Delete commands from the database."""
251  await self.hasshass.async_add_executor_job(
252  ft.partial(self.delete_commanddelete_command, **kwargs)
253  )
254 
255 
256 # These can be removed if no deprecated constant are in this module anymore
257 __getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
258 __dir__ = ft.partial(
259  dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
260 )
261 __all__ = all_with_deprecated_constants(globals())
None delete_command(self, **Any kwargs)
Definition: __init__.py:245
RemoteEntityFeature supported_features(self)
Definition: __init__.py:188
None async_delete_command(self, **Any kwargs)
Definition: __init__.py:249
dict[str, Any]|None state_attributes(self)
Definition: __init__.py:217
None learn_command(self, **Any kwargs)
Definition: __init__.py:237
None send_command(self, Iterable[str] command, **Any kwargs)
Definition: __init__.py:227
None async_learn_command(self, **Any kwargs)
Definition: __init__.py:241
None async_send_command(self, Iterable[str] command, **Any kwargs)
Definition: __init__.py:231
RemoteEntityFeature supported_features_compat(self)
Definition: __init__.py:193
None _report_deprecated_supported_features_values(self, IntFlag replacement)
Definition: entity.py:1645
int|None supported_features(self)
Definition: entity.py:861
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:101
bool is_on(HomeAssistant hass, str entity_id)
Definition: __init__.py:96
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:163
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:158
list[str] all_with_deprecated_constants(dict[str, Any] module_globals)
Definition: deprecation.py:356