Optimistically set tplink light states (#30189)

* Optimistically handling state changes.
Using retries when command fail.

* Fixing endless update loop.

* Address PR comments.
pull/30695/head
Robert Van Gorkom 2020-01-11 18:45:01 -08:00 committed by Martin Hjelmare
parent 9266fc0cd7
commit a0b0dc0aca
2 changed files with 475 additions and 211 deletions

View File

@ -1,6 +1,8 @@
"""Support for TPLink lights."""
from datetime import timedelta
import logging
import time
from typing import Any, Dict, NamedTuple, Tuple, cast
from pyHS100 import SmartBulb, SmartDeviceException
@ -24,6 +26,7 @@ from . import CONF_LIGHT, DOMAIN as TPLINK_DOMAIN
from .common import async_add_entities_retry
PARALLEL_UPDATES = 0
SCAN_INTERVAL = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
@ -72,192 +75,327 @@ def brightness_from_percentage(percent):
return (percent * 255.0) / 100.0
LightState = NamedTuple(
"LightState",
(
("state", bool),
("brightness", int),
("color_temp", float),
("hs", Tuple[int, int]),
("emeter_params", dict),
),
)
LightFeatures = NamedTuple(
"LightFeatures",
(
("sysinfo", Dict[str, Any]),
("mac", str),
("alias", str),
("model", str),
("supported_features", int),
("min_mireds", float),
("max_mireds", float),
),
)
class TPLinkSmartBulb(Light):
"""Representation of a TPLink Smart Bulb."""
def __init__(self, smartbulb: SmartBulb) -> None:
"""Initialize the bulb."""
self.smartbulb = smartbulb
self._sysinfo = None
self._state = None
self._available = False
self._color_temp = None
self._brightness = None
self._hs = None
self._supported_features = None
self._min_mireds = None
self._max_mireds = None
self._emeter_params = {}
self._mac = None
self._alias = None
self._model = None
self._light_features = cast(LightFeatures, None)
self._light_state = cast(LightState, None)
self._is_available = True
self._is_setting_light_state = False
@property
def unique_id(self):
"""Return a unique ID."""
return self._mac
return self._light_features.mac
@property
def name(self):
"""Return the name of the Smart Bulb."""
return self._alias
return self._light_features.alias
@property
def device_info(self):
"""Return information about the device."""
return {
"name": self._alias,
"model": self._model,
"name": self._light_features.alias,
"model": self._light_features.model,
"manufacturer": "TP-Link",
"connections": {(dr.CONNECTION_NETWORK_MAC, self._mac)},
"sw_version": self._sysinfo["sw_ver"],
"connections": {(dr.CONNECTION_NETWORK_MAC, self._light_features.mac)},
"sw_version": self._light_features.sysinfo["sw_ver"],
}
@property
def available(self) -> bool:
"""Return if bulb is available."""
return self._available
return self._is_available
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
return self._emeter_params
return self._light_state.emeter_params
def turn_on(self, **kwargs):
async def async_turn_on(self, **kwargs):
"""Turn the light on."""
self._state = True
self.smartbulb.state = SmartBulb.BULB_STATE_ON
brightness = (
int(kwargs[ATTR_BRIGHTNESS])
if ATTR_BRIGHTNESS in kwargs
else self._light_state.brightness
if self._light_state.brightness is not None
else 255
)
color_tmp = (
int(kwargs[ATTR_COLOR_TEMP])
if ATTR_COLOR_TEMP in kwargs
else self._light_state.color_temp
)
if ATTR_COLOR_TEMP in kwargs:
self._color_temp = kwargs.get(ATTR_COLOR_TEMP)
self.smartbulb.color_temp = mired_to_kelvin(self._color_temp)
await self.async_set_light_state_retry(
self._light_state,
LightState(
state=True,
brightness=brightness,
color_temp=color_tmp,
hs=tuple(kwargs.get(ATTR_HS_COLOR, self._light_state.hs or ())),
emeter_params=self._light_state.emeter_params,
),
)
brightness_value = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255)
brightness_pct = brightness_to_percentage(brightness_value)
if ATTR_HS_COLOR in kwargs:
self._hs = kwargs.get(ATTR_HS_COLOR)
hue, sat = self._hs
hsv = (int(hue), int(sat), brightness_pct)
self.smartbulb.hsv = hsv
elif ATTR_BRIGHTNESS in kwargs:
self._brightness = brightness_value
self.smartbulb.brightness = brightness_pct
def turn_off(self, **kwargs):
async def async_turn_off(self, **kwargs):
"""Turn the light off."""
self._state = False
self.smartbulb.state = SmartBulb.BULB_STATE_OFF
await self.async_set_light_state_retry(
self._light_state,
LightState(
state=False,
brightness=self._light_state.brightness,
color_temp=self._light_state.color_temp,
hs=self._light_state.hs,
emeter_params=self._light_state.emeter_params,
),
)
@property
def min_mireds(self):
"""Return minimum supported color temperature."""
return self._min_mireds
return self._light_features.min_mireds
@property
def max_mireds(self):
"""Return maximum supported color temperature."""
return self._max_mireds
return self._light_features.max_mireds
@property
def color_temp(self):
"""Return the color temperature of this light in mireds for HA."""
return self._color_temp
return self._light_state.color_temp
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self._brightness
return self._light_state.brightness
@property
def hs_color(self):
"""Return the color."""
return self._hs
return self._light_state.hs
@property
def is_on(self):
"""Return True if device is on."""
return self._state
return self._light_state.state
def update(self):
"""Update the TP-Link Bulb's state."""
if self._supported_features is None:
# First run, update by blocking.
self.do_update()
# State is currently being set, ignore.
if self._is_setting_light_state:
return
# Initial run, perform call blocking.
if not self._light_features:
self.do_update_retry(False)
# Subsequent runs should not block.
else:
# Not first run, update in the background.
self.hass.add_job(self.do_update)
self.hass.add_job(self.do_update_retry, True)
def do_update(self):
"""Update states."""
def do_update_retry(self, update_state: bool) -> None:
"""Update state data with retry.""" ""
try:
if self._supported_features is None:
self.get_features()
self._state = self.smartbulb.state == SmartBulb.BULB_STATE_ON
if self._supported_features & SUPPORT_BRIGHTNESS:
self._brightness = brightness_from_percentage(self.smartbulb.brightness)
if self._supported_features & SUPPORT_COLOR_TEMP:
if (
self.smartbulb.color_temp is not None
and self.smartbulb.color_temp != 0
):
self._color_temp = kelvin_to_mired(self.smartbulb.color_temp)
if self._supported_features & SUPPORT_COLOR:
hue, sat, _ = self.smartbulb.hsv
self._hs = (hue, sat)
if self.smartbulb.has_emeter:
self._emeter_params[ATTR_CURRENT_POWER_W] = "{:.1f}".format(
self.smartbulb.current_consumption()
)
daily_statistics = self.smartbulb.get_emeter_daily()
monthly_statistics = self.smartbulb.get_emeter_monthly()
try:
self._emeter_params[ATTR_DAILY_ENERGY_KWH] = "{:.3f}".format(
daily_statistics[int(time.strftime("%d"))]
)
self._emeter_params[ATTR_MONTHLY_ENERGY_KWH] = "{:.3f}".format(
monthly_statistics[int(time.strftime("%m"))]
)
except KeyError:
# device returned no daily/monthly history
pass
self._available = True
# Update light features only once.
self._light_features = (
self._light_features or self.get_light_features_retry()
)
self._light_state = self.get_light_state_retry(self._light_features)
self._is_available = True
except (SmartDeviceException, OSError) as ex:
if self._available:
if self._is_available:
_LOGGER.warning(
"Could not read state for %s: %s", self.smartbulb.host, ex
"Could not read data for %s: %s", self.smartbulb.host, ex
)
self._available = False
self._is_available = False
# The local variables were updates asyncronousally,
# we need the entity registry to poll this object's properties for
# updated information. Calling schedule_update_ha_state will only
# cause a loop.
if update_state:
self.schedule_update_ha_state()
@property
def supported_features(self):
"""Flag supported features."""
return self._supported_features
return self._light_features.supported_features
def get_features(self):
def get_light_features_retry(self) -> LightFeatures:
"""Retry the retrieval of the supported features."""
try:
return self.get_light_features()
except (SmartDeviceException, OSError):
pass
_LOGGER.debug("Retrying getting light features")
return self.get_light_features()
def get_light_features(self):
"""Determine all supported features in one go."""
self._sysinfo = self.smartbulb.sys_info
self._supported_features = 0
self._mac = self.smartbulb.mac
self._alias = self.smartbulb.alias
self._model = self.smartbulb.model
sysinfo = self.smartbulb.sys_info
supported_features = 0
mac = self.smartbulb.mac
alias = self.smartbulb.alias
model = self.smartbulb.model
min_mireds = None
max_mireds = None
if self.smartbulb.is_dimmable:
self._supported_features += SUPPORT_BRIGHTNESS
supported_features += SUPPORT_BRIGHTNESS
if getattr(self.smartbulb, "is_variable_color_temp", False):
self._supported_features += SUPPORT_COLOR_TEMP
self._min_mireds = kelvin_to_mired(
self.smartbulb.valid_temperature_range[1]
)
self._max_mireds = kelvin_to_mired(
self.smartbulb.valid_temperature_range[0]
)
supported_features += SUPPORT_COLOR_TEMP
min_mireds = kelvin_to_mired(self.smartbulb.valid_temperature_range[1])
max_mireds = kelvin_to_mired(self.smartbulb.valid_temperature_range[0])
if getattr(self.smartbulb, "is_color", False):
self._supported_features += SUPPORT_COLOR
supported_features += SUPPORT_COLOR
return LightFeatures(
sysinfo=sysinfo,
mac=mac,
alias=alias,
model=model,
supported_features=supported_features,
min_mireds=min_mireds,
max_mireds=max_mireds,
)
def get_light_state_retry(self, light_features: LightFeatures) -> LightState:
"""Retry the retrieval of getting light states."""
try:
return self.get_light_state(light_features)
except (SmartDeviceException, OSError):
pass
_LOGGER.debug("Retrying getting light state")
return self.get_light_state(light_features)
def get_light_state(self, light_features: LightFeatures) -> LightState:
"""Get the light state."""
emeter_params = {}
brightness = None
color_temp = None
hue_saturation = None
state = self.smartbulb.state == SmartBulb.BULB_STATE_ON
if light_features.supported_features & SUPPORT_BRIGHTNESS:
brightness = brightness_from_percentage(self.smartbulb.brightness)
if light_features.supported_features & SUPPORT_COLOR_TEMP:
if self.smartbulb.color_temp is not None and self.smartbulb.color_temp != 0:
color_temp = kelvin_to_mired(self.smartbulb.color_temp)
if light_features.supported_features & SUPPORT_COLOR:
hue, sat, _ = self.smartbulb.hsv
hue_saturation = (hue, sat)
if self.smartbulb.has_emeter:
emeter_params[ATTR_CURRENT_POWER_W] = "{:.1f}".format(
self.smartbulb.current_consumption()
)
daily_statistics = self.smartbulb.get_emeter_daily()
monthly_statistics = self.smartbulb.get_emeter_monthly()
try:
emeter_params[ATTR_DAILY_ENERGY_KWH] = "{:.3f}".format(
daily_statistics[int(time.strftime("%d"))]
)
emeter_params[ATTR_MONTHLY_ENERGY_KWH] = "{:.3f}".format(
monthly_statistics[int(time.strftime("%m"))]
)
except KeyError:
# device returned no daily/monthly history
pass
return LightState(
state=state,
brightness=brightness,
color_temp=color_temp,
hs=hue_saturation,
emeter_params=emeter_params,
)
async def async_set_light_state_retry(
self, old_light_state: LightState, new_light_state: LightState
) -> None:
"""Set the light state with retry."""
# Optimistically setting the light state.
self._light_state = new_light_state
# Tell the device to set the states.
self._is_setting_light_state = True
try:
await self.hass.async_add_executor_job(
self.set_light_state, old_light_state, new_light_state
)
self._is_available = True
self._is_setting_light_state = False
return
except (SmartDeviceException, OSError):
pass
try:
_LOGGER.debug("Retrying setting light state")
await self.hass.async_add_executor_job(
self.set_light_state, old_light_state, new_light_state
)
self._is_available = True
except (SmartDeviceException, OSError) as ex:
self._is_available = False
_LOGGER.warning("Could not set data for %s: %s", self.smartbulb.host, ex)
self._is_setting_light_state = False
def set_light_state(
self, old_light_state: LightState, new_light_state: LightState
) -> None:
"""Set the light state."""
# Calling the API with the new state information.
if new_light_state.state != old_light_state.state:
if new_light_state.state:
self.smartbulb.state = SmartBulb.BULB_STATE_ON
else:
self.smartbulb.state = SmartBulb.BULB_STATE_OFF
return
if new_light_state.color_temp != old_light_state.color_temp:
self.smartbulb.color_temp = mired_to_kelvin(new_light_state.color_temp)
brightness_pct = brightness_to_percentage(new_light_state.brightness)
if new_light_state.hs != old_light_state.hs and len(new_light_state.hs) > 1:
hue, sat = new_light_state.hs
hsv = (int(hue), int(sat), brightness_pct)
self.smartbulb.hsv = hsv
elif new_light_state.brightness != old_light_state.brightness:
self.smartbulb.brightness = brightness_pct

View File

@ -1,9 +1,15 @@
"""Tests for light platform."""
from unittest.mock import patch
from typing import Callable, NamedTuple
from unittest.mock import Mock, patch
from pyHS100 import SmartBulb
from pyHS100 import SmartDeviceException
import pytest
from homeassistant.components import tplink
from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
)
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
@ -20,9 +26,25 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
LightMockData = NamedTuple(
"LightMockData",
(
("sys_info", dict),
("light_state", dict),
("set_light_state", Callable[[dict], None]),
("set_light_state_mock", Mock),
("get_light_state_mock", Mock),
("current_consumption_mock", Mock),
("get_sysinfo_mock", Mock),
("get_emeter_daily_mock", Mock),
("get_emeter_monthly_mock", Mock),
),
)
async def test_light(hass: HomeAssistant) -> None:
"""Test function."""
@pytest.fixture(name="light_mock_data")
def light_mock_data_fixture() -> None:
"""Create light mock data."""
sys_info = {
"sw_ver": "1.2.3",
"hw_ver": "2.3.4",
@ -44,22 +66,26 @@ async def test_light(hass: HomeAssistant) -> None:
}
light_state = {
"on_off": SmartBulb.BULB_STATE_ON,
"on_off": True,
"dft_on_state": {
"brightness": 12,
"color_temp": 3200,
"hue": 100,
"saturation": 200,
"hue": 110,
"saturation": 90,
},
"brightness": 13,
"color_temp": 3300,
"hue": 110,
"saturation": 210,
"saturation": 90,
}
def set_light_state(state):
def set_light_state(state) -> None:
nonlocal light_state
drt_on_state = light_state["dft_on_state"]
drt_on_state.update(state.get("dft_on_state", {}))
light_state.update(state)
light_state["dft_on_state"] = drt_on_state
set_light_state_patch = patch(
"homeassistant.components.tplink.common.SmartBulb.set_light_state",
@ -112,109 +138,209 @@ async def test_light(hass: HomeAssistant) -> None:
},
)
with set_light_state_patch, get_light_state_patch, current_consumption_patch, get_sysinfo_patch, get_emeter_daily_patch, get_emeter_monthly_patch:
await async_setup_component(
hass,
tplink.DOMAIN,
{
tplink.DOMAIN: {
CONF_DISCOVERY: False,
CONF_LIGHT: [{CONF_HOST: "123.123.123.123"}],
}
},
)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.light1"},
blocking=True,
with set_light_state_patch as set_light_state_mock, get_light_state_patch as get_light_state_mock, current_consumption_patch as current_consumption_mock, get_sysinfo_patch as get_sysinfo_mock, get_emeter_daily_patch as get_emeter_daily_mock, get_emeter_monthly_patch as get_emeter_monthly_mock:
yield LightMockData(
sys_info=sys_info,
light_state=light_state,
set_light_state=set_light_state,
set_light_state_mock=set_light_state_mock,
get_light_state_mock=get_light_state_mock,
current_consumption_mock=current_consumption_mock,
get_sysinfo_mock=get_sysinfo_mock,
get_emeter_daily_mock=get_emeter_daily_mock,
get_emeter_monthly_mock=get_emeter_monthly_mock,
)
assert hass.states.get("light.light1").state == "off"
assert light_state["on_off"] == 0
await hass.async_block_till_done()
async def update_entity(hass: HomeAssistant, entity_id: str) -> None:
"""Run an update action for an entity."""
await hass.services.async_call(
HA_DOMAIN, SERVICE_UPDATE_ENTITY, {ATTR_ENTITY_ID: entity_id}, blocking=True,
)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "light.light1",
ATTR_COLOR_TEMP: 312,
ATTR_BRIGHTNESS: 50,
},
blocking=True,
)
await hass.async_block_till_done()
async def test_light(hass: HomeAssistant, light_mock_data: LightMockData) -> None:
"""Test function."""
light_state = light_mock_data.light_state
set_light_state = light_mock_data.set_light_state
state = hass.states.get("light.light1")
assert state.state == "on"
assert state.attributes["brightness"] == 48.45
assert state.attributes["hs_color"] == (110, 210)
assert state.attributes["color_temp"] == 312
assert light_state["on_off"] == 1
await async_setup_component(hass, HA_DOMAIN, {})
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "light.light1",
ATTR_BRIGHTNESS: 55,
ATTR_HS_COLOR: (23, 27),
},
blocking=True,
)
await async_setup_component(
hass,
tplink.DOMAIN,
{
tplink.DOMAIN: {
CONF_DISCOVERY: False,
CONF_LIGHT: [{CONF_HOST: "123.123.123.123"}],
}
},
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert hass.states.get("light.light1")
state = hass.states.get("light.light1")
assert state.state == "on"
assert state.attributes["brightness"] == 53.55
assert state.attributes["hs_color"] == (23, 27)
assert state.attributes["color_temp"] == 312
assert light_state["brightness"] == 21
assert light_state["hue"] == 23
assert light_state["saturation"] == 27
await hass.services.async_call(
LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "light.light1"}, blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.light1")
light_state["on_off"] = 0
light_state["dft_on_state"]["on_off"] = 0
light_state["brightness"] = 66
light_state["dft_on_state"]["brightness"] = 66
light_state["color_temp"] = 6400
light_state["dft_on_state"]["color_temp"] = 123
light_state["hue"] = 77
light_state["dft_on_state"]["hue"] = 77
light_state["saturation"] = 78
light_state["dft_on_state"]["saturation"] = 78
assert hass.states.get("light.light1").state == "off"
assert light_state["on_off"] == 0
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.light1"},
blocking=True,
)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.light1", ATTR_COLOR_TEMP: 222, ATTR_BRIGHTNESS: 50},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.light1")
await hass.async_block_till_done()
state = hass.states.get("light.light1")
assert state.state == "on"
assert state.attributes["brightness"] == 48.45
assert state.attributes["hs_color"] == (110, 90)
assert state.attributes["color_temp"] == 222
assert light_state["on_off"] == 1
state = hass.states.get("light.light1")
assert state.state == "off"
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.light1", ATTR_BRIGHTNESS: 55, ATTR_HS_COLOR: (23, 27)},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.light1")
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.light1"},
blocking=True,
)
state = hass.states.get("light.light1")
assert state.state == "on"
assert state.attributes["brightness"] == 53.55
assert state.attributes["hs_color"] == (23, 27)
assert light_state["brightness"] == 21
assert light_state["hue"] == 23
assert light_state["saturation"] == 27
await hass.async_block_till_done()
light_state["on_off"] = 0
light_state["dft_on_state"]["on_off"] = 0
light_state["brightness"] = 66
light_state["dft_on_state"]["brightness"] = 66
light_state["color_temp"] = 6400
light_state["dft_on_state"]["color_temp"] = 123
light_state["hue"] = 77
light_state["dft_on_state"]["hue"] = 77
light_state["saturation"] = 78
light_state["dft_on_state"]["saturation"] = 78
state = hass.states.get("light.light1")
assert state.attributes["brightness"] == 168.3
assert state.attributes["hs_color"] == (77, 78)
assert state.attributes["color_temp"] == 156
assert light_state["brightness"] == 66
assert light_state["hue"] == 77
assert light_state["saturation"] == 78
await hass.services.async_call(
LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "light.light1"}, blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.light1")
state = hass.states.get("light.light1")
assert state.state == "off"
await hass.services.async_call(
LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "light.light1"}, blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.light1")
state = hass.states.get("light.light1")
assert state.state == "on"
assert state.attributes["brightness"] == 168.3
assert state.attributes["hs_color"] == (77, 78)
assert state.attributes["color_temp"] == 156
assert light_state["brightness"] == 66
assert light_state["hue"] == 77
assert light_state["saturation"] == 78
set_light_state({"brightness": 91, "dft_on_state": {"brightness": 91}})
await update_entity(hass, "light.light1")
state = hass.states.get("light.light1")
assert state.attributes["brightness"] == 232.05
async def test_get_light_state_retry(
hass: HomeAssistant, light_mock_data: LightMockData
) -> None:
"""Test function."""
# Setup test for retries for sysinfo.
get_sysinfo_call_count = 0
def get_sysinfo_side_effect():
nonlocal get_sysinfo_call_count
get_sysinfo_call_count += 1
# Need to fail on the 2nd call because the first call is used to
# determine if the device is online during the light platform's
# setup hook.
if get_sysinfo_call_count == 2:
raise SmartDeviceException()
return light_mock_data.sys_info
light_mock_data.get_sysinfo_mock.side_effect = get_sysinfo_side_effect
# Setup test for retries of getting state information.
get_state_call_count = 0
def get_light_state_side_effect():
nonlocal get_state_call_count
get_state_call_count += 1
if get_state_call_count == 1:
raise SmartDeviceException()
return light_mock_data.light_state
light_mock_data.get_light_state_mock.side_effect = get_light_state_side_effect
# Setup test for retries of setting state information.
set_state_call_count = 0
def set_light_state_side_effect(state_data: dict):
nonlocal set_state_call_count, light_mock_data
set_state_call_count += 1
if set_state_call_count == 1:
raise SmartDeviceException()
light_mock_data.set_light_state(state_data)
light_mock_data.set_light_state_mock.side_effect = set_light_state_side_effect
# Setup component.
await async_setup_component(hass, HA_DOMAIN, {})
await hass.async_block_till_done()
await async_setup_component(
hass,
tplink.DOMAIN,
{
tplink.DOMAIN: {
CONF_DISCOVERY: False,
CONF_LIGHT: [{CONF_HOST: "123.123.123.123"}],
}
},
)
await hass.async_block_till_done()
await hass.services.async_call(
LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "light.light1"}, blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.light1")
assert light_mock_data.get_sysinfo_mock.call_count > 1
assert light_mock_data.get_light_state_mock.call_count > 1
assert light_mock_data.set_light_state_mock.call_count > 1
assert light_mock_data.get_sysinfo_mock.call_count < 40
assert light_mock_data.get_light_state_mock.call_count < 40
assert light_mock_data.set_light_state_mock.call_count < 10