diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 215f2a8d1e8..7e1394f7d03 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -157,10 +157,10 @@ def async_remote_ui_url(hass: HomeAssistant) -> str: if not hass.data[DOMAIN].client.prefs.remote_enabled: raise CloudNotAvailable - if not hass.data[DOMAIN].remote.instance_domain: + if not (remote_domain := hass.data[DOMAIN].client.prefs.remote_domain): raise CloudNotAvailable - return f"https://{hass.data[DOMAIN].remote.instance_domain}" + return f"https://{remote_domain}" def is_cloudhook_request(request): @@ -235,7 +235,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: Platform.TTS, DOMAIN, {}, config ) + async def _on_initialized(): + """Update preferences.""" + await prefs.async_update(remote_domain=cloud.remote.instance_domain) + cloud.iot.register_on_connect(_on_connect) + cloud.register_on_initialized(_on_initialized) await cloud.initialize() await http_api.async_setup(hass) diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index ce3ecf6271d..ea0240acccf 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -18,6 +18,7 @@ PREF_ALIASES = "aliases" PREF_SHOULD_EXPOSE = "should_expose" PREF_GOOGLE_LOCAL_WEBHOOK_ID = "google_local_webhook_id" PREF_USERNAME = "username" +PREF_REMOTE_DOMAIN = "remote_domain" PREF_ALEXA_DEFAULT_EXPOSE = "alexa_default_expose" PREF_GOOGLE_DEFAULT_EXPOSE = "google_default_expose" PREF_TTS_DEFAULT_VOICE = "tts_default_voice" diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 0bb00cd5ced..b83c4c4cca9 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.51.0"], + "requirements": ["hass-nabucasa==0.52.0"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index 3cc6e8deb60..e6747c42c45 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -30,6 +30,7 @@ from .const import ( PREF_GOOGLE_REPORT_STATE, PREF_GOOGLE_SECURE_DEVICES_PIN, PREF_OVERRIDE_NAME, + PREF_REMOTE_DOMAIN, PREF_SHOULD_EXPOSE, PREF_TTS_DEFAULT_VOICE, PREF_USERNAME, @@ -85,6 +86,7 @@ class CloudPreferences: alexa_default_expose=UNDEFINED, google_default_expose=UNDEFINED, tts_default_voice=UNDEFINED, + remote_domain=UNDEFINED, ): """Update user preferences.""" prefs = {**self._prefs} @@ -103,6 +105,7 @@ class CloudPreferences: (PREF_ALEXA_DEFAULT_EXPOSE, alexa_default_expose), (PREF_GOOGLE_DEFAULT_EXPOSE, google_default_expose), (PREF_TTS_DEFAULT_VOICE, tts_default_voice), + (PREF_REMOTE_DOMAIN, remote_domain), ): if value is not UNDEFINED: prefs[key] = value @@ -208,6 +211,11 @@ class CloudPreferences: return True + @property + def remote_domain(self): + """Return remote domain.""" + return self._prefs.get(PREF_REMOTE_DOMAIN) + @property def alexa_enabled(self): """Return if Alexa is enabled.""" @@ -321,5 +329,6 @@ class CloudPreferences: PREF_GOOGLE_ENTITY_CONFIGS: {}, PREF_GOOGLE_LOCAL_WEBHOOK_ID: webhook.async_generate_id(), PREF_GOOGLE_SECURE_DEVICES_PIN: None, + PREF_REMOTE_DOMAIN: None, PREF_USERNAME: username, } diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 26a2d930d18..c292ea63068 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -84,6 +84,8 @@ async def websocket_get_entity(hass, connection, msg): vol.Coerce(RegistryEntryDisabler), RegistryEntryDisabler.USER.value ), ), + vol.Inclusive("options_domain", "entity_option"): str, + vol.Inclusive("options", "entity_option"): vol.Any(None, dict), } ) async def websocket_update_entity(hass, connection, msg): @@ -93,7 +95,8 @@ async def websocket_update_entity(hass, connection, msg): """ registry = await async_get_registry(hass) - if msg["entity_id"] not in registry.entities: + entity_id = msg["entity_id"] + if entity_id not in registry.entities: connection.send_message( websocket_api.error_message(msg["id"], ERR_NOT_FOUND, "Entity not found") ) @@ -105,7 +108,7 @@ async def websocket_update_entity(hass, connection, msg): if key in msg: changes[key] = msg[key] - if "new_entity_id" in msg and msg["new_entity_id"] != msg["entity_id"]: + if "new_entity_id" in msg and msg["new_entity_id"] != entity_id: changes["new_entity_id"] = msg["new_entity_id"] if hass.states.get(msg["new_entity_id"]) is not None: connection.send_message( @@ -118,7 +121,7 @@ async def websocket_update_entity(hass, connection, msg): return if "disabled_by" in msg and msg["disabled_by"] is None: - entity = registry.entities[msg["entity_id"]] + entity = registry.entities[entity_id] if entity.device_id: device_registry = await hass.helpers.device_registry.async_get_registry() device = device_registry.async_get(entity.device_id) @@ -132,12 +135,28 @@ async def websocket_update_entity(hass, connection, msg): try: if changes: - entry = registry.async_update_entity(msg["entity_id"], **changes) + registry.async_update_entity(entity_id, **changes) except ValueError as err: connection.send_message( websocket_api.error_message(msg["id"], "invalid_info", str(err)) ) return + + if "new_entity_id" in msg: + entity_id = msg["new_entity_id"] + + try: + if "options_domain" in msg: + registry.async_update_entity_options( + entity_id, msg["options_domain"], msg["options"] + ) + except ValueError as err: + connection.send_message( + websocket_api.error_message(msg["id"], "invalid_info", str(err)) + ) + return + + entry = registry.async_get(entity_id) result = {"entity_entry": _entry_ext_dict(entry)} if "disabled_by" in changes and changes["disabled_by"] is None: config_entry = hass.config_entries.async_get_entry(entry.config_entry_id) @@ -195,6 +214,7 @@ def _entry_ext_dict(entry): data = _entry_dict(entry) data["capabilities"] = entry.capabilities data["device_class"] = entry.device_class + data["options"] = entry.options data["original_device_class"] = entry.original_device_class data["original_icon"] = entry.original_icon data["original_name"] = entry.original_name diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index eac78fd63e0..71907d9d5aa 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ certifi>=2021.5.30 ciso8601==2.2.0 cryptography==35.0.0 emoji==1.5.0 -hass-nabucasa==0.51.0 +hass-nabucasa==0.52.0 home-assistant-frontend==20220118.0 httpx==0.21.0 ifaddr==0.1.7 diff --git a/requirements_all.txt b/requirements_all.txt index 67aef0c210d..0056681658b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -809,7 +809,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.51.0 +hass-nabucasa==0.52.0 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0f0f1b2c27e..6cee1437298 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -522,7 +522,7 @@ habitipy==0.2.0 hangups==0.4.17 # homeassistant.components.cloud -hass-nabucasa==0.51.0 +hass-nabucasa==0.52.0 # homeassistant.components.tasmota hatasmota==0.3.1 diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index f4ca4cbd75a..4a513aff117 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -171,6 +171,7 @@ async def test_remote_ui_url(hass, mock_cloud_fixture): with pytest.raises(cloud.CloudNotAvailable): cloud.async_remote_ui_url(hass) - cl.remote._instance_domain = "example.com" + # Remote finished initializing + cl.client.prefs._prefs["remote_domain"] = "example.com" assert cloud.async_remote_ui_url(hass) == "https://example.com" diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py index b4065d855ff..49e47cce6e2 100644 --- a/tests/components/config/test_entity_registry.py +++ b/tests/components/config/test_entity_registry.py @@ -111,6 +111,7 @@ async def test_get_entity(hass, client): "entity_id": "test_domain.name", "icon": None, "name": "Hello World", + "options": {}, "original_device_class": None, "original_icon": None, "original_name": None, @@ -138,6 +139,7 @@ async def test_get_entity(hass, client): "entity_id": "test_domain.no_name", "icon": None, "name": None, + "options": {}, "original_device_class": None, "original_icon": None, "original_name": None, @@ -197,6 +199,7 @@ async def test_update_entity(hass, client): "entity_id": "test_domain.world", "icon": "icon:after update", "name": "after update", + "options": {}, "original_device_class": None, "original_icon": None, "original_name": None, @@ -250,6 +253,7 @@ async def test_update_entity(hass, client): "entity_id": "test_domain.world", "icon": "icon:after update", "name": "after update", + "options": {}, "original_device_class": None, "original_icon": None, "original_name": None, @@ -259,6 +263,40 @@ async def test_update_entity(hass, client): "reload_delay": 30, } + # UPDATE ENTITY OPTION + await client.send_json( + { + "id": 9, + "type": "config/entity_registry/update", + "entity_id": "test_domain.world", + "options_domain": "sensor", + "options": {"unit_of_measurement": "beard_second"}, + } + ) + + msg = await client.receive_json() + + assert msg["result"] == { + "entity_entry": { + "area_id": "mock-area-id", + "capabilities": None, + "config_entry_id": None, + "device_class": "custom_device_class", + "device_id": None, + "disabled_by": None, + "entity_category": None, + "entity_id": "test_domain.world", + "icon": "icon:after update", + "name": "after update", + "options": {"sensor": {"unit_of_measurement": "beard_second"}}, + "original_device_class": None, + "original_icon": None, + "original_name": None, + "platform": "test_platform", + "unique_id": "1234", + }, + } + async def test_update_entity_require_restart(hass, client): """Test updating entity.""" @@ -307,6 +345,7 @@ async def test_update_entity_require_restart(hass, client): "entity_id": "test_domain.world", "icon": None, "name": None, + "options": {}, "original_device_class": None, "original_icon": None, "original_name": None, @@ -411,6 +450,7 @@ async def test_update_entity_no_changes(hass, client): "entity_id": "test_domain.world", "icon": None, "name": "name of entity", + "options": {}, "original_device_class": None, "original_icon": None, "original_name": None, @@ -494,6 +534,7 @@ async def test_update_entity_id(hass, client): "entity_id": "test_domain.planet", "icon": None, "name": None, + "options": {}, "original_device_class": None, "original_icon": None, "original_name": None,