Home Assistant Unofficial Reference 2024.12.1
reproduce_state.py
Go to the documentation of this file.
1 """Reproduce an Light state."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Iterable, Mapping
7 import logging
8 from typing import Any, NamedTuple, cast
9 
10 from homeassistant.const import (
11  ATTR_ENTITY_ID,
12  SERVICE_TURN_OFF,
13  SERVICE_TURN_ON,
14  STATE_OFF,
15  STATE_ON,
16 )
17 from homeassistant.core import Context, HomeAssistant, State
18 
19 from . import (
20  ATTR_BRIGHTNESS,
21  ATTR_COLOR_MODE,
22  ATTR_COLOR_TEMP,
23  ATTR_EFFECT,
24  ATTR_HS_COLOR,
25  ATTR_RGB_COLOR,
26  ATTR_RGBW_COLOR,
27  ATTR_RGBWW_COLOR,
28  ATTR_TRANSITION,
29  ATTR_WHITE,
30  ATTR_XY_COLOR,
31  DOMAIN,
32  ColorMode,
33 )
34 
35 _LOGGER = logging.getLogger(__name__)
36 
37 VALID_STATES = {STATE_ON, STATE_OFF}
38 
39 ATTR_GROUP = [ATTR_BRIGHTNESS, ATTR_EFFECT]
40 
41 COLOR_GROUP = [
42  ATTR_HS_COLOR,
43  ATTR_COLOR_TEMP,
44  ATTR_RGB_COLOR,
45  ATTR_RGBW_COLOR,
46  ATTR_RGBWW_COLOR,
47  ATTR_XY_COLOR,
48 ]
49 
50 
51 class ColorModeAttr(NamedTuple):
52  """Map service data parameter to state attribute for a color mode."""
53 
54  parameter: str
55  state_attr: str
56 
57 
58 COLOR_MODE_TO_ATTRIBUTE = {
59  ColorMode.COLOR_TEMP: ColorModeAttr(ATTR_COLOR_TEMP, ATTR_COLOR_TEMP),
60  ColorMode.HS: ColorModeAttr(ATTR_HS_COLOR, ATTR_HS_COLOR),
61  ColorMode.RGB: ColorModeAttr(ATTR_RGB_COLOR, ATTR_RGB_COLOR),
62  ColorMode.RGBW: ColorModeAttr(ATTR_RGBW_COLOR, ATTR_RGBW_COLOR),
63  ColorMode.RGBWW: ColorModeAttr(ATTR_RGBWW_COLOR, ATTR_RGBWW_COLOR),
64  ColorMode.WHITE: ColorModeAttr(ATTR_WHITE, ATTR_BRIGHTNESS),
65  ColorMode.XY: ColorModeAttr(ATTR_XY_COLOR, ATTR_XY_COLOR),
66 }
67 
68 
69 def _color_mode_same(cur_state: State, state: State) -> bool:
70  """Test if color_mode is same."""
71  cur_color_mode = cur_state.attributes.get(ATTR_COLOR_MODE, ColorMode.UNKNOWN)
72  saved_color_mode = state.attributes.get(ATTR_COLOR_MODE, ColorMode.UNKNOWN)
73 
74  # Guard for scenes etc. which where created before color modes were introduced
75  if saved_color_mode == ColorMode.UNKNOWN:
76  return True
77  return cast(bool, cur_color_mode == saved_color_mode)
78 
79 
81  hass: HomeAssistant,
82  state: State,
83  *,
84  context: Context | None = None,
85  reproduce_options: dict[str, Any] | None = None,
86 ) -> None:
87  """Reproduce a single state."""
88  if (cur_state := hass.states.get(state.entity_id)) is None:
89  _LOGGER.warning("Unable to find entity %s", state.entity_id)
90  return
91 
92  if state.state not in VALID_STATES:
93  _LOGGER.warning(
94  "Invalid state specified for %s: %s", state.entity_id, state.state
95  )
96  return
97 
98  # Return if we are already at the right state.
99  if (
100  cur_state.state == state.state
101  and _color_mode_same(cur_state, state)
102  and all(
103  check_attr_equal(cur_state.attributes, state.attributes, attr)
104  for attr in ATTR_GROUP + COLOR_GROUP
105  )
106  ):
107  return
108 
109  service_data: dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id}
110 
111  if reproduce_options is not None and ATTR_TRANSITION in reproduce_options:
112  service_data[ATTR_TRANSITION] = reproduce_options[ATTR_TRANSITION]
113 
114  if state.state == STATE_ON:
115  service = SERVICE_TURN_ON
116  for attr in ATTR_GROUP:
117  # All attributes that are not colors
118  if (attr_state := state.attributes.get(attr)) is not None:
119  service_data[attr] = attr_state
120 
121  if (
122  state.attributes.get(ATTR_COLOR_MODE, ColorMode.UNKNOWN)
123  != ColorMode.UNKNOWN
124  ):
125  color_mode = state.attributes[ATTR_COLOR_MODE]
126  if cm_attr := COLOR_MODE_TO_ATTRIBUTE.get(color_mode):
127  if (cm_attr_state := state.attributes.get(cm_attr.state_attr)) is None:
128  _LOGGER.warning(
129  "Color mode %s specified but attribute %s missing for: %s",
130  color_mode,
131  cm_attr.state_attr,
132  state.entity_id,
133  )
134  return
135  service_data[cm_attr.parameter] = cm_attr_state
136  else:
137  # Fall back to Choosing the first color that is specified
138  for color_attr in COLOR_GROUP:
139  if (color_attr_state := state.attributes.get(color_attr)) is not None:
140  service_data[color_attr] = color_attr_state
141  break
142 
143  elif state.state == STATE_OFF:
144  service = SERVICE_TURN_OFF
145 
146  await hass.services.async_call(
147  DOMAIN, service, service_data, context=context, blocking=True
148  )
149 
150 
152  hass: HomeAssistant,
153  states: Iterable[State],
154  *,
155  context: Context | None = None,
156  reproduce_options: dict[str, Any] | None = None,
157 ) -> None:
158  """Reproduce Light states."""
159  await asyncio.gather(
160  *(
162  hass, state, context=context, reproduce_options=reproduce_options
163  )
164  for state in states
165  )
166  )
167 
168 
169 def check_attr_equal(attr1: Mapping, attr2: Mapping, attr_str: str) -> bool:
170  """Return true if the given attributes are equal."""
171  return attr1.get(attr_str) == attr2.get(attr_str)
bool check_attr_equal(Mapping attr1, Mapping attr2, str attr_str)
bool _color_mode_same(State cur_state, State state)
None _async_reproduce_state(HomeAssistant hass, State state, *Context|None context=None, dict[str, Any]|None reproduce_options=None)
None async_reproduce_states(HomeAssistant hass, Iterable[State] states, *Context|None context=None, dict[str, Any]|None reproduce_options=None)