From 966df6a4116e7808b0d4199d6eb343db14cf35cc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 8 Feb 2020 17:26:58 -0800 Subject: [PATCH 01/11] Guard writing automation/scene/script config (#31568) --- homeassistant/components/config/__init__.py | 28 ++++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index ad7ae14ecb7..682e23dd14c 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -94,6 +94,7 @@ class BaseEditConfigView(HomeAssistantView): self.data_schema = data_schema self.post_write_hook = post_write_hook self.data_validator = data_validator + self.mutation_lock = asyncio.Lock() def _empty_config(self): """Empty config if file not found.""" @@ -114,8 +115,9 @@ class BaseEditConfigView(HomeAssistantView): async def get(self, request, config_key): """Fetch device specific config.""" hass = request.app["hass"] - current = await self.read_config(hass) - value = self._get_value(hass, current, config_key) + async with self.mutation_lock: + current = await self.read_config(hass) + value = self._get_value(hass, current, config_key) if value is None: return self.json_message("Resource not found", 404) @@ -148,10 +150,11 @@ class BaseEditConfigView(HomeAssistantView): path = hass.config.path(self.path) - current = await self.read_config(hass) - self._write_value(hass, current, config_key, data) + async with self.mutation_lock: + current = await self.read_config(hass) + self._write_value(hass, current, config_key, data) - await hass.async_add_executor_job(_write, path, current) + await hass.async_add_executor_job(_write, path, current) if self.post_write_hook is not None: hass.async_create_task( @@ -163,15 +166,16 @@ class BaseEditConfigView(HomeAssistantView): async def delete(self, request, config_key): """Remove an entry.""" hass = request.app["hass"] - current = await self.read_config(hass) - value = self._get_value(hass, current, config_key) - path = hass.config.path(self.path) + async with self.mutation_lock: + current = await self.read_config(hass) + value = self._get_value(hass, current, config_key) + path = hass.config.path(self.path) - if value is None: - return self.json_message("Resource not found", 404) + if value is None: + return self.json_message("Resource not found", 404) - self._delete_value(hass, current, config_key) - await hass.async_add_executor_job(_write, path, current) + self._delete_value(hass, current, config_key) + await hass.async_add_executor_job(_write, path, current) if self.post_write_hook is not None: hass.async_create_task(self.post_write_hook(ACTION_DELETE, config_key)) From 295963f8e8c554bf8b614dae06aca95ccc99ade6 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 10 Feb 2020 17:54:52 -0500 Subject: [PATCH 02/11] For vizio integration, set unique ID early to prevent multiple zeroconf discovery items for the same device to appear (#31686) * set unique ID early to prevent multiple zeroconf discovery items for the same device to appear * add test --- homeassistant/components/vizio/config_flow.py | 5 ++++ tests/components/vizio/test_config_flow.py | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 04f70da4a8c..4fba0f06165 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -205,6 +205,11 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -> Dict[str, Any]: """Handle zeroconf discovery.""" + # Set unique ID early to prevent device from getting rediscovered multiple times + await self.async_set_unique_id( + unique_id=discovery_info[CONF_HOST].split(":")[0], raise_on_progress=True + ) + discovery_info[ CONF_HOST ] = f"{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}" diff --git a/tests/components/vizio/test_config_flow.py b/tests/components/vizio/test_config_flow.py index cf6cdb6afdb..044ca11bc8d 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -506,3 +506,29 @@ async def test_zeroconf_flow_already_configured( # Flow should abort because device is already setup assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_setup" + + +async def test_zeroconf_dupe_fail( + hass: HomeAssistantType, + vizio_connect: pytest.fixture, + vizio_bypass_setup: pytest.fixture, + vizio_guess_device_type: pytest.fixture, +) -> None: + """Test zeroconf config flow when device gets discovered multiple times.""" + discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info + ) + + # Form should always show even if all required properties are discovered + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info + ) + + # Flow should abort because device is already setup + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_in_progress" From 7edf0460cc5ecb02d6e75b731a69f04fee44016e Mon Sep 17 00:00:00 2001 From: cgtobi Date: Wed, 12 Feb 2020 03:02:13 +0100 Subject: [PATCH 03/11] Fix missing device class in netatmo binary sensors (#31693) * Bring back device class * Add door tag sensors types * Actually discover individual tags per camera --- .../components/netatmo/binary_sensor.py | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/netatmo/binary_sensor.py b/homeassistant/components/netatmo/binary_sensor.py index 6d0de6dcceb..5f419bda2c2 100644 --- a/homeassistant/components/netatmo/binary_sensor.py +++ b/homeassistant/components/netatmo/binary_sensor.py @@ -24,7 +24,10 @@ PRESENCE_SENSOR_TYPES = { } TAG_SENSOR_TYPES = {"Tag Vibration": "vibration", "Tag Open": "opening"} -SENSOR_TYPES = {"NACamera": WELCOME_SENSOR_TYPES, "NOC": PRESENCE_SENSOR_TYPES} +SENSOR_TYPES = { + "NACamera": WELCOME_SENSOR_TYPES, + "NOC": PRESENCE_SENSOR_TYPES, +} CONF_HOME = "home" CONF_CAMERAS = "cameras" @@ -61,12 +64,28 @@ async def async_setup_entry(hass, entry, async_add_entities): sensor_types.update(SENSOR_TYPES[camera["type"]]) # Tags are only supported with Netatmo Welcome indoor cameras - if camera["type"] == "NACamera" and data.get_modules(camera["id"]): - sensor_types.update(TAG_SENSOR_TYPES) + modules = data.get_modules(camera["id"]) + if camera["type"] == "NACamera" and modules: + for module in modules: + for sensor_type in TAG_SENSOR_TYPES: + _LOGGER.debug( + "Adding camera tag %s (%s)", + module["name"], + module["id"], + ) + entities.append( + NetatmoBinarySensor( + data, + camera["id"], + home_id, + sensor_type, + module["id"], + ) + ) - for sensor_name in sensor_types: + for sensor_type in sensor_types: entities.append( - NetatmoBinarySensor(data, camera["id"], home_id, sensor_name) + NetatmoBinarySensor(data, camera["id"], home_id, sensor_type) ) except pyatmo.NoDevice: _LOGGER.debug("No camera entities to add") @@ -115,6 +134,15 @@ class NetatmoBinarySensor(BinarySensorDevice): """Return the unique ID for this sensor.""" return self._unique_id + @property + def device_class(self): + """Return the class of this sensor.""" + if self._camera_type == "NACamera": + return WELCOME_SENSOR_TYPES.get(self._sensor_type) + if self._camera_type == "NOC": + return PRESENCE_SENSOR_TYPES.get(self._sensor_type) + return TAG_SENSOR_TYPES.get(self._sensor_type) + @property def device_info(self): """Return the device info for the sensor.""" From 30e302a8a3ddf35791e458f8528fcb94c8502d9d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 11 Feb 2020 00:07:46 -0800 Subject: [PATCH 04/11] Fix person reload service (#31716) --- homeassistant/components/person/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index dabcc046f7a..a1620c578e3 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -315,7 +315,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): conf = await entity_component.async_prepare_reload(skip_reset=True) if conf is None: return - await yaml_collection.async_load(await filter_yaml_data(hass, conf[DOMAIN])) + await yaml_collection.async_load( + await filter_yaml_data(hass, conf.get(DOMAIN, [])) + ) service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, async_reload_yaml From 656c901f7b26234f9ddad6fb7ded99ece065abe1 Mon Sep 17 00:00:00 2001 From: SukramJ Date: Wed, 12 Feb 2020 19:12:26 +0100 Subject: [PATCH 05/11] Fix smoke detection for HomematicIP Cloud (#31753) --- homeassistant/components/homematicip_cloud/binary_sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index f16dfc986f0..52a4583be46 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -227,7 +227,11 @@ class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice): @property def is_on(self) -> bool: """Return true if smoke is detected.""" - return self._device.smokeDetectorAlarmType != SmokeDetectorAlarmType.IDLE_OFF + if self._device.smokeDetectorAlarmType: + return ( + self._device.smokeDetectorAlarmType != SmokeDetectorAlarmType.IDLE_OFF + ) + return False class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice): From f042c6f8d577cddc7b1b3485451e803568be7216 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 12 Feb 2020 23:53:26 +0100 Subject: [PATCH 06/11] Updated frontend to 20200130.3 (#31771) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 8c54d20429c..10d2884f182 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20200130.2" + "home-assistant-frontend==20200130.3" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index af83672603b..50b9a4398dd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ cryptography==2.8 defusedxml==0.6.0 distro==1.4.0 hass-nabucasa==0.31 -home-assistant-frontend==20200130.2 +home-assistant-frontend==20200130.3 importlib-metadata==1.4.0 jinja2>=2.10.3 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 79d7d694f82..b3346d8fa0f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -679,7 +679,7 @@ hole==0.5.0 holidays==0.9.12 # homeassistant.components.frontend -home-assistant-frontend==20200130.2 +home-assistant-frontend==20200130.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3c4e17ac465..6cb9a63a208 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -247,7 +247,7 @@ hole==0.5.0 holidays==0.9.12 # homeassistant.components.frontend -home-assistant-frontend==20200130.2 +home-assistant-frontend==20200130.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.8 From af9832d46826c799107d020ca3b657c80ae8c58a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 14 Feb 2020 15:27:31 -0800 Subject: [PATCH 07/11] Fix person device_trackers null (#31829) --- homeassistant/components/person/__init__.py | 39 ++++++++++++++++----- homeassistant/helpers/collection.py | 6 +++- tests/components/person/test_init.py | 14 +++++++- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index a1620c578e3..9cd3e882c48 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -88,7 +88,11 @@ _UNDEF = object() async def async_create_person(hass, name, *, user_id=None, device_trackers=None): """Create a new person.""" await hass.data[DOMAIN][1].async_create_item( - {ATTR_NAME: name, ATTR_USER_ID: user_id, "device_trackers": device_trackers} + { + ATTR_NAME: name, + ATTR_USER_ID: user_id, + CONF_DEVICE_TRACKERS: device_trackers or [], + } ) @@ -103,14 +107,14 @@ async def async_add_user_device_tracker( if person.get(ATTR_USER_ID) != user_id: continue - device_trackers = person["device_trackers"] + device_trackers = person[CONF_DEVICE_TRACKERS] if device_tracker_entity_id in device_trackers: return await coll.async_update_item( person[collection.CONF_ID], - {"device_trackers": device_trackers + [device_tracker_entity_id]}, + {CONF_DEVICE_TRACKERS: device_trackers + [device_tracker_entity_id]}, ) break @@ -161,6 +165,23 @@ class PersonStorageCollection(collection.StorageCollection): super().__init__(store, logger, id_manager) self.yaml_collection = yaml_collection + async def _async_load_data(self) -> Optional[dict]: + """Load the data. + + A past bug caused onboarding to create invalid person objects. + This patches it up. + """ + data = await super()._async_load_data() + + if data is None: + return data + + for person in data["items"]: + if person[CONF_DEVICE_TRACKERS] is None: + person[CONF_DEVICE_TRACKERS] = [] + + return data + async def async_load(self) -> None: """Load the Storage collection.""" await super().async_load() @@ -179,14 +200,16 @@ class PersonStorageCollection(collection.StorageCollection): return for person in list(self.data.values()): - if entity_id not in person["device_trackers"]: + if entity_id not in person[CONF_DEVICE_TRACKERS]: continue await self.async_update_item( person[collection.CONF_ID], { - "device_trackers": [ - devt for devt in person["device_trackers"] if devt != entity_id + CONF_DEVICE_TRACKERS: [ + devt + for devt in person[CONF_DEVICE_TRACKERS] + if devt != entity_id ] }, ) @@ -408,7 +431,7 @@ class Person(RestoreEntity): self._unsub_track_device() self._unsub_track_device = None - trackers = self._config.get(CONF_DEVICE_TRACKERS) + trackers = self._config[CONF_DEVICE_TRACKERS] if trackers: _LOGGER.debug("Subscribe to device trackers for %s", self.entity_id) @@ -428,7 +451,7 @@ class Person(RestoreEntity): def _update_state(self): """Update the state.""" latest_non_gps_home = latest_not_home = latest_gps = latest = None - for entity_id in self._config.get(CONF_DEVICE_TRACKERS, []): + for entity_id in self._config[CONF_DEVICE_TRACKERS]: state = self.hass.states.get(entity_id) if not state or state.state in IGNORE_STATES: diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 1b3721788f5..025c6c07dee 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -158,9 +158,13 @@ class StorageCollection(ObservableCollection): """Home Assistant object.""" return self.store.hass + async def _async_load_data(self) -> Optional[dict]: + """Load the data.""" + return cast(Optional[dict], await self.store.async_load()) + async def async_load(self) -> None: """Load the storage Manager.""" - raw_storage = cast(Optional[dict], await self.store.async_load()) + raw_storage = await self._async_load_data() if raw_storage is None: raw_storage = {"items": []} diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index e5a414d95ad..76350619983 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -1,7 +1,7 @@ """The tests for the person component.""" import logging -from unittest.mock import patch +from asynctest import patch import pytest from homeassistant.components import person @@ -773,3 +773,15 @@ async def test_reload(hass, hass_admin_user): assert state_2 is None assert state_3 is not None assert state_3.name == "Person 3" + + +async def test_person_storage_fixing_device_trackers(storage_collection): + """Test None device trackers become lists.""" + with patch.object( + storage_collection.store, + "async_load", + return_value={"items": [{"id": "bla", "name": "bla", "device_trackers": None}]}, + ): + await storage_collection.async_load() + + assert storage_collection.data["bla"]["device_trackers"] == [] From 7705eb7941c6d25ce9ef273cad7144ba817f3308 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 14 Feb 2020 15:28:11 -0800 Subject: [PATCH 08/11] Google Assistant: Remove speaker type and earlier filter out devices from being locally exposed (#31830) * Remove speaker type * Do not expose locks or alarms to Google Local --- .../components/google_assistant/const.py | 3 ++- .../components/google_assistant/helpers.py | 15 ++++++++++++++- .../components/google_assistant/smart_home.py | 4 +--- tests/components/google_assistant/test_helpers.py | 10 ++++++++++ .../google_assistant/test_smart_home.py | 1 - 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index add625d2de4..c9f8d857b62 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -133,7 +133,6 @@ DEVICE_CLASS_TO_GOOGLE_TYPES = { (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_OPENING): TYPE_SENSOR, (binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_WINDOW): TYPE_SENSOR, (media_player.DOMAIN, media_player.DEVICE_CLASS_TV): TYPE_TV, - (media_player.DOMAIN, media_player.DEVICE_CLASS_SPEAKER): TYPE_SPEAKER, (sensor.DOMAIN, sensor.DEVICE_CLASS_TEMPERATURE): TYPE_SENSOR, (sensor.DOMAIN, sensor.DEVICE_CLASS_HUMIDITY): TYPE_SENSOR, } @@ -146,3 +145,5 @@ STORE_AGENT_USER_IDS = "agent_user_ids" SOURCE_CLOUD = "cloud" SOURCE_LOCAL = "local" + +NOT_EXPOSE_LOCAL = {TYPE_ALARM, TYPE_LOCK} diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index f1b7a89bffe..6ba301c01e8 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -28,6 +28,7 @@ from .const import ( DOMAIN, DOMAIN_TO_GOOGLE_TYPES, ERR_FUNCTION_NOT_SUPPORTED, + NOT_EXPOSE_LOCAL, SOURCE_LOCAL, STORE_AGENT_USER_IDS, ) @@ -351,6 +352,18 @@ class GoogleEntity: """If entity should be exposed.""" return self.config.should_expose(self.state) + @callback + def should_expose_local(self) -> bool: + """Return if the entity should be exposed locally.""" + return ( + self.should_expose() + and get_google_type( + self.state.domain, self.state.attributes.get(ATTR_DEVICE_CLASS) + ) + not in NOT_EXPOSE_LOCAL + and not self.might_2fa() + ) + @callback def is_supported(self) -> bool: """Return if the entity is supported by Google.""" @@ -401,7 +414,7 @@ class GoogleEntity: if aliases: device["name"]["nicknames"] = [name] + aliases - if self.config.is_local_sdk_active: + if self.config.is_local_sdk_active and self.should_expose_local(): device["otherDeviceIds"] = [{"deviceId": self.entity_id}] device["customData"] = { "webhookId": self.config.local_sdk_webhook_id, diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index bf6c32505aa..97c872bdaf8 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -243,9 +243,7 @@ async def async_devices_reachable(hass, data: RequestData, payload): "devices": [ entity.reachable_device_serialize() for entity in async_get_entities(hass, data.config) - if entity.entity_id in google_ids - and entity.should_expose() - and not entity.might_2fa() + if entity.entity_id in google_ids and entity.should_expose_local() ] } diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index 9c8a868e68d..8d2aaa63c48 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -7,6 +7,7 @@ import pytest from homeassistant.components.google_assistant import helpers from homeassistant.components.google_assistant.const import ( # noqa: F401 EVENT_COMMAND_RECEIVED, + NOT_EXPOSE_LOCAL, ) from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -46,6 +47,15 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass): "webhookId": "mock-webhook-id", } + for device_type in NOT_EXPOSE_LOCAL: + with patch( + "homeassistant.components.google_assistant.helpers.get_google_type", + return_value=device_type, + ): + serialized = await entity.sync_serialize(None) + assert "otherDeviceIds" not in serialized + assert "customData" not in serialized + async def test_config_local_sdk(hass, hass_client): """Test the local SDK.""" diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index aa073c699f8..7e98f162f22 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -682,7 +682,6 @@ async def test_device_class_cover(hass, device_class, google_type): "device_class,google_type", [ ("non_existing_class", "action.devices.types.SWITCH"), - ("speaker", "action.devices.types.SPEAKER"), ("tv", "action.devices.types.TV"), ], ) From f26cbbdef9a097f9172c4eba869ef43f3a23afdc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 14 Feb 2020 22:58:39 +0100 Subject: [PATCH 09/11] Spotify integration hotfixes (#31835) * Remove services file, incorrect info * Guard currently playing for being a NoneType * Revert "Guard currently playing for being a NoneType" This reverts commit f5f56b0db03b407e058d45cd3549af1388916e06. * Guard currently playing item is None * Process review suggestions --- homeassistant/components/spotify/media_player.py | 9 ++++++--- homeassistant/components/spotify/services.yaml | 9 --------- 2 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 homeassistant/components/spotify/services.yaml diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index 8bd5782f7ee..9588f428a66 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -157,7 +157,8 @@ class SpotifyMediaPlayer(MediaPlayerDevice): @property def media_content_id(self) -> Optional[str]: """Return the media URL.""" - return self._currently_playing.get("item", {}).get("name") + item = self._currently_playing.get("item") or {} + return item.get("name") @property def media_content_type(self) -> Optional[str]: @@ -203,7 +204,8 @@ class SpotifyMediaPlayer(MediaPlayerDevice): @property def media_title(self) -> Optional[str]: """Return the media title.""" - return self._currently_playing.get("item", {}).get("name") + item = self._currently_playing.get("item") or {} + return item.get("name") @property def media_artist(self) -> Optional[str]: @@ -224,7 +226,8 @@ class SpotifyMediaPlayer(MediaPlayerDevice): @property def media_track(self) -> Optional[int]: """Track number of current playing media, music track only.""" - return self._currently_playing.get("item", {}).get("track_number") + item = self._currently_playing.get("item") or {} + return item.get("track_number") @property def media_playlist(self): diff --git a/homeassistant/components/spotify/services.yaml b/homeassistant/components/spotify/services.yaml deleted file mode 100644 index e532f736652..00000000000 --- a/homeassistant/components/spotify/services.yaml +++ /dev/null @@ -1,9 +0,0 @@ -play_playlist: - description: Play a Spotify playlist. - fields: - media_content_id: - description: Spotify URI of the playlist. - example: 'spotify:playlist:0IpRnqCHSjun48oQRX1Dy7' - random_song: - description: True to select random song at start, False to start from beginning. - example: true \ No newline at end of file From 8140c033fa23584da130c077c0f5aeafe36a7734 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 14 Feb 2020 15:30:23 -0800 Subject: [PATCH 10/11] Bumped version to 0.105.4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 4ca501b1d23..bdd22081c29 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 105 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 350726d938a525b9d89c2500a9a59572a58d1c4c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 14 Feb 2020 15:47:14 -0800 Subject: [PATCH 11/11] Revert "For vizio integration, set unique ID early to prevent multiple zeroconf discovery items for the same device to appear (#31686)" This reverts commit 295963f8e8c554bf8b614dae06aca95ccc99ade6. --- homeassistant/components/vizio/config_flow.py | 5 ---- tests/components/vizio/test_config_flow.py | 26 ------------------- 2 files changed, 31 deletions(-) diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 4fba0f06165..04f70da4a8c 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -205,11 +205,6 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -> Dict[str, Any]: """Handle zeroconf discovery.""" - # Set unique ID early to prevent device from getting rediscovered multiple times - await self.async_set_unique_id( - unique_id=discovery_info[CONF_HOST].split(":")[0], raise_on_progress=True - ) - discovery_info[ CONF_HOST ] = f"{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}" diff --git a/tests/components/vizio/test_config_flow.py b/tests/components/vizio/test_config_flow.py index 044ca11bc8d..cf6cdb6afdb 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -506,29 +506,3 @@ async def test_zeroconf_flow_already_configured( # Flow should abort because device is already setup assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_setup" - - -async def test_zeroconf_dupe_fail( - hass: HomeAssistantType, - vizio_connect: pytest.fixture, - vizio_bypass_setup: pytest.fixture, - vizio_guess_device_type: pytest.fixture, -) -> None: - """Test zeroconf config flow when device gets discovered multiple times.""" - discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info - ) - - # Form should always show even if all required properties are discovered - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info - ) - - # Flow should abort because device is already setup - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_in_progress"