core/homeassistant/components/wiz/light.py

254 lines
8.7 KiB
Python
Raw Normal View History

"""WiZ integration."""
from __future__ import annotations
import contextlib
import logging
from typing import Any
from pywizlight import PilotBuilder
from pywizlight.bulblibrary import BulbClass, BulbType
from pywizlight.exceptions import WizLightNotKnownBulb
from pywizlight.rgbcw import convertHSfromRGBCW
from pywizlight.scenes import get_id_from_scene_name
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_HS_COLOR,
ATTR_RGB_COLOR,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
SUPPORT_EFFECT,
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
import homeassistant.util.color as color_utils
from .const import DOMAIN
from .models import WizData
_LOGGER = logging.getLogger(__name__)
DEFAULT_COLOR_MODES = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP}
DEFAULT_MIN_MIREDS = 153
DEFAULT_MAX_MIREDS = 454
def get_supported_color_modes(bulb_type: BulbType) -> set[str]:
"""Flag supported features."""
if not bulb_type:
# fallback
return DEFAULT_COLOR_MODES
color_modes = set()
try:
features = bulb_type.features
if features.color:
color_modes.add(COLOR_MODE_HS)
if features.color_tmp:
color_modes.add(COLOR_MODE_COLOR_TEMP)
if not color_modes and features.brightness:
color_modes.add(COLOR_MODE_BRIGHTNESS)
return color_modes
except WizLightNotKnownBulb:
_LOGGER.warning("Bulb is not present in the library. Fallback to full feature")
return DEFAULT_COLOR_MODES
def supports_effects(bulb_type: BulbType) -> bool:
"""Check if a bulb supports effects."""
with contextlib.suppress(WizLightNotKnownBulb):
return bool(bulb_type.features.effect)
return True # default is true
def get_min_max_mireds(bulb_type: BulbType) -> tuple[int, int]:
"""Return the coldest and warmest color_temp that this light supports."""
if bulb_type is None:
return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS
# DW bulbs have no kelvin
if bulb_type.bulb_type == BulbClass.DW:
return 0, 0
# If bulbtype is TW or RGB then return the kelvin value
try:
return color_utils.color_temperature_kelvin_to_mired(
bulb_type.kelvin_range.max
), color_utils.color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min)
except WizLightNotKnownBulb:
_LOGGER.debug("Kelvin is not present in the library. Fallback to 6500")
return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the WiZ Platform from config_flow."""
wiz_data: WizData = hass.data[DOMAIN][entry.entry_id]
async_add_entities([WizBulbEntity(wiz_data, entry.title)])
class WizBulbEntity(CoordinatorEntity, LightEntity):
"""Representation of WiZ Light bulb."""
def __init__(self, wiz_data: WizData, name: str) -> None:
"""Initialize an WiZLight."""
super().__init__(wiz_data.coordinator)
self._light = wiz_data.bulb
bulb_type: BulbType = self._light.bulbtype
self._attr_unique_id = self._light.mac
self._attr_name = name
self._attr_effect_list = wiz_data.scenes
self._attr_min_mireds, self._attr_max_mireds = get_min_max_mireds(bulb_type)
self._attr_supported_color_modes = get_supported_color_modes(bulb_type)
if supports_effects(bulb_type):
self._attr_supported_features = SUPPORT_EFFECT
self._attr_device_info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, self._light.mac)},
name=name,
manufacturer="WiZ",
model=bulb_type.name,
)
@property
def is_on(self) -> bool | None:
"""Return true if light is on."""
is_on: bool | None = self._light.status
return is_on
@property
def brightness(self) -> int | None:
"""Return the brightness of the light."""
if (brightness := self._light.state.get_brightness()) is None:
return None
if 0 <= int(brightness) <= 255:
return int(brightness)
_LOGGER.error("Received invalid brightness : %s. Expected: 0-255", brightness)
return None
@property
def color_mode(self) -> str:
"""Return the current color mode."""
color_modes = self.supported_color_modes
assert color_modes is not None
if (
COLOR_MODE_COLOR_TEMP in color_modes
and self._light.state.get_colortemp() is not None
):
return COLOR_MODE_COLOR_TEMP
if (
COLOR_MODE_HS in color_modes
and (rgb := self._light.state.get_rgb()) is not None
and rgb[0] is not None
):
return COLOR_MODE_HS
return COLOR_MODE_BRIGHTNESS
@property
def hs_color(self) -> tuple[float, float] | None:
"""Return the hs color value."""
colortemp = self._light.state.get_colortemp()
if colortemp is not None and colortemp != 0:
return None
if (rgb := self._light.state.get_rgb()) is None:
return None
if rgb[0] is None:
# this is the case if the temperature was changed
# do nothing until the RGB color was changed
return None
if (warmwhite := self._light.state.get_warm_white()) is None:
return None
hue_sat = convertHSfromRGBCW(rgb, warmwhite)
hue: float = hue_sat[0]
sat: float = hue_sat[1]
return hue, sat
@property
def color_temp(self) -> int | None:
"""Return the CT color value in mireds."""
colortemp = self._light.state.get_colortemp()
if colortemp is None or colortemp == 0:
return None
_LOGGER.debug(
"[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp
)
return color_utils.color_temperature_kelvin_to_mired(colortemp)
@property
def effect(self) -> str | None:
"""Return the current effect."""
effect: str | None = self._light.state.get_scene()
return effect
async def async_turn_on(self, **kwargs: Any) -> None:
"""Instruct the light to turn on."""
brightness = None
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs.get(ATTR_BRIGHTNESS)
if ATTR_RGB_COLOR in kwargs:
pilot = PilotBuilder(rgb=kwargs.get(ATTR_RGB_COLOR), brightness=brightness)
if ATTR_HS_COLOR in kwargs:
pilot = PilotBuilder(
hucolor=(kwargs[ATTR_HS_COLOR][0], kwargs[ATTR_HS_COLOR][1]),
brightness=brightness,
)
else:
colortemp = None
if ATTR_COLOR_TEMP in kwargs:
kelvin = color_utils.color_temperature_mired_to_kelvin(
kwargs[ATTR_COLOR_TEMP]
)
colortemp = kelvin
_LOGGER.debug(
"[wizlight %s] kelvin changed and send to bulb: %s",
self._light.ip,
colortemp,
)
sceneid = None
if ATTR_EFFECT in kwargs:
sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT])
if sceneid == 1000: # rhythm
pilot = PilotBuilder()
else:
pilot = PilotBuilder(
brightness=brightness, colortemp=colortemp, scene=sceneid
)
_LOGGER.debug(
"[wizlight %s] Pilot will be send with brightness=%s, colortemp=%s, scene=%s",
self._light.ip,
brightness,
colortemp,
sceneid,
)
sceneid = None
if ATTR_EFFECT in kwargs:
sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT])
if sceneid == 1000: # rhythm
pilot = PilotBuilder()
else:
pilot = PilotBuilder(
brightness=brightness, colortemp=colortemp, scene=sceneid
)
await self._light.turn_on(pilot)
await self.coordinator.async_request_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Instruct the light to turn off."""
await self._light.turn_off()
await self.coordinator.async_request_refresh()