Fix tplink HS220 dimmers (#33909)

* HS220 dimmers are handled as lights with a limited feature set
This commit is contained in:
J. Nick Koston 2020-04-09 17:07:39 -05:00 committed by GitHub
parent 425c97626a
commit 6b2baae0de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 210 additions and 4 deletions

View File

@ -345,7 +345,7 @@ class TPLinkSmartBulb(Light):
def _get_light_state(self) -> LightState: def _get_light_state(self) -> LightState:
"""Get the light state.""" """Get the light state."""
self._update_emeter() self._update_emeter()
return self._light_state_from_params(self.smartbulb.get_light_state()) return self._light_state_from_params(self._get_device_state())
def _update_emeter(self): def _update_emeter(self):
if not self.smartbulb.has_emeter: if not self.smartbulb.has_emeter:
@ -427,7 +427,40 @@ class TPLinkSmartBulb(Light):
if not diff: if not diff:
return return
return self.smartbulb.set_light_state(diff) return self._set_device_state(diff)
def _get_device_state(self):
"""State of the bulb or smart dimmer switch."""
if isinstance(self.smartbulb, SmartBulb):
return self.smartbulb.get_light_state()
# Its not really a bulb, its a dimmable SmartPlug (aka Wall Switch)
return {
LIGHT_STATE_ON_OFF: self.smartbulb.state,
LIGHT_STATE_BRIGHTNESS: self.smartbulb.brightness,
LIGHT_STATE_COLOR_TEMP: 0,
LIGHT_STATE_HUE: 0,
LIGHT_STATE_SATURATION: 0,
}
def _set_device_state(self, state):
"""Set state of the bulb or smart dimmer switch."""
if isinstance(self.smartbulb, SmartBulb):
return self.smartbulb.set_light_state(state)
# Its not really a bulb, its a dimmable SmartPlug (aka Wall Switch)
if LIGHT_STATE_BRIGHTNESS in state:
# Brightness of 0 is accepted by the
# device but the underlying library rejects it
# so we turn off instead.
if state[LIGHT_STATE_BRIGHTNESS]:
self.smartbulb.brightness = state[LIGHT_STATE_BRIGHTNESS]
else:
self.smartbulb.state = 0
elif LIGHT_STATE_ON_OFF in state:
self.smartbulb.state = state[LIGHT_STATE_ON_OFF]
return self._get_device_state()
def _light_state_diff(old_light_state: LightState, new_light_state: LightState): def _light_state_diff(old_light_state: LightState, new_light_state: LightState):

View File

@ -1,6 +1,6 @@
"""Tests for light platform.""" """Tests for light platform."""
from typing import Callable, NamedTuple from typing import Callable, NamedTuple
from unittest.mock import Mock, patch from unittest.mock import Mock, PropertyMock, patch
from pyHS100 import SmartDeviceException from pyHS100 import SmartDeviceException
import pytest import pytest
@ -16,7 +16,11 @@ from homeassistant.components.light import (
ATTR_HS_COLOR, ATTR_HS_COLOR,
DOMAIN as LIGHT_DOMAIN, DOMAIN as LIGHT_DOMAIN,
) )
from homeassistant.components.tplink.common import CONF_DISCOVERY, CONF_LIGHT from homeassistant.components.tplink.common import (
CONF_DIMMER,
CONF_DISCOVERY,
CONF_LIGHT,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
CONF_HOST, CONF_HOST,
@ -41,6 +45,16 @@ class LightMockData(NamedTuple):
get_emeter_monthly_mock: Mock get_emeter_monthly_mock: Mock
class SmartSwitchMockData(NamedTuple):
"""Mock smart switch data."""
sys_info: dict
light_state: dict
state_mock: Mock
brightness_mock: Mock
get_sysinfo_mock: Mock
@pytest.fixture(name="light_mock_data") @pytest.fixture(name="light_mock_data")
def light_mock_data_fixture() -> None: def light_mock_data_fixture() -> None:
"""Create light mock data.""" """Create light mock data."""
@ -152,6 +166,75 @@ def light_mock_data_fixture() -> None:
) )
@pytest.fixture(name="dimmer_switch_mock_data")
def dimmer_switch_mock_data_fixture() -> None:
"""Create dimmer switch mock data."""
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": "switch",
"hwId": "1234",
"fwId": "4567",
"oemId": "891011",
"dev_name": "dimmer1",
"rssi": 11,
"latitude": "0",
"longitude": "0",
"is_color": False,
"is_dimmable": True,
"is_variable_color_temp": False,
"model": "HS220",
"alias": "dimmer1",
"feature": ":",
}
light_state = {
"on_off": 1,
"brightness": 13,
}
def state(*args, **kwargs):
nonlocal light_state
if len(args) == 0:
return light_state["on_off"]
light_state["on_off"] = args[0]
def brightness(*args, **kwargs):
nonlocal light_state
if len(args) == 0:
return light_state["brightness"]
if light_state["brightness"] == 0:
light_state["on_off"] = 0
else:
light_state["on_off"] = 1
light_state["brightness"] = args[0]
get_sysinfo_patch = patch(
"homeassistant.components.tplink.common.SmartDevice.get_sysinfo",
return_value=sys_info,
)
state_patch = patch(
"homeassistant.components.tplink.common.SmartPlug.state",
new_callable=PropertyMock,
side_effect=state,
)
brightness_patch = patch(
"homeassistant.components.tplink.common.SmartPlug.brightness",
new_callable=PropertyMock,
side_effect=brightness,
)
with brightness_patch as brightness_mock, state_patch as state_mock, get_sysinfo_patch as get_sysinfo_mock:
yield SmartSwitchMockData(
sys_info=sys_info,
light_state=light_state,
brightness_mock=brightness_mock,
state_mock=state_mock,
get_sysinfo_mock=get_sysinfo_mock,
)
async def update_entity(hass: HomeAssistant, entity_id: str) -> None: async def update_entity(hass: HomeAssistant, entity_id: str) -> None:
"""Run an update action for an entity.""" """Run an update action for an entity."""
await hass.services.async_call( await hass.services.async_call(
@ -160,6 +243,96 @@ async def update_entity(hass: HomeAssistant, entity_id: str) -> None:
await hass.async_block_till_done() await hass.async_block_till_done()
async def test_smartswitch(
hass: HomeAssistant, dimmer_switch_mock_data: SmartSwitchMockData
) -> None:
"""Test function."""
light_state = dimmer_switch_mock_data.light_state
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_DIMMER: [{CONF_HOST: "123.123.123.123"}],
}
},
)
await hass.async_block_till_done()
assert hass.states.get("light.dimmer1")
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.dimmer1"},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
assert hass.states.get("light.dimmer1").state == "off"
assert light_state["on_off"] == 0
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.dimmer1", ATTR_BRIGHTNESS: 50},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
state = hass.states.get("light.dimmer1")
assert state.state == "on"
assert state.attributes["brightness"] == 48.45
assert light_state["on_off"] == 1
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.dimmer1", ATTR_BRIGHTNESS: 55},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
state = hass.states.get("light.dimmer1")
assert state.state == "on"
assert state.attributes["brightness"] == 53.55
assert light_state["brightness"] == 21
light_state["on_off"] = 0
light_state["brightness"] = 66
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.dimmer1"},
blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
state = hass.states.get("light.dimmer1")
assert state.state == "off"
await hass.services.async_call(
LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "light.dimmer1"}, blocking=True,
)
await hass.async_block_till_done()
await update_entity(hass, "light.dimmer1")
state = hass.states.get("light.dimmer1")
assert state.state == "on"
assert state.attributes["brightness"] == 168.3
assert light_state["brightness"] == 66
async def test_light(hass: HomeAssistant, light_mock_data: LightMockData) -> None: async def test_light(hass: HomeAssistant, light_mock_data: LightMockData) -> None:
"""Test function.""" """Test function."""
light_state = light_mock_data.light_state light_state = light_mock_data.light_state