1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00
ha-core/homeassistant/components/flux_led/number.py
J. Nick Koston e222e1b6f0
Add device configuration entities to flux_led (#62786)
Co-authored-by: Chris Talkington <chris@talkingtontech.com>
2022-01-06 21:02:19 -10:00

292 lines
9.2 KiB
Python

"""Support for LED numbers."""
from __future__ import annotations
from abc import abstractmethod
import logging
from typing import cast
from flux_led.protocol import (
MUSIC_PIXELS_MAX,
MUSIC_PIXELS_PER_SEGMENT_MAX,
MUSIC_SEGMENTS_MAX,
PIXELS_MAX,
PIXELS_PER_SEGMENT_MAX,
SEGMENTS_MAX,
)
from homeassistant import config_entries
from homeassistant.components.light import EFFECT_RANDOM
from homeassistant.components.number import NumberEntity, NumberMode
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import FluxLedUpdateCoordinator
from .entity import FluxEntity
from .util import _effect_brightness
_LOGGER = logging.getLogger(__name__)
DEBOUNCE_TIME = 1
async def async_setup_entry(
hass: HomeAssistant,
entry: config_entries.ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Flux lights."""
coordinator: FluxLedUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
device = coordinator.device
entities: list[
FluxSpeedNumber
| FluxPixelsPerSegmentNumber
| FluxSegmentsNumber
| FluxMusicPixelsPerSegmentNumber
| FluxMusicSegmentsNumber
] = []
name = entry.data[CONF_NAME]
unique_id = entry.unique_id
if device.pixels_per_segment is not None:
entities.append(
FluxPixelsPerSegmentNumber(
coordinator,
unique_id,
f"{name} Pixels Per Segment",
"pixels_per_segment",
)
)
if device.segments is not None:
entities.append(
FluxSegmentsNumber(coordinator, unique_id, f"{name} Segments", "segments")
)
if device.music_pixels_per_segment is not None:
entities.append(
FluxMusicPixelsPerSegmentNumber(
coordinator,
unique_id,
f"{name} Music Pixels Per Segment",
"music_pixels_per_segment",
)
)
if device.music_segments is not None:
entities.append(
FluxMusicSegmentsNumber(
coordinator, unique_id, f"{name} Music Segments", "music_segments"
)
)
if device.effect_list and device.effect_list != [EFFECT_RANDOM]:
entities.append(
FluxSpeedNumber(coordinator, unique_id, f"{name} Effect Speed", None)
)
if entities:
async_add_entities(entities)
class FluxSpeedNumber(FluxEntity, CoordinatorEntity, NumberEntity):
"""Defines a flux_led speed number."""
_attr_min_value = 1
_attr_max_value = 100
_attr_step = 1
_attr_mode = NumberMode.SLIDER
_attr_icon = "mdi:speedometer"
@property
def value(self) -> float:
"""Return the effect speed."""
return cast(float, self._device.speed)
async def async_set_value(self, value: float) -> None:
"""Set the flux speed value."""
current_effect = self._device.effect
new_speed = int(value)
if not current_effect:
raise HomeAssistantError(
"Speed can only be adjusted when an effect is active"
)
if not self._device.speed_adjust_off and not self._device.is_on:
raise HomeAssistantError("Speed can only be adjusted when the light is on")
await self._device.async_set_effect(
current_effect, new_speed, _effect_brightness(self._device.brightness)
)
await self.coordinator.async_request_refresh()
class FluxConfigNumber(FluxEntity, CoordinatorEntity, NumberEntity):
"""Base class for flux config numbers."""
_attr_entity_category = EntityCategory.CONFIG
_attr_min_value = 1
_attr_step = 1
_attr_mode = NumberMode.BOX
def __init__(
self,
coordinator: FluxLedUpdateCoordinator,
unique_id: str | None,
name: str,
key: str | None,
) -> None:
"""Initialize the flux number."""
super().__init__(coordinator, unique_id, name, key)
self._debouncer: Debouncer | None = None
self._pending_value: int | None = None
async def async_added_to_hass(self) -> None:
"""Set up the debouncer when adding to hass."""
self._debouncer = Debouncer(
hass=self.hass,
logger=_LOGGER,
cooldown=DEBOUNCE_TIME,
immediate=False,
function=self._async_set_value,
)
await super().async_added_to_hass()
async def async_set_value(self, value: float) -> None:
"""Set the value."""
self._pending_value = int(value)
assert self._debouncer is not None
await self._debouncer.async_call()
@abstractmethod
async def _async_set_value(self) -> None:
"""Call on debounce to set the value."""
def _pixels_and_segments_fit_in_music_mode(self) -> bool:
"""Check if the base pixel and segment settings will fit for music mode.
If they fit, they do not need to be configured.
"""
pixels_per_segment = self._device.pixels_per_segment
segments = self._device.segments
assert pixels_per_segment is not None
assert segments is not None
return bool(
pixels_per_segment <= MUSIC_PIXELS_PER_SEGMENT_MAX
and segments <= MUSIC_SEGMENTS_MAX
and pixels_per_segment * segments <= MUSIC_PIXELS_MAX
)
class FluxPixelsPerSegmentNumber(FluxConfigNumber):
"""Defines a flux_led pixels per segment number."""
_attr_icon = "mdi:dots-grid"
@property
def max_value(self) -> int:
"""Return the max value."""
return min(
PIXELS_PER_SEGMENT_MAX, int(PIXELS_MAX / (self._device.segments or 1))
)
@property
def value(self) -> int:
"""Return the pixels per segment."""
assert self._device.pixels_per_segment is not None
return self._device.pixels_per_segment
async def _async_set_value(self) -> None:
"""Set the pixels per segment."""
assert self._pending_value is not None
await self._device.async_set_device_config(
pixels_per_segment=self._pending_value
)
class FluxSegmentsNumber(FluxConfigNumber):
"""Defines a flux_led segments number."""
_attr_icon = "mdi:segment"
@property
def max_value(self) -> int:
"""Return the max value."""
assert self._device.pixels_per_segment is not None
return min(
SEGMENTS_MAX, int(PIXELS_MAX / (self._device.pixels_per_segment or 1))
)
@property
def value(self) -> int:
"""Return the segments."""
assert self._device.segments is not None
return self._device.segments
async def _async_set_value(self) -> None:
"""Set the segments."""
assert self._pending_value is not None
await self._device.async_set_device_config(segments=self._pending_value)
class FluxMusicNumber(FluxConfigNumber):
"""A number that is only available if the base pixels do not fit in music mode."""
@property
def available(self) -> bool:
"""Return if music pixels per segment can be set."""
return super().available and not self._pixels_and_segments_fit_in_music_mode()
class FluxMusicPixelsPerSegmentNumber(FluxMusicNumber):
"""Defines a flux_led music pixels per segment number."""
_attr_icon = "mdi:dots-grid"
@property
def max_value(self) -> int:
"""Return the max value."""
assert self._device.music_segments is not None
return min(
MUSIC_PIXELS_PER_SEGMENT_MAX,
int(MUSIC_PIXELS_MAX / (self._device.music_segments or 1)),
)
@property
def value(self) -> int:
"""Return the music pixels per segment."""
assert self._device.music_pixels_per_segment is not None
return self._device.music_pixels_per_segment
async def _async_set_value(self) -> None:
"""Set the music pixels per segment."""
assert self._pending_value is not None
await self._device.async_set_device_config(
music_pixels_per_segment=self._pending_value
)
class FluxMusicSegmentsNumber(FluxMusicNumber):
"""Defines a flux_led music segments number."""
_attr_icon = "mdi:segment"
@property
def max_value(self) -> int:
"""Return the max value."""
assert self._device.pixels_per_segment is not None
return min(
MUSIC_SEGMENTS_MAX,
int(MUSIC_PIXELS_MAX / (self._device.music_pixels_per_segment or 1)),
)
@property
def value(self) -> int:
"""Return the music segments."""
assert self._device.music_segments is not None
return self._device.music_segments
async def _async_set_value(self) -> None:
"""Set the music segments."""
assert self._pending_value is not None
await self._device.async_set_device_config(music_segments=self._pending_value)