Manual updates (#17278)

pull/17221/merge
Paulus Schoutsen 2018-10-09 16:54:38 +02:00 committed by Pascal Vizeli
parent cf249e3e5e
commit e903f7ffda
5 changed files with 72 additions and 1 deletions

View File

@ -12,6 +12,8 @@ import itertools as it
import logging import logging
from typing import Awaitable from typing import Awaitable
import voluptuous as vol
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.config as conf_util import homeassistant.config as conf_util
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -21,11 +23,16 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
RESTART_EXIT_CODE) RESTART_EXIT_CODE)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
SERVICE_CHECK_CONFIG = 'check_config' SERVICE_CHECK_CONFIG = 'check_config'
SERVICE_UPDATE_ENTITY = 'update_entity'
SCHEMA_UPDATE_ENTITY = vol.Schema({
ATTR_ENTITY_ID: cv.entity_id
})
def is_on(hass, entity_id=None): def is_on(hass, entity_id=None):
@ -133,12 +140,20 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
if call.service == SERVICE_HOMEASSISTANT_RESTART: if call.service == SERVICE_HOMEASSISTANT_RESTART:
hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE)) hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE))
async def async_handle_update_service(call):
"""Service handler for updating an entity."""
await hass.helpers.entity_component.async_update_entity(
call.data[ATTR_ENTITY_ID])
hass.services.async_register( hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
hass.services.async_register( hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service) ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service)
hass.services.async_register( hass.services.async_register(
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service) ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_UPDATE_ENTITY, async_handle_update_service,
schema=SCHEMA_UPDATE_ENTITY)
async def async_handle_reload_config(call): async def async_handle_reload_config(call):
"""Service handler for reloading core config.""" """Service handler for reloading core config."""

View File

@ -346,7 +346,7 @@ class Entity:
if hasattr(self, 'async_update'): if hasattr(self, 'async_update'):
await self.async_update() await self.async_update()
elif hasattr(self, 'update'): elif hasattr(self, 'update'):
await self.hass.async_add_job(self.update) await self.hass.async_add_executor_job(self.update)
finally: finally:
self._update_staged = False self._update_staged = False
if warning: if warning:

View File

@ -2,6 +2,7 @@
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
from itertools import chain from itertools import chain
import logging
from homeassistant import config as conf_util from homeassistant import config as conf_util
from homeassistant.setup import async_prepare_setup_platform from homeassistant.setup import async_prepare_setup_platform
@ -11,10 +12,33 @@ from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform, discovery from homeassistant.helpers import config_per_platform, discovery
from homeassistant.helpers.service import extract_entity_ids from homeassistant.helpers.service import extract_entity_ids
from homeassistant.loader import bind_hass
from homeassistant.util import slugify from homeassistant.util import slugify
from .entity_platform import EntityPlatform from .entity_platform import EntityPlatform
DEFAULT_SCAN_INTERVAL = timedelta(seconds=15) DEFAULT_SCAN_INTERVAL = timedelta(seconds=15)
DATA_INSTANCES = 'entity_components'
@bind_hass
async def async_update_entity(hass, entity_id):
"""Trigger an update for an entity."""
domain = entity_id.split('.', 1)[0]
entity_comp = hass.data.get(DATA_INSTANCES, {}).get(domain)
if entity_comp is None:
logging.getLogger(__name__).warning(
'Forced update failed. Component for %s not loaded.', entity_id)
return
entity = entity_comp.get_entity(entity_id)
if entity is None:
logging.getLogger(__name__).warning(
'Forced update failed. Entity %s not found.', entity_id)
return
await entity.async_update_ha_state(True)
class EntityComponent: class EntityComponent:
@ -45,6 +69,8 @@ class EntityComponent:
self.async_add_entities = self._platforms[domain].async_add_entities self.async_add_entities = self._platforms[domain].async_add_entities
self.add_entities = self._platforms[domain].add_entities self.add_entities = self._platforms[domain].add_entities
hass.data.setdefault(DATA_INSTANCES, {})[domain] = self
@property @property
def entities(self): def entities(self):
"""Return an iterable that returns all entities.""" """Return an iterable that returns all entities."""

View File

@ -355,3 +355,17 @@ async def test_turn_on_to_not_block_for_domains_without_service(hass):
'light', 'turn_on', {'entity_id': ['light.bla', 'light.test']}, True) 'light', 'turn_on', {'entity_id': ['light.bla', 'light.test']}, True)
assert mock_call.call_args_list[1][0] == ( assert mock_call.call_args_list[1][0] == (
'sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False) 'sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False)
async def test_entity_update(hass):
"""Test being able to call entity update."""
await comps.async_setup(hass, {})
with patch('homeassistant.helpers.entity_component.async_update_entity',
return_value=mock_coro()) as mock_update:
await hass.services.async_call('homeassistant', 'update_entity', {
'entity_id': 'light.kitchen'
}, blocking=True)
assert len(mock_update.mock_calls) == 1
assert mock_update.mock_calls[0][1][1] == 'light.kitchen'

View File

@ -415,3 +415,19 @@ async def test_unload_entry_fails_if_never_loaded(hass):
with pytest.raises(ValueError): with pytest.raises(ValueError):
await component.async_unload_entry(entry) await component.async_unload_entry(entry)
async def test_update_entity(hass):
"""Test that we can update an entity with the helper."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
entity = MockEntity()
entity.async_update_ha_state = Mock(return_value=mock_coro())
await component.async_add_entities([entity])
# Called as part of async_add_entities
assert len(entity.async_update_ha_state.mock_calls) == 1
await hass.helpers.entity_component.async_update_entity(entity.entity_id)
assert len(entity.async_update_ha_state.mock_calls) == 2
assert entity.async_update_ha_state.mock_calls[-1][1][0] is True