mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
add support for color temperature and color to Google Assistant (#10039)
* add support for color temperature and color; also add some extra deviceInfo attributes * change so that default behaviour doesn't turn off device if the action isn't handled * add tests * fix lint * more lint * use attributes were applicable * removed debug logging * fix unassigned if only None returned * report more data in QUERY * better tests for color and temperature * fixes after dev merge * remove deviceInfo as not part of a device state (PR #10399) * fix after merge
This commit is contained in:
parent
f494c32866
commit
d4bd4c114b
@ -128,6 +128,7 @@ class GoogleAssistantView(HomeAssistantView):
|
|||||||
ent_ids = [ent.get('id') for ent in command.get('devices', [])]
|
ent_ids = [ent.get('id') for ent in command.get('devices', [])]
|
||||||
execution = command.get('execution')[0]
|
execution = command.get('execution')[0]
|
||||||
for eid in ent_ids:
|
for eid in ent_ids:
|
||||||
|
success = False
|
||||||
domain = eid.split('.')[0]
|
domain = eid.split('.')[0]
|
||||||
(service, service_data) = determine_service(
|
(service, service_data) = determine_service(
|
||||||
eid, execution.get('command'), execution.get('params'),
|
eid, execution.get('command'), execution.get('params'),
|
||||||
|
@ -8,6 +8,7 @@ from aiohttp.web import Request, Response # NOQA
|
|||||||
from typing import Dict, Tuple, Any, Optional # NOQA
|
from typing import Dict, Tuple, Any, Optional # NOQA
|
||||||
from homeassistant.helpers.entity import Entity # NOQA
|
from homeassistant.helpers.entity import Entity # NOQA
|
||||||
from homeassistant.core import HomeAssistant # NOQA
|
from homeassistant.core import HomeAssistant # NOQA
|
||||||
|
from homeassistant.util import color
|
||||||
from homeassistant.util.unit_system import UnitSystem # NOQA
|
from homeassistant.util.unit_system import UnitSystem # NOQA
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -22,7 +23,8 @@ from homeassistant.components import (
|
|||||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_GOOGLE_ASSISTANT_NAME, ATTR_GOOGLE_ASSISTANT_TYPE,
|
ATTR_GOOGLE_ASSISTANT_NAME, COMMAND_COLOR,
|
||||||
|
ATTR_GOOGLE_ASSISTANT_TYPE,
|
||||||
COMMAND_BRIGHTNESS, COMMAND_ONOFF, COMMAND_ACTIVATESCENE,
|
COMMAND_BRIGHTNESS, COMMAND_ONOFF, COMMAND_ACTIVATESCENE,
|
||||||
COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT,
|
COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT,
|
||||||
COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, COMMAND_THERMOSTAT_SET_MODE,
|
COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, COMMAND_THERMOSTAT_SET_MODE,
|
||||||
@ -78,6 +80,7 @@ def entity_to_device(entity: Entity, units: UnitSystem):
|
|||||||
device = {
|
device = {
|
||||||
'id': entity.entity_id,
|
'id': entity.entity_id,
|
||||||
'name': {},
|
'name': {},
|
||||||
|
'attributes': {},
|
||||||
'traits': [],
|
'traits': [],
|
||||||
'willReportState': False,
|
'willReportState': False,
|
||||||
}
|
}
|
||||||
@ -102,6 +105,23 @@ def entity_to_device(entity: Entity, units: UnitSystem):
|
|||||||
for feature, trait in class_data[2].items():
|
for feature, trait in class_data[2].items():
|
||||||
if feature & supported > 0:
|
if feature & supported > 0:
|
||||||
device['traits'].append(trait)
|
device['traits'].append(trait)
|
||||||
|
|
||||||
|
# Actions require this attributes for a device
|
||||||
|
# supporting temperature
|
||||||
|
# For IKEA trådfri, these attributes only seem to
|
||||||
|
# be set only if the device is on?
|
||||||
|
if trait == TRAIT_COLOR_TEMP:
|
||||||
|
if entity.attributes.get(
|
||||||
|
light.ATTR_MAX_MIREDS) is not None:
|
||||||
|
device['attributes']['temperatureMinK'] = \
|
||||||
|
int(round(color.color_temperature_mired_to_kelvin(
|
||||||
|
entity.attributes.get(light.ATTR_MAX_MIREDS))))
|
||||||
|
if entity.attributes.get(
|
||||||
|
light.ATTR_MIN_MIREDS) is not None:
|
||||||
|
device['attributes']['temperatureMaxK'] = \
|
||||||
|
int(round(color.color_temperature_mired_to_kelvin(
|
||||||
|
entity.attributes.get(light.ATTR_MIN_MIREDS))))
|
||||||
|
|
||||||
if entity.domain == climate.DOMAIN:
|
if entity.domain == climate.DOMAIN:
|
||||||
modes = ','.join(
|
modes = ','.join(
|
||||||
m for m in entity.attributes.get(climate.ATTR_OPERATION_LIST, [])
|
m for m in entity.attributes.get(climate.ATTR_OPERATION_LIST, [])
|
||||||
@ -156,12 +176,35 @@ def query_device(entity: Entity, units: UnitSystem) -> dict:
|
|||||||
|
|
||||||
final_brightness = 100 * (final_brightness / 255)
|
final_brightness = 100 * (final_brightness / 255)
|
||||||
|
|
||||||
return {
|
query_response = {
|
||||||
"on": final_state,
|
"on": final_state,
|
||||||
"online": True,
|
"online": True,
|
||||||
"brightness": int(final_brightness)
|
"brightness": int(final_brightness)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supported_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
if supported_features & \
|
||||||
|
(light.SUPPORT_COLOR_TEMP | light.SUPPORT_RGB_COLOR):
|
||||||
|
query_response["color"] = {}
|
||||||
|
|
||||||
|
if entity.attributes.get(light.ATTR_COLOR_TEMP) is not None:
|
||||||
|
query_response["color"]["temperature"] = \
|
||||||
|
int(round(color.color_temperature_mired_to_kelvin(
|
||||||
|
entity.attributes.get(light.ATTR_COLOR_TEMP))))
|
||||||
|
|
||||||
|
if entity.attributes.get(light.ATTR_COLOR_NAME) is not None:
|
||||||
|
query_response["color"]["name"] = \
|
||||||
|
entity.attributes.get(light.ATTR_COLOR_NAME)
|
||||||
|
|
||||||
|
if entity.attributes.get(light.ATTR_RGB_COLOR) is not None:
|
||||||
|
color_rgb = entity.attributes.get(light.ATTR_RGB_COLOR)
|
||||||
|
if color_rgb is not None:
|
||||||
|
query_response["color"]["spectrumRGB"] = \
|
||||||
|
int(color.color_rgb_to_hex(
|
||||||
|
color_rgb[0], color_rgb[1], color_rgb[2]), 16)
|
||||||
|
|
||||||
|
return query_response
|
||||||
|
|
||||||
|
|
||||||
# erroneous bug on old pythons and pylint
|
# erroneous bug on old pythons and pylint
|
||||||
# https://github.com/PyCQA/pylint/issues/1212
|
# https://github.com/PyCQA/pylint/issues/1212
|
||||||
@ -217,7 +260,27 @@ def determine_service(
|
|||||||
service_data['brightness'] = int(brightness / 100 * 255)
|
service_data['brightness'] = int(brightness / 100 * 255)
|
||||||
return (SERVICE_TURN_ON, service_data)
|
return (SERVICE_TURN_ON, service_data)
|
||||||
|
|
||||||
if command == COMMAND_ACTIVATESCENE or (COMMAND_ONOFF == command and
|
_LOGGER.debug("Handling command %s with data %s", command, params)
|
||||||
params.get('on') is True):
|
if command == COMMAND_COLOR:
|
||||||
|
color_data = params.get('color')
|
||||||
|
if color_data is not None:
|
||||||
|
if color_data.get('temperature', 0) > 0:
|
||||||
|
service_data[light.ATTR_KELVIN] = color_data.get('temperature')
|
||||||
|
return (SERVICE_TURN_ON, service_data)
|
||||||
|
if color_data.get('spectrumRGB', 0) > 0:
|
||||||
|
# blue is 255 so pad up to 6 chars
|
||||||
|
hex_value = \
|
||||||
|
('%0x' % int(color_data.get('spectrumRGB'))).zfill(6)
|
||||||
|
service_data[light.ATTR_RGB_COLOR] = \
|
||||||
|
color.rgb_hex_to_rgb_list(hex_value)
|
||||||
|
return (SERVICE_TURN_ON, service_data)
|
||||||
|
|
||||||
|
if command == COMMAND_ACTIVATESCENE:
|
||||||
return (SERVICE_TURN_ON, service_data)
|
return (SERVICE_TURN_ON, service_data)
|
||||||
return (SERVICE_TURN_OFF, service_data)
|
|
||||||
|
if COMMAND_ONOFF == command:
|
||||||
|
if params.get('on') is True:
|
||||||
|
return (SERVICE_TURN_ON, service_data)
|
||||||
|
return (SERVICE_TURN_OFF, service_data)
|
||||||
|
|
||||||
|
return (None, service_data)
|
||||||
|
@ -181,6 +181,8 @@ def test_query_request(hass_fixture, assistant_client):
|
|||||||
'id': "light.ceiling_lights",
|
'id': "light.ceiling_lights",
|
||||||
}, {
|
}, {
|
||||||
'id': "light.bed_light",
|
'id': "light.bed_light",
|
||||||
|
}, {
|
||||||
|
'id': "light.kitchen_lights",
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
@ -193,10 +195,12 @@ def test_query_request(hass_fixture, assistant_client):
|
|||||||
body = yield from result.json()
|
body = yield from result.json()
|
||||||
assert body.get('requestId') == reqid
|
assert body.get('requestId') == reqid
|
||||||
devices = body['payload']['devices']
|
devices = body['payload']['devices']
|
||||||
assert len(devices) == 2
|
assert len(devices) == 3
|
||||||
assert devices['light.bed_light']['on'] is False
|
assert devices['light.bed_light']['on'] is False
|
||||||
assert devices['light.ceiling_lights']['on'] is True
|
assert devices['light.ceiling_lights']['on'] is True
|
||||||
assert devices['light.ceiling_lights']['brightness'] == 70
|
assert devices['light.ceiling_lights']['brightness'] == 70
|
||||||
|
assert devices['light.kitchen_lights']['color']['spectrumRGB'] == 16727919
|
||||||
|
assert devices['light.kitchen_lights']['color']['temperature'] == 4166
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -321,6 +325,31 @@ def test_execute_request(hass_fixture, assistant_client):
|
|||||||
"on": False
|
"on": False
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
}, {
|
||||||
|
"devices": [{
|
||||||
|
"id": "light.kitchen_lights",
|
||||||
|
}],
|
||||||
|
"execution": [{
|
||||||
|
"command": "action.devices.commands.ColorAbsolute",
|
||||||
|
"params": {
|
||||||
|
"color": {
|
||||||
|
"spectrumRGB": 16711680,
|
||||||
|
"temperature": 2100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"devices": [{
|
||||||
|
"id": "light.kitchen_lights",
|
||||||
|
}],
|
||||||
|
"execution": [{
|
||||||
|
"command": "action.devices.commands.ColorAbsolute",
|
||||||
|
"params": {
|
||||||
|
"color": {
|
||||||
|
"spectrumRGB": 16711680
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
@ -333,7 +362,10 @@ def test_execute_request(hass_fixture, assistant_client):
|
|||||||
body = yield from result.json()
|
body = yield from result.json()
|
||||||
assert body.get('requestId') == reqid
|
assert body.get('requestId') == reqid
|
||||||
commands = body['payload']['commands']
|
commands = body['payload']['commands']
|
||||||
assert len(commands) == 3
|
assert len(commands) == 5
|
||||||
ceiling = hass_fixture.states.get('light.ceiling_lights')
|
ceiling = hass_fixture.states.get('light.ceiling_lights')
|
||||||
assert ceiling.state == 'off'
|
assert ceiling.state == 'off'
|
||||||
|
kitchen = hass_fixture.states.get('light.kitchen_lights')
|
||||||
|
assert kitchen.attributes.get(light.ATTR_COLOR_TEMP) == 476
|
||||||
|
assert kitchen.attributes.get(light.ATTR_RGB_COLOR) == (255, 0, 0)
|
||||||
assert hass_fixture.states.get('switch.decorative_lights').state == 'off'
|
assert hass_fixture.states.get('switch.decorative_lights').state == 'off'
|
||||||
|
@ -17,6 +17,57 @@ DETERMINE_SERVICE_TESTS = [{ # Test light brightness
|
|||||||
const.SERVICE_TURN_ON,
|
const.SERVICE_TURN_ON,
|
||||||
{'entity_id': 'light.test', 'brightness': 242}
|
{'entity_id': 'light.test', 'brightness': 242}
|
||||||
)
|
)
|
||||||
|
}, { # Test light color temperature
|
||||||
|
'entity_id': 'light.test',
|
||||||
|
'command': ga.const.COMMAND_COLOR,
|
||||||
|
'params': {
|
||||||
|
'color': {
|
||||||
|
'temperature': 2300,
|
||||||
|
'name': 'warm white'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'expected': (
|
||||||
|
const.SERVICE_TURN_ON,
|
||||||
|
{'entity_id': 'light.test', 'kelvin': 2300}
|
||||||
|
)
|
||||||
|
}, { # Test light color blue
|
||||||
|
'entity_id': 'light.test',
|
||||||
|
'command': ga.const.COMMAND_COLOR,
|
||||||
|
'params': {
|
||||||
|
'color': {
|
||||||
|
'spectrumRGB': 255,
|
||||||
|
'name': 'blue'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'expected': (
|
||||||
|
const.SERVICE_TURN_ON,
|
||||||
|
{'entity_id': 'light.test', 'rgb_color': [0, 0, 255]}
|
||||||
|
)
|
||||||
|
}, { # Test light color yellow
|
||||||
|
'entity_id': 'light.test',
|
||||||
|
'command': ga.const.COMMAND_COLOR,
|
||||||
|
'params': {
|
||||||
|
'color': {
|
||||||
|
'spectrumRGB': 16776960,
|
||||||
|
'name': 'yellow'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'expected': (
|
||||||
|
const.SERVICE_TURN_ON,
|
||||||
|
{'entity_id': 'light.test', 'rgb_color': [255, 255, 0]}
|
||||||
|
)
|
||||||
|
}, { # Test unhandled action/service
|
||||||
|
'entity_id': 'light.test',
|
||||||
|
'command': ga.const.COMMAND_COLOR,
|
||||||
|
'params': {
|
||||||
|
'color': {
|
||||||
|
'unhandled': 2300
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'expected': (
|
||||||
|
None,
|
||||||
|
{'entity_id': 'light.test'}
|
||||||
|
)
|
||||||
}, { # Test switch to light custom type
|
}, { # Test switch to light custom type
|
||||||
'entity_id': 'switch.decorative_lights',
|
'entity_id': 'switch.decorative_lights',
|
||||||
'command': ga.const.COMMAND_ONOFF,
|
'command': ga.const.COMMAND_ONOFF,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user