From 49ecca9cb9b81dfa0a49bc9d2834c768e8669d98 Mon Sep 17 00:00:00 2001 From: "Patrick T.C" <124277+ptc@users.noreply.github.com> Date: Mon, 11 Feb 2019 19:59:34 +0100 Subject: [PATCH] Set cover level using emulated_hue (#19594) * set cover level using emulated_hue * changed mapping for service turn_on/off for cover. * removed whitespace for the sake of hound * using const for domains instead of hardcoded strings. * change length of lines for the sake of hound * fixed under-intended line * changed intent for the sake of hound --- .../components/emulated_hue/hue_api.py | 45 +++++-- tests/components/emulated_hue/test_hue_api.py | 120 +++++++++++++++++- 2 files changed, 151 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 174ee38715a..fdf1d35201e 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -19,6 +19,16 @@ from homeassistant.components.fan import ( ATTR_SPEED, SUPPORT_SET_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH ) + +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, ATTR_POSITION, SERVICE_SET_COVER_POSITION, + SUPPORT_SET_POSITION +) + +from homeassistant.components import ( + cover, fan, media_player, light, script, scene +) + from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.const import KEY_REAL_IP from homeassistant.util.network import is_local @@ -239,13 +249,13 @@ class HueOneLightChangeView(HomeAssistantView): # Make sure the entity actually supports brightness entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if entity.domain == "light": + if entity.domain == light.DOMAIN: if entity_features & SUPPORT_BRIGHTNESS: if brightness is not None: data[ATTR_BRIGHTNESS] = brightness # If the requested entity is a script add some variables - elif entity.domain == "script": + elif entity.domain == script.DOMAIN: data['variables'] = { 'requested_state': STATE_ON if result else STATE_OFF } @@ -254,7 +264,7 @@ class HueOneLightChangeView(HomeAssistantView): data['variables']['requested_level'] = brightness # If the requested entity is a media player, convert to volume - elif entity.domain == "media_player": + elif entity.domain == media_player.DOMAIN: if entity_features & SUPPORT_VOLUME_SET: if brightness is not None: turn_on_needed = True @@ -264,15 +274,21 @@ class HueOneLightChangeView(HomeAssistantView): data[ATTR_MEDIA_VOLUME_LEVEL] = brightness / 100.0 # If the requested entity is a cover, convert to open_cover/close_cover - elif entity.domain == "cover": + elif entity.domain == cover.DOMAIN: domain = entity.domain if service == SERVICE_TURN_ON: service = SERVICE_OPEN_COVER else: service = SERVICE_CLOSE_COVER + if entity_features & SUPPORT_SET_POSITION: + if brightness is not None: + domain = entity.domain + service = SERVICE_SET_COVER_POSITION + data[ATTR_POSITION] = brightness + # If the requested entity is a fan, convert to speed - elif entity.domain == "fan": + elif entity.domain == fan.DOMAIN: if entity_features & SUPPORT_SET_SPEED: if brightness is not None: domain = entity.domain @@ -344,19 +360,19 @@ def parse_hue_api_put_light_body(request_json, entity): # Make sure the entity actually supports brightness entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if entity.domain == "light": + if entity.domain == light.DOMAIN: if entity_features & SUPPORT_BRIGHTNESS: report_brightness = True result = (brightness > 0) - elif entity.domain == "scene": + elif entity.domain == scene.DOMAIN: brightness = None report_brightness = False result = True - elif (entity.domain == "script" or - entity.domain == "media_player" or - entity.domain == "fan"): + elif entity.domain in [ + script.DOMAIN, media_player.DOMAIN, + fan.DOMAIN, cover.DOMAIN]: # Convert 0-255 to 0-100 level = brightness / 255 * 100 brightness = round(level) @@ -378,16 +394,16 @@ def get_entity_state(config, entity): # Make sure the entity actually supports brightness entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if entity.domain == "light": + if entity.domain == light.DOMAIN: if entity_features & SUPPORT_BRIGHTNESS: pass - elif entity.domain == "media_player": + elif entity.domain == media_player.DOMAIN: level = entity.attributes.get( ATTR_MEDIA_VOLUME_LEVEL, 1.0 if final_state else 0.0) # Convert 0.0-1.0 to 0-255 final_brightness = round(min(1.0, level) * 255) - elif entity.domain == "fan": + elif entity.domain == fan.DOMAIN: speed = entity.attributes.get(ATTR_SPEED, 0) # Convert 0.0-1.0 to 0-255 final_brightness = 0 @@ -397,6 +413,9 @@ def get_entity_state(config, entity): final_brightness = 170 elif speed == SPEED_HIGH: final_brightness = 255 + elif entity.domain == cover.DOMAIN: + level = entity.attributes.get(ATTR_CURRENT_POSITION, 0) + final_brightness = round(level / 100 * 255) else: final_state, final_brightness = cached_state # Make sure brightness is valid diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 70fe894debf..5e3d6d1019c 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -11,13 +11,17 @@ from tests.common import get_test_instance_port from homeassistant import core, const, setup import homeassistant.components as core_components from homeassistant.components import ( - fan, http, light, script, emulated_hue, media_player) + fan, http, light, script, emulated_hue, media_player, cover) from homeassistant.components.emulated_hue import Config from homeassistant.components.emulated_hue.hue_api import ( HUE_API_STATE_ON, HUE_API_STATE_BRI, HueUsernameView, HueOneLightStateView, HueAllLightsStateView, HueOneLightChangeView, HueAllGroupsStateView) from homeassistant.const import STATE_ON, STATE_OFF +import homeassistant.util.dt as dt_util +from datetime import timedelta +from tests.common import async_fire_time_changed + HTTP_SERVER_PORT = get_test_instance_port() BRIDGE_SERVER_PORT = get_test_instance_port() @@ -91,6 +95,15 @@ def hass_hue(loop, hass): ] })) + loop.run_until_complete( + setup.async_setup_component(hass, cover.DOMAIN, { + 'cover': [ + { + 'platform': 'demo', + } + ] + })) + # Kitchen light is explicitly excluded from being exposed kitchen_light_entity = hass.states.get('light.kitchen_lights') attrs = dict(kitchen_light_entity.attributes) @@ -115,6 +128,14 @@ def hass_hue(loop, hass): script_entity.entity_id, script_entity.state, attributes=attrs ) + # Expose cover + cover_entity = hass.states.get('cover.living_room_window') + attrs = dict(cover_entity.attributes) + attrs[emulated_hue.ATTR_EMULATED_HUE_HIDDEN] = False + hass.states.async_set( + cover_entity.entity_id, cover_entity.state, attributes=attrs + ) + return hass @@ -127,7 +148,11 @@ def hue_client(loop, hass_hue, aiohttp_client): emulated_hue.CONF_ENTITIES: { 'light.bed_light': { emulated_hue.CONF_ENTITY_HIDDEN: True + }, + 'cover.living_room_window': { + emulated_hue.CONF_ENTITY_HIDDEN: False } + } }) @@ -163,6 +188,7 @@ def test_discover_lights(hue_client): assert 'media_player.lounge_room' in devices assert 'fan.living_room_fan' in devices assert 'fan.ceiling_fan' not in devices + assert 'cover.living_room_window' in devices @asyncio.coroutine @@ -317,6 +343,98 @@ def test_put_light_state_media_player(hass_hue, hue_client): assert walkman.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL] == level +async def test_close_cover(hass_hue, hue_client): + """Test opening cover .""" + COVER_ID = "cover.living_room_window" + # Turn the office light off first + await hass_hue.services.async_call( + cover.DOMAIN, const.SERVICE_CLOSE_COVER, + {const.ATTR_ENTITY_ID: COVER_ID}, + blocking=True) + + cover_test = hass_hue.states.get(COVER_ID) + assert cover_test.state == 'closing' + + for _ in range(7): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass_hue, future) + await hass_hue.async_block_till_done() + + cover_test = hass_hue.states.get(COVER_ID) + assert cover_test.state == 'closed' + + # Go through the API to turn it on + cover_result = await perform_put_light_state( + hass_hue, hue_client, + COVER_ID, True, 100) + + assert cover_result.status == 200 + assert 'application/json' in cover_result.headers['content-type'] + + for _ in range(7): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass_hue, future) + await hass_hue.async_block_till_done() + + cover_result_json = await cover_result.json() + + assert len(cover_result_json) == 2 + + # Check to make sure the state changed + cover_test_2 = hass_hue.states.get(COVER_ID) + assert cover_test_2.state == 'open' + + +async def test_set_position_cover(hass_hue, hue_client): + """Test setting postion cover .""" + COVER_ID = "cover.living_room_window" + # Turn the office light off first + await hass_hue.services.async_call( + cover.DOMAIN, const.SERVICE_CLOSE_COVER, + {const.ATTR_ENTITY_ID: COVER_ID}, + blocking=True) + + cover_test = hass_hue.states.get(COVER_ID) + assert cover_test.state == 'closing' + + for _ in range(7): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass_hue, future) + await hass_hue.async_block_till_done() + + cover_test = hass_hue.states.get(COVER_ID) + assert cover_test.state == 'closed' + + level = 20 + brightness = round(level/100*255) + + # Go through the API to open + cover_result = await perform_put_light_state( + hass_hue, hue_client, + COVER_ID, False, brightness) + + assert cover_result.status == 200 + assert 'application/json' in cover_result.headers['content-type'] + + cover_result_json = await cover_result.json() + + assert len(cover_result_json) == 2 + assert True, cover_result_json[0]['success'][ + '/lights/cover.living_room_window/state/on'] + assert cover_result_json[1]['success'][ + '/lights/cover.living_room_window/state/bri'] == level + + for _ in range(100): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass_hue, future) + await hass_hue.async_block_till_done() + + # Check to make sure the state changed + cover_test_2 = hass_hue.states.get(COVER_ID) + assert cover_test_2.state == 'open' + assert cover_test_2.attributes.get('current_position') == level + + @asyncio.coroutine def test_put_light_state_fan(hass_hue, hue_client): """Test turning on fan and setting speed."""