def set_hsv(self, state: Tuple[int, int, int], duration=1): """ Sets new HSV, if supported :param tuple state: hue, saturation and value (degrees, %, %) """ if not self.is_color: return None if not isinstance(state[0], int) or not (0 <= state[0] <= 360): raise SmartDeviceException('Invalid hue value: {} ' '(valid range: 0-360)'.format(state[0])) if not isinstance(state[1], int) or not (0 <= state[1] <= 100): raise SmartDeviceException('Invalid saturation value: {} ' '(valid range: 0-100%)'.format( state[1])) if not isinstance(state[2], int) or not (0 <= state[2] <= 100): raise SmartDeviceException('Invalid brightness value: {} ' '(valid range: 0-100%)'.format( state[2])) light_state = { "hue": state[0], "saturation": state[1], "brightness": state[2], "color_temp": 0, "ignore_default": 1, "transition_period": max(min(int(duration * 1000), 10000), 0) } self.set_light_state(light_state)
def hsv(self, state: Tuple[int, int, int]): """ Sets new HSV, if supported :param tuple state: hue, saturation and value (degrees, %, %) """ if not self.is_color: return None if not isinstance(state[0], int) or not (0 <= state[0] <= 360): raise SmartDeviceException( 'Invalid hue value: {} ' '(valid range: 0-360)'.format(state[0])) if not isinstance(state[1], int) or not (0 <= state[1] <= 100): raise SmartDeviceException( 'Invalid saturation value: {} ' '(valid range: 0-100%)'.format(state[1])) if not isinstance(state[2], int) or not (0 <= state[2] <= 100): raise SmartDeviceException( 'Invalid brightness value: {} ' '(valid range: 0-100%)'.format(state[2])) light_state = { "hue": state[0], "saturation": state[1], "brightness": state[2], "color_temp": 0 } self.set_light_state(light_state)
def set_hsv(self, hue: int, saturation: int, value: int): """Set new HSV. :param tuple state: hue, saturation and value (degrees, %, %) """ if not self.is_color: raise SmartDeviceException("Bulb does not support color.") if not isinstance(hue, int) or not (0 <= hue <= 360): raise ValueError( "Invalid hue value: {} " "(valid range: 0-360)".format(hue) ) if not isinstance(saturation, int) or not (0 <= saturation <= 100): raise ValueError( "Invalid saturation value: {} " "(valid range: 0-100%)".format(saturation) ) self._raise_for_invalid_brightness(value) light_state = { "hue": hue, "saturation": saturation, "brightness": value, "color_temp": 0, } self.set_light_state(light_state)
async def test_async_add_entities_retry_cancel(hass: HomeAssistantType): """Test interval callback.""" async_add_entities_callback = MagicMock() callback_side_effects = [ False, False, True, # Object 1 False, True, # Object 2 SmartDeviceException("My error"), False, True, # Object 3 True, # Object 4 ] callback = MagicMock(side_effect=callback_side_effects) objects = ["Object 1", "Object 2", "Object 3", "Object 4"] cancel = await async_add_entities_retry( hass, async_add_entities_callback, objects, callback, interval=timedelta(milliseconds=100), ) cancel() await hass.async_block_till_done() assert callback.call_count == 4
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() return light_mock_data.set_light_state(state_data)
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
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
def set_brightness(self, brightness: int) -> None: """Set the current brightness of the device. :param int brightness: brightness in percent """ if not self.is_dimmable: # pragma: no cover raise SmartDeviceException("Bulb is not dimmable.") self._raise_for_invalid_brightness(brightness) light_state = {"brightness": brightness} self.set_light_state(light_state)
def transition_period(self, period: int): """Set default transition period. :param int period: transition period in milliseconds, default 500 """ if period < 0: raise SmartDeviceException( 'Invalid transition value: {}' '(requires positive int value)'.format(period)) self._transition_period = period
def brightness(self) -> int: """Return current brightness on dimmers. Will return a range between 0 - 100. :returns: integer :rtype: int """ if not self.is_dimmable: raise SmartDeviceException("Device is not dimmable.") return int(self.sys_info["brightness"])
def brightness(self) -> int: """Current brightness of the device. :return: brightness in percent :rtype: int """ if not self.is_dimmable: # pragma: no cover raise SmartDeviceException("Bulb is not dimmable.") light_state = self.get_light_state() if not self.is_on: return int(light_state["dft_on_state"]["brightness"]) else: return int(light_state["brightness"])
def _get_device_class(info: dict) -> Optional[Type[SmartDevice]]: """Find SmartDevice subclass for device described by passed data.""" if "system" in info and "get_sysinfo" in info["system"]: sysinfo = info["system"]["get_sysinfo"] if "type" in sysinfo: type_ = sysinfo["type"] elif "mic_type" in sysinfo: type_ = sysinfo["mic_type"] else: raise SmartDeviceException( "Unable to find the device type field!") else: raise SmartDeviceException( "No 'system' nor 'get_sysinfo' in response") if "smartplug" in type_.lower() and "children" in sysinfo: return SmartStrip elif "smartplug" in type_.lower(): return SmartPlug elif "smartbulb" in type_.lower(): return SmartBulb return None
def color_temp(self) -> int: """Return color temperature of the device. :return: Color temperature in Kelvin :rtype: int """ if not self.is_variable_color_temp: raise SmartDeviceException("Bulb does not support colortemp.") light_state = self.get_light_state() if not self.is_on: return int(light_state["dft_on_state"]["color_temp"]) else: return int(light_state["color_temp"])
def set_color_temp(self, temp: int) -> None: """Set the color temperature of the device. :param int temp: The new color temperature, in Kelvin """ if not self.is_variable_color_temp: raise SmartDeviceException("Bulb does not support colortemp.") if (temp < self.valid_temperature_range[0] or temp > self.valid_temperature_range[1]): raise ValueError("Temperature should be between {} " "and {}".format(*self.valid_temperature_range)) light_state = {"color_temp": temp} self.set_light_state(light_state)
def set_brightness(self, brightness: int, period: int = -1) -> None: """Set the current brightness of the device. :param int brightness: brightness in percent :param int period: transition period in milliseconds """ if not self.is_dimmable: # pragma: no cover raise SmartDeviceException("Bulb is not dimmable.") self._raise_for_invalid_brightness(brightness) if period < 0: period = self._transition_period light_state = {"brightness": brightness, "transition_period": period} self.set_light_state(light_state)
async def test_configuring_devices_from_multiple_sources(hass): """Test static and discover devices are not duplicated.""" with patch( "homeassistant.components.tplink.common.Discover.discover" ) as discover, patch( "homeassistant.components.tplink.common.SmartDevice._query_helper" ), patch( "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup" ): discover_device_fail = SmartPlug("123.123.123.123") discover_device_fail.get_sysinfo = MagicMock( side_effect=SmartDeviceException()) discover.return_value = { "123.123.123.1": SmartBulb("123.123.123.1"), "123.123.123.2": SmartPlug("123.123.123.2"), "123.123.123.3": SmartBulb("123.123.123.3"), "123.123.123.4": SmartPlug("123.123.123.4"), "123.123.123.123": discover_device_fail, "123.123.123.124": UnknownSmartDevice("123.123.123.124"), } await async_setup_component( hass, tplink.DOMAIN, { tplink.DOMAIN: { CONF_LIGHT: [{ CONF_HOST: "123.123.123.1" }], CONF_SWITCH: [{ CONF_HOST: "123.123.123.2" }], CONF_DIMMER: [{ CONF_HOST: "123.123.123.22" }], } }, ) await hass.async_block_till_done() assert len(discover.mock_calls) == 1 assert len(hass.data[tplink.DOMAIN][CONF_LIGHT]) == 3 assert len(hass.data[tplink.DOMAIN][CONF_SWITCH]) == 2
async def test_async_add_entities_retry(hass: HomeAssistantType): """Test interval callback.""" async_add_entities_callback = MagicMock() # The objects that will be passed to async_add_entities_callback. objects = [ "Object 1", "Object 2", "Object 3", "Object 4", ] # For each call to async_add_entities_callback, the following side effects # will be triggered in order. This set of side effects accuratley simulates # 3 attempts to add all entities while also handling several return types. # To help understand what's going on, a comment exists describing what the # object list looks like throughout the iterations. callback_side_effects = [ # OB1, OB2, OB3, OB4 False, False, True, # Object 3 False, # OB1, OB2, OB4 True, # Object 1 SmartDeviceException("My error"), False, # OB2, OB4 True, # Object 2 True, # Object 4 ] callback = MagicMock(side_effect=callback_side_effects) await async_add_entities_retry(hass, async_add_entities_callback, objects, callback, interval=timedelta(milliseconds=100)) await hass.async_block_till_done() assert callback.call_count == len(callback_side_effects)
def set_color_temp(self, temp: int, period: int = -1) -> None: """Set the color temperature of the device. :param int temp: The new color temperature, in Kelvin :param int period: transition period in milliseconds """ if not self.is_variable_color_temp: raise SmartDeviceException("Bulb does not support colortemp.") if (temp < self.valid_temperature_range[0] or temp > self.valid_temperature_range[1]): raise ValueError("Temperature should be between {} " "and {}".format(*self.valid_temperature_range)) if period < 0: period = self._transition_period light_state = {"color_temp": temp, "transition_period": period} self.set_light_state(light_state)
def hsv(self) -> Tuple[int, int, int]: """Return the current HSV state of the bulb. :return: hue, saturation and value (degrees, %, %) :rtype: tuple """ if not self.is_color: raise SmartDeviceException("Bulb does not support color.") light_state = self.get_light_state() if not self.is_on: hue = light_state["dft_on_state"]["hue"] saturation = light_state["dft_on_state"]["saturation"] value = light_state["dft_on_state"]["brightness"] else: hue = light_state["hue"] saturation = light_state["saturation"] value = light_state["brightness"] return hue, saturation, value
def set_brightness(self, value: int): """Set the new dimmer brightness level. Note: When setting brightness, if the light is not already on, it will be turned on automatically. :param value: integer between 1 and 100 """ if not self.is_dimmable: raise SmartDeviceException("Device is not dimmable.") if not isinstance(value, int): raise ValueError("Brightness must be integer, " "not of %s.", type(value)) elif 0 < value <= 100: self.turn_on() self._query_helper("smartlife.iot.dimmer", "set_brightness", {"brightness": value}) else: raise ValueError("Brightness value %s is not valid." % value)
async def test_not_ready(hass: HomeAssistant): """Test for not ready when configured devices are not available.""" config = { tplink.DOMAIN: { CONF_DISCOVERY: False, CONF_SWITCH: [{ CONF_HOST: "321.321.321.321" }], } } with patch( "homeassistant.components.tplink.common.Discover.discover" ), patch( "homeassistant.components.tplink.get_static_devices" ) as get_static_devices, patch( "homeassistant.components.tplink.common.SmartDevice._query_helper" ), patch( "homeassistant.components.tplink.light.async_setup_entry", return_value=mock_coro(True), ), patch( "homeassistant.components.tplink.switch.async_setup_entry", return_value=mock_coro(True), ), patch("homeassistant.components.tplink.common.SmartPlug.is_dimmable", False): switch = SmartPlug("321.321.321.321") switch.get_sysinfo = MagicMock(side_effect=SmartDeviceException()) get_static_devices.return_value = SmartDevices([], [switch]) await async_setup_component(hass, tplink.DOMAIN, config) await hass.async_block_till_done() entries = hass.config_entries.async_entries(tplink.DOMAIN) assert len(entries) == 1 assert entries[0].state is config_entries.ConfigEntryState.SETUP_RETRY
async def test_not_available_at_startup(hass: HomeAssistant): """Test when configured devices are not available.""" config = { tplink.DOMAIN: { CONF_DISCOVERY: False, CONF_SWITCH: [{ CONF_HOST: "321.321.321.321" }], } } with patch( "homeassistant.components.tplink.common.Discover.discover" ), patch( "homeassistant.components.tplink.get_static_devices" ) as get_static_devices, patch( "homeassistant.components.tplink.common.SmartDevice._query_helper" ), patch( "homeassistant.components.tplink.light.async_setup_entry", return_value=mock_coro(True), ), patch("homeassistant.components.tplink.common.SmartPlug.is_dimmable", False): switch = SmartPlug("321.321.321.321") switch.get_sysinfo = MagicMock(side_effect=SmartDeviceException()) get_static_devices.return_value = SmartDevices([], [switch]) # run setup while device unreachable await async_setup_component(hass, tplink.DOMAIN, config) await hass.async_block_till_done() entries = hass.config_entries.async_entries(tplink.DOMAIN) assert len(entries) == 1 assert entries[0].state is config_entries.ConfigEntryState.LOADED entities = hass.states.async_entity_ids(SWITCH_DOMAIN) assert len(entities) == 0 # retrying with still unreachable device async_fire_time_changed(hass, dt.utcnow() + UNAVAILABLE_RETRY_DELAY) await hass.async_block_till_done() entries = hass.config_entries.async_entries(tplink.DOMAIN) assert len(entries) == 1 assert entries[0].state is config_entries.ConfigEntryState.LOADED entities = hass.states.async_entity_ids(SWITCH_DOMAIN) assert len(entities) == 0 # retrying with now reachable device switch.get_sysinfo = MagicMock( return_value=SMARTPLUG_HS100_DATA["sysinfo"]) async_fire_time_changed(hass, dt.utcnow() + UNAVAILABLE_RETRY_DELAY) await hass.async_block_till_done() entries = hass.config_entries.async_entries(tplink.DOMAIN) assert len(entries) == 1 assert entries[0].state is config_entries.ConfigEntryState.LOADED entities = hass.states.async_entity_ids(SWITCH_DOMAIN) assert len(entities) == 1