Home Assistant Unofficial Reference 2024.12.1
number.py
Go to the documentation of this file.
1 """Support for LED numbers."""
2 
3 from __future__ import annotations
4 
5 from abc import abstractmethod
6 from collections.abc import Coroutine
7 import logging
8 from typing import Any, cast
9 
10 from flux_led.protocol import (
11  MUSIC_PIXELS_MAX,
12  MUSIC_PIXELS_PER_SEGMENT_MAX,
13  MUSIC_SEGMENTS_MAX,
14  PIXELS_MAX,
15  PIXELS_PER_SEGMENT_MAX,
16  SEGMENTS_MAX,
17 )
18 
19 from homeassistant import config_entries
20 from homeassistant.components.light import EFFECT_RANDOM
21 from homeassistant.components.number import NumberEntity, NumberMode
22 from homeassistant.const import EntityCategory
23 from homeassistant.core import HomeAssistant
24 from homeassistant.exceptions import HomeAssistantError
25 from homeassistant.helpers.debounce import Debouncer
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.helpers.update_coordinator import CoordinatorEntity
28 
29 from .const import DOMAIN
30 from .coordinator import FluxLedUpdateCoordinator
31 from .entity import FluxEntity
32 from .util import _effect_brightness
33 
34 _LOGGER = logging.getLogger(__name__)
35 
36 DEBOUNCE_TIME = 1
37 
38 
40  hass: HomeAssistant,
42  async_add_entities: AddEntitiesCallback,
43 ) -> None:
44  """Set up the Flux lights."""
45  coordinator: FluxLedUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
46  device = coordinator.device
47  entities: list[
48  FluxSpeedNumber
49  | FluxPixelsPerSegmentNumber
50  | FluxSegmentsNumber
51  | FluxMusicPixelsPerSegmentNumber
52  | FluxMusicSegmentsNumber
53  ] = []
54  base_unique_id = entry.unique_id or entry.entry_id
55 
56  if device.pixels_per_segment is not None:
57  entities.append(
59  coordinator,
60  base_unique_id,
61  "pixels_per_segment",
62  )
63  )
64  if device.segments is not None:
65  entities.append(FluxSegmentsNumber(coordinator, base_unique_id, "segments"))
66  if device.music_pixels_per_segment is not None:
67  entities.append(
69  coordinator,
70  base_unique_id,
71  "music_pixels_per_segment",
72  )
73  )
74  if device.music_segments is not None:
75  entities.append(
76  FluxMusicSegmentsNumber(coordinator, base_unique_id, "music_segments")
77  )
78  if device.effect_list and device.effect_list != [EFFECT_RANDOM]:
79  entities.append(FluxSpeedNumber(coordinator, base_unique_id, None))
80 
81  async_add_entities(entities)
82 
83 
85  FluxEntity, CoordinatorEntity[FluxLedUpdateCoordinator], NumberEntity
86 ):
87  """Defines a flux_led speed number."""
88 
89  _attr_native_min_value = 1
90  _attr_native_max_value = 100
91  _attr_native_step = 1
92  _attr_mode = NumberMode.SLIDER
93  _attr_translation_key = "effect_speed"
94 
95  @property
96  def native_value(self) -> float:
97  """Return the effect speed."""
98  return cast(float, self._device.speed)
99 
100  async def async_set_native_value(self, value: float) -> None:
101  """Set the flux speed value."""
102  current_effect = self._device.effect
103  new_speed = int(value)
104  if not current_effect:
105  raise HomeAssistantError(
106  "Speed can only be adjusted when an effect is active"
107  )
108  if not self._device.speed_adjust_off and not self._device.is_on:
109  raise HomeAssistantError("Speed can only be adjusted when the light is on")
110  await self._device.async_set_effect(
111  current_effect, new_speed, _effect_brightness(self._device.brightness)
112  )
113  await self.coordinator.async_request_refresh()
114 
115 
117  FluxEntity, CoordinatorEntity[FluxLedUpdateCoordinator], NumberEntity
118 ):
119  """Base class for flux config numbers."""
120 
121  _attr_entity_category = EntityCategory.CONFIG
122  _attr_native_min_value = 1
123  _attr_native_step = 1
124  _attr_mode = NumberMode.BOX
125 
126  def __init__(
127  self,
128  coordinator: FluxLedUpdateCoordinator,
129  base_unique_id: str,
130  key: str | None,
131  ) -> None:
132  """Initialize the flux number."""
133  super().__init__(coordinator, base_unique_id, key)
134  self._debouncer_debouncer: Debouncer[Coroutine[Any, Any, None]] | None = None
135  self._pending_value_pending_value: int | None = None
136 
137  async def async_added_to_hass(self) -> None:
138  """Set up the debouncer when adding to hass."""
139  self._debouncer_debouncer = Debouncer(
140  hass=self.hasshasshass,
141  logger=_LOGGER,
142  cooldown=DEBOUNCE_TIME,
143  immediate=False,
144  function=self._async_set_native_value_async_set_native_value,
145  )
146  await super().async_added_to_hass()
147 
148  async def async_set_native_value(self, value: float) -> None:
149  """Set the value."""
150  self._pending_value_pending_value = int(value)
151  assert self._debouncer_debouncer is not None
152  await self._debouncer_debouncer.async_call()
153 
154  @abstractmethod
155  async def _async_set_native_value(self) -> None:
156  """Call on debounce to set the value."""
157 
159  """Check if the base pixel and segment settings will fit for music mode.
160 
161  If they fit, they do not need to be configured.
162  """
163  pixels_per_segment = self._device.pixels_per_segment
164  segments = self._device.segments
165  assert pixels_per_segment is not None
166  assert segments is not None
167  return bool(
168  pixels_per_segment <= MUSIC_PIXELS_PER_SEGMENT_MAX
169  and segments <= MUSIC_SEGMENTS_MAX
170  and pixels_per_segment * segments <= MUSIC_PIXELS_MAX
171  )
172 
173 
175  """Defines a flux_led pixels per segment number."""
176 
177  _attr_translation_key = "pixels_per_segment"
178 
179  @property
180  def native_max_value(self) -> int:
181  """Return the max value."""
182  return min(
183  PIXELS_PER_SEGMENT_MAX, int(PIXELS_MAX / (self._device.segments or 1))
184  )
185 
186  @property
187  def native_value(self) -> int:
188  """Return the pixels per segment."""
189  assert self._device.pixels_per_segment is not None
190  return self._device.pixels_per_segment
191 
192  async def _async_set_native_value(self) -> None:
193  """Set the pixels per segment."""
194  assert self._pending_value_pending_value is not None
195  await self._device.async_set_device_config(
196  pixels_per_segment=self._pending_value_pending_value
197  )
198 
199 
201  """Defines a flux_led segments number."""
202 
203  _attr_translation_key = "segments"
204 
205  @property
206  def native_max_value(self) -> int:
207  """Return the max value."""
208  assert self._device.pixels_per_segment is not None
209  return min(
210  SEGMENTS_MAX, int(PIXELS_MAX / (self._device.pixels_per_segment or 1))
211  )
212 
213  @property
214  def native_value(self) -> int:
215  """Return the segments."""
216  assert self._device.segments is not None
217  return self._device.segments
218 
219  async def _async_set_native_value(self) -> None:
220  """Set the segments."""
221  assert self._pending_value_pending_value is not None
222  await self._device.async_set_device_config(segments=self._pending_value_pending_value)
223 
224 
226  """A number that is only available if the base pixels do not fit in music mode."""
227 
228  @property
229  def available(self) -> bool:
230  """Return if music pixels per segment can be set."""
231  return super().available and not self._pixels_and_segments_fit_in_music_mode_pixels_and_segments_fit_in_music_mode()
232 
233 
235  """Defines a flux_led music pixels per segment number."""
236 
237  _attr_translation_key = "music_pixels_per_segment"
238 
239  @property
240  def native_max_value(self) -> int:
241  """Return the max value."""
242  assert self._device.music_segments is not None
243  return min(
244  MUSIC_PIXELS_PER_SEGMENT_MAX,
245  int(MUSIC_PIXELS_MAX / (self._device.music_segments or 1)),
246  )
247 
248  @property
249  def native_value(self) -> int:
250  """Return the music pixels per segment."""
251  assert self._device.music_pixels_per_segment is not None
252  return self._device.music_pixels_per_segment
253 
254  async def _async_set_native_value(self) -> None:
255  """Set the music pixels per segment."""
256  assert self._pending_value_pending_value is not None
257  await self._device.async_set_device_config(
258  music_pixels_per_segment=self._pending_value_pending_value
259  )
260 
261 
263  """Defines a flux_led music segments number."""
264 
265  _attr_translation_key = "music_segments"
266 
267  @property
268  def native_max_value(self) -> int:
269  """Return the max value."""
270  assert self._device.pixels_per_segment is not None
271  return min(
272  MUSIC_SEGMENTS_MAX,
273  int(MUSIC_PIXELS_MAX / (self._device.music_pixels_per_segment or 1)),
274  )
275 
276  @property
277  def native_value(self) -> int:
278  """Return the music segments."""
279  assert self._device.music_segments is not None
280  return self._device.music_segments
281 
282  async def _async_set_native_value(self) -> None:
283  """Set the music segments."""
284  assert self._pending_value_pending_value is not None
285  await self._device.async_set_device_config(music_segments=self._pending_value_pending_value)
None __init__(self, FluxLedUpdateCoordinator coordinator, str base_unique_id, str|None key)
Definition: number.py:131
None async_setup_entry(HomeAssistant hass, config_entries.ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: number.py:43
int _effect_brightness(int brightness)
Definition: util.py:55