From 78e831b08ed1eae81620cef4cb43672d8c566eb2 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Mon, 16 Dec 2019 17:24:50 -0800 Subject: [PATCH] Make tplink light more responsive (#28652) * Making tplink light more responsive. * Adding light platform tests. * Addressing PR feedback. * Mocking the module, not the api. * Using sync method for background update. --- .coveragerc | 1 - homeassistant/components/tplink/const.py | 2 + homeassistant/components/tplink/light.py | 27 ++- tests/components/tplink/test_light.py | 220 +++++++++++++++++++++++ 4 files changed, 242 insertions(+), 8 deletions(-) create mode 100644 tests/components/tplink/test_light.py diff --git a/.coveragerc b/.coveragerc index e16a622cc61..fc7a4691ef2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -708,7 +708,6 @@ omit = homeassistant/components/totalconnect/* homeassistant/components/touchline/climate.py homeassistant/components/tplink/device_tracker.py - homeassistant/components/tplink/light.py homeassistant/components/tplink/switch.py homeassistant/components/tplink_lte/* homeassistant/components/traccar/device_tracker.py diff --git a/homeassistant/components/tplink/const.py b/homeassistant/components/tplink/const.py index 583c25e285c..8b85b8afd74 100644 --- a/homeassistant/components/tplink/const.py +++ b/homeassistant/components/tplink/const.py @@ -1,3 +1,5 @@ """Const for TP-Link.""" +import datetime DOMAIN = "tplink" +MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(seconds=8) diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 117ebf75025..ec3307fc87e 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -126,23 +126,27 @@ class TPLinkSmartBulb(Light): def turn_on(self, **kwargs): """Turn the light on.""" + self._state = True self.smartbulb.state = SmartBulb.BULB_STATE_ON if ATTR_COLOR_TEMP in kwargs: - self.smartbulb.color_temp = mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) + self._color_temp = kwargs.get(ATTR_COLOR_TEMP) + self.smartbulb.color_temp = mired_to_kelvin(self._color_temp) - brightness = brightness_to_percentage( - kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255) - ) + brightness_value = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255) + brightness_pct = brightness_to_percentage(brightness_value) if ATTR_HS_COLOR in kwargs: - hue, sat = kwargs.get(ATTR_HS_COLOR) - hsv = (int(hue), int(sat), brightness) + 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.smartbulb.brightness = brightness + self._brightness = brightness_value + self.smartbulb.brightness = brightness_pct def turn_off(self, **kwargs): """Turn the light off.""" + self._state = False self.smartbulb.state = SmartBulb.BULB_STATE_OFF @property @@ -177,6 +181,15 @@ class TPLinkSmartBulb(Light): def update(self): """Update the TP-Link Bulb's state.""" + if self._supported_features is None: + # First run, update by blocking. + self.do_update() + else: + # Not first run, update in the background. + self.hass.add_job(self.do_update) + + def do_update(self): + """Update states.""" try: if self._supported_features is None: self.get_features() diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py new file mode 100644 index 00000000000..8d1d4d94738 --- /dev/null +++ b/tests/components/tplink/test_light.py @@ -0,0 +1,220 @@ +"""Tests for light platform.""" +from unittest.mock import patch + +from pyHS100 import SmartBulb + +from homeassistant.components import tplink +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + DOMAIN as LIGHT_DOMAIN, +) +from homeassistant.components.tplink.common import CONF_DISCOVERY, CONF_LIGHT +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_HOST, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + + +async def test_light(hass: HomeAssistant) -> None: + """Test function.""" + sys_info = { + "sw_ver": "1.2.3", + "hw_ver": "2.3.4", + "mac": "aa:bb:cc:dd:ee:ff", + "mic_mac": "00:11:22:33:44", + "type": "light", + "hwId": "1234", + "fwId": "4567", + "oemId": "891011", + "dev_name": "light1", + "rssi": 11, + "latitude": "0", + "longitude": "0", + "is_color": True, + "is_dimmable": True, + "is_variable_color_temp": True, + "model": "LB120", + "alias": "light1", + } + + light_state = { + "on_off": SmartBulb.BULB_STATE_ON, + "dft_on_state": { + "brightness": 12, + "color_temp": 3200, + "hue": 100, + "saturation": 200, + }, + "brightness": 13, + "color_temp": 3300, + "hue": 110, + "saturation": 210, + } + + def set_light_state(state): + nonlocal light_state + light_state.update(state) + + set_light_state_patch = patch( + "homeassistant.components.tplink.common.SmartBulb.set_light_state", + side_effect=set_light_state, + ) + get_light_state_patch = patch( + "homeassistant.components.tplink.common.SmartBulb.get_light_state", + return_value=light_state, + ) + current_consumption_patch = patch( + "homeassistant.components.tplink.common.SmartDevice.current_consumption", + return_value=3.23, + ) + get_sysinfo_patch = patch( + "homeassistant.components.tplink.common.SmartDevice.get_sysinfo", + return_value=sys_info, + ) + get_emeter_daily_patch = patch( + "homeassistant.components.tplink.common.SmartDevice.get_emeter_daily", + return_value={ + 1: 1.01, + 2: 1.02, + 3: 1.03, + 4: 1.04, + 5: 1.05, + 6: 1.06, + 7: 1.07, + 8: 1.08, + 9: 1.09, + 10: 1.10, + 11: 1.11, + 12: 1.12, + }, + ) + get_emeter_monthly_patch = patch( + "homeassistant.components.tplink.common.SmartDevice.get_emeter_monthly", + return_value={ + 1: 2.01, + 2: 2.02, + 3: 2.03, + 4: 2.04, + 5: 2.05, + 6: 2.06, + 7: 2.07, + 8: 2.08, + 9: 2.09, + 10: 2.10, + 11: 2.11, + 12: 2.12, + }, + ) + + 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, + ) + + assert hass.states.get("light.light1").state == "off" + assert light_state["on_off"] == 0 + + 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() + + 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 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() + + 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 + + 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 + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.light1"}, + blocking=True, + ) + + await hass.async_block_till_done() + + 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() + + 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