From 78e831b08ed1eae81620cef4cb43672d8c566eb2 Mon Sep 17 00:00:00 2001
From: Robert Van Gorkom <vangorra@users.noreply.github.com>
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
-- 
GitLab