Add color support to emulated hue (#19590)
* [Hue API] Add color support Adds color support to the hue api (specifically hue/saturation). Switched from using a tuple to convey state internally to using a dict to make adding new fields easier. * [Hue API] Add unit test for color supportpull/22646/head
parent
8a86a79040
commit
471afb4702
|
@ -4,42 +4,42 @@ import logging
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from homeassistant import core
|
from homeassistant import core
|
||||||
from homeassistant.const import (
|
from homeassistant.components import (
|
||||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
climate, cover, fan, light, media_player, scene, script)
|
||||||
SERVICE_VOLUME_SET, SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, STATE_ON,
|
|
||||||
STATE_OFF, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, ATTR_SUPPORTED_FEATURES
|
|
||||||
)
|
|
||||||
from homeassistant.components.light import (
|
|
||||||
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS
|
|
||||||
)
|
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
SERVICE_SET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE
|
SERVICE_SET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE)
|
||||||
)
|
|
||||||
from homeassistant.components.media_player.const import (
|
|
||||||
ATTR_MEDIA_VOLUME_LEVEL, SUPPORT_VOLUME_SET,
|
|
||||||
)
|
|
||||||
from homeassistant.components.fan import (
|
|
||||||
ATTR_SPEED, SUPPORT_SET_SPEED, SPEED_OFF, SPEED_LOW,
|
|
||||||
SPEED_MEDIUM, SPEED_HIGH
|
|
||||||
)
|
|
||||||
|
|
||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
ATTR_CURRENT_POSITION, ATTR_POSITION, SERVICE_SET_COVER_POSITION,
|
ATTR_CURRENT_POSITION, ATTR_POSITION, SERVICE_SET_COVER_POSITION,
|
||||||
SUPPORT_SET_POSITION
|
SUPPORT_SET_POSITION)
|
||||||
)
|
from homeassistant.components.fan import (
|
||||||
|
ATTR_SPEED, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF,
|
||||||
from homeassistant.components import (
|
SUPPORT_SET_SPEED)
|
||||||
climate, cover, fan, media_player, light, script, scene
|
|
||||||
)
|
|
||||||
|
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.components.http.const import KEY_REAL_IP
|
from homeassistant.components.http.const import KEY_REAL_IP
|
||||||
|
from homeassistant.components.light import (
|
||||||
|
ATTR_BRIGHTNESS, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR)
|
||||||
|
from homeassistant.components.media_player.const import (
|
||||||
|
ATTR_MEDIA_VOLUME_LEVEL, SUPPORT_VOLUME_SET)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE,
|
||||||
|
HTTP_BAD_REQUEST, HTTP_NOT_FOUND, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER,
|
||||||
|
SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_SET, STATE_OFF, STATE_ON)
|
||||||
from homeassistant.util.network import is_local
|
from homeassistant.util.network import is_local
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
HUE_API_STATE_ON = 'on'
|
HUE_API_STATE_ON = 'on'
|
||||||
HUE_API_STATE_BRI = 'bri'
|
HUE_API_STATE_BRI = 'bri'
|
||||||
|
HUE_API_STATE_HUE = 'hue'
|
||||||
|
HUE_API_STATE_SAT = 'sat'
|
||||||
|
|
||||||
|
HUE_API_STATE_HUE_MAX = 65535.0
|
||||||
|
HUE_API_STATE_SAT_MAX = 254.0
|
||||||
|
HUE_API_STATE_BRI_MAX = 255.0
|
||||||
|
|
||||||
|
STATE_BRIGHTNESS = HUE_API_STATE_BRI
|
||||||
|
STATE_HUE = HUE_API_STATE_HUE
|
||||||
|
STATE_SATURATION = HUE_API_STATE_SAT
|
||||||
|
|
||||||
|
|
||||||
class HueUsernameView(HomeAssistantView):
|
class HueUsernameView(HomeAssistantView):
|
||||||
|
@ -140,11 +140,11 @@ class HueAllLightsStateView(HomeAssistantView):
|
||||||
|
|
||||||
for entity in hass.states.async_all():
|
for entity in hass.states.async_all():
|
||||||
if self.config.is_entity_exposed(entity):
|
if self.config.is_entity_exposed(entity):
|
||||||
state, brightness = get_entity_state(self.config, entity)
|
state = get_entity_state(self.config, entity)
|
||||||
|
|
||||||
number = self.config.entity_id_to_number(entity.entity_id)
|
number = self.config.entity_id_to_number(entity.entity_id)
|
||||||
json_response[number] = entity_to_json(
|
json_response[number] = entity_to_json(self.config,
|
||||||
self.config, entity, state, brightness)
|
entity, state)
|
||||||
|
|
||||||
return self.json(json_response)
|
return self.json(json_response)
|
||||||
|
|
||||||
|
@ -179,9 +179,9 @@ class HueOneLightStateView(HomeAssistantView):
|
||||||
_LOGGER.error('Entity not exposed: %s', entity_id)
|
_LOGGER.error('Entity not exposed: %s', entity_id)
|
||||||
return web.Response(text="Entity not exposed", status=404)
|
return web.Response(text="Entity not exposed", status=404)
|
||||||
|
|
||||||
state, brightness = get_entity_state(self.config, entity)
|
state = get_entity_state(self.config, entity)
|
||||||
|
|
||||||
json_response = entity_to_json(self.config, entity, state, brightness)
|
json_response = entity_to_json(self.config, entity, state)
|
||||||
|
|
||||||
return self.json(json_response)
|
return self.json(json_response)
|
||||||
|
|
||||||
|
@ -234,8 +234,6 @@ class HueOneLightChangeView(HomeAssistantView):
|
||||||
_LOGGER.error('Unable to parse data: %s', request_json)
|
_LOGGER.error('Unable to parse data: %s', request_json)
|
||||||
return web.Response(text="Bad request", status=400)
|
return web.Response(text="Bad request", status=400)
|
||||||
|
|
||||||
result, brightness = parsed
|
|
||||||
|
|
||||||
# Choose general HA domain
|
# Choose general HA domain
|
||||||
domain = core.DOMAIN
|
domain = core.DOMAIN
|
||||||
|
|
||||||
|
@ -243,7 +241,7 @@ class HueOneLightChangeView(HomeAssistantView):
|
||||||
turn_on_needed = False
|
turn_on_needed = False
|
||||||
|
|
||||||
# Convert the resulting "on" status into the service we need to call
|
# Convert the resulting "on" status into the service we need to call
|
||||||
service = SERVICE_TURN_ON if result else SERVICE_TURN_OFF
|
service = SERVICE_TURN_ON if parsed[STATE_ON] else SERVICE_TURN_OFF
|
||||||
|
|
||||||
# Construct what we need to send to the service
|
# Construct what we need to send to the service
|
||||||
data = {ATTR_ENTITY_ID: entity_id}
|
data = {ATTR_ENTITY_ID: entity_id}
|
||||||
|
@ -252,18 +250,32 @@ class HueOneLightChangeView(HomeAssistantView):
|
||||||
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
|
||||||
if entity.domain == light.DOMAIN:
|
if entity.domain == light.DOMAIN:
|
||||||
if entity_features & SUPPORT_BRIGHTNESS:
|
if parsed[STATE_ON]:
|
||||||
if brightness is not None:
|
if entity_features & SUPPORT_BRIGHTNESS:
|
||||||
data[ATTR_BRIGHTNESS] = brightness
|
if parsed[STATE_BRIGHTNESS] is not None:
|
||||||
|
data[ATTR_BRIGHTNESS] = parsed[STATE_BRIGHTNESS]
|
||||||
|
if entity_features & SUPPORT_COLOR:
|
||||||
|
if parsed[STATE_HUE] is not None:
|
||||||
|
if parsed[STATE_SATURATION]:
|
||||||
|
sat = parsed[STATE_SATURATION]
|
||||||
|
else:
|
||||||
|
sat = 0
|
||||||
|
hue = parsed[STATE_HUE]
|
||||||
|
|
||||||
|
# Convert hs values to hass hs values
|
||||||
|
sat = int((sat / HUE_API_STATE_SAT_MAX) * 100)
|
||||||
|
hue = int((hue / HUE_API_STATE_HUE_MAX) * 360)
|
||||||
|
|
||||||
|
data[ATTR_HS_COLOR] = (hue, sat)
|
||||||
|
|
||||||
# If the requested entity is a script add some variables
|
# If the requested entity is a script add some variables
|
||||||
elif entity.domain == script.DOMAIN:
|
elif entity.domain == script.DOMAIN:
|
||||||
data['variables'] = {
|
data['variables'] = {
|
||||||
'requested_state': STATE_ON if result else STATE_OFF
|
'requested_state': STATE_ON if parsed[STATE_ON] else STATE_OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
if brightness is not None:
|
if parsed[STATE_BRIGHTNESS] is not None:
|
||||||
data['variables']['requested_level'] = brightness
|
data['variables']['requested_level'] = parsed[STATE_BRIGHTNESS]
|
||||||
|
|
||||||
# If the requested entity is a climate, set the temperature
|
# If the requested entity is a climate, set the temperature
|
||||||
elif entity.domain == climate.DOMAIN:
|
elif entity.domain == climate.DOMAIN:
|
||||||
|
@ -272,20 +284,21 @@ class HueOneLightChangeView(HomeAssistantView):
|
||||||
service = None
|
service = None
|
||||||
|
|
||||||
if entity_features & SUPPORT_TARGET_TEMPERATURE:
|
if entity_features & SUPPORT_TARGET_TEMPERATURE:
|
||||||
if brightness is not None:
|
if parsed[STATE_BRIGHTNESS] is not None:
|
||||||
domain = entity.domain
|
domain = entity.domain
|
||||||
service = SERVICE_SET_TEMPERATURE
|
service = SERVICE_SET_TEMPERATURE
|
||||||
data[ATTR_TEMPERATURE] = brightness
|
data[ATTR_TEMPERATURE] = parsed[STATE_BRIGHTNESS]
|
||||||
|
|
||||||
# If the requested entity is a media player, convert to volume
|
# If the requested entity is a media player, convert to volume
|
||||||
elif entity.domain == media_player.DOMAIN:
|
elif entity.domain == media_player.DOMAIN:
|
||||||
if entity_features & SUPPORT_VOLUME_SET:
|
if entity_features & SUPPORT_VOLUME_SET:
|
||||||
if brightness is not None:
|
if parsed[STATE_BRIGHTNESS] is not None:
|
||||||
turn_on_needed = True
|
turn_on_needed = True
|
||||||
domain = entity.domain
|
domain = entity.domain
|
||||||
service = SERVICE_VOLUME_SET
|
service = SERVICE_VOLUME_SET
|
||||||
# Convert 0-100 to 0.0-1.0
|
# Convert 0-100 to 0.0-1.0
|
||||||
data[ATTR_MEDIA_VOLUME_LEVEL] = brightness / 100.0
|
data[ATTR_MEDIA_VOLUME_LEVEL] = \
|
||||||
|
parsed[STATE_BRIGHTNESS] / 100.0
|
||||||
|
|
||||||
# If the requested entity is a cover, convert to open_cover/close_cover
|
# If the requested entity is a cover, convert to open_cover/close_cover
|
||||||
elif entity.domain == cover.DOMAIN:
|
elif entity.domain == cover.DOMAIN:
|
||||||
|
@ -296,17 +309,18 @@ class HueOneLightChangeView(HomeAssistantView):
|
||||||
service = SERVICE_CLOSE_COVER
|
service = SERVICE_CLOSE_COVER
|
||||||
|
|
||||||
if entity_features & SUPPORT_SET_POSITION:
|
if entity_features & SUPPORT_SET_POSITION:
|
||||||
if brightness is not None:
|
if parsed[STATE_BRIGHTNESS] is not None:
|
||||||
domain = entity.domain
|
domain = entity.domain
|
||||||
service = SERVICE_SET_COVER_POSITION
|
service = SERVICE_SET_COVER_POSITION
|
||||||
data[ATTR_POSITION] = brightness
|
data[ATTR_POSITION] = parsed[STATE_BRIGHTNESS]
|
||||||
|
|
||||||
# If the requested entity is a fan, convert to speed
|
# If the requested entity is a fan, convert to speed
|
||||||
elif entity.domain == fan.DOMAIN:
|
elif entity.domain == fan.DOMAIN:
|
||||||
if entity_features & SUPPORT_SET_SPEED:
|
if entity_features & SUPPORT_SET_SPEED:
|
||||||
if brightness is not None:
|
if parsed[STATE_BRIGHTNESS] is not None:
|
||||||
domain = entity.domain
|
domain = entity.domain
|
||||||
# Convert 0-100 to a fan speed
|
# Convert 0-100 to a fan speed
|
||||||
|
brightness = parsed[STATE_BRIGHTNESS]
|
||||||
if brightness == 0:
|
if brightness == 0:
|
||||||
data[ATTR_SPEED] = SPEED_OFF
|
data[ATTR_SPEED] = SPEED_OFF
|
||||||
elif 0 < brightness <= 33.3:
|
elif 0 < brightness <= 33.3:
|
||||||
|
@ -325,7 +339,7 @@ class HueOneLightChangeView(HomeAssistantView):
|
||||||
# they'll map to "on". Thus, instead of reporting its actual
|
# they'll map to "on". Thus, instead of reporting its actual
|
||||||
# status, we report what Alexa will want to see, which is the same
|
# status, we report what Alexa will want to see, which is the same
|
||||||
# as the actual requested command.
|
# as the actual requested command.
|
||||||
config.cached_states[entity_id] = (result, brightness)
|
config.cached_states[entity_id] = parsed
|
||||||
|
|
||||||
# Separate call to turn on needed
|
# Separate call to turn on needed
|
||||||
if turn_on_needed:
|
if turn_on_needed:
|
||||||
|
@ -338,73 +352,120 @@ class HueOneLightChangeView(HomeAssistantView):
|
||||||
domain, service, data, blocking=True))
|
domain, service, data, blocking=True))
|
||||||
|
|
||||||
json_response = \
|
json_response = \
|
||||||
[create_hue_success_response(entity_id, HUE_API_STATE_ON, result)]
|
[create_hue_success_response(
|
||||||
|
entity_id, HUE_API_STATE_ON, parsed[STATE_ON])]
|
||||||
|
|
||||||
if brightness is not None:
|
if parsed[STATE_BRIGHTNESS] is not None:
|
||||||
json_response.append(create_hue_success_response(
|
json_response.append(create_hue_success_response(
|
||||||
entity_id, HUE_API_STATE_BRI, brightness))
|
entity_id, HUE_API_STATE_BRI, parsed[STATE_BRIGHTNESS]))
|
||||||
|
if parsed[STATE_HUE] is not None:
|
||||||
|
json_response.append(create_hue_success_response(
|
||||||
|
entity_id, HUE_API_STATE_HUE, parsed[STATE_HUE]))
|
||||||
|
if parsed[STATE_SATURATION] is not None:
|
||||||
|
json_response.append(create_hue_success_response(
|
||||||
|
entity_id, HUE_API_STATE_SAT, parsed[STATE_SATURATION]))
|
||||||
|
|
||||||
return self.json(json_response)
|
return self.json(json_response)
|
||||||
|
|
||||||
|
|
||||||
def parse_hue_api_put_light_body(request_json, entity):
|
def parse_hue_api_put_light_body(request_json, entity):
|
||||||
"""Parse the body of a request to change the state of a light."""
|
"""Parse the body of a request to change the state of a light."""
|
||||||
|
data = {
|
||||||
|
STATE_BRIGHTNESS: None,
|
||||||
|
STATE_HUE: None,
|
||||||
|
STATE_ON: False,
|
||||||
|
STATE_SATURATION: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure the entity actually supports brightness
|
||||||
|
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
|
||||||
if HUE_API_STATE_ON in request_json:
|
if HUE_API_STATE_ON in request_json:
|
||||||
if not isinstance(request_json[HUE_API_STATE_ON], bool):
|
if not isinstance(request_json[HUE_API_STATE_ON], bool):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if request_json['on']:
|
if request_json[HUE_API_STATE_ON]:
|
||||||
# Echo requested device be turned on
|
# Echo requested device be turned on
|
||||||
brightness = None
|
data[STATE_BRIGHTNESS] = None
|
||||||
report_brightness = False
|
data[STATE_ON] = True
|
||||||
result = True
|
|
||||||
else:
|
else:
|
||||||
# Echo requested device be turned off
|
# Echo requested device be turned off
|
||||||
brightness = None
|
data[STATE_BRIGHTNESS] = None
|
||||||
report_brightness = False
|
data[STATE_ON] = False
|
||||||
result = False
|
|
||||||
|
if HUE_API_STATE_HUE in request_json:
|
||||||
|
try:
|
||||||
|
# Clamp brightness from 0 to 65535
|
||||||
|
data[STATE_HUE] = \
|
||||||
|
max(0, min(int(request_json[HUE_API_STATE_HUE]),
|
||||||
|
HUE_API_STATE_HUE_MAX))
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if HUE_API_STATE_SAT in request_json:
|
||||||
|
try:
|
||||||
|
# Clamp saturation from 0 to 254
|
||||||
|
data[STATE_SATURATION] = \
|
||||||
|
max(0, min(int(request_json[HUE_API_STATE_SAT]),
|
||||||
|
HUE_API_STATE_SAT_MAX))
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
if HUE_API_STATE_BRI in request_json:
|
if HUE_API_STATE_BRI in request_json:
|
||||||
try:
|
try:
|
||||||
# Clamp brightness from 0 to 255
|
# Clamp brightness from 0 to 255
|
||||||
brightness = \
|
data[STATE_BRIGHTNESS] = \
|
||||||
max(0, min(int(request_json[HUE_API_STATE_BRI]), 255))
|
max(0, min(int(request_json[HUE_API_STATE_BRI]),
|
||||||
|
HUE_API_STATE_BRI_MAX))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Make sure the entity actually supports brightness
|
|
||||||
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
|
||||||
|
|
||||||
if entity.domain == light.DOMAIN:
|
if entity.domain == light.DOMAIN:
|
||||||
if entity_features & SUPPORT_BRIGHTNESS:
|
data[STATE_ON] = (data[STATE_BRIGHTNESS] > 0)
|
||||||
report_brightness = True
|
if not entity_features & SUPPORT_BRIGHTNESS:
|
||||||
result = (brightness > 0)
|
data[STATE_BRIGHTNESS] = None
|
||||||
|
|
||||||
elif entity.domain == scene.DOMAIN:
|
elif entity.domain == scene.DOMAIN:
|
||||||
brightness = None
|
data[STATE_BRIGHTNESS] = None
|
||||||
report_brightness = False
|
data[STATE_ON] = True
|
||||||
result = True
|
|
||||||
|
|
||||||
elif entity.domain in [
|
elif entity.domain in [
|
||||||
script.DOMAIN, media_player.DOMAIN,
|
script.DOMAIN, media_player.DOMAIN,
|
||||||
fan.DOMAIN, cover.DOMAIN, climate.DOMAIN]:
|
fan.DOMAIN, cover.DOMAIN, climate.DOMAIN]:
|
||||||
# Convert 0-255 to 0-100
|
# Convert 0-255 to 0-100
|
||||||
level = brightness / 255 * 100
|
level = (data[STATE_BRIGHTNESS] / HUE_API_STATE_BRI_MAX) * 100
|
||||||
brightness = round(level)
|
data[STATE_BRIGHTNESS] = round(level)
|
||||||
report_brightness = True
|
data[STATE_ON] = True
|
||||||
result = True
|
|
||||||
|
|
||||||
return (result, brightness) if report_brightness else (result, None)
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_entity_state(config, entity):
|
def get_entity_state(config, entity):
|
||||||
"""Retrieve and convert state and brightness values for an entity."""
|
"""Retrieve and convert state and brightness values for an entity."""
|
||||||
cached_state = config.cached_states.get(entity.entity_id, None)
|
cached_state = config.cached_states.get(entity.entity_id, None)
|
||||||
|
data = {
|
||||||
|
STATE_BRIGHTNESS: None,
|
||||||
|
STATE_HUE: None,
|
||||||
|
STATE_ON: False,
|
||||||
|
STATE_SATURATION: None
|
||||||
|
}
|
||||||
|
|
||||||
if cached_state is None:
|
if cached_state is None:
|
||||||
final_state = entity.state != STATE_OFF
|
data[STATE_ON] = entity.state != STATE_OFF
|
||||||
final_brightness = entity.attributes.get(
|
if data[STATE_ON]:
|
||||||
ATTR_BRIGHTNESS, 255 if final_state else 0)
|
data[STATE_BRIGHTNESS] = entity.attributes.get(ATTR_BRIGHTNESS)
|
||||||
|
hue_sat = entity.attributes.get(ATTR_HS_COLOR, None)
|
||||||
|
if hue_sat is not None:
|
||||||
|
hue = hue_sat[0]
|
||||||
|
sat = hue_sat[1]
|
||||||
|
# convert hass hs values back to hue hs values
|
||||||
|
data[STATE_HUE] = int((hue / 360.0) * HUE_API_STATE_HUE_MAX)
|
||||||
|
data[STATE_SATURATION] = \
|
||||||
|
int((sat / 100.0) * HUE_API_STATE_SAT_MAX)
|
||||||
|
else:
|
||||||
|
data[STATE_BRIGHTNESS] = 0
|
||||||
|
data[STATE_HUE] = 0
|
||||||
|
data[STATE_SATURATION] = 0
|
||||||
|
|
||||||
# Make sure the entity actually supports brightness
|
# Make sure the entity actually supports brightness
|
||||||
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
@ -416,41 +477,53 @@ def get_entity_state(config, entity):
|
||||||
elif entity.domain == climate.DOMAIN:
|
elif entity.domain == climate.DOMAIN:
|
||||||
temperature = entity.attributes.get(ATTR_TEMPERATURE, 0)
|
temperature = entity.attributes.get(ATTR_TEMPERATURE, 0)
|
||||||
# Convert 0-100 to 0-255
|
# Convert 0-100 to 0-255
|
||||||
final_brightness = round(temperature * 255 / 100)
|
data[STATE_BRIGHTNESS] = round(temperature * 255 / 100)
|
||||||
elif entity.domain == media_player.DOMAIN:
|
elif entity.domain == media_player.DOMAIN:
|
||||||
level = entity.attributes.get(
|
level = entity.attributes.get(
|
||||||
ATTR_MEDIA_VOLUME_LEVEL, 1.0 if final_state else 0.0)
|
ATTR_MEDIA_VOLUME_LEVEL, 1.0 if data[STATE_ON] else 0.0)
|
||||||
# Convert 0.0-1.0 to 0-255
|
# Convert 0.0-1.0 to 0-255
|
||||||
final_brightness = round(min(1.0, level) * 255)
|
data[STATE_BRIGHTNESS] = \
|
||||||
|
round(min(1.0, level) * HUE_API_STATE_BRI_MAX)
|
||||||
elif entity.domain == fan.DOMAIN:
|
elif entity.domain == fan.DOMAIN:
|
||||||
speed = entity.attributes.get(ATTR_SPEED, 0)
|
speed = entity.attributes.get(ATTR_SPEED, 0)
|
||||||
# Convert 0.0-1.0 to 0-255
|
# Convert 0.0-1.0 to 0-255
|
||||||
final_brightness = 0
|
data[STATE_BRIGHTNESS] = 0
|
||||||
if speed == SPEED_LOW:
|
if speed == SPEED_LOW:
|
||||||
final_brightness = 85
|
data[STATE_BRIGHTNESS] = 85
|
||||||
elif speed == SPEED_MEDIUM:
|
elif speed == SPEED_MEDIUM:
|
||||||
final_brightness = 170
|
data[STATE_BRIGHTNESS] = 170
|
||||||
elif speed == SPEED_HIGH:
|
elif speed == SPEED_HIGH:
|
||||||
final_brightness = 255
|
data[STATE_BRIGHTNESS] = 255
|
||||||
elif entity.domain == cover.DOMAIN:
|
elif entity.domain == cover.DOMAIN:
|
||||||
level = entity.attributes.get(ATTR_CURRENT_POSITION, 0)
|
level = entity.attributes.get(ATTR_CURRENT_POSITION, 0)
|
||||||
final_brightness = round(level / 100 * 255)
|
data[STATE_BRIGHTNESS] = round(level / 100 * HUE_API_STATE_BRI_MAX)
|
||||||
else:
|
else:
|
||||||
final_state, final_brightness = cached_state
|
data = cached_state
|
||||||
# Make sure brightness is valid
|
# Make sure brightness is valid
|
||||||
if final_brightness is None:
|
if data[STATE_BRIGHTNESS] is None:
|
||||||
final_brightness = 255 if final_state else 0
|
data[STATE_BRIGHTNESS] = 255 if data[STATE_ON] else 0
|
||||||
|
# Make sure hue/saturation are valid
|
||||||
|
if (data[STATE_HUE] is None) or (data[STATE_SATURATION] is None):
|
||||||
|
data[STATE_HUE] = 0
|
||||||
|
data[STATE_SATURATION] = 0
|
||||||
|
|
||||||
return (final_state, final_brightness)
|
# If the light is off, set the color to off
|
||||||
|
if data[STATE_BRIGHTNESS] == 0:
|
||||||
|
data[STATE_HUE] = 0
|
||||||
|
data[STATE_SATURATION] = 0
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def entity_to_json(config, entity, is_on=None, brightness=None):
|
def entity_to_json(config, entity, state):
|
||||||
"""Convert an entity to its Hue bridge JSON representation."""
|
"""Convert an entity to its Hue bridge JSON representation."""
|
||||||
return {
|
return {
|
||||||
'state':
|
'state':
|
||||||
{
|
{
|
||||||
HUE_API_STATE_ON: is_on,
|
HUE_API_STATE_ON: state[STATE_ON],
|
||||||
HUE_API_STATE_BRI: brightness,
|
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
|
||||||
|
HUE_API_STATE_HUE: state[STATE_HUE],
|
||||||
|
HUE_API_STATE_SAT: state[STATE_SATURATION],
|
||||||
'reachable': True
|
'reachable': True
|
||||||
},
|
},
|
||||||
'type': 'Dimmable light',
|
'type': 'Dimmable light',
|
||||||
|
|
|
@ -13,7 +13,8 @@ from homeassistant.components import (
|
||||||
fan, http, light, script, emulated_hue, media_player, cover, climate)
|
fan, http, light, script, emulated_hue, media_player, cover, climate)
|
||||||
from homeassistant.components.emulated_hue import Config
|
from homeassistant.components.emulated_hue import Config
|
||||||
from homeassistant.components.emulated_hue.hue_api import (
|
from homeassistant.components.emulated_hue.hue_api import (
|
||||||
HUE_API_STATE_ON, HUE_API_STATE_BRI, HueUsernameView, HueOneLightStateView,
|
HUE_API_STATE_ON, HUE_API_STATE_BRI, HUE_API_STATE_HUE, HUE_API_STATE_SAT,
|
||||||
|
HueUsernameView, HueOneLightStateView,
|
||||||
HueAllLightsStateView, HueOneLightChangeView, HueAllGroupsStateView)
|
HueAllLightsStateView, HueOneLightChangeView, HueAllGroupsStateView)
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
|
|
||||||
|
@ -221,12 +222,13 @@ def test_discover_lights(hue_client):
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_get_light_state(hass_hue, hue_client):
|
def test_get_light_state(hass_hue, hue_client):
|
||||||
"""Test the getting of light state."""
|
"""Test the getting of light state."""
|
||||||
# Turn office light on and set to 127 brightness
|
# Turn office light on and set to 127 brightness, and set light color
|
||||||
yield from hass_hue.services.async_call(
|
yield from hass_hue.services.async_call(
|
||||||
light.DOMAIN, const.SERVICE_TURN_ON,
|
light.DOMAIN, const.SERVICE_TURN_ON,
|
||||||
{
|
{
|
||||||
const.ATTR_ENTITY_ID: 'light.ceiling_lights',
|
const.ATTR_ENTITY_ID: 'light.ceiling_lights',
|
||||||
light.ATTR_BRIGHTNESS: 127
|
light.ATTR_BRIGHTNESS: 127,
|
||||||
|
light.ATTR_RGB_COLOR: (1, 2, 7)
|
||||||
},
|
},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
|
|
||||||
|
@ -235,6 +237,8 @@ def test_get_light_state(hass_hue, hue_client):
|
||||||
|
|
||||||
assert office_json['state'][HUE_API_STATE_ON] is True
|
assert office_json['state'][HUE_API_STATE_ON] is True
|
||||||
assert office_json['state'][HUE_API_STATE_BRI] == 127
|
assert office_json['state'][HUE_API_STATE_BRI] == 127
|
||||||
|
assert office_json['state'][HUE_API_STATE_HUE] == 41869
|
||||||
|
assert office_json['state'][HUE_API_STATE_SAT] == 217
|
||||||
|
|
||||||
# Check all lights view
|
# Check all lights view
|
||||||
result = yield from hue_client.get('/api/username/lights')
|
result = yield from hue_client.get('/api/username/lights')
|
||||||
|
@ -261,6 +265,8 @@ def test_get_light_state(hass_hue, hue_client):
|
||||||
|
|
||||||
assert office_json['state'][HUE_API_STATE_ON] is False
|
assert office_json['state'][HUE_API_STATE_ON] is False
|
||||||
assert office_json['state'][HUE_API_STATE_BRI] == 0
|
assert office_json['state'][HUE_API_STATE_BRI] == 0
|
||||||
|
assert office_json['state'][HUE_API_STATE_HUE] == 0
|
||||||
|
assert office_json['state'][HUE_API_STATE_SAT] == 0
|
||||||
|
|
||||||
# Make sure bedroom light isn't accessible
|
# Make sure bedroom light isn't accessible
|
||||||
yield from perform_get_light_state(
|
yield from perform_get_light_state(
|
||||||
|
@ -287,6 +293,19 @@ def test_put_light_state(hass_hue, hue_client):
|
||||||
assert ceiling_lights.state == STATE_ON
|
assert ceiling_lights.state == STATE_ON
|
||||||
assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 153
|
assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 153
|
||||||
|
|
||||||
|
# update light state through api
|
||||||
|
yield from perform_put_light_state(
|
||||||
|
hass_hue, hue_client,
|
||||||
|
'light.ceiling_lights', True,
|
||||||
|
hue=4369, saturation=127, brightness=123)
|
||||||
|
|
||||||
|
# go through api to get the state back
|
||||||
|
ceiling_json = yield from perform_get_light_state(
|
||||||
|
hue_client, 'light.ceiling_lights', 200)
|
||||||
|
assert ceiling_json['state'][HUE_API_STATE_BRI] == 123
|
||||||
|
assert ceiling_json['state'][HUE_API_STATE_HUE] == 4369
|
||||||
|
assert ceiling_json['state'][HUE_API_STATE_SAT] == 127
|
||||||
|
|
||||||
# Go through the API to turn it off
|
# Go through the API to turn it off
|
||||||
ceiling_result = yield from perform_put_light_state(
|
ceiling_result = yield from perform_put_light_state(
|
||||||
hass_hue, hue_client,
|
hass_hue, hue_client,
|
||||||
|
@ -302,6 +321,11 @@ def test_put_light_state(hass_hue, hue_client):
|
||||||
# Check to make sure the state changed
|
# Check to make sure the state changed
|
||||||
ceiling_lights = hass_hue.states.get('light.ceiling_lights')
|
ceiling_lights = hass_hue.states.get('light.ceiling_lights')
|
||||||
assert ceiling_lights.state == STATE_OFF
|
assert ceiling_lights.state == STATE_OFF
|
||||||
|
ceiling_json = yield from perform_get_light_state(
|
||||||
|
hue_client, 'light.ceiling_lights', 200)
|
||||||
|
assert ceiling_json['state'][HUE_API_STATE_BRI] == 0
|
||||||
|
assert ceiling_json['state'][HUE_API_STATE_HUE] == 0
|
||||||
|
assert ceiling_json['state'][HUE_API_STATE_SAT] == 0
|
||||||
|
|
||||||
# Make sure we can't change the bedroom light state
|
# Make sure we can't change the bedroom light state
|
||||||
bedroom_result = yield from perform_put_light_state(
|
bedroom_result = yield from perform_put_light_state(
|
||||||
|
@ -706,7 +730,8 @@ def perform_get_light_state(client, entity_id, expected_status):
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def perform_put_light_state(hass_hue, client, entity_id, is_on,
|
def perform_put_light_state(hass_hue, client, entity_id, is_on,
|
||||||
brightness=None, content_type='application/json'):
|
brightness=None, content_type='application/json',
|
||||||
|
hue=None, saturation=None):
|
||||||
"""Test the setting of a light state."""
|
"""Test the setting of a light state."""
|
||||||
req_headers = {'Content-Type': content_type}
|
req_headers = {'Content-Type': content_type}
|
||||||
|
|
||||||
|
@ -714,6 +739,10 @@ def perform_put_light_state(hass_hue, client, entity_id, is_on,
|
||||||
|
|
||||||
if brightness is not None:
|
if brightness is not None:
|
||||||
data[HUE_API_STATE_BRI] = brightness
|
data[HUE_API_STATE_BRI] = brightness
|
||||||
|
if hue is not None:
|
||||||
|
data[HUE_API_STATE_HUE] = hue
|
||||||
|
if saturation is not None:
|
||||||
|
data[HUE_API_STATE_SAT] = saturation
|
||||||
|
|
||||||
result = yield from client.put(
|
result = yield from client.put(
|
||||||
'/api/username/lights/{}/state'.format(entity_id), headers=req_headers,
|
'/api/username/lights/{}/state'.format(entity_id), headers=req_headers,
|
||||||
|
|
Loading…
Reference in New Issue