mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
WiZ cleanups part 1 (#65746)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
f3c5f9c972
commit
342f5182b9
@ -203,6 +203,7 @@ homeassistant.components.webostv.*
|
||||
homeassistant.components.websocket_api.*
|
||||
homeassistant.components.wemo.*
|
||||
homeassistant.components.whois.*
|
||||
homeassistant.components.wiz.*
|
||||
homeassistant.components.zodiac.*
|
||||
homeassistant.components.zeroconf.*
|
||||
homeassistant.components.zone.*
|
||||
|
@ -1,38 +1,66 @@
|
||||
"""WiZ Platform integration."""
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from pywizlight import wizlight
|
||||
from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, WIZ_EXCEPTIONS
|
||||
from .models import WizData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = ["light"]
|
||||
PLATFORMS = [Platform.LIGHT]
|
||||
|
||||
REQUEST_REFRESH_DELAY = 0.35
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up the wiz integration from a config entry."""
|
||||
ip_address = entry.data.get(CONF_HOST)
|
||||
ip_address = entry.data[CONF_HOST]
|
||||
_LOGGER.debug("Get bulb with IP: %s", ip_address)
|
||||
bulb = wizlight(ip_address)
|
||||
try:
|
||||
bulb = wizlight(ip_address)
|
||||
scenes = await bulb.getSupportedScenes()
|
||||
await bulb.getMac()
|
||||
except (
|
||||
WizLightTimeOutError,
|
||||
WizLightConnectionError,
|
||||
ConnectionRefusedError,
|
||||
) as err:
|
||||
scenes = await bulb.getSupportedScenes()
|
||||
# ValueError gets thrown if the bulb type
|
||||
# cannot be determined on the first try.
|
||||
# This is likely because way the library
|
||||
# processes responses and can be cleaned up
|
||||
# in the future.
|
||||
except (ValueError, *WIZ_EXCEPTIONS) as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData(bulb=bulb, scenes=scenes)
|
||||
async def _async_update() -> None:
|
||||
"""Update the WiZ device."""
|
||||
try:
|
||||
await bulb.updateState()
|
||||
except WIZ_EXCEPTIONS as ex:
|
||||
raise UpdateFailed(f"Failed to update device at {ip_address}: {ex}") from ex
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass=hass,
|
||||
logger=_LOGGER,
|
||||
name=entry.title,
|
||||
update_interval=timedelta(seconds=15),
|
||||
update_method=_async_update,
|
||||
# We don't want an immediate refresh since the device
|
||||
# takes a moment to reflect the state change
|
||||
request_refresh_debouncer=Debouncer(
|
||||
hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False
|
||||
),
|
||||
)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData(
|
||||
coordinator=coordinator, bulb=bulb, scenes=scenes
|
||||
)
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
@ -42,11 +70,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
return unload_ok
|
||||
|
||||
|
||||
@dataclass
|
||||
class WizData:
|
||||
"""Data for the wiz integration."""
|
||||
|
||||
bulb: wizlight
|
||||
scenes: list
|
||||
|
@ -1,37 +1,38 @@
|
||||
"""Config flow for WiZ Platform."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pywizlight import wizlight
|
||||
from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
from .const import DEFAULT_NAME, DOMAIN
|
||||
from .utils import _short_mac
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for WiZ."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
bulb = wizlight(user_input[CONF_HOST])
|
||||
try:
|
||||
mac = await bulb.getMac()
|
||||
bulbtype = await bulb.get_bulbtype()
|
||||
except WizLightTimeOutError:
|
||||
errors["base"] = "bulb_time_out"
|
||||
except ConnectionRefusedError:
|
||||
@ -43,10 +44,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
await self.async_set_unique_id(mac)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_NAME], data=user_input
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: user_input[CONF_HOST]}
|
||||
)
|
||||
bulb_type = bulbtype.bulb_type.value if bulbtype else "Unknown"
|
||||
name = f"{DEFAULT_NAME} {bulb_type} {_short_mac(mac)}"
|
||||
return self.async_create_entry(
|
||||
title=name,
|
||||
data=user_input,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
step_id="user",
|
||||
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
|
||||
errors=errors,
|
||||
)
|
||||
|
@ -1,4 +1,13 @@
|
||||
"""Constants for the WiZ Platform integration."""
|
||||
from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError
|
||||
|
||||
DOMAIN = "wiz"
|
||||
DEFAULT_NAME = "WiZ"
|
||||
|
||||
WIZ_EXCEPTIONS = (
|
||||
OSError,
|
||||
WizLightTimeOutError,
|
||||
TimeoutError,
|
||||
WizLightConnectionError,
|
||||
ConnectionRefusedError,
|
||||
)
|
||||
|
@ -1,12 +1,13 @@
|
||||
"""WiZ integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pywizlight import PilotBuilder, wizlight
|
||||
from pywizlight import PilotBuilder
|
||||
from pywizlight.bulblibrary import BulbClass, BulbType
|
||||
from pywizlight.exceptions import WizLightNotKnownBulb, WizLightTimeOutError
|
||||
from pywizlight.exceptions import WizLightNotKnownBulb
|
||||
from pywizlight.rgbcw import convertHSfromRGBCW
|
||||
from pywizlight.scenes import get_id_from_scene_name
|
||||
|
||||
@ -16,83 +17,177 @@ from homeassistant.components.light import (
|
||||
ATTR_EFFECT,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_RGB_COLOR,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
COLOR_MODE_BRIGHTNESS,
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
COLOR_MODE_HS,
|
||||
SUPPORT_EFFECT,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
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__)
|
||||
|
||||
SUPPORT_FEATURES_RGB = (
|
||||
SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT
|
||||
)
|
||||
DEFAULT_COLOR_MODES = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP}
|
||||
DEFAULT_MIN_MIREDS = 153
|
||||
DEFAULT_MAX_MIREDS = 454
|
||||
|
||||
|
||||
# set poll interval to 15 sec because of changes from external to the bulb
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
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
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
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."""
|
||||
# Assign configuration variables.
|
||||
wiz_data = hass.data[DOMAIN][entry.entry_id]
|
||||
wizbulb = WizBulbEntity(wiz_data.bulb, entry.data.get(CONF_NAME), wiz_data.scenes)
|
||||
# Add devices with defined name
|
||||
async_add_entities([wizbulb], update_before_add=True)
|
||||
return True
|
||||
wiz_data: WizData = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities([WizBulbEntity(wiz_data, entry.title)])
|
||||
|
||||
|
||||
class WizBulbEntity(LightEntity):
|
||||
class WizBulbEntity(CoordinatorEntity, LightEntity):
|
||||
"""Representation of WiZ Light bulb."""
|
||||
|
||||
def __init__(self, light: wizlight, name, scenes):
|
||||
def __init__(self, wiz_data: WizData, name: str) -> None:
|
||||
"""Initialize an WiZLight."""
|
||||
self._light = light
|
||||
self._state = None
|
||||
self._brightness = None
|
||||
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._rgb_color = None
|
||||
self._temperature = None
|
||||
self._hscolor = None
|
||||
self._available = None
|
||||
self._effect = None
|
||||
self._scenes: list[str] = scenes
|
||||
self._bulbtype: BulbType = light.bulbtype
|
||||
self._mac = light.mac
|
||||
self._attr_unique_id = light.mac
|
||||
# new init states
|
||||
self._attr_min_mireds = self.get_min_mireds()
|
||||
self._attr_max_mireds = self.get_max_mireds()
|
||||
self._attr_supported_features = self.get_supported_features()
|
||||
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 brightness(self):
|
||||
"""Return the brightness of the light."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def rgb_color(self):
|
||||
"""Return the color property."""
|
||||
return self._rgb_color
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the hs color value."""
|
||||
return self._hscolor
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if light is on."""
|
||||
return self._state
|
||||
is_on: bool | None = self._light.status
|
||||
return is_on
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
@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
|
||||
|
||||
@ -150,199 +245,9 @@ class WizBulbEntity(LightEntity):
|
||||
)
|
||||
|
||||
await self._light.turn_on(pilot)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Instruct the light to turn off."""
|
||||
await self._light.turn_off()
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return the CT color value in mireds."""
|
||||
return self._temperature
|
||||
|
||||
def get_min_mireds(self) -> int:
|
||||
"""Return the coldest color_temp that this light supports."""
|
||||
if self._bulbtype is None:
|
||||
return color_utils.color_temperature_kelvin_to_mired(6500)
|
||||
# DW bulbs have no kelvin
|
||||
if self._bulbtype.bulb_type == BulbClass.DW:
|
||||
return 0
|
||||
# If bulbtype is TW or RGB then return the kelvin value
|
||||
try:
|
||||
return color_utils.color_temperature_kelvin_to_mired(
|
||||
self._bulbtype.kelvin_range.max
|
||||
)
|
||||
except WizLightNotKnownBulb:
|
||||
_LOGGER.debug("Kelvin is not present in the library. Fallback to 6500")
|
||||
return color_utils.color_temperature_kelvin_to_mired(6500)
|
||||
|
||||
def get_max_mireds(self) -> int:
|
||||
"""Return the warmest color_temp that this light supports."""
|
||||
if self._bulbtype is None:
|
||||
return color_utils.color_temperature_kelvin_to_mired(2200)
|
||||
# DW bulbs have no kelvin
|
||||
if self._bulbtype.bulb_type == BulbClass.DW:
|
||||
return 0
|
||||
# If bulbtype is TW or RGB then return the kelvin value
|
||||
try:
|
||||
return color_utils.color_temperature_kelvin_to_mired(
|
||||
self._bulbtype.kelvin_range.min
|
||||
)
|
||||
except WizLightNotKnownBulb:
|
||||
_LOGGER.debug("Kelvin is not present in the library. Fallback to 2200")
|
||||
return color_utils.color_temperature_kelvin_to_mired(2200)
|
||||
|
||||
def get_supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
if not self._bulbtype:
|
||||
# fallback
|
||||
return SUPPORT_FEATURES_RGB
|
||||
features = 0
|
||||
try:
|
||||
# Map features for better reading
|
||||
if self._bulbtype.features.brightness:
|
||||
features = features | SUPPORT_BRIGHTNESS
|
||||
if self._bulbtype.features.color:
|
||||
features = features | SUPPORT_COLOR
|
||||
if self._bulbtype.features.effect:
|
||||
features = features | SUPPORT_EFFECT
|
||||
if self._bulbtype.features.color_tmp:
|
||||
features = features | SUPPORT_COLOR_TEMP
|
||||
return features
|
||||
except WizLightNotKnownBulb:
|
||||
_LOGGER.warning(
|
||||
"Bulb is not present in the library. Fallback to full feature"
|
||||
)
|
||||
return SUPPORT_FEATURES_RGB
|
||||
|
||||
@property
|
||||
def effect(self):
|
||||
"""Return the current effect."""
|
||||
return self._effect
|
||||
|
||||
@property
|
||||
def effect_list(self):
|
||||
"""Return the list of supported effects.
|
||||
|
||||
URL: https://docs.pro.wizconnected.com/#light-modes
|
||||
"""
|
||||
return self._scenes
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if light is available."""
|
||||
return self._available
|
||||
|
||||
async def async_update(self):
|
||||
"""Fetch new state data for this light."""
|
||||
await self.update_state()
|
||||
|
||||
if self._state is not None and self._state is not False:
|
||||
self.update_brightness()
|
||||
self.update_temperature()
|
||||
self.update_color()
|
||||
self.update_effect()
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Get device specific attributes."""
|
||||
return {
|
||||
"connections": {(CONNECTION_NETWORK_MAC, self._mac)},
|
||||
"name": self._attr_name,
|
||||
"manufacturer": "WiZ Light Platform",
|
||||
"model": self._bulbtype.name,
|
||||
}
|
||||
|
||||
def update_state_available(self):
|
||||
"""Update the state if bulb is available."""
|
||||
self._state = self._light.status
|
||||
self._available = True
|
||||
|
||||
def update_state_unavailable(self):
|
||||
"""Update the state if bulb is unavailable."""
|
||||
self._state = False
|
||||
self._available = False
|
||||
|
||||
async def update_state(self):
|
||||
"""Update the state."""
|
||||
try:
|
||||
await self._light.updateState()
|
||||
except (ConnectionRefusedError, TimeoutError, WizLightTimeOutError) as ex:
|
||||
_LOGGER.debug(ex)
|
||||
self.update_state_unavailable()
|
||||
else:
|
||||
if self._light.state is None:
|
||||
self.update_state_unavailable()
|
||||
else:
|
||||
self.update_state_available()
|
||||
_LOGGER.debug(
|
||||
"[wizlight %s] updated state: %s and available: %s",
|
||||
self._light.ip,
|
||||
self._state,
|
||||
self._available,
|
||||
)
|
||||
|
||||
def update_brightness(self):
|
||||
"""Update the brightness."""
|
||||
if self._light.state.get_brightness() is None:
|
||||
return
|
||||
brightness = self._light.state.get_brightness()
|
||||
if 0 <= int(brightness) <= 255:
|
||||
self._brightness = int(brightness)
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Received invalid brightness : %s. Expected: 0-255", brightness
|
||||
)
|
||||
self._brightness = None
|
||||
|
||||
def update_temperature(self):
|
||||
"""Update the temperature."""
|
||||
colortemp = self._light.state.get_colortemp()
|
||||
if colortemp is None or colortemp == 0:
|
||||
self._temperature = None
|
||||
return
|
||||
|
||||
_LOGGER.debug(
|
||||
"[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp
|
||||
)
|
||||
temperature = color_utils.color_temperature_kelvin_to_mired(colortemp)
|
||||
self._temperature = temperature
|
||||
|
||||
def update_color(self):
|
||||
"""Update the hs color."""
|
||||
colortemp = self._light.state.get_colortemp()
|
||||
if colortemp is not None and colortemp != 0:
|
||||
self._hscolor = None
|
||||
return
|
||||
if self._light.state.get_rgb() is None:
|
||||
return
|
||||
|
||||
rgb = self._light.state.get_rgb()
|
||||
if rgb[0] is None:
|
||||
# this is the case if the temperature was changed
|
||||
# do nothing until the RGB color was changed
|
||||
return
|
||||
warmwhite = self._light.state.get_warm_white()
|
||||
if warmwhite is None:
|
||||
return
|
||||
self._hscolor = convertHSfromRGBCW(rgb, warmwhite)
|
||||
|
||||
def update_effect(self):
|
||||
"""Update the bulb scene."""
|
||||
self._effect = self._light.state.get_scene()
|
||||
|
||||
async def get_bulb_type(self):
|
||||
"""Get the bulb type."""
|
||||
if self._bulbtype is not None:
|
||||
return self._bulbtype
|
||||
try:
|
||||
self._bulbtype = await self._light.get_bulbtype()
|
||||
_LOGGER.info(
|
||||
"[wizlight %s] Initiate the WiZ bulb as %s",
|
||||
self._light.ip,
|
||||
self._bulbtype.name,
|
||||
)
|
||||
except WizLightTimeOutError:
|
||||
_LOGGER.debug(
|
||||
"[wizlight %s] Bulbtype update failed - Timeout", self._light.ip
|
||||
)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
@ -3,11 +3,7 @@
|
||||
"name": "WiZ",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/wiz",
|
||||
"requirements": [
|
||||
"pywizlight==0.4.15"
|
||||
],
|
||||
"requirements": ["pywizlight==0.4.16"],
|
||||
"iot_class": "local_polling",
|
||||
"codeowners": [
|
||||
"@sbidy"
|
||||
]
|
||||
"codeowners": ["@sbidy"]
|
||||
}
|
15
homeassistant/components/wiz/models.py
Normal file
15
homeassistant/components/wiz/models.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""WiZ integration models."""
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pywizlight import wizlight
|
||||
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
|
||||
@dataclass
|
||||
class WizData:
|
||||
"""Data for the wiz integration."""
|
||||
|
||||
coordinator: DataUpdateCoordinator
|
||||
bulb: wizlight
|
||||
scenes: list
|
@ -3,13 +3,9 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"name": "[%key:common::config_flow::data::name%]"
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
},
|
||||
"description": "Please enter a hostname or IP address and name to add a new bulb:"
|
||||
},
|
||||
"confirm": {
|
||||
"description": "[%key:common::config_flow::description::confirm_setup%]"
|
||||
"description": "Enter the IP address of the device."
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
|
@ -11,15 +11,11 @@
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Do you want to add a new Bulb?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Hostname or IP",
|
||||
"name": "Name"
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Please enter a hostname or IP address and name to add a new bulb:"
|
||||
"description": "Enter the IP address of the device."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
7
homeassistant/components/wiz/utils.py
Normal file
7
homeassistant/components/wiz/utils.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""WiZ utils."""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def _short_mac(mac: str) -> str:
|
||||
"""Get the short mac address from the full mac."""
|
||||
return mac.replace(":", "").upper()[-6:]
|
11
mypy.ini
11
mypy.ini
@ -2050,6 +2050,17 @@ no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.wiz.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.zodiac.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -2051,7 +2051,7 @@ pywemo==0.7.0
|
||||
pywilight==0.0.70
|
||||
|
||||
# homeassistant.components.wiz
|
||||
pywizlight==0.4.15
|
||||
pywizlight==0.4.16
|
||||
|
||||
# homeassistant.components.xeoma
|
||||
pyxeoma==1.4.1
|
||||
|
@ -1276,7 +1276,7 @@ pywemo==0.7.0
|
||||
pywilight==0.0.70
|
||||
|
||||
# homeassistant.components.wiz
|
||||
pywizlight==0.4.15
|
||||
pywizlight==0.4.16
|
||||
|
||||
# homeassistant.components.zerproc
|
||||
pyzerproc==0.4.8
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Test the WiZ Platform config flow."""
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@ -9,27 +10,49 @@ from homeassistant.components.wiz.config_flow import (
|
||||
WizLightTimeOutError,
|
||||
)
|
||||
from homeassistant.components.wiz.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||
from homeassistant.const import CONF_HOST
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
FAKE_BULB_CONFIG = '{"method":"getSystemConfig","env":"pro","result":\
|
||||
{"mac":"ABCABCABCABC",\
|
||||
"homeId":653906,\
|
||||
"roomId":989983,\
|
||||
"moduleName":"ESP_0711_STR",\
|
||||
"fwVersion":"1.21.0",\
|
||||
"groupId":0,"drvConf":[20,2],\
|
||||
"ewf":[255,0,255,255,0,0,0],\
|
||||
"ewfHex":"ff00ffff000000",\
|
||||
"ping":0}}'
|
||||
|
||||
TEST_SYSTEM_INFO = {"id": "ABCABCABCABC", "name": "Test Bulb"}
|
||||
FAKE_MAC = "ABCABCABCABC"
|
||||
FAKE_BULB_CONFIG = {
|
||||
"method": "getSystemConfig",
|
||||
"env": "pro",
|
||||
"result": {
|
||||
"mac": FAKE_MAC,
|
||||
"homeId": 653906,
|
||||
"roomId": 989983,
|
||||
"moduleName": "ESP_0711_STR",
|
||||
"fwVersion": "1.21.0",
|
||||
"groupId": 0,
|
||||
"drvConf": [20, 2],
|
||||
"ewf": [255, 0, 255, 255, 0, 0, 0],
|
||||
"ewfHex": "ff00ffff000000",
|
||||
"ping": 0,
|
||||
},
|
||||
}
|
||||
FAKE_EXTENDED_WHITE_RANGE = [2200, 2700, 6500, 6500]
|
||||
TEST_SYSTEM_INFO = {"id": FAKE_MAC, "name": "Test Bulb"}
|
||||
TEST_CONNECTION = {CONF_HOST: "1.1.1.1"}
|
||||
TEST_NO_IP = {CONF_HOST: "this is no IP input"}
|
||||
|
||||
|
||||
TEST_CONNECTION = {CONF_HOST: "1.1.1.1", CONF_NAME: "Test Bulb"}
|
||||
def _patch_wizlight():
|
||||
@contextmanager
|
||||
def _patcher():
|
||||
with patch(
|
||||
"homeassistant.components.wiz.wizlight.getBulbConfig",
|
||||
return_value=FAKE_BULB_CONFIG,
|
||||
), patch(
|
||||
"homeassistant.components.wiz.wizlight.getExtendedWhiteRange",
|
||||
return_value=FAKE_EXTENDED_WHITE_RANGE,
|
||||
), patch(
|
||||
"homeassistant.components.wiz.wizlight.getMac",
|
||||
return_value=FAKE_MAC,
|
||||
):
|
||||
yield
|
||||
|
||||
TEST_NO_IP = {CONF_HOST: "this is no IP input", CONF_NAME: "Test Bulb"}
|
||||
return _patcher()
|
||||
|
||||
|
||||
async def test_form(hass):
|
||||
@ -40,13 +63,7 @@ async def test_form(hass):
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {}
|
||||
# Patch functions
|
||||
with patch(
|
||||
"homeassistant.components.wiz.wizlight.getBulbConfig",
|
||||
return_value=FAKE_BULB_CONFIG,
|
||||
), patch(
|
||||
"homeassistant.components.wiz.wizlight.getMac",
|
||||
return_value="ABCABCABCABC",
|
||||
) as mock_setup, patch(
|
||||
with _patch_wizlight(), patch(
|
||||
"homeassistant.components.wiz.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
@ -57,9 +74,10 @@ async def test_form(hass):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == "create_entry"
|
||||
assert result2["title"] == "Test Bulb"
|
||||
assert result2["data"] == TEST_CONNECTION
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert result2["title"] == "WiZ Dimmable White ABCABC"
|
||||
assert result2["data"] == {
|
||||
CONF_HOST: "1.1.1.1",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
@ -98,8 +116,6 @@ async def test_form_updates_unique_id(hass):
|
||||
unique_id=TEST_SYSTEM_INFO["id"],
|
||||
data={
|
||||
CONF_HOST: "dummy",
|
||||
CONF_NAME: TEST_SYSTEM_INFO["name"],
|
||||
"id": TEST_SYSTEM_INFO["id"],
|
||||
},
|
||||
)
|
||||
|
||||
@ -108,13 +124,7 @@ async def test_form_updates_unique_id(hass):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.wiz.wizlight.getBulbConfig",
|
||||
return_value=FAKE_BULB_CONFIG,
|
||||
), patch(
|
||||
"homeassistant.components.wiz.wizlight.getMac",
|
||||
return_value="ABCABCABCABC",
|
||||
), patch(
|
||||
with _patch_wizlight(), patch(
|
||||
"homeassistant.components.wiz.async_setup_entry",
|
||||
return_value=True,
|
||||
):
|
||||
|
Loading…
x
Reference in New Issue
Block a user