parent
9b3a3fc1ac
commit
b87eb9d79e
4 changed files with 134 additions and 9 deletions
|
@ -22,6 +22,8 @@ from .const import (
|
||||||
CONF_EXPOSE, CONF_ALIASES, CONF_ROOM_HINT, CONF_ALLOW_UNLOCK,
|
CONF_EXPOSE, CONF_ALIASES, CONF_ROOM_HINT, CONF_ALLOW_UNLOCK,
|
||||||
DEFAULT_ALLOW_UNLOCK
|
DEFAULT_ALLOW_UNLOCK
|
||||||
)
|
)
|
||||||
|
from .const import EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED # noqa: F401
|
||||||
|
from .const import EVENT_QUERY_RECEIVED # noqa: F401
|
||||||
from .http import async_register_http
|
from .http import async_register_http
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
|
@ -42,3 +42,8 @@ ERR_NOT_SUPPORTED = "notSupported"
|
||||||
ERR_PROTOCOL_ERROR = 'protocolError'
|
ERR_PROTOCOL_ERROR = 'protocolError'
|
||||||
ERR_UNKNOWN_ERROR = 'unknownError'
|
ERR_UNKNOWN_ERROR = 'unknownError'
|
||||||
ERR_FUNCTION_NOT_SUPPORTED = 'functionNotSupported'
|
ERR_FUNCTION_NOT_SUPPORTED = 'functionNotSupported'
|
||||||
|
|
||||||
|
# Event types
|
||||||
|
EVENT_COMMAND_RECEIVED = 'google_assistant_command_received'
|
||||||
|
EVENT_QUERY_RECEIVED = 'google_assistant_query_received'
|
||||||
|
EVENT_SYNC_RECEIVED = 'google_assistant_sync_received'
|
||||||
|
|
|
@ -8,7 +8,7 @@ from homeassistant.util.decorator import Registry
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, STATE_UNAVAILABLE,
|
CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, STATE_UNAVAILABLE,
|
||||||
ATTR_SUPPORTED_FEATURES
|
ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID,
|
||||||
)
|
)
|
||||||
from homeassistant.components import (
|
from homeassistant.components import (
|
||||||
climate,
|
climate,
|
||||||
|
@ -32,7 +32,8 @@ from .const import (
|
||||||
TYPE_THERMOSTAT, TYPE_FAN,
|
TYPE_THERMOSTAT, TYPE_FAN,
|
||||||
CONF_ALIASES, CONF_ROOM_HINT,
|
CONF_ALIASES, CONF_ROOM_HINT,
|
||||||
ERR_FUNCTION_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE,
|
ERR_FUNCTION_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE,
|
||||||
ERR_UNKNOWN_ERROR
|
ERR_UNKNOWN_ERROR,
|
||||||
|
EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED, EVENT_QUERY_RECEIVED
|
||||||
)
|
)
|
||||||
from .helpers import SmartHomeError
|
from .helpers import SmartHomeError
|
||||||
|
|
||||||
|
@ -214,7 +215,8 @@ async def _process(hass, config, message):
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = await handler(hass, config, inputs[0].get('payload'))
|
result = await handler(hass, config, request_id,
|
||||||
|
inputs[0].get('payload'))
|
||||||
except SmartHomeError as err:
|
except SmartHomeError as err:
|
||||||
return {
|
return {
|
||||||
'requestId': request_id,
|
'requestId': request_id,
|
||||||
|
@ -233,11 +235,15 @@ async def _process(hass, config, message):
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register('action.devices.SYNC')
|
@HANDLERS.register('action.devices.SYNC')
|
||||||
async def async_devices_sync(hass, config, payload):
|
async def async_devices_sync(hass, config, request_id, payload):
|
||||||
"""Handle action.devices.SYNC request.
|
"""Handle action.devices.SYNC request.
|
||||||
|
|
||||||
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
|
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
|
||||||
"""
|
"""
|
||||||
|
hass.bus.async_fire(EVENT_SYNC_RECEIVED, {
|
||||||
|
'request_id': request_id
|
||||||
|
})
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
for state in hass.states.async_all():
|
for state in hass.states.async_all():
|
||||||
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
||||||
|
@ -255,14 +261,16 @@ async def async_devices_sync(hass, config, payload):
|
||||||
|
|
||||||
devices.append(serialized)
|
devices.append(serialized)
|
||||||
|
|
||||||
return {
|
response = {
|
||||||
'agentUserId': config.agent_user_id,
|
'agentUserId': config.agent_user_id,
|
||||||
'devices': devices,
|
'devices': devices,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register('action.devices.QUERY')
|
@HANDLERS.register('action.devices.QUERY')
|
||||||
async def async_devices_query(hass, config, payload):
|
async def async_devices_query(hass, config, request_id, payload):
|
||||||
"""Handle action.devices.QUERY request.
|
"""Handle action.devices.QUERY request.
|
||||||
|
|
||||||
https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
|
https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
|
||||||
|
@ -272,6 +280,11 @@ async def async_devices_query(hass, config, payload):
|
||||||
devid = device['id']
|
devid = device['id']
|
||||||
state = hass.states.get(devid)
|
state = hass.states.get(devid)
|
||||||
|
|
||||||
|
hass.bus.async_fire(EVENT_QUERY_RECEIVED, {
|
||||||
|
'request_id': request_id,
|
||||||
|
ATTR_ENTITY_ID: devid,
|
||||||
|
})
|
||||||
|
|
||||||
if not state:
|
if not state:
|
||||||
# If we can't find a state, the device is offline
|
# If we can't find a state, the device is offline
|
||||||
devices[devid] = {'online': False}
|
devices[devid] = {'online': False}
|
||||||
|
@ -283,7 +296,7 @@ async def async_devices_query(hass, config, payload):
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register('action.devices.EXECUTE')
|
@HANDLERS.register('action.devices.EXECUTE')
|
||||||
async def handle_devices_execute(hass, config, payload):
|
async def handle_devices_execute(hass, config, request_id, payload):
|
||||||
"""Handle action.devices.EXECUTE request.
|
"""Handle action.devices.EXECUTE request.
|
||||||
|
|
||||||
https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
|
https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
|
||||||
|
@ -296,6 +309,12 @@ async def handle_devices_execute(hass, config, payload):
|
||||||
command['execution']):
|
command['execution']):
|
||||||
entity_id = device['id']
|
entity_id = device['id']
|
||||||
|
|
||||||
|
hass.bus.async_fire(EVENT_COMMAND_RECEIVED, {
|
||||||
|
'request_id': request_id,
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
'execution': execution
|
||||||
|
})
|
||||||
|
|
||||||
# Happens if error occurred. Skip entity for further processing
|
# Happens if error occurred. Skip entity for further processing
|
||||||
if entity_id in results:
|
if entity_id in results:
|
||||||
continue
|
continue
|
||||||
|
@ -341,7 +360,7 @@ async def handle_devices_execute(hass, config, payload):
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register('action.devices.DISCONNECT')
|
@HANDLERS.register('action.devices.DISCONNECT')
|
||||||
async def async_devices_disconnect(hass, config, payload):
|
async def async_devices_disconnect(hass, config, request_id, payload):
|
||||||
"""Handle action.devices.DISCONNECT request.
|
"""Handle action.devices.DISCONNECT request.
|
||||||
|
|
||||||
https://developers.google.com/actions/smarthome/create#actiondevicesdisconnect
|
https://developers.google.com/actions/smarthome/create#actiondevicesdisconnect
|
||||||
|
|
|
@ -7,7 +7,8 @@ from homeassistant.components.climate.const import (
|
||||||
ATTR_MIN_TEMP, ATTR_MAX_TEMP, STATE_HEAT, SUPPORT_OPERATION_MODE
|
ATTR_MIN_TEMP, ATTR_MAX_TEMP, STATE_HEAT, SUPPORT_OPERATION_MODE
|
||||||
)
|
)
|
||||||
from homeassistant.components.google_assistant import (
|
from homeassistant.components.google_assistant import (
|
||||||
const, trait, helpers, smart_home as sh)
|
const, trait, helpers, smart_home as sh,
|
||||||
|
EVENT_COMMAND_RECEIVED, EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED)
|
||||||
from homeassistant.components.light.demo import DemoLight
|
from homeassistant.components.light.demo import DemoLight
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,6 +49,9 @@ async def test_sync_message(hass):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
events = []
|
||||||
|
hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append)
|
||||||
|
|
||||||
result = await sh.async_handle_message(hass, config, {
|
result = await sh.async_handle_message(hass, config, {
|
||||||
"requestId": REQ_ID,
|
"requestId": REQ_ID,
|
||||||
"inputs": [{
|
"inputs": [{
|
||||||
|
@ -85,6 +89,13 @@ async def test_sync_message(hass):
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].event_type == EVENT_SYNC_RECEIVED
|
||||||
|
assert events[0].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_query_message(hass):
|
async def test_query_message(hass):
|
||||||
|
@ -109,6 +120,9 @@ async def test_query_message(hass):
|
||||||
light2.entity_id = 'light.another_light'
|
light2.entity_id = 'light.another_light'
|
||||||
await light2.async_update_ha_state()
|
await light2.async_update_ha_state()
|
||||||
|
|
||||||
|
events = []
|
||||||
|
hass.bus.async_listen(EVENT_QUERY_RECEIVED, events.append)
|
||||||
|
|
||||||
result = await sh.async_handle_message(hass, BASIC_CONFIG, {
|
result = await sh.async_handle_message(hass, BASIC_CONFIG, {
|
||||||
"requestId": REQ_ID,
|
"requestId": REQ_ID,
|
||||||
"inputs": [{
|
"inputs": [{
|
||||||
|
@ -149,12 +163,33 @@ async def test_query_message(hass):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert len(events) == 3
|
||||||
|
assert events[0].event_type == EVENT_QUERY_RECEIVED
|
||||||
|
assert events[0].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
'entity_id': 'light.demo_light'
|
||||||
|
}
|
||||||
|
assert events[1].event_type == EVENT_QUERY_RECEIVED
|
||||||
|
assert events[1].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
'entity_id': 'light.another_light'
|
||||||
|
}
|
||||||
|
assert events[2].event_type == EVENT_QUERY_RECEIVED
|
||||||
|
assert events[2].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
'entity_id': 'light.non_existing'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_execute(hass):
|
async def test_execute(hass):
|
||||||
"""Test an execute command."""
|
"""Test an execute command."""
|
||||||
await async_setup_component(hass, 'light', {
|
await async_setup_component(hass, 'light', {
|
||||||
'light': {'platform': 'demo'}
|
'light': {'platform': 'demo'}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
events = []
|
||||||
|
hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
'light', 'turn_off', {'entity_id': 'light.ceiling_lights'},
|
'light', 'turn_off', {'entity_id': 'light.ceiling_lights'},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
|
@ -209,6 +244,52 @@ async def test_execute(hass):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert len(events) == 4
|
||||||
|
assert events[0].event_type == EVENT_COMMAND_RECEIVED
|
||||||
|
assert events[0].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
'entity_id': 'light.non_existing',
|
||||||
|
'execution': {
|
||||||
|
'command': 'action.devices.commands.OnOff',
|
||||||
|
'params': {
|
||||||
|
'on': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert events[1].event_type == EVENT_COMMAND_RECEIVED
|
||||||
|
assert events[1].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
'entity_id': 'light.non_existing',
|
||||||
|
'execution': {
|
||||||
|
'command': 'action.devices.commands.BrightnessAbsolute',
|
||||||
|
'params': {
|
||||||
|
'brightness': 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert events[2].event_type == EVENT_COMMAND_RECEIVED
|
||||||
|
assert events[2].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
'entity_id': 'light.ceiling_lights',
|
||||||
|
'execution': {
|
||||||
|
'command': 'action.devices.commands.OnOff',
|
||||||
|
'params': {
|
||||||
|
'on': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert events[3].event_type == EVENT_COMMAND_RECEIVED
|
||||||
|
assert events[3].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
'entity_id': 'light.ceiling_lights',
|
||||||
|
'execution': {
|
||||||
|
'command': 'action.devices.commands.BrightnessAbsolute',
|
||||||
|
'params': {
|
||||||
|
'brightness': 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_raising_error_trait(hass):
|
async def test_raising_error_trait(hass):
|
||||||
"""Test raising an error while executing a trait command."""
|
"""Test raising an error while executing a trait command."""
|
||||||
|
@ -218,6 +299,11 @@ async def test_raising_error_trait(hass):
|
||||||
ATTR_SUPPORTED_FEATURES: SUPPORT_OPERATION_MODE,
|
ATTR_SUPPORTED_FEATURES: SUPPORT_OPERATION_MODE,
|
||||||
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
|
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
events = []
|
||||||
|
hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
result = await sh.async_handle_message(hass, BASIC_CONFIG, {
|
result = await sh.async_handle_message(hass, BASIC_CONFIG, {
|
||||||
"requestId": REQ_ID,
|
"requestId": REQ_ID,
|
||||||
"inputs": [{
|
"inputs": [{
|
||||||
|
@ -250,6 +336,19 @@ async def test_raising_error_trait(hass):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].event_type == EVENT_COMMAND_RECEIVED
|
||||||
|
assert events[0].data == {
|
||||||
|
'request_id': REQ_ID,
|
||||||
|
'entity_id': 'climate.bla',
|
||||||
|
'execution': {
|
||||||
|
'command': 'action.devices.commands.ThermostatTemperatureSetpoint',
|
||||||
|
'params': {
|
||||||
|
'thermostatTemperatureSetpoint': 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_serialize_input_boolean():
|
def test_serialize_input_boolean():
|
||||||
"""Test serializing an input boolean entity."""
|
"""Test serializing an input boolean entity."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue