Skip to content

Commit

Permalink
Set cover level using emulated_hue (#19594)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ptc authored and cgarwood committed Feb 11, 2019
1 parent 788f798 commit 49ecca9
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 14 deletions.
45 changes: 32 additions & 13 deletions homeassistant/components/emulated_hue/hue_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
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
Expand Down Expand Up @@ -239,13 +249,13 @@ async def put(self, request, username, entity_number):
# 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
}
Expand All @@ -254,7 +264,7 @@ async def put(self, request, username, entity_number):
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
Expand All @@ -264,15 +274,21 @@ async def put(self, request, username, entity_number):
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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
120 changes: 119 additions & 1 deletion tests/components/emulated_hue/test_hue_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
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()

Expand Down Expand Up @@ -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)
Expand All @@ -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


Expand All @@ -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
}

}
})

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down

0 comments on commit 49ecca9

Please sign in to comment.