Add Google Assistant support for setting climate temperature and operation mode. (#10174)
Fixes #10025.pull/10224/merge
parent
fb34f94d9c
commit
4da8ec0a05
|
@ -16,8 +16,9 @@ CONF_ALIASES = 'aliases'
|
|||
|
||||
DEFAULT_EXPOSE_BY_DEFAULT = True
|
||||
DEFAULT_EXPOSED_DOMAINS = [
|
||||
'switch', 'light', 'group', 'media_player', 'fan', 'cover'
|
||||
'switch', 'light', 'group', 'media_player', 'fan', 'cover', 'climate'
|
||||
]
|
||||
CLIMATE_SUPPORTED_MODES = {'heat', 'cool', 'off', 'on', 'heatcool'}
|
||||
|
||||
PREFIX_TRAITS = 'action.devices.traits.'
|
||||
TRAIT_ONOFF = PREFIX_TRAITS + 'OnOff'
|
||||
|
@ -25,14 +26,21 @@ TRAIT_BRIGHTNESS = PREFIX_TRAITS + 'Brightness'
|
|||
TRAIT_RGB_COLOR = PREFIX_TRAITS + 'ColorSpectrum'
|
||||
TRAIT_COLOR_TEMP = PREFIX_TRAITS + 'ColorTemperature'
|
||||
TRAIT_SCENE = PREFIX_TRAITS + 'Scene'
|
||||
TRAIT_TEMPERATURE_SETTING = PREFIX_TRAITS + 'TemperatureSetting'
|
||||
|
||||
PREFIX_COMMANDS = 'action.devices.commands.'
|
||||
COMMAND_ONOFF = PREFIX_COMMANDS + 'OnOff'
|
||||
COMMAND_BRIGHTNESS = PREFIX_COMMANDS + 'BrightnessAbsolute'
|
||||
COMMAND_COLOR = PREFIX_COMMANDS + 'ColorAbsolute'
|
||||
COMMAND_ACTIVATESCENE = PREFIX_COMMANDS + 'ActivateScene'
|
||||
COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT = (
|
||||
PREFIX_COMMANDS + 'ThermostatTemperatureSetpoint')
|
||||
COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE = (
|
||||
PREFIX_COMMANDS + 'ThermostatTemperatureSetRange')
|
||||
COMMAND_THERMOSTAT_SET_MODE = PREFIX_COMMANDS + 'ThermostatSetMode'
|
||||
|
||||
PREFIX_TYPES = 'action.devices.types.'
|
||||
TYPE_LIGHT = PREFIX_TYPES + 'LIGHT'
|
||||
TYPE_SWITCH = PREFIX_TYPES + 'SWITCH'
|
||||
TYPE_SCENE = PREFIX_TYPES + 'SCENE'
|
||||
TYPE_THERMOSTAT = PREFIX_TYPES + 'THERMOSTAT'
|
||||
|
|
|
@ -15,16 +15,18 @@ from homeassistant.const import (
|
|||
SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||
)
|
||||
from homeassistant.components import (
|
||||
switch, light, cover, media_player, group, fan, scene, script
|
||||
switch, light, cover, media_player, group, fan, scene, script, climate
|
||||
)
|
||||
|
||||
from .const import (
|
||||
ATTR_GOOGLE_ASSISTANT_NAME, ATTR_GOOGLE_ASSISTANT_TYPE,
|
||||
COMMAND_BRIGHTNESS, COMMAND_ONOFF, COMMAND_ACTIVATESCENE,
|
||||
COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT,
|
||||
COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, COMMAND_THERMOSTAT_SET_MODE,
|
||||
TRAIT_ONOFF, TRAIT_BRIGHTNESS, TRAIT_COLOR_TEMP,
|
||||
TRAIT_RGB_COLOR, TRAIT_SCENE,
|
||||
TYPE_LIGHT, TYPE_SCENE, TYPE_SWITCH,
|
||||
CONF_ALIASES,
|
||||
TRAIT_RGB_COLOR, TRAIT_SCENE, TRAIT_TEMPERATURE_SETTING,
|
||||
TYPE_LIGHT, TYPE_SCENE, TYPE_SWITCH, TYPE_THERMOSTAT,
|
||||
CONF_ALIASES, CLIMATE_SUPPORTED_MODES
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -54,11 +56,12 @@ MAPPING_COMPONENT = {
|
|||
media_player.SUPPORT_VOLUME_SET: TRAIT_BRIGHTNESS
|
||||
}
|
||||
],
|
||||
climate.DOMAIN: [TYPE_THERMOSTAT, TRAIT_TEMPERATURE_SETTING, None],
|
||||
} # type: Dict[str, list]
|
||||
|
||||
|
||||
def make_actions_response(request_id: str, payload: dict) -> dict:
|
||||
"""Helper to simplify format for response."""
|
||||
"""Make response message."""
|
||||
return {'requestId': request_id, 'payload': payload}
|
||||
|
||||
|
||||
|
@ -96,6 +99,14 @@ def entity_to_device(entity: Entity):
|
|||
for feature, trait in class_data[2].items():
|
||||
if feature & supported > 0:
|
||||
device['traits'].append(trait)
|
||||
if entity.domain == climate.DOMAIN:
|
||||
modes = ','.join(
|
||||
m for m in entity.attributes.get(climate.ATTR_OPERATION_LIST, [])
|
||||
if m in CLIMATE_SUPPORTED_MODES)
|
||||
device['attributes'] = {
|
||||
'availableThermostatModes': modes,
|
||||
'thermostatTemperatureUnit': 'C',
|
||||
}
|
||||
|
||||
return device
|
||||
|
||||
|
@ -152,6 +163,23 @@ def determine_service(entity_id: str, command: str,
|
|||
return (cover.SERVICE_OPEN_COVER, service_data)
|
||||
return (cover.SERVICE_CLOSE_COVER, service_data)
|
||||
|
||||
# special climate handling
|
||||
if domain == climate.DOMAIN:
|
||||
if command == COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT:
|
||||
service_data['temperature'] = params.get(
|
||||
'thermostatTemperatureSetpoint', 25)
|
||||
return (climate.SERVICE_SET_TEMPERATURE, service_data)
|
||||
if command == COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE:
|
||||
service_data['target_temp_high'] = params.get(
|
||||
'thermostatTemperatureSetpointHigh', 25)
|
||||
service_data['target_temp_low'] = params.get(
|
||||
'thermostatTemperatureSetpointLow', 18)
|
||||
return (climate.SERVICE_SET_TEMPERATURE, service_data)
|
||||
if command == COMMAND_THERMOSTAT_SET_MODE:
|
||||
service_data['operation_mode'] = params.get(
|
||||
'thermostatMode', 'off')
|
||||
return (climate.SERVICE_SET_OPERATION_MODE, service_data)
|
||||
|
||||
if command == COMMAND_BRIGHTNESS:
|
||||
brightness = params.get('brightness')
|
||||
service_data['brightness'] = int(brightness / 100 * 255)
|
||||
|
|
|
@ -202,4 +202,32 @@ DEMO_DEVICES = [{
|
|||
'traits': ['action.devices.traits.Scene'],
|
||||
'type': 'action.devices.types.SCENE',
|
||||
'willReportState': False
|
||||
}, {
|
||||
'id': 'climate.hvac',
|
||||
'name': {
|
||||
'name': 'Hvac'
|
||||
},
|
||||
'traits': ['action.devices.traits.TemperatureSetting'],
|
||||
'type': 'action.devices.types.THERMOSTAT',
|
||||
'willReportState': False,
|
||||
'attributes': {
|
||||
'availableThermostatModes': 'heat,cool,off',
|
||||
'thermostatTemperatureUnit': 'C',
|
||||
},
|
||||
}, {
|
||||
'id': 'climate.heatpump',
|
||||
'name': {
|
||||
'name': 'HeatPump'
|
||||
},
|
||||
'traits': ['action.devices.traits.TemperatureSetting'],
|
||||
'type': 'action.devices.types.THERMOSTAT',
|
||||
'willReportState': False
|
||||
}, {
|
||||
'id': 'climate.ecobee',
|
||||
'name': {
|
||||
'name': 'Ecobee'
|
||||
},
|
||||
'traits': ['action.devices.traits.TemperatureSetting'],
|
||||
'type': 'action.devices.types.THERMOSTAT',
|
||||
'willReportState': False
|
||||
}]
|
||||
|
|
|
@ -6,7 +6,7 @@ import pytest
|
|||
|
||||
from homeassistant import setup, const, core
|
||||
from homeassistant.components import (
|
||||
http, async_setup, light, cover, media_player, fan, switch
|
||||
http, async_setup, light, cover, media_player, fan, switch, climate
|
||||
)
|
||||
from homeassistant.components import google_assistant as ga
|
||||
from tests.common import get_test_instance_port
|
||||
|
@ -45,7 +45,7 @@ def assistant_client(loop, hass_fixture, test_client):
|
|||
|
||||
@pytest.fixture
|
||||
def hass_fixture(loop, hass):
|
||||
"""Setup a hass instance for these tests."""
|
||||
"""Set up a hass instance for these tests."""
|
||||
# We need to do this to get access to homeassistant/turn_(on,off)
|
||||
loop.run_until_complete(async_setup(hass, {core.DOMAIN: {}}))
|
||||
|
||||
|
@ -89,6 +89,13 @@ def hass_fixture(loop, hass):
|
|||
}]
|
||||
}))
|
||||
|
||||
loop.run_until_complete(
|
||||
setup.async_setup_component(hass, climate.DOMAIN, {
|
||||
'climate': [{
|
||||
'platform': 'demo'
|
||||
}]
|
||||
}))
|
||||
|
||||
# Kitchen light is explicitly excluded from being exposed
|
||||
ceiling_lights_entity = hass.states.get('light.ceiling_lights')
|
||||
attrs = dict(ceiling_lights_entity.attributes)
|
||||
|
@ -142,15 +149,18 @@ def test_sync_request(hass_fixture, assistant_client):
|
|||
body = yield from result.json()
|
||||
assert body.get('requestId') == reqid
|
||||
devices = body['payload']['devices']
|
||||
# assert len(devices) == 4
|
||||
assert len(devices) == len(DEMO_DEVICES)
|
||||
# HACK this is kind of slow and lazy
|
||||
for dev in devices:
|
||||
for demo in DEMO_DEVICES:
|
||||
if dev['id'] == demo['id']:
|
||||
assert dev['name'] == demo['name']
|
||||
assert set(dev['traits']) == set(demo['traits'])
|
||||
assert dev['type'] == demo['type']
|
||||
assert (
|
||||
sorted([dev['id'] for dev in devices])
|
||||
== sorted([dev['id'] for dev in DEMO_DEVICES]))
|
||||
|
||||
for dev, demo in zip(
|
||||
sorted(devices, key=lambda d: d['id']),
|
||||
sorted(DEMO_DEVICES, key=lambda d: d['id'])):
|
||||
assert dev['name'] == demo['name']
|
||||
assert set(dev['traits']) == set(demo['traits'])
|
||||
assert dev['type'] == demo['type']
|
||||
if 'attributes' in demo:
|
||||
assert dev['attributes'] == demo['attributes']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import asyncio
|
||||
|
||||
from homeassistant import const
|
||||
from homeassistant.components import climate
|
||||
from homeassistant.components import google_assistant as ga
|
||||
|
||||
DETERMINE_SERVICE_TESTS = [{ # Test light brightness
|
||||
|
@ -73,6 +74,34 @@ DETERMINE_SERVICE_TESTS = [{ # Test light brightness
|
|||
const.SERVICE_VOLUME_SET,
|
||||
{'entity_id': 'media_player.living_room', 'volume_level': 0.3}
|
||||
),
|
||||
}, { # Test climate temperature
|
||||
'entity_id': 'climate.living_room',
|
||||
'command': ga.const.COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT,
|
||||
'params': {'thermostatTemperatureSetpoint': 24.5},
|
||||
'expected': (
|
||||
climate.SERVICE_SET_TEMPERATURE,
|
||||
{'entity_id': 'climate.living_room', 'temperature': 24.5}
|
||||
),
|
||||
}, { # Test climate temperature range
|
||||
'entity_id': 'climate.living_room',
|
||||
'command': ga.const.COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE,
|
||||
'params': {
|
||||
'thermostatTemperatureSetpointHigh': 24.5,
|
||||
'thermostatTemperatureSetpointLow': 20.5,
|
||||
},
|
||||
'expected': (
|
||||
climate.SERVICE_SET_TEMPERATURE,
|
||||
{'entity_id': 'climate.living_room',
|
||||
'target_temp_high': 24.5, 'target_temp_low': 20.5}
|
||||
),
|
||||
}, { # Test climate operation mode
|
||||
'entity_id': 'climate.living_room',
|
||||
'command': ga.const.COMMAND_THERMOSTAT_SET_MODE,
|
||||
'params': {'thermostatMode': 'heat'},
|
||||
'expected': (
|
||||
climate.SERVICE_SET_OPERATION_MODE,
|
||||
{'entity_id': 'climate.living_room', 'operation_mode': 'heat'}
|
||||
),
|
||||
}]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue