Avoid divide by zero errors in tplink light integration (#48235)

This commit is contained in:
Mario Limonciello 2021-03-31 10:16:24 -05:00 committed by GitHub
parent 9e1a17c62e
commit 9fd6980144
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 165 additions and 3 deletions

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
import re
import time import time
from typing import Any, NamedTuple, cast from typing import Any, NamedTuple, cast
@ -60,6 +61,21 @@ LIGHT_SYSINFO_IS_COLOR = "is_color"
MAX_ATTEMPTS = 300 MAX_ATTEMPTS = 300
SLEEP_TIME = 2 SLEEP_TIME = 2
TPLINK_KELVIN = {
"LB130": (2500, 9000),
"LB120": (2700, 6500),
"LB230": (2500, 9000),
"KB130": (2500, 9000),
"KL130": (2500, 9000),
"KL125": (2500, 6500),
r"KL120\(EU\)": (2700, 6500),
r"KL120\(US\)": (2700, 5000),
r"KL430\(US\)": (2500, 9000),
}
FALLBACK_MIN_COLOR = 2700
FALLBACK_MAX_COLOR = 5000
async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_entities): async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_entities):
"""Set up lights.""" """Set up lights."""
@ -269,6 +285,19 @@ class TPLinkSmartBulb(LightEntity):
"""Flag supported features.""" """Flag supported features."""
return self._light_features.supported_features return self._light_features.supported_features
def _get_valid_temperature_range(self):
"""Return the device-specific white temperature range (in Kelvin).
:return: White temperature range in Kelvin (minimum, maximum)
"""
model = self.smartbulb.sys_info[LIGHT_SYSINFO_MODEL]
for obj, temp_range in TPLINK_KELVIN.items():
if re.match(obj, model):
return temp_range
# pyHS100 is abandoned, but some bulb definitions aren't present
# use "safe" values for something that advertises color temperature
return FALLBACK_MIN_COLOR, FALLBACK_MAX_COLOR
def _get_light_features(self): def _get_light_features(self):
"""Determine all supported features in one go.""" """Determine all supported features in one go."""
sysinfo = self.smartbulb.sys_info sysinfo = self.smartbulb.sys_info
@ -285,9 +314,7 @@ class TPLinkSmartBulb(LightEntity):
supported_features += SUPPORT_BRIGHTNESS supported_features += SUPPORT_BRIGHTNESS
if sysinfo.get(LIGHT_SYSINFO_IS_VARIABLE_COLOR_TEMP): if sysinfo.get(LIGHT_SYSINFO_IS_VARIABLE_COLOR_TEMP):
supported_features += SUPPORT_COLOR_TEMP supported_features += SUPPORT_COLOR_TEMP
# Have to make another api request here in max_range, min_range = self._get_valid_temperature_range()
# order to not re-implement pyHS100 here
max_range, min_range = self.smartbulb.valid_temperature_range
min_mireds = kelvin_to_mired(min_range) min_mireds = kelvin_to_mired(min_range)
max_mireds = kelvin_to_mired(max_range) max_mireds = kelvin_to_mired(max_range)
if sysinfo.get(LIGHT_SYSINFO_IS_COLOR): if sysinfo.get(LIGHT_SYSINFO_IS_COLOR):

View File

@ -60,6 +60,116 @@ class SmartSwitchMockData(NamedTuple):
get_sysinfo_mock: Mock get_sysinfo_mock: Mock
@pytest.fixture(name="unknown_light_mock_data")
def unknown_light_mock_data_fixture() -> None:
"""Create light 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": "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": "Foo",
"alias": "light1",
}
light_state = {
"on_off": True,
"dft_on_state": {
"brightness": 12,
"color_temp": 3200,
"hue": 110,
"saturation": 90,
},
"brightness": 13,
"color_temp": 3300,
"hue": 110,
"saturation": 90,
}
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
return light_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 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,
)
@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."""
@ -343,6 +453,31 @@ async def test_smartswitch(
assert sys_info["brightness"] == 66 assert sys_info["brightness"] == 66
async def test_unknown_light(
hass: HomeAssistant, unknown_light_mock_data: LightMockData
) -> None:
"""Test function."""
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()
state = hass.states.get("light.light1")
assert state.state == "on"
assert state.attributes["min_mireds"] == 200
assert state.attributes["max_mireds"] == 370
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