diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index bdb89dd60fa..8715f0baa96 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -31,7 +31,7 @@ SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' SERVICE_CHECK_CONFIG = 'check_config' SERVICE_UPDATE_ENTITY = 'update_entity' SCHEMA_UPDATE_ENTITY = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_id + ATTR_ENTITY_ID: cv.entity_ids }) @@ -142,8 +142,11 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: 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]) + tasks = [hass.helpers.entity_component.async_update_entity(entity) + for entity in call.data[ATTR_ENTITY_ID]] + + if tasks: + await asyncio.wait(tasks) hass.services.async_register( ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 0e92595ae78..d2b671df8b0 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181026.0'] +REQUIREMENTS = ['home-assistant-frontend==20181026.1'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 141f3c98334..c45791160ac 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -24,6 +24,7 @@ FORMAT_JSON = 'json' OLD_WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config' WS_TYPE_GET_LOVELACE_UI = 'lovelace/config' +WS_TYPE_MIGRATE_CONFIG = 'lovelace/config/migrate' WS_TYPE_GET_CARD = 'lovelace/config/card/get' WS_TYPE_UPDATE_CARD = 'lovelace/config/card/update' WS_TYPE_ADD_CARD = 'lovelace/config/card/add' @@ -33,6 +34,10 @@ SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ OLD_WS_TYPE_GET_LOVELACE_UI), }) +SCHEMA_MIGRATE_CONFIG = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_MIGRATE_CONFIG, +}) + SCHEMA_GET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_GET_CARD, vol.Required('card_id'): str, @@ -132,6 +137,11 @@ def load_yaml(fname: str) -> JSON_TYPE: def load_config(fname: str) -> JSON_TYPE: + """Load a YAML file.""" + return load_yaml(fname) + + +def migrate_config(fname: str) -> JSON_TYPE: """Load a YAML file and adds id to views and cards if not present.""" config = load_yaml(fname) # Check if all views and cards have an id or else add one @@ -243,6 +253,10 @@ async def async_setup(hass, config): OLD_WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, SCHEMA_GET_LOVELACE_UI) + hass.components.websocket_api.async_register_command( + WS_TYPE_MIGRATE_CONFIG, websocket_lovelace_migrate_config, + SCHEMA_MIGRATE_CONFIG) + hass.components.websocket_api.async_register_command( WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, SCHEMA_GET_LOVELACE_UI) @@ -286,6 +300,30 @@ async def websocket_lovelace_config(hass, connection, msg): connection.send_message(message) +@websocket_api.async_response +async def websocket_lovelace_migrate_config(hass, connection, msg): + """Migrate lovelace UI config.""" + error = None + try: + config = await hass.async_add_executor_job( + migrate_config, hass.config.path(LOVELACE_CONFIG_FILE)) + message = websocket_api.result_message( + msg['id'], config + ) + except FileNotFoundError: + error = ('file_not_found', + 'Could not find ui-lovelace.yaml in your config dir.') + except UnsupportedYamlError as err: + error = 'unsupported_error', str(err) + except HomeAssistantError as err: + error = 'load_error', str(err) + + if error is not None: + message = websocket_api.error_message(msg['id'], *error) + + connection.send_message(message) + + @websocket_api.async_response async def websocket_lovelace_get_card(hass, connection, msg): """Send lovelace card config over websocket config.""" diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index a45d9ceb0d6..1dc158bc2ab 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -109,26 +109,30 @@ async def async_setup(hass, config): return True conf = config[DOMAIN] - latitude = conf.get(CONF_LATITUDE) - longitude = conf.get(CONF_LONGITUDE) - identifier = '{0}, {1}'.format(latitude, longitude) + identifier = '{0}, {1}'.format( + conf.get(CONF_LATITUDE, hass.config.latitude), + conf.get(CONF_LONGITUDE, hass.config.longitude)) if identifier in configured_instances(hass): return True + data = { + CONF_API_KEY: conf[CONF_API_KEY], + CONF_BINARY_SENSORS: conf[CONF_BINARY_SENSORS], + CONF_SENSORS: conf[CONF_SENSORS], + CONF_SCAN_INTERVAL: conf[CONF_SCAN_INTERVAL], + } + + if CONF_LATITUDE in conf: + data[CONF_LATITUDE] = conf[CONF_LATITUDE] + if CONF_LONGITUDE in conf: + data[CONF_LONGITUDE] = conf[CONF_LONGITUDE] + if CONF_ELEVATION in conf: + data[CONF_ELEVATION] = conf[CONF_ELEVATION] + hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, - context={'source': SOURCE_IMPORT}, - data={ - CONF_API_KEY: conf[CONF_API_KEY], - CONF_LATITUDE: latitude, - CONF_LONGITUDE: longitude, - CONF_ELEVATION: conf.get(CONF_ELEVATION), - CONF_BINARY_SENSORS: conf[CONF_BINARY_SENSORS], - CONF_SENSORS: conf[CONF_SENSORS], - CONF_SCAN_INTERVAL: conf[CONF_SCAN_INTERVAL], - })) + DOMAIN, context={'source': SOURCE_IMPORT}, data=data)) return True @@ -143,10 +147,11 @@ async def async_setup_entry(hass, config_entry): openuv = OpenUV( Client( config_entry.data[CONF_API_KEY], - config_entry.data[CONF_LATITUDE], - config_entry.data[CONF_LONGITUDE], + config_entry.data.get(CONF_LATITUDE, hass.config.latitude), + config_entry.data.get(CONF_LONGITUDE, hass.config.longitude), websession, - altitude=config_entry.data[CONF_ELEVATION]), + altitude=config_entry.data.get( + CONF_ELEVATION, hass.config.elevation)), config_entry.data.get(CONF_BINARY_SENSORS, {}).get( CONF_MONITORED_CONDITIONS, list(BINARY_SENSORS)), config_entry.data.get(CONF_SENSORS, {}).get( @@ -158,8 +163,9 @@ async def async_setup_entry(hass, config_entry): raise ConfigEntryNotReady for component in ('binary_sensor', 'sensor'): - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - config_entry, component)) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup( + config_entry, component)) async def refresh(event_time): """Refresh OpenUV data.""" diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index 27ffe5c3985..11301baf5c5 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -17,7 +17,8 @@ def configured_instances(hass): """Return a set of configured OpenUV instances.""" return set( '{0}, {1}'.format( - entry.data[CONF_LATITUDE], entry.data[CONF_LONGITUDE]) + entry.data.get(CONF_LATITUDE, hass.config.latitude), + entry.data.get(CONF_LONGITUDE, hass.config.longitude)) for entry in hass.config_entries.async_entries(DOMAIN)) @@ -36,12 +37,9 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): """Show the form to the user.""" data_schema = vol.Schema({ vol.Required(CONF_API_KEY): str, - vol.Optional(CONF_LATITUDE, default=self.hass.config.latitude): - cv.latitude, - vol.Optional(CONF_LONGITUDE, default=self.hass.config.longitude): - cv.longitude, - vol.Optional(CONF_ELEVATION, default=self.hass.config.elevation): - vol.Coerce(float), + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_ELEVATION): vol.Coerce(float), }) return self.async_show_form( @@ -61,11 +59,9 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): if not user_input: return await self._show_form() - latitude = user_input[CONF_LATITUDE] - longitude = user_input[CONF_LONGITUDE] - elevation = user_input[CONF_ELEVATION] - - identifier = '{0}, {1}'.format(latitude, longitude) + identifier = '{0}, {1}'.format( + user_input.get(CONF_LATITUDE, self.hass.config.latitude), + user_input.get(CONF_LONGITUDE, self.hass.config.longitude)) if identifier in configured_instances(self.hass): return await self._show_form({CONF_LATITUDE: 'identifier_exists'}) @@ -78,11 +74,6 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): scan_interval = user_input.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) - user_input.update({ - CONF_LATITUDE: latitude, - CONF_LONGITUDE: longitude, - CONF_ELEVATION: elevation, - CONF_SCAN_INTERVAL: scan_interval.seconds, - }) + user_input[CONF_SCAN_INTERVAL] = scan_interval.seconds return self.async_create_entry(title=identifier, data=user_input) diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 62da489aab4..e8512d67fc4 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -527,6 +527,12 @@ homeassistant: entity_id: description: The entity_id of the device to turn off. example: light.living_room + update_entity: + description: Force one or more entities to update its data + fields: + entity_id: + description: One or multiple entity_ids to update. Can be a list. + example: light.living_room xiaomi_aqara: play_ringtone: diff --git a/homeassistant/components/twilio.py b/homeassistant/components/twilio.py index 9f9767e4675..b32f9ee8efc 100644 --- a/homeassistant/components/twilio.py +++ b/homeassistant/components/twilio.py @@ -34,9 +34,9 @@ CONFIG_SCHEMA = vol.Schema({ def setup(hass, config): """Set up the Twilio component.""" - from twilio.rest import TwilioRestClient + from twilio.rest import Client conf = config[DOMAIN] - hass.data[DATA_TWILIO] = TwilioRestClient( + hass.data[DATA_TWILIO] = Client( conf.get(CONF_ACCOUNT_SID), conf.get(CONF_AUTH_TOKEN)) hass.http.register_view(TwilioReceiveDataView()) return True diff --git a/homeassistant/const.py b/homeassistant/const.py index 5882bd62314..effdec4db39 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 81 -PATCH_VERSION = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) diff --git a/requirements_all.txt b/requirements_all.txt index ecb09679d42..5ad0e9aae8f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -466,7 +466,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181026.0 +home-assistant-frontend==20181026.1 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7da93c5e0bc..d7a7c761b4b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181026.0 +home-assistant-frontend==20181026.1 # homeassistant.components.homematicip_cloud homematicip==0.9.8 diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 1ce0f9ff602..c728d1c581c 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -8,8 +8,8 @@ from ruamel.yaml import YAML from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.components.websocket_api.const import TYPE_RESULT -from homeassistant.components.lovelace import (load_yaml, - save_yaml, load_config, +from homeassistant.components.lovelace import (load_yaml, migrate_config, + save_yaml, UnsupportedYamlError) TEST_YAML_A = """\ @@ -162,7 +162,7 @@ class TestYAML(unittest.TestCase): with patch('homeassistant.components.lovelace.load_yaml', return_value=self.yaml.load(TEST_YAML_A)), \ patch('homeassistant.components.lovelace.save_yaml'): - data = load_config(fname) + data = migrate_config(fname) assert 'id' in data['views'][0]['cards'][0] assert 'id' in data['views'][1] @@ -171,8 +171,8 @@ class TestYAML(unittest.TestCase): fname = self._path_for("test7") with patch('homeassistant.components.lovelace.load_yaml', return_value=self.yaml.load(TEST_YAML_B)): - data = load_config(fname) - self.assertEqual(data, self.yaml.load(TEST_YAML_B)) + data = migrate_config(fname) + assert data == self.yaml.load(TEST_YAML_B) async def test_deprecated_lovelace_ui(hass, hass_ws_client): diff --git a/tests/components/test_init.py b/tests/components/test_init.py index b9152bbdd6a..139a97463ea 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -364,7 +364,7 @@ async def test_entity_update(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' + 'entity_id': ['light.kitchen'] }, blocking=True) assert len(mock_update.mock_calls) == 1