From f61f6504954a69edeebf571de4f826cd9c4f72d9 Mon Sep 17 00:00:00 2001 From: Penny Wood Date: Sat, 2 Mar 2019 15:31:57 +0800 Subject: [PATCH] Get room hints from areas (#21519) * Get google room hint from area. * Test case for area code. * Updates as per code review. --- .../components/google_assistant/smart_home.py | 30 ++++-- .../google_assistant/test_smart_home.py | 100 +++++++++++++++++- 2 files changed, 121 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 21316c62085..31323decd6c 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -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 @@ -89,8 +90,7 @@ class _GoogleEntity: 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 @@ -132,13 +132,31 @@ class _GoogleEntity: 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 @@ -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) diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index d1ec80844b6..76fb7b5ddde 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -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) @@ -11,6 +13,9 @@ from homeassistant.components.google_assistant import ( 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, @@ -20,6 +25,17 @@ BASIC_CONFIG = helpers.Config( 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( @@ -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( @@ -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'},