From ccdf5d13bd66e7752d9edf95cae09cc58b3ad1db Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Jun 2020 21:21:42 +0200 Subject: [PATCH 01/47] Bumped version to 0.111.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2837c686c5d..1b8b2b8969c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0b0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 2c49311ee45883942901c106a301509db0b6c81b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 08:48:39 -0700 Subject: [PATCH 02/47] Guard blowing up converting 0 mired/kelvin (#35486) --- homeassistant/components/hue/light.py | 19 ++++++++++++---- tests/util/test_color.py | 32 ++++++++++----------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index c9d543dba94..c8d7de55ce8 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -296,18 +296,29 @@ class HueLight(LightEntity): @property def min_mireds(self): """Return the coldest color_temp that this light supports.""" - if self.is_group or "ct" not in self.light.controlcapabilities: + if self.is_group: return super().min_mireds - return self.light.controlcapabilities["ct"]["min"] + min_mireds = self.light.controlcapabilities.get("ct", {}).get("min") + + # We filter out '0' too, which can be incorrectly reported by 3rd party buls + if not min_mireds: + return super().min_mireds + + return min_mireds @property def max_mireds(self): """Return the warmest color_temp that this light supports.""" - if self.is_group or "ct" not in self.light.controlcapabilities: + if self.is_group: return super().max_mireds - return self.light.controlcapabilities["ct"]["max"] + max_mireds = self.light.controlcapabilities.get("ct", {}).get("max") + + if not max_mireds: + return super().max_mireds + + return max_mireds @property def is_on(self): diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 99ebcd72554..8f520f4a7ec 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -288,28 +288,20 @@ def test_gamut(): assert not color_util.check_valid_gamut(GAMUT_INVALID_4) -def test_should_return_25000_kelvin_when_input_is_40_mired(): - """Function should return 25000K if given 40 mired.""" - kelvin = color_util.color_temperature_mired_to_kelvin(40) - assert kelvin == 25000 +def test_color_temperature_mired_to_kelvin(): + """Test color_temperature_mired_to_kelvin.""" + assert color_util.color_temperature_mired_to_kelvin(40) == 25000 + assert color_util.color_temperature_mired_to_kelvin(200) == 5000 + with pytest.raises(ZeroDivisionError): + assert color_util.color_temperature_mired_to_kelvin(0) -def test_should_return_5000_kelvin_when_input_is_200_mired(): - """Function should return 5000K if given 200 mired.""" - kelvin = color_util.color_temperature_mired_to_kelvin(200) - assert kelvin == 5000 - - -def test_should_return_40_mired_when_input_is_25000_kelvin(): - """Function should return 40 mired when given 25000 Kelvin.""" - mired = color_util.color_temperature_kelvin_to_mired(25000) - assert mired == 40 - - -def test_should_return_200_mired_when_input_is_5000_kelvin(): - """Function should return 200 mired when given 5000 Kelvin.""" - mired = color_util.color_temperature_kelvin_to_mired(5000) - assert mired == 200 +def test_color_temperature_kelvin_to_mired(): + """Test color_temperature_kelvin_to_mired.""" + assert color_util.color_temperature_kelvin_to_mired(25000) == 40 + assert color_util.color_temperature_kelvin_to_mired(5000) == 200 + with pytest.raises(ZeroDivisionError): + assert color_util.color_temperature_kelvin_to_mired(0) def test_returns_same_value_for_any_two_temperatures_below_1000(): From 3aa4bb54692c06364e6ee74aa5881ec00c714440 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 4 Jun 2020 11:59:39 -0500 Subject: [PATCH 03/47] Add roku exception handling for service calls (#36328) --- homeassistant/components/roku/__init__.py | 18 +++++++++++++++++- homeassistant/components/roku/media_player.py | 14 +++++++++++++- homeassistant/components/roku/remote.py | 5 ++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index ef233f64a1b..a3357ec4cf9 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -4,7 +4,7 @@ from datetime import timedelta import logging from typing import Any, Dict -from rokuecp import Roku, RokuError +from rokuecp import Roku, RokuConnectionError, RokuError from rokuecp.models import Device import voluptuous as vol @@ -92,6 +92,22 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo return unload_ok +def roku_exception_handler(func): + """Decorate Roku calls to handle Roku exceptions.""" + + async def handler(self, *args, **kwargs): + try: + await func(self, *args, **kwargs) + except RokuConnectionError as error: + if self.available: + _LOGGER.error("Error communicating with API: %s", error) + except RokuError as error: + if self.available: + _LOGGER.error("Invalid response from API: %s", error) + + return handler + + class RokuDataUpdateCoordinator(DataUpdateCoordinator): """Class to manage fetching Roku data.""" diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 69c9e24ad89..168d4a4a6fe 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -19,7 +19,7 @@ from homeassistant.components.media_player.const import ( ) from homeassistant.const import STATE_HOME, STATE_IDLE, STATE_PLAYING, STATE_STANDBY -from . import RokuDataUpdateCoordinator, RokuEntity +from . import RokuDataUpdateCoordinator, RokuEntity, roku_exception_handler from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -161,49 +161,60 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): """List of available input sources.""" return ["Home"] + sorted(app.name for app in self.coordinator.data.apps) + @roku_exception_handler async def async_turn_on(self) -> None: """Turn on the Roku.""" await self.coordinator.roku.remote("poweron") + @roku_exception_handler async def async_turn_off(self) -> None: """Turn off the Roku.""" await self.coordinator.roku.remote("poweroff") + @roku_exception_handler async def async_media_pause(self) -> None: """Send pause command.""" if self.state != STATE_STANDBY: await self.coordinator.roku.remote("play") + @roku_exception_handler async def async_media_play(self) -> None: """Send play command.""" if self.state != STATE_STANDBY: await self.coordinator.roku.remote("play") + @roku_exception_handler async def async_media_play_pause(self) -> None: """Send play/pause command.""" if self.state != STATE_STANDBY: await self.coordinator.roku.remote("play") + @roku_exception_handler async def async_media_previous_track(self) -> None: """Send previous track command.""" await self.coordinator.roku.remote("reverse") + @roku_exception_handler async def async_media_next_track(self) -> None: """Send next track command.""" await self.coordinator.roku.remote("forward") + @roku_exception_handler async def async_mute_volume(self, mute) -> None: """Mute the volume.""" await self.coordinator.roku.remote("volume_mute") + @roku_exception_handler async def async_volume_up(self) -> None: """Volume up media player.""" await self.coordinator.roku.remote("volume_up") + @roku_exception_handler async def async_volume_down(self) -> None: """Volume down media player.""" await self.coordinator.roku.remote("volume_down") + @roku_exception_handler async def async_play_media(self, media_type: str, media_id: str, **kwargs) -> None: """Tune to channel.""" if media_type != MEDIA_TYPE_CHANNEL: @@ -216,6 +227,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): await self.coordinator.roku.tune(media_id) + @roku_exception_handler async def async_select_source(self, source: str) -> None: """Select input source.""" if source == "Home": diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 99e398fea68..5b893b6a0f8 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -5,7 +5,7 @@ from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType -from . import RokuDataUpdateCoordinator, RokuEntity +from . import RokuDataUpdateCoordinator, RokuEntity, roku_exception_handler from .const import DOMAIN @@ -43,14 +43,17 @@ class RokuRemote(RokuEntity, RemoteEntity): """Return true if device is on.""" return not self.coordinator.data.state.standby + @roku_exception_handler async def async_turn_on(self, **kwargs) -> None: """Turn the device on.""" await self.coordinator.roku.remote("poweron") + @roku_exception_handler async def async_turn_off(self, **kwargs) -> None: """Turn the device off.""" await self.coordinator.roku.remote("poweroff") + @roku_exception_handler async def async_send_command(self, command: List, **kwargs) -> None: """Send a command to one device.""" num_repeats = kwargs[ATTR_NUM_REPEATS] From 4cd04e3f99aa4aa4f773ee4a3da129710ae54a42 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 4 Jun 2020 02:39:49 -0500 Subject: [PATCH 04/47] Update sonarr to 0.2.2 (#36429) --- homeassistant/components/sonarr/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json index 61c30102e34..c1edb8ec521 100644 --- a/homeassistant/components/sonarr/manifest.json +++ b/homeassistant/components/sonarr/manifest.json @@ -3,7 +3,7 @@ "name": "Sonarr", "documentation": "https://www.home-assistant.io/integrations/sonarr", "codeowners": ["@ctalkington"], - "requirements": ["sonarr==0.2.1"], + "requirements": ["sonarr==0.2.2"], "config_flow": true, "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index 1e17ef46fae..4336412fa41 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1990,7 +1990,7 @@ somecomfort==0.5.2 somfy-mylink-synergy==1.0.6 # homeassistant.components.sonarr -sonarr==0.2.1 +sonarr==0.2.2 # homeassistant.components.marytts speak2mary==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 158790eb6d1..a6b782fc400 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -813,7 +813,7 @@ solaredge==0.0.2 somecomfort==0.5.2 # homeassistant.components.sonarr -sonarr==0.2.1 +sonarr==0.2.2 # homeassistant.components.marytts speak2mary==1.4.0 From 84bd2067e4874718c4c9ee6a8d593960aecb2c5c Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 4 Jun 2020 10:15:30 +0200 Subject: [PATCH 05/47] Fix deCONZ groups don't report ctmax/min (#36432) * Groups don't report ctmax/min --- homeassistant/components/deconz/light.py | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 513be407907..fc0c01b30df 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -82,7 +82,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_group(gateway.api.groups.values()) -class DeconzLight(DeconzDevice, LightEntity): +class DeconzBaseLight(DeconzDevice, LightEntity): """Representation of a deCONZ light.""" def __init__(self, device, gateway): @@ -130,16 +130,6 @@ class DeconzLight(DeconzDevice, LightEntity): return color_util.color_xy_to_hs(*self._device.xy) return None - @property - def max_mireds(self): - """Return the warmest color_temp that this light supports.""" - return self._device.ctmax or super().max_mireds - - @property - def min_mireds(self): - """Return the coldest color_temp that this light supports.""" - return self._device.ctmin or super().min_mireds - @property def is_on(self): """Return true if light is on.""" @@ -214,7 +204,21 @@ class DeconzLight(DeconzDevice, LightEntity): return attributes -class DeconzGroup(DeconzLight): +class DeconzLight(DeconzBaseLight): + """Representation of a deCONZ light.""" + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + return self._device.ctmax or super().max_mireds + + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + return self._device.ctmin or super().min_mireds + + +class DeconzGroup(DeconzBaseLight): """Representation of a deCONZ group.""" def __init__(self, device, gateway): From c7da4d77ce4dfe080e3380f8b4d6fcfc823bd317 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 01:13:01 -0700 Subject: [PATCH 06/47] Add partial mobile app sensor validation (#36433) --- homeassistant/components/mobile_app/const.py | 1 + .../components/mobile_app/webhook.py | 33 ++++++++++++++++--- tests/components/mobile_app/test_entity.py | 7 +++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index a9cdc676932..0fc4a5ee407 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -57,6 +57,7 @@ ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available" ERR_ENCRYPTION_REQUIRED = "encryption_required" ERR_SENSOR_NOT_REGISTERED = "not_registered" ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id" +ERR_INVALID_FORMAT = "invalid_format" ATTR_SENSOR_ATTRIBUTES = "attributes" diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index adc90c15e98..09618390e18 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -76,6 +76,7 @@ from .const import ( ERR_ENCRYPTION_ALREADY_ENABLED, ERR_ENCRYPTION_NOT_AVAILABLE, ERR_ENCRYPTION_REQUIRED, + ERR_INVALID_FORMAT, ERR_SENSOR_DUPLICATE_UNIQUE_ID, ERR_SENSOR_NOT_REGISTERED, SIGNAL_LOCATION_UPDATE, @@ -394,20 +395,31 @@ async def webhook_register_sensor(hass, config_entry, data): vol.All( cv.ensure_list, [ + # Partial schema, enough to identify schema. + # We don't validate everything because otherwise 1 invalid sensor + # will invalidate all sensors. vol.Schema( { - vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, - vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, - vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, - } + }, + extra=vol.ALLOW_EXTRA, ) ], ) ) async def webhook_update_sensor_states(hass, config_entry, data): """Handle an update sensor states webhook.""" + sensor_schema_full = vol.Schema( + { + vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, + vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, + vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), + vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), + vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, + } + ) + resp = {} for sensor in data: entity_type = sensor[ATTR_SENSOR_TYPE] @@ -429,6 +441,19 @@ async def webhook_update_sensor_states(hass, config_entry, data): entry = hass.data[DOMAIN][entity_type][unique_store_key] + try: + sensor = sensor_schema_full(sensor) + except vol.Invalid as err: + err_msg = vol.humanize.humanize_error(sensor, err) + _LOGGER.error( + "Received invalid sensor payload for %s: %s", unique_id, err_msg + ) + resp[unique_id] = { + "success": False, + "error": {"code": ERR_INVALID_FORMAT, "message": err_msg}, + } + continue + new_state = {**entry, **sensor} hass.data[DOMAIN][entity_type][unique_store_key] = new_state diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_entity.py index 78259cd1145..16b21e7b264 100644 --- a/tests/components/mobile_app/test_entity.py +++ b/tests/components/mobile_app/test_entity.py @@ -57,13 +57,18 @@ async def test_sensor(hass, create_registrations, webhook_client): "state": 123, "type": "sensor", "unique_id": "battery_state", - } + }, + # This invalid data should not invalidate whole request + {"type": "sensor", "unique_id": "invalid_state", "invalid": "data"}, ], }, ) assert update_resp.status == 200 + json = await update_resp.json() + assert json["invalid_state"]["success"] is False + updated_entity = hass.states.get("sensor.test_1_battery_state") assert updated_entity.state == "123" From 1b5a601417a93273875988f8be12fa477f67b974 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jun 2020 11:51:06 -0500 Subject: [PATCH 07/47] Ensure verbose logging flag is respected. (#36444) --- homeassistant/bootstrap.py | 2 +- homeassistant/util/logging.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index a4c5fa14fa8..086416b7d35 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -288,7 +288,7 @@ def async_enable_logging( logger = logging.getLogger("") logger.addHandler(err_handler) - logger.setLevel(logging.INFO) + logger.setLevel(logging.INFO if verbose else logging.WARNING) # Save the log file location for access by other components. hass.data[DATA_LOGGING] = err_log_path diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 943e701a144..ed710f573f4 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -77,9 +77,7 @@ def async_activate_log_queue_handler(hass: HomeAssistant) -> None: logging.root.removeHandler(handler) migrated_handlers.append(handler) - listener = logging.handlers.QueueListener( - simple_queue, *migrated_handlers, respect_handler_level=False - ) + listener = logging.handlers.QueueListener(simple_queue, *migrated_handlers) listener.start() From 36207e56b977c954dc8fc29ffb5c45152eb7e8b7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 10:00:31 -0700 Subject: [PATCH 08/47] Fix invalid device info for Daikin devices (#36448) --- homeassistant/components/daikin/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index 2bd47651172..35ea9ff6f35 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -158,7 +158,6 @@ class DaikinApi: info = self.device.values return { "connections": {(CONNECTION_NETWORK_MAC, self.device.mac)}, - "identifiers": self.device.mac, "manufacturer": "Daikin", "model": info.get("model"), "name": info.get("name"), From 0840092b29a76ed5af7ebb1ee3c2a062cb9a46a7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 10:02:49 -0700 Subject: [PATCH 09/47] Bumped version to 0.111.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1b8b2b8969c..4fa16578d32 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b0" +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 0a779ac64c587f180b33d1bb5aa75caa08d0669c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 01:13:01 -0700 Subject: [PATCH 10/47] Add partial mobile app sensor validation (#36433) --- homeassistant/components/mobile_app/const.py | 1 + .../components/mobile_app/webhook.py | 33 ++++++++++++++++--- tests/components/mobile_app/test_entity.py | 7 +++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index a9cdc676932..0fc4a5ee407 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -57,6 +57,7 @@ ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available" ERR_ENCRYPTION_REQUIRED = "encryption_required" ERR_SENSOR_NOT_REGISTERED = "not_registered" ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id" +ERR_INVALID_FORMAT = "invalid_format" ATTR_SENSOR_ATTRIBUTES = "attributes" diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index adc90c15e98..09618390e18 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -76,6 +76,7 @@ from .const import ( ERR_ENCRYPTION_ALREADY_ENABLED, ERR_ENCRYPTION_NOT_AVAILABLE, ERR_ENCRYPTION_REQUIRED, + ERR_INVALID_FORMAT, ERR_SENSOR_DUPLICATE_UNIQUE_ID, ERR_SENSOR_NOT_REGISTERED, SIGNAL_LOCATION_UPDATE, @@ -394,20 +395,31 @@ async def webhook_register_sensor(hass, config_entry, data): vol.All( cv.ensure_list, [ + # Partial schema, enough to identify schema. + # We don't validate everything because otherwise 1 invalid sensor + # will invalidate all sensors. vol.Schema( { - vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, - vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, - vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, - } + }, + extra=vol.ALLOW_EXTRA, ) ], ) ) async def webhook_update_sensor_states(hass, config_entry, data): """Handle an update sensor states webhook.""" + sensor_schema_full = vol.Schema( + { + vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, + vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, + vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), + vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), + vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, + } + ) + resp = {} for sensor in data: entity_type = sensor[ATTR_SENSOR_TYPE] @@ -429,6 +441,19 @@ async def webhook_update_sensor_states(hass, config_entry, data): entry = hass.data[DOMAIN][entity_type][unique_store_key] + try: + sensor = sensor_schema_full(sensor) + except vol.Invalid as err: + err_msg = vol.humanize.humanize_error(sensor, err) + _LOGGER.error( + "Received invalid sensor payload for %s: %s", unique_id, err_msg + ) + resp[unique_id] = { + "success": False, + "error": {"code": ERR_INVALID_FORMAT, "message": err_msg}, + } + continue + new_state = {**entry, **sensor} hass.data[DOMAIN][entity_type][unique_store_key] = new_state diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_entity.py index 78259cd1145..16b21e7b264 100644 --- a/tests/components/mobile_app/test_entity.py +++ b/tests/components/mobile_app/test_entity.py @@ -57,13 +57,18 @@ async def test_sensor(hass, create_registrations, webhook_client): "state": 123, "type": "sensor", "unique_id": "battery_state", - } + }, + # This invalid data should not invalidate whole request + {"type": "sensor", "unique_id": "invalid_state", "invalid": "data"}, ], }, ) assert update_resp.status == 200 + json = await update_resp.json() + assert json["invalid_state"]["success"] is False + updated_entity = hass.states.get("sensor.test_1_battery_state") assert updated_entity.state == "123" From 882359d7847131155831f2c3cab43256463b2691 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Jun 2020 21:43:58 +0200 Subject: [PATCH 11/47] Fix iOS app crashing on None values in Zeroconf service info (#36490) --- homeassistant/components/zeroconf/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index c2fcb88c305..2c6ae4f4225 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -120,10 +120,10 @@ def setup(hass, config): params = { "version": __version__, - "external_url": None, - "internal_url": None, + "external_url": "", + "internal_url": "", # Old base URL, for backward compatibility - "base_url": None, + "base_url": "", # Always needs authentication "requires_api_password": True, } From 3cdcd5d22334aa91c3f033ac12a62e3acded79a0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2020 12:44:11 -0700 Subject: [PATCH 12/47] Bumped version to 0.110.5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1c49e3447e6..eb2bdb91252 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 110 -PATCH_VERSION = "4" +PATCH_VERSION = "5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 2cf987bfc2dbcd7bf87061105f4e594ae09a1f6f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Jun 2020 12:29:09 -0500 Subject: [PATCH 13/47] Update myq for latest api changes (#36469) --- homeassistant/components/myq/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index 953f7a31097..8a68eae03ea 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==2.0.2"], + "requirements": ["pymyq==2.0.3"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index 01d99251e22..15d8f257edd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1468,7 +1468,7 @@ pymsteams==0.1.12 pymusiccast==0.1.6 # homeassistant.components.myq -pymyq==2.0.2 +pymyq==2.0.3 # homeassistant.components.mysensors pymysensors==0.18.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 79af61a03e2..2ed01366cde 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -627,7 +627,7 @@ pymodbus==2.3.0 pymonoprice==0.3 # homeassistant.components.myq -pymyq==2.0.2 +pymyq==2.0.3 # homeassistant.components.nut pynut2==2.1.2 From ca511cb06cc97deb05ea4a316e07000e935820f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Jun 2020 16:33:26 -0500 Subject: [PATCH 14/47] Upgrade zeroconf to 0.27.1 (#36277) --- homeassistant/components/discovery/manifest.json | 1 + homeassistant/components/dyson/manifest.json | 1 + homeassistant/components/soundtouch/manifest.json | 1 + homeassistant/components/ssdp/manifest.json | 1 + homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zeroconf/test_init.py | 4 ++-- tests/test_requirements.py | 4 +++- 10 files changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/discovery/manifest.json b/homeassistant/components/discovery/manifest.json index 76e4ff701c5..b2065edfc54 100644 --- a/homeassistant/components/discovery/manifest.json +++ b/homeassistant/components/discovery/manifest.json @@ -3,6 +3,7 @@ "name": "Discovery", "documentation": "https://www.home-assistant.io/integrations/discovery", "requirements": ["netdisco==2.6.0"], + "after_dependencies": ["zeroconf"], "codeowners": [], "quality_scale": "internal" } diff --git a/homeassistant/components/dyson/manifest.json b/homeassistant/components/dyson/manifest.json index 60800963842..35a76180e2e 100644 --- a/homeassistant/components/dyson/manifest.json +++ b/homeassistant/components/dyson/manifest.json @@ -3,5 +3,6 @@ "name": "Dyson", "documentation": "https://www.home-assistant.io/integrations/dyson", "requirements": ["libpurecool==0.6.1"], + "after_dependencies": ["zeroconf"], "codeowners": ["@etheralm"] } diff --git a/homeassistant/components/soundtouch/manifest.json b/homeassistant/components/soundtouch/manifest.json index c9cc4f32734..58bdab1a2d7 100644 --- a/homeassistant/components/soundtouch/manifest.json +++ b/homeassistant/components/soundtouch/manifest.json @@ -3,5 +3,6 @@ "name": "Bose Soundtouch", "documentation": "https://www.home-assistant.io/integrations/soundtouch", "requirements": ["libsoundtouch==0.8"], + "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index a2683346b63..85b91ff005c 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -3,5 +3,6 @@ "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", "requirements": ["defusedxml==0.6.0", "netdisco==2.6.0"], + "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index e28594d5598..c5e0efe1fe3 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.26.3"], + "requirements": ["zeroconf==0.27.1"], "dependencies": ["api"], "codeowners": ["@robbiet480", "@Kane610"], "quality_scale": "internal" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b7fd9f43d2b..d78680c6131 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -25,7 +25,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.17 voluptuous-serialize==2.3.0 voluptuous==0.11.7 -zeroconf==0.26.3 +zeroconf==0.27.1 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 4336412fa41..421f3703e35 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2239,7 +2239,7 @@ youtube_dl==2020.05.29 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.26.3 +zeroconf==0.27.1 # homeassistant.components.zha zha-quirks==0.0.39 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a6b782fc400..4ff9eb99931 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -915,7 +915,7 @@ xmltodict==0.12.0 ya_ma==0.3.8 # homeassistant.components.zeroconf -zeroconf==0.26.3 +zeroconf==0.27.1 # homeassistant.components.zha zha-quirks==0.0.39 diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 74069fa5faa..45b1d9b1171 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -40,7 +40,7 @@ def get_service_info_mock(service_type, name): return ServiceInfo( service_type, name, - address=b"\n\x00\x00\x14", + addresses=[b"\n\x00\x00\x14"], port=80, weight=0, priority=0, @@ -56,7 +56,7 @@ def get_homekit_info_mock(model, pairing_status): return ServiceInfo( service_type, name, - address=b"\n\x00\x00\x14", + addresses=[b"\n\x00\x00\x14"], port=80, weight=0, priority=0, diff --git a/tests/test_requirements.py b/tests/test_requirements.py index f98485e8006..20202f91e89 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -221,8 +221,10 @@ async def test_discovery_requirements_ssdp(hass): ) as mock_process: await async_get_integration_with_requirements(hass, "ssdp_comp") - assert len(mock_process.mock_calls) == 1 + assert len(mock_process.mock_calls) == 3 assert mock_process.mock_calls[0][1][2] == ssdp.requirements + # Ensure zeroconf is a dep for ssdp + assert mock_process.mock_calls[1][1][1] == "zeroconf" @pytest.mark.parametrize( From e053f75a08955eda8d2b02da49e32ba8efe71b89 Mon Sep 17 00:00:00 2001 From: shbatm Date: Thu, 4 Jun 2020 17:22:35 -0500 Subject: [PATCH 15/47] Fix error on empty UOM for ISY994 Climate Device (#36454) --- homeassistant/components/isy994/climate.py | 2 +- homeassistant/components/isy994/cover.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index 8299265a381..7dfd9a083d3 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -133,7 +133,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): # Which state values used depends on the mode property's UOM: uom = hvac_mode.uom # Handle special case for ISYv4 Firmware: - if uom == UOM_ISYV4_NONE: + if uom in (UOM_ISYV4_NONE, ""): uom = ( UOM_HVAC_MODE_INSTEON if self._node.protocol == PROTO_INSTEON diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index bbcc6f3bf15..41273f61f01 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -83,8 +83,8 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): def set_cover_position(self, **kwargs): """Move the cover to a specific position.""" - position = kwargs.get(ATTR_POSITION) - if position and self._node.uom == UOM_8_BIT_RANGE: + position = kwargs[ATTR_POSITION] + if self._node.uom == UOM_8_BIT_RANGE: position = int(position * 255 / 100) if not self._node.turn_on(val=position): _LOGGER.error("Unable to set cover position") From 1831b3bfb7f14d2394d9fe25f82e39cbca35c35b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2020 00:11:40 -0700 Subject: [PATCH 16/47] Bump hass-nabucasa to 0.34.5 (#36461) --- homeassistant/components/cloud/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/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 33a13b9462d..5fb12bbb102 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.34.4"], + "requirements": ["hass-nabucasa==0.34.5"], "dependencies": ["http", "webhook", "alexa"], "after_dependencies": ["google_assistant"], "codeowners": ["@home-assistant/cloud"] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d78680c6131..479d60bc47b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ ciso8601==2.1.3 cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 -hass-nabucasa==0.34.4 +hass-nabucasa==0.34.5 home-assistant-frontend==20200603.1 importlib-metadata==1.6.0 jinja2>=2.11.1 diff --git a/requirements_all.txt b/requirements_all.txt index 421f3703e35..03d4c63bd88 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -704,7 +704,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.4 +hass-nabucasa==0.34.5 # homeassistant.components.mqtt hbmqtt==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4ff9eb99931..cd0251f5e73 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -303,7 +303,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.4 +hass-nabucasa==0.34.5 # homeassistant.components.mqtt hbmqtt==0.9.5 From 1646113b69d07ed95424a3d87398639821c5de73 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Jun 2020 12:29:09 -0500 Subject: [PATCH 17/47] Update myq for latest api changes (#36469) --- homeassistant/components/myq/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index 953f7a31097..8a68eae03ea 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==2.0.2"], + "requirements": ["pymyq==2.0.3"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index 03d4c63bd88..99459e56d91 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1468,7 +1468,7 @@ pymsteams==0.1.12 pymusiccast==0.1.6 # homeassistant.components.myq -pymyq==2.0.2 +pymyq==2.0.3 # homeassistant.components.mysensors pymysensors==0.18.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cd0251f5e73..64d6b9522a8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -639,7 +639,7 @@ pymodbus==2.3.0 pymonoprice==0.3 # homeassistant.components.myq -pymyq==2.0.2 +pymyq==2.0.3 # homeassistant.components.nut pynut2==2.1.2 From 33ec31409157c968733d1e7ebf8bc0ae10076799 Mon Sep 17 00:00:00 2001 From: Lindsay Ward Date: Fri, 5 Jun 2020 16:40:50 +1000 Subject: [PATCH 18/47] Fix yeelight_sunflower hs_color using RGB values (#36470) --- homeassistant/components/yeelightsunflower/light.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/yeelightsunflower/light.py b/homeassistant/components/yeelightsunflower/light.py index abe3d5a404b..00ea467c0d1 100644 --- a/homeassistant/components/yeelightsunflower/light.py +++ b/homeassistant/components/yeelightsunflower/light.py @@ -44,7 +44,7 @@ class SunflowerBulb(LightEntity): self._available = light.available self._brightness = light.brightness self._is_on = light.is_on - self._hs_color = light.rgb_color + self._rgb_color = light.rgb_color self._unique_id = light.zid @property @@ -75,7 +75,7 @@ class SunflowerBulb(LightEntity): @property def hs_color(self): """Return the color property.""" - return self._hs_color + return color_util.color_RGB_to_hs(*self._rgb_color) @property def supported_features(self): @@ -109,4 +109,4 @@ class SunflowerBulb(LightEntity): self._available = self._light.available self._brightness = self._light.brightness self._is_on = self._light.is_on - self._hs_color = color_util.color_RGB_to_hs(*self._light.rgb_color) + self._rgb_color = self._light.rgb_color From d1c083eed9db963e1e97bc8f341bbbe4b197624a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Jun 2020 12:30:20 -0500 Subject: [PATCH 19/47] Upgrade pysonos to 0.0.31 (#36483) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index 020bfbade56..7ce4af02e45 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["pysonos==0.0.30"], + "requirements": ["pysonos==0.0.31"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:ZonePlayer:1" diff --git a/requirements_all.txt b/requirements_all.txt index 99459e56d91..63ff5e9acaf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1615,7 +1615,7 @@ pysnmp==4.4.12 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.30 +pysonos==0.0.31 # homeassistant.components.spc pyspcwebgw==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 64d6b9522a8..e49ff4c82ad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -693,7 +693,7 @@ pysmartthings==0.7.1 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.30 +pysonos==0.0.31 # homeassistant.components.spc pyspcwebgw==0.4.0 From a1b2dcc5a884383f8fc38128c88bee1014f30c2e Mon Sep 17 00:00:00 2001 From: jrester <31157644+jrester@users.noreply.github.com> Date: Fri, 5 Jun 2020 22:03:17 +0200 Subject: [PATCH 20/47] Update tesla-powerwall to 0.2.10 (#36486) modified: homeassistant/components/powerwall/manifest.json modified: requirements_all.txt modified: requirements_test_all.txt --- homeassistant/components/powerwall/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index 7b2095c4a2a..da5f6e4b7ed 100644 --- a/homeassistant/components/powerwall/manifest.json +++ b/homeassistant/components/powerwall/manifest.json @@ -3,6 +3,6 @@ "name": "Tesla Powerwall", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/powerwall", - "requirements": ["tesla-powerwall==0.2.8"], + "requirements": ["tesla-powerwall==0.2.10"], "codeowners": ["@bdraco", "@jrester"] } diff --git a/requirements_all.txt b/requirements_all.txt index 63ff5e9acaf..99cd6880fdf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2078,7 +2078,7 @@ temperusb==1.5.3 # tensorflow==1.13.2 # homeassistant.components.powerwall -tesla-powerwall==0.2.8 +tesla-powerwall==0.2.10 # homeassistant.components.tesla teslajsonpy==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e49ff4c82ad..9b520efc75b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -844,7 +844,7 @@ sunwatcher==0.2.1 tellduslive==0.10.11 # homeassistant.components.powerwall -tesla-powerwall==0.2.8 +tesla-powerwall==0.2.10 # homeassistant.components.tesla teslajsonpy==0.8.1 From 85ee0a1a3ae44f1823874f879e31828a227f7ee4 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Fri, 5 Jun 2020 17:11:46 -0400 Subject: [PATCH 21/47] Process events from ZHA Window Covering Remote (#36489) --- .../components/zha/core/channels/closures.py | 7 ++- tests/components/zha/test_cover.py | 52 ++++++++++++++++++- tests/components/zha/zha_devices_list.py | 2 +- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zha/core/channels/closures.py b/homeassistant/components/zha/core/channels/closures.py index 826c99fbd3b..5641deffb60 100644 --- a/homeassistant/components/zha/core/channels/closures.py +++ b/homeassistant/components/zha/core/channels/closures.py @@ -7,7 +7,7 @@ from homeassistant.core import callback from .. import registries from ..const import REPORT_CONFIG_IMMEDIATE, SIGNAL_ATTR_UPDATED -from .base import ZigbeeChannel +from .base import ClientChannel, ZigbeeChannel _LOGGER = logging.getLogger(__name__) @@ -50,6 +50,11 @@ class Shade(ZigbeeChannel): """Shade channel.""" +@registries.CLIENT_CHANNELS_REGISTRY.register(closures.WindowCovering.cluster_id) +class WindowCoveringClient(ClientChannel): + """Window client channel.""" + + @registries.ZIGBEE_CHANNEL_REGISTRY.register(closures.WindowCovering.cluster_id) class WindowCovering(ZigbeeChannel): """Window channel.""" diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index c3404a2bb83..2c497f6880f 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -15,18 +15,24 @@ from homeassistant.components.cover import ( SERVICE_SET_COVER_POSITION, SERVICE_STOP_COVER, ) -from homeassistant.const import STATE_CLOSED, STATE_OPEN, STATE_UNAVAILABLE +from homeassistant.const import ( + ATTR_COMMAND, + STATE_CLOSED, + STATE_OPEN, + STATE_UNAVAILABLE, +) from homeassistant.core import CoreState, State from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, + make_zcl_header, send_attributes_report, ) from tests.async_mock import AsyncMock, MagicMock, call, patch -from tests.common import mock_coro, mock_restore_cache +from tests.common import async_capture_events, mock_coro, mock_restore_cache @pytest.fixture @@ -43,6 +49,20 @@ def zigpy_cover_device(zigpy_device_mock): return zigpy_device_mock(endpoints) +@pytest.fixture +def zigpy_cover_remote(zigpy_device_mock): + """Zigpy cover remote device.""" + + endpoints = { + 1: { + "device_type": 0x0203, + "in_clusters": [], + "out_clusters": [closures.WindowCovering.cluster_id], + } + } + return zigpy_device_mock(endpoints) + + @pytest.fixture def zigpy_shade_device(zigpy_device_mock): """Zigpy shade device.""" @@ -375,3 +395,31 @@ async def test_keen_vent(hass, zha_device_joined_restored, zigpy_keen_vent): assert cluster_level.request.call_count == 1 assert hass.states.get(entity_id).state == STATE_OPEN assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 100 + + +async def test_cover_remote(hass, zha_device_joined_restored, zigpy_cover_remote): + """Test zha cover remote.""" + + # load up cover domain + await zha_device_joined_restored(zigpy_cover_remote) + + cluster = zigpy_cover_remote.endpoints[1].out_clusters[ + closures.WindowCovering.cluster_id + ] + zha_events = async_capture_events(hass, "zha_event") + + # up command + hdr = make_zcl_header(0, global_command=False) + cluster.handle_message(hdr, []) + await hass.async_block_till_done() + + assert len(zha_events) == 1 + assert zha_events[0].data[ATTR_COMMAND] == "up_open" + + # down command + hdr = make_zcl_header(1, global_command=False) + cluster.handle_message(hdr, []) + await hass.async_block_till_done() + + assert len(zha_events) == 2 + assert zha_events[1].data[ATTR_COMMAND] == "down_close" diff --git a/tests/components/zha/zha_devices_list.py b/tests/components/zha/zha_devices_list.py index 0b1ec9ae2c6..d4ea1377d97 100644 --- a/tests/components/zha/zha_devices_list.py +++ b/tests/components/zha/zha_devices_list.py @@ -813,7 +813,7 @@ DEVICES = [ "entity_id": "sensor.ikea_of_sweden_tradfri_on_off_switch_77665544_power", } }, - "event_channels": ["1:0x0006", "1:0x0008", "1:0x0019"], + "event_channels": ["1:0x0006", "1:0x0008", "1:0x0019", "1:0x0102"], "manufacturer": "IKEA of Sweden", "model": "TRADFRI on/off switch", "node_descriptor": b"\x02@\x80|\x11RR\x00\x00,R\x00\x00", From 7520aa5b2594d69c2f04a0c5a21bd0fac83c50c8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Jun 2020 21:43:58 +0200 Subject: [PATCH 22/47] Fix iOS app crashing on None values in Zeroconf service info (#36490) --- homeassistant/components/zeroconf/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 436b38fd704..9f0d203d2b3 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -130,10 +130,10 @@ def setup(hass, config): "location_name": hass.config.location_name, "uuid": uuid, "version": __version__, - "external_url": None, - "internal_url": None, + "external_url": "", + "internal_url": "", # Old base URL, for backward compatibility - "base_url": None, + "base_url": "", # Always needs authentication "requires_api_password": True, } From 401b0dce68e6b1bd4ef1fe0da549cafc958b754d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2020 14:34:22 -0700 Subject: [PATCH 23/47] Bumped version to 0.111.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 4fa16578d32..756567e9745 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 4b1761ccb5a5e03f47064becd9e19514f5fdceb1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2020 01:59:55 -0700 Subject: [PATCH 24/47] Use builtin mock (#36473) --- tests/components/gogogate2/conftest.py | 3 ++- tests/components/marytts/test_tts.py | 3 +-- tests/components/seventeentrack/test_sensor.py | 12 ++++++------ tests/components/vera/common.py | 2 +- tests/components/vera/conftest.py | 3 ++- tests/components/vera/test_config_flow.py | 3 +-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/components/gogogate2/conftest.py b/tests/components/gogogate2/conftest.py index 6e2e58d8f9c..31e85c5f14e 100644 --- a/tests/components/gogogate2/conftest.py +++ b/tests/components/gogogate2/conftest.py @@ -1,12 +1,13 @@ """Fixtures for tests.""" -from mock import patch import pytest from homeassistant.core import HomeAssistant from .common import ComponentFactory +from tests.async_mock import patch + @pytest.fixture() def component_factory(hass: HomeAssistant): diff --git a/tests/components/marytts/test_tts.py b/tests/components/marytts/test_tts.py index 637ed1900b8..da221a7effd 100644 --- a/tests/components/marytts/test_tts.py +++ b/tests/components/marytts/test_tts.py @@ -4,8 +4,6 @@ import os import shutil from urllib.parse import urlencode -from mock import Mock, patch - from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, DOMAIN as DOMAIN_MP, @@ -16,6 +14,7 @@ from homeassistant.config import async_process_ha_core_config from homeassistant.const import HTTP_INTERNAL_SERVER_ERROR from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant, mock_service diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 272f2fe7d76..330f4a66152 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -2,7 +2,6 @@ import datetime from typing import Union -import mock from py17track.package import Package import pytest @@ -14,6 +13,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component from homeassistant.util import utcnow +from tests.async_mock import MagicMock, patch from tests.common import async_fire_time_changed VALID_CONFIG_MINIMAL = { @@ -113,7 +113,7 @@ class ProfileMock: @pytest.fixture(autouse=True, name="mock_client") def fixture_mock_client(): """Mock py17track client.""" - with mock.patch( + with patch( "homeassistant.components.seventeentrack.sensor.SeventeenTrackClient", new=ClientMock, ): @@ -137,7 +137,7 @@ async def _goto_future(hass, future=None): """Move to future.""" if not future: future = utcnow() + datetime.timedelta(minutes=10) - with mock.patch("homeassistant.util.utcnow", return_value=future): + with patch("homeassistant.util.utcnow", return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -245,7 +245,7 @@ async def test_delivered_not_shown(hass): ) ProfileMock.package_list = [package] - hass.components.persistent_notification = mock.MagicMock() + hass.components.persistent_notification = MagicMock() await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED) assert not hass.states.async_entity_ids() hass.components.persistent_notification.create.assert_called() @@ -258,7 +258,7 @@ async def test_delivered_shown(hass): ) ProfileMock.package_list = [package] - hass.components.persistent_notification = mock.MagicMock() + hass.components.persistent_notification = MagicMock() await _setup_seventeentrack(hass, VALID_CONFIG_FULL) assert hass.states.get("sensor.seventeentrack_package_456") is not None @@ -283,7 +283,7 @@ async def test_becomes_delivered_not_shown_notification(hass): ) ProfileMock.package_list = [package_delivered] - hass.components.persistent_notification = mock.MagicMock() + hass.components.persistent_notification = MagicMock() await _goto_future(hass) hass.components.persistent_notification.create.assert_called() diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py index 5574c93c515..31e7c706ec9 100644 --- a/tests/components/vera/common.py +++ b/tests/components/vera/common.py @@ -2,13 +2,13 @@ from typing import Callable, Dict, NamedTuple, Tuple -from mock import MagicMock import pyvera as pv from homeassistant.components.vera.const import CONF_CONTROLLER, DOMAIN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock from tests.common import MockConfigEntry SetupCallback = Callable[[pv.VeraController, dict], None] diff --git a/tests/components/vera/conftest.py b/tests/components/vera/conftest.py index 2c15d3e4182..193c0733736 100644 --- a/tests/components/vera/conftest.py +++ b/tests/components/vera/conftest.py @@ -1,10 +1,11 @@ """Fixtures for tests.""" -from mock import patch import pytest from .common import ComponentFactory +from tests.async_mock import patch + @pytest.fixture() def vera_component_factory(): diff --git a/tests/components/vera/test_config_flow.py b/tests/components/vera/test_config_flow.py index 3915d4d0577..793e313125c 100644 --- a/tests/components/vera/test_config_flow.py +++ b/tests/components/vera/test_config_flow.py @@ -1,5 +1,4 @@ """Vera tests.""" -from mock import patch from requests.exceptions import RequestException from homeassistant import config_entries, data_entry_flow @@ -12,7 +11,7 @@ from homeassistant.data_entry_flow import ( RESULT_TYPE_FORM, ) -from tests.async_mock import MagicMock +from tests.async_mock import MagicMock, patch from tests.common import MockConfigEntry From c987ca735e3b170e955e31c897781f0f2b7d7d5b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 4 Jun 2020 20:32:00 +0200 Subject: [PATCH 25/47] Bump pychromecast to 6.0.0 (#36414) * Revert "Prevent race in pychromecast.start_discovery (#36350)" This reverts commit 391983a0cf56a226120057390ddd33586019b827. * Adapt to pychromecast 6.0.0 --- homeassistant/components/cast/discovery.py | 13 +++---- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cast/test_media_player.py | 38 +++++++++------------ 5 files changed, 24 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index 4d58fc3383a..e7d13ea0a18 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -3,7 +3,6 @@ import logging import threading import pychromecast -import zeroconf from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant @@ -85,14 +84,12 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: ) _LOGGER.debug("Starting internal pychromecast discovery.") - listener = pychromecast.discovery.CastListener( - internal_add_callback, internal_remove_callback - ) - browser = zeroconf.ServiceBrowser( - ChromeCastZeroconf.get_zeroconf() or zeroconf.Zeroconf(), - "_googlecast._tcp.local.", - listener, + listener = pychromecast.CastListener( + internal_add_callback, + internal_remove_callback, + internal_add_callback, # Use internal_add_callback also for updates ) + browser = pychromecast.start_discovery(listener, ChromeCastZeroconf.get_zeroconf()) def stop_discovery(event): """Stop discovery of new chromecasts.""" diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 0be595de549..edf0373dd5d 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==5.3.0"], + "requirements": ["pychromecast==6.0.0"], "after_dependencies": ["cloud","zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 99cd6880fdf..3c64d0886a2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1245,7 +1245,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==5.3.0 +pychromecast==6.0.0 # homeassistant.components.cmus pycmus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9b520efc75b..be9b90152f6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -539,7 +539,7 @@ pyblackbird==0.5 pybotvac==0.0.17 # homeassistant.components.cast -pychromecast==5.3.0 +pychromecast==6.0.0 # homeassistant.components.coolmaster pycoolmasternet==0.0.4 diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index ce9e4ad7bf8..90b3896396c 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -80,17 +80,17 @@ async def async_setup_cast_internal_discovery(hass, config=None, discovery_info= browser = MagicMock(zc={}) with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + "homeassistant.components.cast.discovery.pychromecast.CastListener", return_value=listener, ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, - ): + ) as start_discovery: add_entities = await async_setup_cast(hass, config, discovery_info) await hass.async_block_till_done() await hass.async_block_till_done() - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 discovery_callback = cast_listener.call_args[0][0] @@ -120,10 +120,10 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_service", return_value=chromecast, ) as get_chromecast, patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + "homeassistant.components.cast.discovery.pychromecast.CastListener", return_value=listener, ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, ): await async_setup_component( @@ -159,17 +159,15 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas async def test_start_discovery_called_once(hass): """Test pychromecast.start_discovery called exactly once.""" with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=Mock(), - ): + ) as start_discovery: await async_setup_cast(hass) - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 await async_setup_cast(hass) - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 async def test_stop_discovery_called_on_stop(hass): @@ -177,15 +175,13 @@ async def test_stop_discovery_called_on_stop(hass): browser = MagicMock(zc={}) with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, - ): + ) as start_discovery: # start_discovery should be called with empty config await async_setup_cast(hass, {}) - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 with patch( "homeassistant.components.cast.discovery.pychromecast.stop_discovery" @@ -197,15 +193,13 @@ async def test_stop_discovery_called_on_stop(hass): stop_discovery.assert_called_once_with(browser) with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, - ): + ) as start_discovery: # start_discovery should be called again on re-startup await async_setup_cast(hass) - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 async def test_create_cast_device_without_uuid(hass): From dd3b0df22d58f5d3b687a74e3e1476c2871362c8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 6 Jun 2020 06:58:26 +0200 Subject: [PATCH 26/47] Update frontend to 20200603.2 (#36494) --- 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 269c16ce9a8..682c3e9b62f 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200603.1"], + "requirements": ["home-assistant-frontend==20200603.2"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 479d60bc47b..f5ea30c53ee 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.5 -home-assistant-frontend==20200603.1 +home-assistant-frontend==20200603.2 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 3c64d0886a2..dec749de4c2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200603.1 +home-assistant-frontend==20200603.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index be9b90152f6..9a8c2bf6622 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -321,7 +321,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200603.1 +home-assistant-frontend==20200603.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From b9bc147339dfe779ab5bdf0c9b80390e0cfdbc97 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 6 Jun 2020 10:02:39 -0700 Subject: [PATCH 27/47] Update netdisco (#36499) --- homeassistant/components/discovery/manifest.json | 2 +- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/discovery/manifest.json b/homeassistant/components/discovery/manifest.json index b2065edfc54..89d800a1c36 100644 --- a/homeassistant/components/discovery/manifest.json +++ b/homeassistant/components/discovery/manifest.json @@ -2,7 +2,7 @@ "domain": "discovery", "name": "Discovery", "documentation": "https://www.home-assistant.io/integrations/discovery", - "requirements": ["netdisco==2.6.0"], + "requirements": ["netdisco==2.7.0"], "after_dependencies": ["zeroconf"], "codeowners": [], "quality_scale": "internal" diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 85b91ff005c..54fac55198e 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["defusedxml==0.6.0", "netdisco==2.6.0"], + "requirements": ["defusedxml==0.6.0", "netdisco==2.7.0"], "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f5ea30c53ee..5178df84da2 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ hass-nabucasa==0.34.5 home-assistant-frontend==20200603.2 importlib-metadata==1.6.0 jinja2>=2.11.1 -netdisco==2.6.0 +netdisco==2.7.0 pip>=8.0.3 python-slugify==4.0.0 pytz>=2020.1 diff --git a/requirements_all.txt b/requirements_all.txt index dec749de4c2..6abc7be4a30 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -946,7 +946,7 @@ netdata==0.1.2 # homeassistant.components.discovery # homeassistant.components.ssdp -netdisco==2.6.0 +netdisco==2.7.0 # homeassistant.components.neurio_energy neurio==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9a8c2bf6622..bcafa9c569e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -399,7 +399,7 @@ nessclient==0.9.15 # homeassistant.components.discovery # homeassistant.components.ssdp -netdisco==2.6.0 +netdisco==2.7.0 # homeassistant.components.nexia nexia==0.9.3 From e43a0087e45a6a4da4868fbf1381666f3fbdda5b Mon Sep 17 00:00:00 2001 From: matgad Date: Sat, 6 Jun 2020 15:32:26 +0200 Subject: [PATCH 28/47] Bump version zigpy-cc (#36506) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index ef96d6efef1..63a87932ba9 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows==0.16.2", "pyserial==3.4", "zha-quirks==0.0.39", - "zigpy-cc==0.4.2", + "zigpy-cc==0.4.4", "zigpy-deconz==0.9.2", "zigpy==0.20.4", "zigpy-xbee==0.12.1", diff --git a/requirements_all.txt b/requirements_all.txt index 6abc7be4a30..8e40ab5d96d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2251,7 +2251,7 @@ zhong_hong_hvac==1.0.9 ziggo-mediabox-xl==1.1.0 # homeassistant.components.zha -zigpy-cc==0.4.2 +zigpy-cc==0.4.4 # homeassistant.components.zha zigpy-deconz==0.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bcafa9c569e..3b2cd18a226 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -921,7 +921,7 @@ zeroconf==0.27.1 zha-quirks==0.0.39 # homeassistant.components.zha -zigpy-cc==0.4.2 +zigpy-cc==0.4.4 # homeassistant.components.zha zigpy-deconz==0.9.2 From 609d202c4d1b46d7ef27541881906206b1ba55fc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 7 Jun 2020 23:37:58 +0200 Subject: [PATCH 29/47] Fix WLED power and brightness with WLED 0.10+ (#36529) --- homeassistant/components/wled/light.py | 163 ++++++++++--- homeassistant/components/wled/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wled/test_light.py | 240 ++++++++++++++++---- 5 files changed, 337 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index 77f8960ada2..6a22bf6852f 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -83,20 +83,78 @@ async def async_setup_entry( update_segments() -class WLEDLight(LightEntity, WLEDDeviceEntity): - """Defines a WLED light.""" +class WLEDMasterLight(LightEntity, WLEDDeviceEntity): + """Defines a WLED master light.""" + + def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator): + """Initialize WLED master light.""" + super().__init__( + entry_id=entry_id, + coordinator=coordinator, + name=f"{coordinator.data.info.name} Master", + icon="mdi:led-strip-variant", + ) + + @property + def unique_id(self) -> str: + """Return the unique ID for this sensor.""" + return f"{self.coordinator.data.info.mac_address}" + + @property + def supported_features(self) -> int: + """Flag supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + + @property + def brightness(self) -> Optional[int]: + """Return the brightness of this light between 1..255.""" + return self.coordinator.data.state.brightness + + @property + def is_on(self) -> bool: + """Return the state of the light.""" + return bool(self.coordinator.data.state.on) + + @wled_exception_handler + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the light.""" + data = {ATTR_ON: False} + + if ATTR_TRANSITION in kwargs: + # WLED uses 100ms per unit, so 10 = 1 second. + data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10) + + await self.coordinator.wled.master(**data) + + @wled_exception_handler + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the light.""" + data = {ATTR_ON: True} + + if ATTR_TRANSITION in kwargs: + # WLED uses 100ms per unit, so 10 = 1 second. + data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10) + + if ATTR_BRIGHTNESS in kwargs: + data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS] + + await self.coordinator.wled.master(**data) + + +class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): + """Defines a WLED light based on a segment.""" def __init__( self, entry_id: str, coordinator: WLEDDataUpdateCoordinator, segment: int ): - """Initialize WLED light.""" + """Initialize WLED segment light.""" self._rgbw = coordinator.data.info.leds.rgbw self._segment = segment - # Only apply the segment ID if it is not the first segment - name = coordinator.data.info.name - if segment != 0: - name += f" {segment}" + # If this is the one and only segment, use a simpler name + name = f"{coordinator.data.info.name} Segment {self._segment}" + if len(coordinator.data.state.segments) == 1: + name = coordinator.data.info.name super().__init__( entry_id=entry_id, @@ -155,7 +213,16 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): @property def brightness(self) -> Optional[int]: """Return the brightness of this light between 1..255.""" - return self.coordinator.data.state.brightness + state = self.coordinator.data.state + + # If this is the one and only segment, calculate brightness based + # on the master and segment brightness + if len(state.segments) == 1: + return int( + (state.segments[self._segment].brightness * state.brightness) / 255 + ) + + return state.segments[self._segment].brightness @property def white_value(self) -> Optional[int]: @@ -187,18 +254,30 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): @property def is_on(self) -> bool: """Return the state of the light.""" - return bool(self.coordinator.data.state.on) + state = self.coordinator.data.state + + # If there is a single segment, take master into account + if len(state.segments) == 1 and not state.on: + return False + + return bool(state.segments[self._segment].on) @wled_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" - data = {ATTR_ON: False, ATTR_SEGMENT_ID: self._segment} + data = {ATTR_ON: False} if ATTR_TRANSITION in kwargs: # WLED uses 100ms per unit, so 10 = 1 second. data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10) - await self.coordinator.wled.light(**data) + # If there is a single segment, control via the master + if len(self.coordinator.data.state.segments) == 1: + await self.coordinator.wled.master(**data) + return + + data[ATTR_SEGMENT_ID] = self._segment + await self.coordinator.wled.segment(**data) @wled_exception_handler async def async_turn_on(self, **kwargs: Any) -> None: @@ -248,7 +327,23 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): else: data[ATTR_COLOR_PRIMARY] += (self.white_value,) - await self.coordinator.wled.light(**data) + # When only 1 segment is present, switch along the master, and use + # the master for power/brightness control. + if len(self.coordinator.data.state.segments) == 1: + master_data = {ATTR_ON: True} + if ATTR_BRIGHTNESS in data: + master_data[ATTR_BRIGHTNESS] = data[ATTR_BRIGHTNESS] + data[ATTR_BRIGHTNESS] = 255 + + if ATTR_TRANSITION in data: + master_data[ATTR_TRANSITION] = data[ATTR_TRANSITION] + del data[ATTR_TRANSITION] + + await self.coordinator.wled.segment(**data) + await self.coordinator.wled.master(**master_data) + return + + await self.coordinator.wled.segment(**data) @wled_exception_handler async def async_effect( @@ -273,45 +368,59 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): if speed is not None: data[ATTR_SPEED] = speed - await self.coordinator.wled.light(**data) + await self.coordinator.wled.segment(**data) @callback def async_update_segments( entry: ConfigEntry, coordinator: WLEDDataUpdateCoordinator, - current: Dict[int, WLEDLight], + current: Dict[int, WLEDSegmentLight], async_add_entities, ) -> None: """Update segments.""" segment_ids = {light.segment_id for light in coordinator.data.state.segments} current_ids = set(current) - # Process new segments, add them to Home Assistant - new_segments = [] - for segment_id in segment_ids - current_ids: - current[segment_id] = WLEDLight(entry.entry_id, coordinator, segment_id) - new_segments.append(current[segment_id]) + # Discard master (if present) + current_ids.discard(-1) - if new_segments: - async_add_entities(new_segments) + # Process new segments, add them to Home Assistant + new_entities = [] + for segment_id in segment_ids - current_ids: + current[segment_id] = WLEDSegmentLight(entry.entry_id, coordinator, segment_id) + new_entities.append(current[segment_id]) + + # More than 1 segment now? Add master controls + if len(current_ids) < 2 and len(segment_ids) > 1: + current[-1] = WLEDMasterLight(entry.entry_id, coordinator) + new_entities.append(current[-1]) + + if new_entities: + async_add_entities(new_entities) # Process deleted segments, remove them from Home Assistant for segment_id in current_ids - segment_ids: coordinator.hass.async_create_task( - async_remove_segment(segment_id, coordinator, current) + async_remove_entity(segment_id, coordinator, current) + ) + + # Remove master if there is only 1 segment left + if len(current_ids) > 1 and len(segment_ids) < 2: + coordinator.hass.async_create_task( + async_remove_entity(-1, coordinator, current) ) -async def async_remove_segment( - segment_id: int, +async def async_remove_entity( + index: int, coordinator: WLEDDataUpdateCoordinator, - current: Dict[int, WLEDLight], + current: Dict[int, WLEDSegmentLight], ) -> None: """Remove WLED segment light from Home Assistant.""" - entity = current[segment_id] + entity = current[index] await entity.async_remove() registry = await async_get_entity_registry(coordinator.hass) if entity.entity_id in registry.entities: registry.async_remove(entity.entity_id) - del current[segment_id] + del current[index] diff --git a/homeassistant/components/wled/manifest.json b/homeassistant/components/wled/manifest.json index aa3f944ed1e..1653ecf1365 100644 --- a/homeassistant/components/wled/manifest.json +++ b/homeassistant/components/wled/manifest.json @@ -3,7 +3,7 @@ "name": "WLED", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wled", - "requirements": ["wled==0.4.1"], + "requirements": ["wled==0.4.2"], "zeroconf": ["_wled._tcp.local."], "codeowners": ["@frenck"], "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index 8e40ab5d96d..8234ee4cd54 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2195,7 +2195,7 @@ wirelesstagpy==0.4.0 withings-api==2.1.3 # homeassistant.components.wled -wled==0.4.1 +wled==0.4.2 # homeassistant.components.xbee xbee-helper==0.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3b2cd18a226..a113578bc65 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -901,7 +901,7 @@ wiffi==1.0.0 withings-api==2.1.3 # homeassistant.components.wled -wled==0.4.1 +wled==0.4.2 # homeassistant.components.bluesound # homeassistant.components.rest diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index 8854b00ff83..35e8ea0f7ef 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -29,6 +29,7 @@ from homeassistant.const import ( ATTR_ICON, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) @@ -50,7 +51,7 @@ async def test_rgb_light_state( entity_registry = await hass.helpers.entity_registry.async_get_registry() # First segment of the strip - state = hass.states.get("light.wled_rgb_light") + state = hass.states.get("light.wled_rgb_light_segment_0") assert state assert state.attributes.get(ATTR_BRIGHTNESS) == 127 assert state.attributes.get(ATTR_EFFECT) == "Solid" @@ -64,12 +65,12 @@ async def test_rgb_light_state( assert state.attributes.get(ATTR_SPEED) == 32 assert state.state == STATE_ON - entry = entity_registry.async_get("light.wled_rgb_light") + entry = entity_registry.async_get("light.wled_rgb_light_segment_0") assert entry assert entry.unique_id == "aabbccddeeff_0" # Second segment of the strip - state = hass.states.get("light.wled_rgb_light_1") + state = hass.states.get("light.wled_rgb_light_segment_1") assert state assert state.attributes.get(ATTR_BRIGHTNESS) == 127 assert state.attributes.get(ATTR_EFFECT) == "Blink" @@ -83,22 +84,32 @@ async def test_rgb_light_state( assert state.attributes.get(ATTR_SPEED) == 16 assert state.state == STATE_ON - entry = entity_registry.async_get("light.wled_rgb_light_1") + entry = entity_registry.async_get("light.wled_rgb_light_segment_1") assert entry assert entry.unique_id == "aabbccddeeff_1" + # Test master control of the lightstrip + state = hass.states.get("light.wled_rgb_light_master") + assert state + assert state.attributes.get(ATTR_BRIGHTNESS) == 127 + assert state.state == STATE_ON -async def test_switch_change_state( + entry = entity_registry.async_get("light.wled_rgb_light_master") + assert entry + assert entry.unique_id == "aabbccddeeff" + + +async def test_segment_change_state( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog ) -> None: - """Test the change of state of the WLED switches.""" + """Test the change of state of the WLED segments.""" await init_integration(hass, aioclient_mock) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_TRANSITION: 5}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5}, blocking=True, ) await hass.async_block_till_done() @@ -106,14 +117,14 @@ async def test_switch_change_state( on=False, segment_id=0, transition=50, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, { ATTR_BRIGHTNESS: 42, ATTR_EFFECT: "Chase", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_RGB_COLOR: [255, 0, 0], ATTR_TRANSITION: 5, }, @@ -129,11 +140,11 @@ async def test_switch_change_state( transition=50, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_COLOR_TEMP: 400}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_COLOR_TEMP: 400}, blocking=True, ) await hass.async_block_till_done() @@ -142,33 +153,178 @@ async def test_switch_change_state( ) +async def test_master_change_state( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +) -> None: + """Test the change of state of the WLED master light control.""" + await init_integration(hass, aioclient_mock) + + with patch("wled.WLED.master") as light_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.wled_rgb_light_master", ATTR_TRANSITION: 5}, + blocking=True, + ) + await hass.async_block_till_done() + light_mock.assert_called_once_with( + on=False, transition=50, + ) + + with patch("wled.WLED.master") as light_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_BRIGHTNESS: 42, + ATTR_ENTITY_ID: "light.wled_rgb_light_master", + ATTR_TRANSITION: 5, + }, + blocking=True, + ) + await hass.async_block_till_done() + light_mock.assert_called_once_with( + brightness=42, on=True, transition=50, + ) + + with patch("wled.WLED.master") as light_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.wled_rgb_light_master", ATTR_TRANSITION: 5}, + blocking=True, + ) + await hass.async_block_till_done() + light_mock.assert_called_once_with( + on=False, transition=50, + ) + + with patch("wled.WLED.master") as light_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_BRIGHTNESS: 42, + ATTR_ENTITY_ID: "light.wled_rgb_light_master", + ATTR_TRANSITION: 5, + }, + blocking=True, + ) + await hass.async_block_till_done() + light_mock.assert_called_once_with( + brightness=42, on=True, transition=50, + ) + + async def test_dynamically_handle_segments( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test if a new/deleted segment is dynamically added/removed.""" await init_integration(hass, aioclient_mock) - assert hass.states.get("light.wled_rgb_light") - assert hass.states.get("light.wled_rgb_light_1") + assert hass.states.get("light.wled_rgb_light_master") + assert hass.states.get("light.wled_rgb_light_segment_0") + assert hass.states.get("light.wled_rgb_light_segment_1") data = json.loads(load_fixture("wled/rgb_single_segment.json")) device = WLEDDevice(data) - # Test removal if segment went missing + # Test removal if segment went missing, including the master entity with patch( "homeassistant.components.wled.WLED.update", return_value=device, ): async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert hass.states.get("light.wled_rgb_light") - assert not hass.states.get("light.wled_rgb_light_1") + assert hass.states.get("light.wled_rgb_light_segment_0") + assert not hass.states.get("light.wled_rgb_light_segment_1") + assert not hass.states.get("light.wled_rgb_light_master") - # Test adding if segment shows up again + # Test adding if segment shows up again, including the master entity async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert hass.states.get("light.wled_rgb_light") - assert hass.states.get("light.wled_rgb_light_1") + assert hass.states.get("light.wled_rgb_light_master") + assert hass.states.get("light.wled_rgb_light_segment_0") + assert hass.states.get("light.wled_rgb_light_segment_1") + + +async def test_single_segment_behavior( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +) -> None: + """Test the behavior of the integration with a single segment.""" + await init_integration(hass, aioclient_mock) + + data = json.loads(load_fixture("wled/rgb_single_segment.json")) + device = WLEDDevice(data) + + # Test absent master + with patch( + "homeassistant.components.wled.WLED.update", return_value=device, + ): + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + assert not hass.states.get("light.wled_rgb_light_master") + + state = hass.states.get("light.wled_rgb_light_segment_0") + assert state + assert state.state == STATE_ON + + # Test segment brightness takes master into account + device.state.brightness = 100 + device.state.segments[0].brightness = 255 + with patch( + "homeassistant.components.wled.WLED.update", return_value=device, + ): + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get("light.wled_rgb_light_segment_0") + assert state + assert state.attributes.get(ATTR_BRIGHTNESS) == 100 + + # Test segment is off when master is off + device.state.on = False + with patch( + "homeassistant.components.wled.WLED.update", return_value=device, + ): + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + state = hass.states.get("light.wled_rgb_light_segment_0") + assert state + assert state.state == STATE_OFF + + # Test master is turned off when turning off a single segment + with patch("wled.WLED.master") as master_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5}, + blocking=True, + ) + await hass.async_block_till_done() + master_mock.assert_called_once_with( + on=False, transition=50, + ) + + # Test master is turned on when turning on a single segment, and segment + # brightness is set to 255. + with patch("wled.WLED.master") as master_mock, patch( + "wled.WLED.segment" + ) as segment_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", + ATTR_TRANSITION: 5, + ATTR_BRIGHTNESS: 42, + }, + blocking=True, + ) + await hass.async_block_till_done() + master_mock.assert_called_once_with(on=True, transition=50, brightness=42) + segment_mock.assert_called_once_with(on=True, segment_id=0, brightness=255) async def test_light_error( @@ -182,12 +338,12 @@ async def test_light_error( await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.wled_rgb_light"}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"}, blocking=True, ) await hass.async_block_till_done() - state = hass.states.get("light.wled_rgb_light") + state = hass.states.get("light.wled_rgb_light_segment_0") assert state.state == STATE_ON assert "Invalid response from API" in caplog.text @@ -199,17 +355,17 @@ async def test_light_connection_error( await init_integration(hass, aioclient_mock) with patch("homeassistant.components.wled.WLED.update"), patch( - "homeassistant.components.wled.WLED.light", side_effect=WLEDConnectionError + "homeassistant.components.wled.WLED.segment", side_effect=WLEDConnectionError ): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.wled_rgb_light"}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"}, blocking=True, ) await hass.async_block_till_done() - state = hass.states.get("light.wled_rgb_light") + state = hass.states.get("light.wled_rgb_light_segment_0") assert state.state == STATE_UNAVAILABLE @@ -224,7 +380,7 @@ async def test_rgbw_light( assert state.attributes.get(ATTR_HS_COLOR) == (0.0, 100.0) assert state.attributes.get(ATTR_WHITE_VALUE) == 139 - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -236,7 +392,7 @@ async def test_rgbw_light( on=True, segment_id=0, color_primary=(255, 159, 70, 139), ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -248,7 +404,7 @@ async def test_rgbw_light( color_primary=(255, 0, 0, 100), on=True, segment_id=0, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -271,13 +427,13 @@ async def test_effect_service( """Test the effect service of a WLED light.""" await init_integration(hass, aioclient_mock) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { ATTR_EFFECT: "Rainbow", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_INTENSITY: 200, ATTR_REVERSE: True, ATTR_SPEED: 100, @@ -289,11 +445,11 @@ async def test_effect_service( effect="Rainbow", intensity=200, reverse=True, segment_id=0, speed=100, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, - {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9}, blocking=True, ) await hass.async_block_till_done() @@ -301,12 +457,12 @@ async def test_effect_service( segment_id=0, effect=9, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_INTENSITY: 200, ATTR_REVERSE: True, ATTR_SPEED: 100, @@ -318,13 +474,13 @@ async def test_effect_service( intensity=200, reverse=True, segment_id=0, speed=100, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { ATTR_EFFECT: "Rainbow", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_REVERSE: True, ATTR_SPEED: 100, }, @@ -335,13 +491,13 @@ async def test_effect_service( effect="Rainbow", reverse=True, segment_id=0, speed=100, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { ATTR_EFFECT: "Rainbow", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_INTENSITY: 200, ATTR_SPEED: 100, }, @@ -352,13 +508,13 @@ async def test_effect_service( effect="Rainbow", intensity=200, segment_id=0, speed=100, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { ATTR_EFFECT: "Rainbow", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_INTENSITY: 200, ATTR_REVERSE: True, }, @@ -381,11 +537,11 @@ async def test_effect_service_error( await hass.services.async_call( DOMAIN, SERVICE_EFFECT, - {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9}, blocking=True, ) await hass.async_block_till_done() - state = hass.states.get("light.wled_rgb_light") + state = hass.states.get("light.wled_rgb_light_segment_0") assert state.state == STATE_ON assert "Invalid response from API" in caplog.text From 865d65c3809fab91788114c49a8701d6d1730aaf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 7 Jun 2020 17:24:48 -0700 Subject: [PATCH 30/47] Bumped version to 0.111.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 756567e9745..113c6e59488 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 482661f82c7739065bec4c5a475aee9ac09d4336 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Jun 2020 19:07:05 +0200 Subject: [PATCH 31/47] Fix mobile_app registering/update sensor values with an unknown state (#36566) --- .../components/mobile_app/webhook.py | 4 +- tests/components/mobile_app/test_entity.py | 79 ++++++++++++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 09618390e18..b278401d864 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -352,7 +352,7 @@ async def webhook_enable_encryption(hass, config_entry, data): vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, vol.Optional(ATTR_SENSOR_UOM): cv.string, - vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), + vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, str, int, float), vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, } ) @@ -414,7 +414,7 @@ async def webhook_update_sensor_states(hass, config_entry, data): { vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, - vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), + vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, str, int, float), vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, } diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_entity.py index 16b21e7b264..9f91e659fb0 100644 --- a/tests/components/mobile_app/test_entity.py +++ b/tests/components/mobile_app/test_entity.py @@ -2,7 +2,7 @@ import logging -from homeassistant.const import UNIT_PERCENTAGE +from homeassistant.const import STATE_UNKNOWN, UNIT_PERCENTAGE from homeassistant.helpers import device_registry _LOGGER = logging.getLogger(__name__) @@ -128,3 +128,80 @@ async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client): dupe_json = await dupe_resp.json() assert dupe_json["success"] is False assert dupe_json["error"]["code"] == "duplicate_unique_id" + + +async def test_register_sensor_no_state(hass, create_registrations, webhook_client): + """Test that sensors can be registered, when there is no (unknown) state.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "Battery State", + "state": None, + "type": "sensor", + "unique_id": "battery_state", + }, + }, + ) + + assert reg_resp.status == 201 + + json = await reg_resp.json() + assert json == {"success": True} + await hass.async_block_till_done() + + entity = hass.states.get("sensor.test_1_battery_state") + assert entity is not None + + assert entity.domain == "sensor" + assert entity.name == "Test 1 Battery State" + assert entity.state == STATE_UNKNOWN + + +async def test_update_sensor_no_state(hass, create_registrations, webhook_client): + """Test that sensors can be updated, when there is no (unknown) state.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "Battery State", + "state": 100, + "type": "sensor", + "unique_id": "battery_state", + }, + }, + ) + + assert reg_resp.status == 201 + + json = await reg_resp.json() + assert json == {"success": True} + await hass.async_block_till_done() + + entity = hass.states.get("sensor.test_1_battery_state") + assert entity is not None + assert entity.state == "100" + + update_resp = await webhook_client.post( + webhook_url, + json={ + "type": "update_sensor_states", + "data": [{"state": None, "type": "sensor", "unique_id": "battery_state"}], + }, + ) + + assert update_resp.status == 200 + + json = await update_resp.json() + assert json == {"battery_state": {"success": True}} + + updated_entity = hass.states.get("sensor.test_1_battery_state") + assert updated_entity.state == STATE_UNKNOWN From 0b7bcc87df2c5955cca3977dbf485de2f4d81c99 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jun 2020 10:14:45 -0700 Subject: [PATCH 32/47] Bumped version to 0.110.6 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index eb2bdb91252..e51ff95e97d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 110 -PATCH_VERSION = "5" +PATCH_VERSION = "6" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 5d77eb1839cacee618526c54b1746ec9a0259f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 8 Jun 2020 04:45:34 +0100 Subject: [PATCH 33/47] Fix intent component initialisation (#36064) The intent component expect this method from every module that is called intent. Fixes #35522 --- homeassistant/components/alexa/intent.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index f879b66268b..c04b493beec 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -40,6 +40,16 @@ def async_setup(hass): hass.http.register_view(AlexaIntentsView) +async def async_setup_intents(hass): + """ + Do intents setup. + + Right now this module does not expose any, but the intent component breaks + without it. + """ + pass # pylint: disable=unnecessary-pass + + class UnknownRequest(HomeAssistantError): """When an unknown Alexa request is passed in.""" From 53ba45cc8fca50e80b984eb71bfb91417f6bda58 Mon Sep 17 00:00:00 2001 From: shbatm Date: Sun, 7 Jun 2020 20:00:53 -0500 Subject: [PATCH 34/47] Add Z-Wave Notification Sensor support to ISY994 (#36548) --- homeassistant/components/isy994/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index cf8b304179d..afbe44011d8 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -232,7 +232,7 @@ NODE_FILTERS = { "RemoteLinc2_ADV", ], FILTER_INSTEON_TYPE: ["0.16.", "0.17.", "0.18.", "9.0.", "9.7."], - FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 185)))), + FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 186)))), }, LOCK: { FILTER_UOM: ["11"], From a84378bb79a36c8df5ddbf4f756004e5fa84f35b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jun 2020 10:20:25 -0700 Subject: [PATCH 35/47] Mobile app fixes (#36559) --- .../components/mobile_app/webhook.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index b278401d864..c155e722976 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -1,4 +1,5 @@ """Webhook handlers for mobile_app.""" +import asyncio from functools import wraps import logging import secrets @@ -28,7 +29,7 @@ from homeassistant.const import ( HTTP_CREATED, ) from homeassistant.core import EventOrigin -from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, TemplateError +from homeassistant.exceptions import ServiceNotFound, TemplateError from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.template import attach @@ -95,6 +96,7 @@ from .helpers import ( _LOGGER = logging.getLogger(__name__) +DELAY_SAVE = 10 WEBHOOK_COMMANDS = Registry() @@ -184,7 +186,10 @@ async def handle_webhook( "Received webhook payload for type %s: %s", webhook_type, webhook_payload ) - return await WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload) + # Shield so we make sure we finish the webhook, even if sender hangs up. + return await asyncio.shield( + WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload) + ) @WEBHOOK_COMMANDS.register("call_service") @@ -376,11 +381,9 @@ async def webhook_register_sensor(hass, config_entry, data): hass.data[DOMAIN][entity_type][unique_store_key] = data - try: - await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass)) - except HomeAssistantError as ex: - _LOGGER.error("Error registering sensor: %s", ex) - return empty_okay_response() + hass.data[DOMAIN][DATA_STORE].async_delay_save( + lambda: savable_state(hass), DELAY_SAVE + ) register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" async_dispatcher_send(hass, register_signal, data) @@ -458,18 +461,14 @@ async def webhook_update_sensor_states(hass, config_entry, data): hass.data[DOMAIN][entity_type][unique_store_key] = new_state - safe = savable_state(hass) - - try: - await hass.data[DOMAIN][DATA_STORE].async_save(safe) - except HomeAssistantError as ex: - _LOGGER.error("Error updating mobile_app registration: %s", ex) - return empty_okay_response() - async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) resp[unique_id] = {"success": True} + hass.data[DOMAIN][DATA_STORE].async_delay_save( + lambda: savable_state(hass), DELAY_SAVE + ) + return webhook_response(resp, registration=config_entry.data) From b3d5717df8889542326d59eb35e234708fad8ccb Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Jun 2020 21:11:37 +0200 Subject: [PATCH 36/47] Fix mobile_app sensor re-registration handling (#36567) --- homeassistant/components/mobile_app/const.py | 1 - .../components/mobile_app/webhook.py | 24 ++++++------ tests/components/mobile_app/test_entity.py | 39 ++++++++++++++++--- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index 0fc4a5ee407..6e83a08c508 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -56,7 +56,6 @@ ERR_ENCRYPTION_ALREADY_ENABLED = "encryption_already_enabled" ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available" ERR_ENCRYPTION_REQUIRED = "encryption_required" ERR_SENSOR_NOT_REGISTERED = "not_registered" -ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id" ERR_INVALID_FORMAT = "invalid_format" diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index c155e722976..d0ab79ab7e2 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -78,7 +78,6 @@ from .const import ( ERR_ENCRYPTION_NOT_AVAILABLE, ERR_ENCRYPTION_REQUIRED, ERR_INVALID_FORMAT, - ERR_SENSOR_DUPLICATE_UNIQUE_ID, ERR_SENSOR_NOT_REGISTERED, SIGNAL_LOCATION_UPDATE, SIGNAL_SENSOR_UPDATE, @@ -364,29 +363,30 @@ async def webhook_enable_encryption(hass, config_entry, data): async def webhook_register_sensor(hass, config_entry, data): """Handle a register sensor webhook.""" entity_type = data[ATTR_SENSOR_TYPE] - unique_id = data[ATTR_SENSOR_UNIQUE_ID] unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}" - - if unique_store_key in hass.data[DOMAIN][entity_type]: - _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id) - return error_response( - ERR_SENSOR_DUPLICATE_UNIQUE_ID, - f"{entity_type} {unique_id} already exists!", - status=409, - ) + existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type] data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID] + # If sensor already is registered, update current state instead + if existing_sensor: + _LOGGER.debug("Re-register existing sensor %s", unique_id) + entry = hass.data[DOMAIN][entity_type][unique_store_key] + data = {**entry, **data} + hass.data[DOMAIN][entity_type][unique_store_key] = data hass.data[DOMAIN][DATA_STORE].async_delay_save( lambda: savable_state(hass), DELAY_SAVE ) - register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" - async_dispatcher_send(hass, register_signal, data) + if existing_sensor: + async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data) + else: + register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" + async_dispatcher_send(hass, register_signal, data) return webhook_response( {"success": True}, registration=config_entry.data, status=HTTP_CREATED, diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_entity.py index 9f91e659fb0..d0d2a4f841a 100644 --- a/tests/components/mobile_app/test_entity.py +++ b/tests/components/mobile_app/test_entity.py @@ -95,8 +95,8 @@ async def test_sensor_must_register(hass, create_registrations, webhook_client): assert json["battery_state"]["error"]["code"] == "not_registered" -async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client): - """Test that sensors must have a unique ID.""" +async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client, caplog): + """Test that a duplicate unique ID in registration updates the sensor.""" webhook_id = create_registrations[1]["webhook_id"] webhook_url = f"/api/webhook/{webhook_id}" @@ -120,14 +120,41 @@ async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client): reg_json = await reg_resp.json() assert reg_json == {"success": True} + await hass.async_block_till_done() + assert "Re-register existing sensor" not in caplog.text + + entity = hass.states.get("sensor.test_1_battery_state") + assert entity is not None + + assert entity.attributes["device_class"] == "battery" + assert entity.attributes["icon"] == "mdi:battery" + assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE + assert entity.attributes["foo"] == "bar" + assert entity.domain == "sensor" + assert entity.name == "Test 1 Battery State" + assert entity.state == "100" + + payload["data"]["state"] = 99 dupe_resp = await webhook_client.post(webhook_url, json=payload) - assert dupe_resp.status == 409 + assert dupe_resp.status == 201 + dupe_reg_json = await dupe_resp.json() + assert dupe_reg_json == {"success": True} + await hass.async_block_till_done() - dupe_json = await dupe_resp.json() - assert dupe_json["success"] is False - assert dupe_json["error"]["code"] == "duplicate_unique_id" + assert "Re-register existing sensor" in caplog.text + + entity = hass.states.get("sensor.test_1_battery_state") + assert entity is not None + + assert entity.attributes["device_class"] == "battery" + assert entity.attributes["icon"] == "mdi:battery" + assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE + assert entity.attributes["foo"] == "bar" + assert entity.domain == "sensor" + assert entity.name == "Test 1 Battery State" + assert entity.state == "99" async def test_register_sensor_no_state(hass, create_registrations, webhook_client): From 111a00aeebfe43332a9076809d6a5bc92c2dc17a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jun 2020 12:28:53 -0700 Subject: [PATCH 37/47] Bumped version to 0.111.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 113c6e59488..e97198a9001 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From d57674953054184d73b64dfd3a2aa056510e9a76 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 9 Jun 2020 20:06:52 +0200 Subject: [PATCH 38/47] Fix mobile_app missing state in sensor registration (#36604) --- .../components/mobile_app/webhook.py | 4 ++- tests/components/mobile_app/test_entity.py | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index b278401d864..610173ca337 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -352,7 +352,9 @@ async def webhook_enable_encryption(hass, config_entry, data): vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, vol.Optional(ATTR_SENSOR_UOM): cv.string, - vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, str, int, float), + vol.Optional(ATTR_SENSOR_STATE, default=None): vol.Any( + None, bool, str, int, float + ), vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, } ) diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_entity.py index 9f91e659fb0..5dc9717ce36 100644 --- a/tests/components/mobile_app/test_entity.py +++ b/tests/components/mobile_app/test_entity.py @@ -161,6 +161,31 @@ async def test_register_sensor_no_state(hass, create_registrations, webhook_clie assert entity.name == "Test 1 Battery State" assert entity.state == STATE_UNKNOWN + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "Backup Battery State", + "type": "sensor", + "unique_id": "backup_battery_state", + }, + }, + ) + + assert reg_resp.status == 201 + + json = await reg_resp.json() + assert json == {"success": True} + await hass.async_block_till_done() + + entity = hass.states.get("sensor.test_1_backup_battery_state") + assert entity + + assert entity.domain == "sensor" + assert entity.name == "Test 1 Backup Battery State" + assert entity.state == STATE_UNKNOWN + async def test_update_sensor_no_state(hass, create_registrations, webhook_client): """Test that sensors can be updated, when there is no (unknown) state.""" From 13fd80affa85ae0e3750eef5111c142ccf5f7c7b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2020 11:07:32 -0700 Subject: [PATCH 39/47] Bumped version to 0.110.7 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e51ff95e97d..7517217f7d2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 110 -PATCH_VERSION = "6" +PATCH_VERSION = "7" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 5a9a95abe48f3a6e1eab84e3d025ee6bef8c3f1d Mon Sep 17 00:00:00 2001 From: Donnie Date: Tue, 9 Jun 2020 11:26:37 -0700 Subject: [PATCH 40/47] Fix nanoleaf incorrect effect update (#36517) --- homeassistant/components/nanoleaf/light.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index 5073a421e49..a7bf75a15d2 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -210,6 +210,10 @@ class NanoleafLight(LightEntity): self._light.brightness = int(brightness / 2.55) if effect: + if effect not in self._effects_list: + raise ValueError( + f"Attempting to apply effect not in the effect list: '{effect}'" + ) self._light.effect = effect def turn_off(self, **kwargs): @@ -227,8 +231,13 @@ class NanoleafLight(LightEntity): self._available = self._light.available self._brightness = self._light.brightness self._color_temp = self._light.color_temperature - self._effect = self._light.effect self._effects_list = self._light.effects + # Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color. + # This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359). + # Until fixed at the library level, we should ensure the effect exists before saving to light properties + self._effect = ( + self._light.effect if self._light.effect in self._effects_list else None + ) self._hs_color = self._light.hue, self._light.saturation self._state = self._light.on except Unavailable as err: From fb7af0384f37afab03a10ec329ac95b726a3501a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 8 Jun 2020 23:05:55 +0200 Subject: [PATCH 41/47] bump aiokef to 0.2.10 (#36574) 0.2.9 generated a lot of calls on the event loop. --- homeassistant/components/kef/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/kef/manifest.json b/homeassistant/components/kef/manifest.json index bb68d37707f..1eb9a9e19c2 100644 --- a/homeassistant/components/kef/manifest.json +++ b/homeassistant/components/kef/manifest.json @@ -3,5 +3,5 @@ "name": "KEF", "documentation": "https://www.home-assistant.io/integrations/kef", "codeowners": ["@basnijholt"], - "requirements": ["aiokef==0.2.9", "getmac==0.8.2"] + "requirements": ["aiokef==0.2.10", "getmac==0.8.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8234ee4cd54..c90ac8a5130 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -197,7 +197,7 @@ aioimaplib==0.7.15 aiokafka==0.5.1 # homeassistant.components.kef -aiokef==0.2.9 +aiokef==0.2.10 # homeassistant.components.lifx aiolifx==0.6.7 From 94c3d9bac0ae3c490d3e2e13da3768fcbf75c549 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2020 01:23:26 -0700 Subject: [PATCH 42/47] Fix default for loading games file ps4 (#36592) --- homeassistant/components/ps4/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index 2a7a667088e..390637c26a3 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -161,7 +161,7 @@ def load_games(hass: HomeAssistantType, unique_id: str) -> dict: """Load games for sources.""" g_file = hass.config.path(GAMES_FILE.format(unique_id)) try: - games = load_json(g_file, dict) + games = load_json(g_file) except HomeAssistantError as error: games = {} _LOGGER.error("Failed to load games file: %s", error) From a69938afa2f46717f3807aa58acfe5536d3faf40 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2020 12:39:29 -0700 Subject: [PATCH 43/47] Bumped version to 0.111.0b5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e97198a9001..031b122f507 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0b5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From d7ad974244cf25c5af73c9af524c3e24ef6d831f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 10 Jun 2020 01:09:34 +0200 Subject: [PATCH 44/47] Escape <> in owntracks translations (#36612) --- homeassistant/components/owntracks/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/owntracks/strings.json b/homeassistant/components/owntracks/strings.json index b82761461ec..12aba21be72 100644 --- a/homeassistant/components/owntracks/strings.json +++ b/homeassistant/components/owntracks/strings.json @@ -8,7 +8,7 @@ }, "abort": { "one_instance_allowed": "Only a single instance is necessary." }, "create_entry": { - "default": "\n\nOn Android, open [the OwnTracks app]({android_url}), go to preferences -> connection. Change the following settings:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: ``\n - Device ID: ``\n\nOn iOS, open [the OwnTracks app]({ios_url}), tap (i) icon in top left -> settings. Change the following settings:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: ``\n\n{secret}\n\nSee [the documentation]({docs_url}) for more information." + "default": "\n\nOn Android, open [the OwnTracks app]({android_url}), go to preferences -> connection. Change the following settings:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\nOn iOS, open [the OwnTracks app]({ios_url}), tap (i) icon in top left -> settings. Change the following settings:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret}\n\nSee [the documentation]({docs_url}) for more information." } } } From 01756011ff12e7ef0a52354d0180b4630309ff72 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2020 16:40:08 -0700 Subject: [PATCH 45/47] Bump hass-nabucasa to 0.34.6 (#36613) --- homeassistant/components/cloud/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/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 5fb12bbb102..b72aec18c34 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.34.5"], + "requirements": ["hass-nabucasa==0.34.6"], "dependencies": ["http", "webhook", "alexa"], "after_dependencies": ["google_assistant"], "codeowners": ["@home-assistant/cloud"] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5178df84da2..1f971ecda57 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ ciso8601==2.1.3 cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 -hass-nabucasa==0.34.5 +hass-nabucasa==0.34.6 home-assistant-frontend==20200603.2 importlib-metadata==1.6.0 jinja2>=2.11.1 diff --git a/requirements_all.txt b/requirements_all.txt index c90ac8a5130..49c704d7b8f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -704,7 +704,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.5 +hass-nabucasa==0.34.6 # homeassistant.components.mqtt hbmqtt==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a113578bc65..420533f0e1f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -303,7 +303,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.5 +hass-nabucasa==0.34.6 # homeassistant.components.mqtt hbmqtt==0.9.5 From d6f7a984b28b1ac70beed10f4aba28082c37370c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 10 Jun 2020 03:34:16 +0200 Subject: [PATCH 46/47] Bump coronavirus to 1.1.1 (#36614) --- homeassistant/components/coronavirus/manifest.json | 8 ++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/coronavirus/manifest.json b/homeassistant/components/coronavirus/manifest.json index 5248cf38221..ae5083a5f98 100644 --- a/homeassistant/components/coronavirus/manifest.json +++ b/homeassistant/components/coronavirus/manifest.json @@ -3,6 +3,10 @@ "name": "Coronavirus (COVID-19)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/coronavirus", - "requirements": ["coronavirus==1.1.0"], - "codeowners": ["@home_assistant/core"] + "requirements": [ + "coronavirus==1.1.1" + ], + "codeowners": [ + "@home_assistant/core" + ] } diff --git a/requirements_all.txt b/requirements_all.txt index 49c704d7b8f..728e70634cf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -442,7 +442,7 @@ connect-box==0.2.5 construct==2.9.45 # homeassistant.components.coronavirus -coronavirus==1.1.0 +coronavirus==1.1.1 # homeassistant.scripts.credstash # credstash==1.15.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 420533f0e1f..c576dd99695 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -191,7 +191,7 @@ colorlog==4.1.0 construct==2.9.45 # homeassistant.components.coronavirus -coronavirus==1.1.0 +coronavirus==1.1.1 # homeassistant.scripts.credstash # credstash==1.15.0 From 1bbd05dee78349942d4f35dd920f5864f12a2458 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Jun 2020 14:58:56 +0200 Subject: [PATCH 47/47] Bumped version to 0.111.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 031b122f507..67ad32d392b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b5" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0)