Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions homeassistant/components/google_assistant/smart_home.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Support for Google Assistant Smart Home API."""
from asyncio import gather
from collections.abc import Mapping
from itertools import product
import logging
Expand Down Expand Up @@ -89,8 +90,7 @@ def traits(self):
return [Trait(self.hass, state, self.config) for Trait in trait.TRAITS
if Trait.supported(domain, features)]

@callback
def sync_serialize(self):
async def sync_serialize(self):
"""Serialize entity for a SYNC response.

https://developers.google.com/actions/smarthome/create-app#actiondevicessync
Expand Down Expand Up @@ -132,13 +132,31 @@ def sync_serialize(self):
if aliases:
device['name']['nicknames'] = aliases

# add room hint if annotated
for trt in traits:
device['attributes'].update(trt.sync_attributes())

room = entity_config.get(CONF_ROOM_HINT)
if room:
device['roomHint'] = room
return device

for trt in traits:
device['attributes'].update(trt.sync_attributes())
dev_reg, ent_reg, area_reg = await gather(
self.hass.helpers.device_registry.async_get_registry(),
self.hass.helpers.entity_registry.async_get_registry(),
self.hass.helpers.area_registry.async_get_registry(),
)

entity_entry = ent_reg.async_get(state.entity_id)
if not (entity_entry and entity_entry.device_id):
return device

device_entry = dev_reg.devices.get(entity_entry.device_id)
if not (device_entry and device_entry.area_id):
return device

area_entry = area_reg.areas.get(device_entry.area_id)
if area_entry and area_entry.name:
device['roomHint'] = area_entry.name

return device

Expand Down Expand Up @@ -253,7 +271,7 @@ async def async_devices_sync(hass, config, request_id, payload):
continue

entity = _GoogleEntity(hass, config, state)
serialized = entity.sync_serialize()
serialized = await entity.sync_serialize()

if serialized is None:
_LOGGER.debug("No mapping for %s domain", entity.state)
Expand Down
100 changes: 97 additions & 3 deletions tests/components/google_assistant/test_smart_home.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Test Google Smart Home."""
import pytest

from homeassistant.core import State
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS)
Expand All @@ -11,6 +13,9 @@
EVENT_COMMAND_RECEIVED, EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED)
from homeassistant.components.light.demo import DemoLight

from homeassistant.helpers import device_registry
from tests.common import (mock_device_registry, mock_registry,
mock_area_registry)

BASIC_CONFIG = helpers.Config(
should_expose=lambda state: True,
Expand All @@ -20,6 +25,17 @@
REQ_ID = 'ff36a3cc-ec34-11e6-b1a0-64510650abcf'


@pytest.fixture
def registries(hass):
"""Registry mock setup."""
from types import SimpleNamespace
ret = SimpleNamespace()
ret.entity = mock_registry(hass)
ret.device = mock_device_registry(hass)
ret.area = mock_area_registry(hass)
return ret


async def test_sync_message(hass):
"""Test a sync message."""
light = DemoLight(
Expand Down Expand Up @@ -98,6 +114,83 @@ async def test_sync_message(hass):
}


async def test_sync_in_area(hass, registries):
"""Test a sync message where room hint comes from area."""
area = registries.area.async_create("Living Room")

device = registries.device.async_get_or_create(
config_entry_id='1234',
connections={
(device_registry.CONNECTION_NETWORK_MAC, '12:34:56:AB:CD:EF')
})
registries.device.async_update_device(device.id, area_id=area.id)

entity = registries.entity.async_get_or_create(
'light', 'test', '1235',
suggested_object_id='demo_light',
device_id=device.id)

light = DemoLight(
None, 'Demo Light',
state=False,
hs_color=(180, 75),
)
light.hass = hass
light.entity_id = entity.entity_id
await light.async_update_ha_state()

config = helpers.Config(
should_expose=lambda _: True,
allow_unlock=False,
agent_user_id='test-agent',
entity_config={}
)

events = []
hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append)

result = await sh.async_handle_message(hass, config, {
"requestId": REQ_ID,
"inputs": [{
"intent": "action.devices.SYNC"
}]
})

assert result == {
'requestId': REQ_ID,
'payload': {
'agentUserId': 'test-agent',
'devices': [{
'id': 'light.demo_light',
'name': {
'name': 'Demo Light'
},
'traits': [
trait.TRAIT_BRIGHTNESS,
trait.TRAIT_ONOFF,
trait.TRAIT_COLOR_SPECTRUM,
trait.TRAIT_COLOR_TEMP,
],
'type': sh.TYPE_LIGHT,
'willReportState': False,
'attributes': {
'colorModel': 'rgb',
'temperatureMinK': 2000,
'temperatureMaxK': 6535,
},
'roomHint': 'Living Room'
}]
}
}
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):
"""Test a sync message."""
light = DemoLight(
Expand Down Expand Up @@ -350,11 +443,12 @@ async def test_raising_error_trait(hass):
}


def test_serialize_input_boolean():
async def test_serialize_input_boolean(hass):
"""Test serializing an input boolean entity."""
state = State('input_boolean.bla', 'on')
entity = sh._GoogleEntity(None, BASIC_CONFIG, state)
assert entity.sync_serialize() == {
entity = sh._GoogleEntity(hass, BASIC_CONFIG, state)
result = await entity.sync_serialize()
assert result == {
'id': 'input_boolean.bla',
'attributes': {},
'name': {'name': 'bla'},
Expand Down