From 6d83dafff2c51a1079b12d83a0c5c86fcb1f3fbb Mon Sep 17 00:00:00 2001 From: Jan De Luyck <5451787+jdeluyck@users.noreply.github.com> Date: Wed, 15 Jan 2020 16:21:55 +0100 Subject: [PATCH 01/27] Update emulated_roku to 0.1.9 (#30791) * Update emulated_roku to 0.1.9 * Update requirements_all --- homeassistant/components/emulated_roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/emulated_roku/manifest.json b/homeassistant/components/emulated_roku/manifest.json index 05cf72019d8..62d51d7d910 100644 --- a/homeassistant/components/emulated_roku/manifest.json +++ b/homeassistant/components/emulated_roku/manifest.json @@ -3,7 +3,7 @@ "name": "Emulated Roku", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/emulated_roku", - "requirements": ["emulated_roku==0.1.8"], + "requirements": ["emulated_roku==0.1.9"], "dependencies": [], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index c4fd9c6c5d6..8df1ea71c81 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -477,7 +477,7 @@ eliqonline==1.2.2 elkm1-lib==0.7.15 # homeassistant.components.emulated_roku -emulated_roku==0.1.8 +emulated_roku==0.1.9 # homeassistant.components.enocean enocean==0.50 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cd30b43de99..51dcaf5226e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -171,7 +171,7 @@ eebrightbox==0.0.4 elgato==0.2.0 # homeassistant.components.emulated_roku -emulated_roku==0.1.8 +emulated_roku==0.1.9 # homeassistant.components.season ephem==3.7.7.0 From 368d04b2a10fff9aa1891fcb9d064b175032923e Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Wed, 15 Jan 2020 19:48:30 +0100 Subject: [PATCH 02/27] update to aiopylgtv 0.2.7 (#30797) --- homeassistant/components/webostv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index ddd7be6f3da..ff254e35159 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -2,7 +2,7 @@ "domain": "webostv", "name": "LG webOS Smart TV", "documentation": "https://www.home-assistant.io/integrations/webostv", - "requirements": ["aiopylgtv==0.2.6"], + "requirements": ["aiopylgtv==0.2.7"], "dependencies": ["configurator"], "codeowners": ["@bendavid"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8df1ea71c81..f59edf2b4b8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -190,7 +190,7 @@ aionotion==1.1.0 aiopvapi==1.6.14 # homeassistant.components.webostv -aiopylgtv==0.2.6 +aiopylgtv==0.2.7 # homeassistant.components.switcher_kis aioswitcher==2019.4.26 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 51dcaf5226e..d7f44aed82b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -69,7 +69,7 @@ aiohue==1.10.1 aionotion==1.1.0 # homeassistant.components.webostv -aiopylgtv==0.2.6 +aiopylgtv==0.2.7 # homeassistant.components.switcher_kis aioswitcher==2019.4.26 From 8f852bd6567565d5b1e8a6c00853b2b3e46bc033 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Thu, 16 Jan 2020 10:48:47 +0100 Subject: [PATCH 03/27] Fix setup error of Mikrotik (#30810) --- homeassistant/components/mikrotik/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mikrotik/__init__.py b/homeassistant/components/mikrotik/__init__.py index 9b533288d86..3892f9cb20d 100644 --- a/homeassistant/components/mikrotik/__init__.py +++ b/homeassistant/components/mikrotik/__init__.py @@ -167,7 +167,7 @@ class MikrotikClient: def get_hostname(self): """Return device host name.""" - data = self.command(MIKROTIK_SERVICES[IDENTITY]) + data = list(self.command(MIKROTIK_SERVICES[IDENTITY])) return data[0][NAME] if data else None def connected(self): From 881b35f9d6541a10fe788afb41600c814d99325e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 16 Jan 2020 01:19:38 -0800 Subject: [PATCH 04/27] Handle no host info in ignored config entries (#30822) --- homeassistant/components/hue/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index cbcb21db7d0..238ea06961d 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -69,7 +69,7 @@ async def async_setup(hass, config): bridges = conf[CONF_BRIDGES] configured_hosts = set( - entry.data["host"] for entry in hass.config_entries.async_entries(DOMAIN) + entry.data.get("host") for entry in hass.config_entries.async_entries(DOMAIN) ) for bridge_conf in bridges: From 5b51f740df99c7efed655f13788042836b643d4a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 16 Jan 2020 11:20:58 +0100 Subject: [PATCH 05/27] Fix mpd time issue (#30825) * Fix mpd time issue * Update homeassistant/components/mpd/media_player.py Co-Authored-By: Franck Nijhof --- homeassistant/components/mpd/media_player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 6460becbb3e..4b6d63b4240 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -133,8 +133,8 @@ class MpdDevice(MediaPlayerDevice): self._status = self._client.status() self._currentsong = self._client.currentsong() - position = self._status["time"] - if self._media_position != position: + position = self._status.get("time") + if position is not None and self._media_position != position: self._media_position_updated_at = dt_util.utcnow() self._media_position = position From 6a8582750cf50870cf379046f84b54f694b31969 Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Thu, 16 Jan 2020 12:30:55 +0100 Subject: [PATCH 06/27] Fix play_media in webostv (#30828) --- homeassistant/components/webostv/media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 4652d6385c1..b7c8a416870 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -351,7 +351,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): partial_match_channel_id = None perfect_match_channel_id = None - for channel in self._client.get_channels(): + for channel in await self._client.get_channels(): if media_id == channel["channelNumber"]: perfect_match_channel_id = channel["channelId"] continue From 8fdf68c8d1af0e45d4dae9d25851bfe22761946b Mon Sep 17 00:00:00 2001 From: Quentame Date: Thu, 16 Jan 2020 18:27:43 +0100 Subject: [PATCH 07/27] Fix iCloud when no family members (issue #30829) (#30836) --- homeassistant/components/icloud/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py index e983f5fac22..fc4a5465bff 100644 --- a/homeassistant/components/icloud/__init__.py +++ b/homeassistant/components/icloud/__init__.py @@ -297,10 +297,11 @@ class IcloudAccount: self._owner_fullname = f"{user_info['firstName']} {user_info['lastName']}" self._family_members_fullname = {} - for prs_id, member in user_info["membersInfo"].items(): - self._family_members_fullname[ - prs_id - ] = f"{member['firstName']} {member['lastName']}" + if user_info.get("membersInfo") is not None: + for prs_id, member in user_info["membersInfo"].items(): + self._family_members_fullname[ + prs_id + ] = f"{member['firstName']} {member['lastName']}" self._devices = {} self.update_devices() From cc126761e129854ccf0009b192c2091c3a63a2bb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 16 Jan 2020 11:28:35 -0800 Subject: [PATCH 08/27] Reinstate and deprecate filename option for hue config (#30846) --- homeassistant/components/hue/__init__.py | 6 +++++- tests/components/hue/test_init.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index 238ea06961d..b294a811c61 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -44,7 +44,11 @@ CONFIG_SCHEMA = vol.Schema( DOMAIN: vol.Schema( { vol.Optional(CONF_BRIDGES): vol.All( - cv.ensure_list, [BRIDGE_CONFIG_SCHEMA] + cv.ensure_list, + [ + cv.deprecated("filename", invalidation_version="0.106.0"), + vol.All(BRIDGE_CONFIG_SCHEMA), + ], ) } ) diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index b48d66990e8..2e147fd097b 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -33,6 +33,7 @@ async def test_setup_defined_hosts_known_auth(hass): hue.CONF_HOST: "0.0.0.0", hue.CONF_ALLOW_HUE_GROUPS: False, hue.CONF_ALLOW_UNREACHABLE: True, + "filename": "bla", } } }, @@ -49,6 +50,7 @@ async def test_setup_defined_hosts_known_auth(hass): hue.CONF_HOST: "0.0.0.0", hue.CONF_ALLOW_HUE_GROUPS: False, hue.CONF_ALLOW_UNREACHABLE: True, + "filename": "bla", } } From 3f54533e72a65825b42469cd80be886932449fed Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 16 Jan 2020 11:33:54 -0800 Subject: [PATCH 09/27] Bumped version to 0.104.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 9095cdc5ced..77e681432db 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 104 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 006419b96c0f1bfb51a327f36b93ed8ec1c682c8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 14 Jan 2020 15:06:30 -0800 Subject: [PATCH 10/27] Whitelist Frenck for release --- azure-pipelines-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index ddf95d354c3..135057f2ae4 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -43,7 +43,7 @@ stages: release="$(Build.SourceBranchName)" created_by="$(curl -s https://api.github.com/repos/home-assistant/home-assistant/releases/tags/${release} | jq --raw-output '.author.login')" - if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480|bramkragten)$ ]]; then + if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480|bramkragten|frenck)$ ]]; then exit 0 fi From 0e1450838e47a89fbcc4a23846252726b1ee1b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per-=C3=98yvind=20Bruun?= Date: Thu, 16 Jan 2020 20:37:53 +0100 Subject: [PATCH 11/27] Fix for issue #29822 (#30849) --- homeassistant/components/msteams/notify.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/msteams/notify.py b/homeassistant/components/msteams/notify.py index c986f1d2363..eafac85c8c2 100644 --- a/homeassistant/components/msteams/notify.py +++ b/homeassistant/components/msteams/notify.py @@ -39,16 +39,18 @@ class MSTeamsNotificationService(BaseNotificationService): def __init__(self, webhook_url): """Initialize the service.""" self._webhook_url = webhook_url - self.teams_message = pymsteams.connectorcard(self._webhook_url) def send_message(self, message=None, **kwargs): """Send a message to the webhook.""" + + teams_message = pymsteams.connectorcard(self._webhook_url) + title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) data = kwargs.get(ATTR_DATA) - self.teams_message.title(title) + teams_message.title(title) - self.teams_message.text(message) + teams_message.text(message) if data is not None: file_url = data.get(ATTR_FILE_URL) @@ -60,8 +62,8 @@ class MSTeamsNotificationService(BaseNotificationService): message_section = pymsteams.cardsection() message_section.addImage(file_url) - self.teams_message.addSection(message_section) + teams_message.addSection(message_section) try: - self.teams_message.send() + teams_message.send() except RuntimeError as err: _LOGGER.error("Could not send notification. Error: %s", err) From e2e01f50202e4aa1fa84bff40472a42df4db6479 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 16 Jan 2020 12:37:29 -0700 Subject: [PATCH 12/27] Fix sensor type creation with multiple Ambient weather stations (#30850) --- .../components/ambient_station/__init__.py | 16 ++++++++-------- .../components/ambient_station/binary_sensor.py | 10 ++++++++-- .../components/ambient_station/const.py | 1 + .../components/ambient_station/sensor.py | 10 ++++++++-- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 58389dd1831..c61e15dfeb5 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -26,6 +26,7 @@ from homeassistant.helpers.event import async_call_later from .config_flow import configured_instances from .const import ( ATTR_LAST_DATA, + ATTR_MONITORED_CONDITIONS, CONF_APP_KEY, DATA_CLIENT, DOMAIN, @@ -341,7 +342,6 @@ class AmbientStation: self._watchdog_listener = None self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY self.client = client - self.monitored_conditions = [] self.stations = {} async def _attempt_connect(self): @@ -398,19 +398,19 @@ class AmbientStation: _LOGGER.debug("New station subscription: %s", data) - self.monitored_conditions = [ + # Only create entities based on the data coming through the socket. + # If the user is monitoring brightness (in W/m^2), make sure we also + # add a calculated sensor for the same data measured in lx: + monitored_conditions = [ k for k in station["lastData"] if k in SENSOR_TYPES ] - - # If the user is monitoring brightness (in W/m^2), - # make sure we also add a calculated sensor for the - # same data measured in lx: - if TYPE_SOLARRADIATION in self.monitored_conditions: - self.monitored_conditions.append(TYPE_SOLARRADIATION_LX) + if TYPE_SOLARRADIATION in monitored_conditions: + monitored_conditions.append(TYPE_SOLARRADIATION_LX) self.stations[station["macAddress"]] = { ATTR_LAST_DATA: station["lastData"], ATTR_LOCATION: station.get("info", {}).get("location"), + ATTR_MONITORED_CONDITIONS: monitored_conditions, ATTR_NAME: station.get("info", {}).get( "name", station["macAddress"] ), diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index 3f02eb9f1e8..1ed6dbd0db4 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -19,7 +19,13 @@ from . import ( TYPE_BATTOUT, AmbientWeatherEntity, ) -from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_BINARY_SENSOR +from .const import ( + ATTR_LAST_DATA, + ATTR_MONITORED_CONDITIONS, + DATA_CLIENT, + DOMAIN, + TYPE_BINARY_SENSOR, +) _LOGGER = logging.getLogger(__name__) @@ -35,7 +41,7 @@ async def async_setup_entry(hass, entry, async_add_entities): binary_sensor_list = [] for mac_address, station in ambient.stations.items(): - for condition in ambient.monitored_conditions: + for condition in station[ATTR_MONITORED_CONDITIONS]: name, _, kind, device_class = SENSOR_TYPES[condition] if kind == TYPE_BINARY_SENSOR: binary_sensor_list.append( diff --git a/homeassistant/components/ambient_station/const.py b/homeassistant/components/ambient_station/const.py index b2df34f2f28..21a6e514b30 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -2,6 +2,7 @@ DOMAIN = "ambient_station" ATTR_LAST_DATA = "last_data" +ATTR_MONITORED_CONDITIONS = "monitored_conditions" CONF_APP_KEY = "app_key" diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 56425221e0d..0120799d6f2 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -9,7 +9,13 @@ from . import ( TYPE_SOLARRADIATION_LX, AmbientWeatherEntity, ) -from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_SENSOR +from .const import ( + ATTR_LAST_DATA, + ATTR_MONITORED_CONDITIONS, + DATA_CLIENT, + DOMAIN, + TYPE_SENSOR, +) _LOGGER = logging.getLogger(__name__) @@ -25,7 +31,7 @@ async def async_setup_entry(hass, entry, async_add_entities): sensor_list = [] for mac_address, station in ambient.stations.items(): - for condition in ambient.monitored_conditions: + for condition in station[ATTR_MONITORED_CONDITIONS]: name, unit, kind, device_class = SENSOR_TYPES[condition] if kind == TYPE_SENSOR: sensor_list.append( From 5a46adfebf6a4cd6073b8f11ec12c7fa554ef643 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Fri, 17 Jan 2020 13:06:10 -0500 Subject: [PATCH 13/27] add multistate back (#30889) --- homeassistant/components/zha/core/const.py | 1 + homeassistant/components/zha/sensor.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 61be496fa1c..848f5805ad5 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -56,6 +56,7 @@ CHANNEL_HUMIDITY = "humidity" CHANNEL_IAS_WD = "ias_wd" CHANNEL_ILLUMINANCE = "illuminance" CHANNEL_LEVEL = ATTR_LEVEL +CHANNEL_MULTISTATE_INPUT = "multistate_input" CHANNEL_OCCUPANCY = "occupancy" CHANNEL_ON_OFF = "on_off" CHANNEL_POWER_CONFIGURATION = "power" diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 3b73a9793c9..ce02bf11d9d 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -26,6 +26,7 @@ from .core.const import ( CHANNEL_ELECTRICAL_MEASUREMENT, CHANNEL_HUMIDITY, CHANNEL_ILLUMINANCE, + CHANNEL_MULTISTATE_INPUT, CHANNEL_POWER_CONFIGURATION, CHANNEL_PRESSURE, CHANNEL_SMARTENERGY_METERING, @@ -227,6 +228,18 @@ class ElectricalMeasurement(Sensor): return round(value * self._channel.multiplier / self._channel.divisor) +@STRICT_MATCH(channel_names=CHANNEL_MULTISTATE_INPUT) +class Text(Sensor): + """Sensor that displays string values.""" + + _device_class = None + _unit = None + + def formatter(self, value) -> str: + """Return string value.""" + return value + + @STRICT_MATCH(generic_ids=CHANNEL_ST_HUMIDITY_CLUSTER) @STRICT_MATCH(channel_names=CHANNEL_HUMIDITY) class Humidity(Sensor): From 586566e6ab11e0de9c13c83c3686648d6a5b902c Mon Sep 17 00:00:00 2001 From: SukramJ Date: Fri, 17 Jan 2020 18:37:32 +0100 Subject: [PATCH 14/27] Fix missing switch groups of HomematicIP Cloud (#30903) --- homeassistant/components/homematicip_cloud/switch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index 8f3f6a3a177..6fdb0b8c95c 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -13,7 +13,7 @@ from homematicip.aio.device import ( AsyncPrintedCircuitBoardSwitch2, AsyncPrintedCircuitBoardSwitchBattery, ) -from homematicip.aio.group import AsyncSwitchingGroup +from homematicip.aio.group import AsyncExtendedLinkedSwitchingGroup, AsyncSwitchingGroup from homeassistant.components.switch import SwitchDevice from homeassistant.config_entries import ConfigEntry @@ -67,7 +67,7 @@ async def async_setup_entry( entities.append(HomematicipMultiSwitch(hap, device, channel)) for group in hap.home.groups: - if isinstance(group, AsyncSwitchingGroup): + if isinstance(group, (AsyncExtendedLinkedSwitchingGroup, AsyncSwitchingGroup)): entities.append(HomematicipGroupSwitch(hap, group)) if entities: From 6ac33e5c7b7f9f83b806382b211d344522c1488e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 18 Jan 2020 00:28:34 +0100 Subject: [PATCH 15/27] Fix issue with group unique id when normalising bridge id (#30904) --- homeassistant/components/deconz/__init__.py | 11 +++++++++-- homeassistant/components/deconz/config_flow.py | 4 ++-- homeassistant/components/deconz/const.py | 3 ++- homeassistant/components/deconz/light.py | 7 ++++++- homeassistant/components/deconz/services.py | 12 ++++++------ tests/components/deconz/test_services.py | 6 +++--- 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 096bc6c2904..507b48da9db 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -1,10 +1,11 @@ """Support for deCONZ devices.""" import voluptuous as vol +from homeassistant.config_entries import _UNDEF from homeassistant.const import EVENT_HOMEASSISTANT_STOP from .config_flow import get_master_gateway -from .const import CONF_MASTER_GATEWAY, DOMAIN +from .const import CONF_BRIDGE_ID, CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN from .gateway import DeconzGateway from .services import async_setup_services, async_unload_services @@ -37,8 +38,14 @@ async def async_setup_entry(hass, config_entry): # 0.104 introduced config entry unique id, this makes upgrading possible if config_entry.unique_id is None: + + new_data = _UNDEF + if CONF_BRIDGE_ID in config_entry.data: + new_data = dict(config_entry.data) + new_data[CONF_GROUP_ID_BASE] = config_entry.data[CONF_BRIDGE_ID] + hass.config_entries.async_update_entry( - config_entry, unique_id=gateway.api.config.bridgeid + config_entry, unique_id=gateway.api.config.bridgeid, data=new_data ) hass.data[DOMAIN][config_entry.unique_id] = gateway diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index dd37cc31fae..5a9ef232e61 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -21,7 +21,7 @@ from homeassistant.helpers import aiohttp_client from .const import ( CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, - CONF_BRIDGEID, + CONF_BRIDGE_ID, DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_PORT, @@ -74,7 +74,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: for bridge in self.bridges: if bridge[CONF_HOST] == user_input[CONF_HOST]: - self.bridge_id = bridge[CONF_BRIDGEID] + self.bridge_id = bridge[CONF_BRIDGE_ID] self.deconz_config = { CONF_HOST: bridge[CONF_HOST], CONF_PORT: bridge[CONF_PORT], diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index 41ef80b367f..e951e61fde7 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -5,7 +5,8 @@ _LOGGER = logging.getLogger(__package__) DOMAIN = "deconz" -CONF_BRIDGEID = "bridgeid" +CONF_BRIDGE_ID = "bridgeid" +CONF_GROUP_ID_BASE = "group_id_base" DEFAULT_PORT = 80 DEFAULT_ALLOW_CLIP_SENSOR = False diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index af708a15391..15d3b828741 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -22,6 +22,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.color as color_util from .const import ( + CONF_GROUP_ID_BASE, COVER_TYPES, DOMAIN as DECONZ_DOMAIN, NEW_GROUP, @@ -205,7 +206,11 @@ class DeconzGroup(DeconzLight): """Set up group and create an unique id.""" super().__init__(device, gateway) - self._unique_id = f"{self.gateway.api.config.bridgeid}-{self._device.deconz_id}" + group_id_base = self.gateway.config_entry.unique_id + if CONF_GROUP_ID_BASE in self.gateway.config_entry.data: + group_id_base = self.gateway.config_entry.data[CONF_GROUP_ID_BASE] + + self._unique_id = f"{group_id_base}-{self._device.deconz_id}" @property def unique_id(self): diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index 9d133acdb1d..f20ff65c434 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -6,7 +6,7 @@ from homeassistant.helpers import config_validation as cv from .config_flow import get_master_gateway from .const import ( _LOGGER, - CONF_BRIDGEID, + CONF_BRIDGE_ID, DOMAIN, NEW_GROUP, NEW_LIGHT, @@ -27,14 +27,14 @@ SERVICE_CONFIGURE_DEVICE_SCHEMA = vol.All( vol.Optional(SERVICE_ENTITY): cv.entity_id, vol.Optional(SERVICE_FIELD): cv.matches_regex("/.*"), vol.Required(SERVICE_DATA): dict, - vol.Optional(CONF_BRIDGEID): str, + vol.Optional(CONF_BRIDGE_ID): str, } ), cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD), ) SERVICE_DEVICE_REFRESH = "device_refresh" -SERVICE_DEVICE_REFRESH_SCHEMA = vol.All(vol.Schema({vol.Optional(CONF_BRIDGEID): str})) +SERVICE_DEVICE_REFRESH_SCHEMA = vol.All(vol.Schema({vol.Optional(CONF_BRIDGE_ID): str})) async def async_setup_services(hass): @@ -97,7 +97,7 @@ async def async_configure_service(hass, data): See Dresden Elektroniks REST API documentation for details: http://dresden-elektronik.github.io/deconz-rest-doc/rest/ """ - bridgeid = data.get(CONF_BRIDGEID) + bridgeid = data.get(CONF_BRIDGE_ID) field = data.get(SERVICE_FIELD, "") entity_id = data.get(SERVICE_ENTITY) data = data[SERVICE_DATA] @@ -119,8 +119,8 @@ async def async_configure_service(hass, data): async def async_refresh_devices_service(hass, data): """Refresh available devices from deCONZ.""" gateway = get_master_gateway(hass) - if CONF_BRIDGEID in data: - gateway = hass.data[DOMAIN][data[CONF_BRIDGEID]] + if CONF_BRIDGE_ID in data: + gateway = hass.data[DOMAIN][data[CONF_BRIDGE_ID]] groups = set(gateway.api.groups.keys()) lights = set(gateway.api.lights.keys()) diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index f77a74006e7..07985e4d9f4 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -5,7 +5,7 @@ import pytest import voluptuous as vol from homeassistant.components import deconz -from homeassistant.components.deconz.const import CONF_BRIDGEID +from homeassistant.components.deconz.const import CONF_BRIDGE_ID from .test_gateway import BRIDGEID, setup_deconz_integration @@ -91,7 +91,7 @@ async def test_configure_service_with_field(hass): data = { deconz.services.SERVICE_FIELD: "/light/2", - CONF_BRIDGEID: BRIDGEID, + CONF_BRIDGE_ID: BRIDGEID, deconz.services.SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, } @@ -180,7 +180,7 @@ async def test_service_refresh_devices(hass): """Test that service can refresh devices.""" gateway = await setup_deconz_integration(hass) - data = {CONF_BRIDGEID: BRIDGEID} + data = {CONF_BRIDGE_ID: BRIDGEID} with patch( "pydeconz.DeconzSession.request", From 6053d02e443bfcd6675a09345d9a5798f79c0175 Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Fri, 17 Jan 2020 18:04:46 -0500 Subject: [PATCH 16/27] Fix Alexa semantics for covers with tilt support. (#30911) * Fix Alexa semantics for covers with tilt support. * Clarify wording. * Korrect grammar. --- .../components/alexa/capabilities.py | 94 +++++++--- homeassistant/components/alexa/entities.py | 4 +- homeassistant/components/alexa/handlers.py | 8 +- homeassistant/components/alexa/resources.py | 15 +- tests/components/alexa/test_smart_home.py | 176 ++++++++++++------ 5 files changed, 207 insertions(+), 90 deletions(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 26d07760747..1dddc815d01 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1073,6 +1073,15 @@ class AlexaSecurityPanelController(AlexaCapability): class AlexaModeController(AlexaCapability): """Implements Alexa.ModeController. + The instance property must be unique across ModeController, RangeController, ToggleController within the same device. + The instance property should be a concatenated string of device domain period and single word. + e.g. fan.speed & fan.direction. + + The instance property must not contain words from other instance property strings within the same device. + e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail. + + An instance property string value may be reused for different devices. + https://developer.amazon.com/docs/device-apis/alexa-modecontroller.html """ @@ -1183,28 +1192,38 @@ class AlexaModeController(AlexaCapability): def semantics(self): """Build and return semantics object.""" + supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) # Cover Position if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + lower_labels = [AlexaSemantics.ACTION_LOWER] + raise_labels = [AlexaSemantics.ACTION_RAISE] self._semantics = AlexaSemantics() + + # Add open/close semantics if tilt is not supported. + if not supported & cover.SUPPORT_SET_TILT_POSITION: + lower_labels.append(AlexaSemantics.ACTION_CLOSE) + raise_labels.append(AlexaSemantics.ACTION_OPEN) + self._semantics.add_states_to_value( + [AlexaSemantics.STATES_CLOSED], + f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}", + ) + self._semantics.add_states_to_value( + [AlexaSemantics.STATES_OPEN], + f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}", + ) + self._semantics.add_action_to_directive( - [AlexaSemantics.ACTION_CLOSE, AlexaSemantics.ACTION_LOWER], + lower_labels, "SetMode", {"mode": f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}"}, ) self._semantics.add_action_to_directive( - [AlexaSemantics.ACTION_OPEN, AlexaSemantics.ACTION_RAISE], + raise_labels, "SetMode", {"mode": f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}"}, ) - self._semantics.add_states_to_value( - [AlexaSemantics.STATES_CLOSED], - f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}", - ) - self._semantics.add_states_to_value( - [AlexaSemantics.STATES_OPEN], - f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}", - ) + return self._semantics.serialize_semantics() return None @@ -1213,6 +1232,15 @@ class AlexaModeController(AlexaCapability): class AlexaRangeController(AlexaCapability): """Implements Alexa.RangeController. + The instance property must be unique across ModeController, RangeController, ToggleController within the same device. + The instance property should be a concatenated string of device domain period and single word. + e.g. fan.speed & fan.direction. + + The instance property must not contain words from other instance property strings within the same device. + e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail. + + An instance property string value may be reused for different devices. + https://developer.amazon.com/docs/device-apis/alexa-rangecontroller.html """ @@ -1268,8 +1296,8 @@ class AlexaRangeController(AlexaCapability): if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": return self.entity.attributes.get(cover.ATTR_CURRENT_POSITION) - # Cover Tilt Position - if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + # Cover Tilt + if self.instance == f"{cover.DOMAIN}.tilt": return self.entity.attributes.get(cover.ATTR_CURRENT_TILT_POSITION) # Input Number Value @@ -1321,10 +1349,10 @@ class AlexaRangeController(AlexaCapability): ) return self._resource.serialize_capability_resources() - # Cover Tilt Position Resources - if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + # Cover Tilt Resources + if self.instance == f"{cover.DOMAIN}.tilt": self._resource = AlexaPresetResource( - ["Tilt Position", AlexaGlobalCatalog.SETTING_OPENING], + ["Tilt", "Angle", AlexaGlobalCatalog.SETTING_DIRECTION], min_value=0, max_value=100, precision=1, @@ -1358,24 +1386,35 @@ class AlexaRangeController(AlexaCapability): def semantics(self): """Build and return semantics object.""" + supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) # Cover Position if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": + lower_labels = [AlexaSemantics.ACTION_LOWER] + raise_labels = [AlexaSemantics.ACTION_RAISE] self._semantics = AlexaSemantics() + + # Add open/close semantics if tilt is not supported. + if not supported & cover.SUPPORT_SET_TILT_POSITION: + lower_labels.append(AlexaSemantics.ACTION_CLOSE) + raise_labels.append(AlexaSemantics.ACTION_OPEN) + self._semantics.add_states_to_value( + [AlexaSemantics.STATES_CLOSED], value=0 + ) + self._semantics.add_states_to_range( + [AlexaSemantics.STATES_OPEN], min_value=1, max_value=100 + ) + self._semantics.add_action_to_directive( - [AlexaSemantics.ACTION_LOWER], "SetRangeValue", {"rangeValue": 0} + lower_labels, "SetRangeValue", {"rangeValue": 0} ) self._semantics.add_action_to_directive( - [AlexaSemantics.ACTION_RAISE], "SetRangeValue", {"rangeValue": 100} - ) - self._semantics.add_states_to_value([AlexaSemantics.STATES_CLOSED], value=0) - self._semantics.add_states_to_range( - [AlexaSemantics.STATES_OPEN], min_value=1, max_value=100 + raise_labels, "SetRangeValue", {"rangeValue": 100} ) return self._semantics.serialize_semantics() - # Cover Tilt Position - if self.instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + # Cover Tilt + if self.instance == f"{cover.DOMAIN}.tilt": self._semantics = AlexaSemantics() self._semantics.add_action_to_directive( [AlexaSemantics.ACTION_CLOSE], "SetRangeValue", {"rangeValue": 0} @@ -1395,6 +1434,15 @@ class AlexaRangeController(AlexaCapability): class AlexaToggleController(AlexaCapability): """Implements Alexa.ToggleController. + The instance property must be unique across ModeController, RangeController, ToggleController within the same device. + The instance property should be a concatenated string of device domain period and single word. + e.g. fan.speed & fan.direction. + + The instance property must not contain words from other instance property strings within the same device. + e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail. + + An instance property string value may be reused for different devices. + https://developer.amazon.com/docs/device-apis/alexa-togglecontroller.html """ diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index d6fa0415640..084231f0090 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -404,9 +404,7 @@ class CoverCapabilities(AlexaEntity): self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}" ) if supported & cover.SUPPORT_SET_TILT_POSITION: - yield AlexaRangeController( - self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}" - ) + yield AlexaRangeController(self.entity, instance=f"{cover.DOMAIN}.tilt") yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index 74c1b24d42b..1cb8980b0b1 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -1118,8 +1118,8 @@ async def async_api_set_range(hass, config, directive, context): service = cover.SERVICE_SET_COVER_POSITION data[cover.ATTR_POSITION] = range_value - # Cover Tilt Position - elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + # Cover Tilt + elif instance == f"{cover.DOMAIN}.tilt": range_value = int(range_value) if range_value == 0: service = cover.SERVICE_CLOSE_COVER_TILT @@ -1192,8 +1192,8 @@ async def async_api_adjust_range(hass, config, directive, context): 100, max(0, range_delta + current) ) - # Cover Tilt Position - elif instance == f"{cover.DOMAIN}.{cover.ATTR_TILT_POSITION}": + # Cover Tilt + elif instance == f"{cover.DOMAIN}.tilt": range_delta = int(range_delta) service = SERVICE_SET_COVER_TILT_POSITION current = entity.attributes.get(cover.ATTR_TILT_POSITION) diff --git a/homeassistant/components/alexa/resources.py b/homeassistant/components/alexa/resources.py index 09927321c36..d2580f3bfea 100644 --- a/homeassistant/components/alexa/resources.py +++ b/homeassistant/components/alexa/resources.py @@ -190,7 +190,12 @@ class AlexaGlobalCatalog: class AlexaCapabilityResource: - """Base class for Alexa capabilityResources, ModeResources, and presetResources objects. + """Base class for Alexa capabilityResources, modeResources, and presetResources objects. + + Resources objects labels must be unique across all modeResources and presetResources within the same device. + To provide support for all supported locales, include one label from the AlexaGlobalCatalog in the labels array. + You cannot use any words from the following list as friendly names: + https://developer.amazon.com/docs/alexa/device-apis/resources-and-assets.html#names-you-cannot-use https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources """ @@ -312,6 +317,14 @@ class AlexaSemantics: Semantics is supported for the following interfaces only: ModeController, RangeController, and ToggleController. + Semantics stateMappings are only supported for one interface of the same type on the same device. If a device has + multiple RangeControllers only one interface may use stateMappings otherwise discovery will fail. + + You can support semantics actionMappings on different controllers for the same device, however each controller must + support different phrases. For example, you can support "raise" on a RangeController, and "open" on a ModeController, + but you can't support "open" on both RangeController and ModeController. Semantics stateMappings are only supported + for one interface on the same device. + https://developer.amazon.com/docs/device-apis/alexa-discovery.html#semantics-object """ diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 37301c3555e..51b1ed83982 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -132,7 +132,7 @@ def get_capability(capabilities, capability_name, instance=None): for capability in capabilities: if instance and capability["instance"] == instance: return capability - elif capability["interface"] == capability_name: + if not instance and capability["interface"] == capability_name: return capability return None @@ -1427,6 +1427,36 @@ async def test_cover_position_range(hass): assert supported_range["maximumValue"] == 100 assert supported_range["precision"] == 1 + # Assert for Position Semantics + position_semantics = range_capability["semantics"] + assert position_semantics is not None + + position_action_mappings = position_semantics["actionMappings"] + assert position_action_mappings is not None + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Lower", "Alexa.Actions.Close"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 0}}, + } in position_action_mappings + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Raise", "Alexa.Actions.Open"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 100}}, + } in position_action_mappings + + position_state_mappings = position_semantics["stateMappings"] + assert position_state_mappings is not None + assert { + "@type": "StatesToValue", + "states": ["Alexa.States.Closed"], + "value": 0, + } in position_state_mappings + assert { + "@type": "StatesToRange", + "states": ["Alexa.States.Open"], + "range": {"minimumValue": 1, "maximumValue": 100}, + } in position_state_mappings + call, _ = await assert_request_calls_service( "Alexa.RangeController", "SetRangeValue", @@ -2454,16 +2484,37 @@ async def test_cover_position_mode(hass): }, } in supported_modes - semantics = mode_capability["semantics"] - assert semantics is not None + # Assert for Position Semantics + position_semantics = mode_capability["semantics"] + assert position_semantics is not None - action_mappings = semantics["actionMappings"] - assert action_mappings is not None + position_action_mappings = position_semantics["actionMappings"] + assert position_action_mappings is not None + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Lower", "Alexa.Actions.Close"], + "directive": {"name": "SetMode", "payload": {"mode": "position.closed"}}, + } in position_action_mappings + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Raise", "Alexa.Actions.Open"], + "directive": {"name": "SetMode", "payload": {"mode": "position.open"}}, + } in position_action_mappings - state_mappings = semantics["stateMappings"] - assert state_mappings is not None + position_state_mappings = position_semantics["stateMappings"] + assert position_state_mappings is not None + assert { + "@type": "StatesToValue", + "states": ["Alexa.States.Closed"], + "value": "position.closed", + } in position_state_mappings + assert { + "@type": "StatesToValue", + "states": ["Alexa.States.Open"], + "value": "position.open", + } in position_state_mappings - call, msg = await assert_request_calls_service( + _, msg = await assert_request_calls_service( "Alexa.ModeController", "SetMode", "cover#test_mode", @@ -2477,7 +2528,7 @@ async def test_cover_position_mode(hass): assert properties["namespace"] == "Alexa.ModeController" assert properties["value"] == "position.closed" - call, msg = await assert_request_calls_service( + _, msg = await assert_request_calls_service( "Alexa.ModeController", "SetMode", "cover#test_mode", @@ -2491,7 +2542,7 @@ async def test_cover_position_mode(hass): assert properties["namespace"] == "Alexa.ModeController" assert properties["value"] == "position.open" - call, msg = await assert_request_calls_service( + _, msg = await assert_request_calls_service( "Alexa.ModeController", "SetMode", "cover#test_mode", @@ -2611,7 +2662,7 @@ async def test_cover_tilt_position_range(hass): range_capability = get_capability(capabilities, "Alexa.RangeController") assert range_capability is not None - assert range_capability["instance"] == "cover.tilt_position" + assert range_capability["instance"] == "cover.tilt" semantics = range_capability["semantics"] assert semantics is not None @@ -2629,7 +2680,7 @@ async def test_cover_tilt_position_range(hass): "cover.set_cover_tilt_position", hass, payload={"rangeValue": "50"}, - instance="cover.tilt_position", + instance="cover.tilt", ) assert call.data["position"] == 50 @@ -2640,7 +2691,7 @@ async def test_cover_tilt_position_range(hass): "cover.close_cover_tilt", hass, payload={"rangeValue": "0"}, - instance="cover.tilt_position", + instance="cover.tilt", ) properties = msg["context"]["properties"][0] assert properties["name"] == "rangeValue" @@ -2654,7 +2705,7 @@ async def test_cover_tilt_position_range(hass): "cover.open_cover_tilt", hass, payload={"rangeValue": "100"}, - instance="cover.tilt_position", + instance="cover.tilt", ) properties = msg["context"]["properties"][0] assert properties["name"] == "rangeValue" @@ -2670,12 +2721,12 @@ async def test_cover_tilt_position_range(hass): False, "cover.set_cover_tilt_position", "tilt_position", - instance="cover.tilt_position", + instance="cover.tilt", ) -async def test_cover_semantics(hass): - """Test cover discovery and semantics.""" +async def test_cover_semantics_position_and_tilt(hass): + """Test cover discovery and semantics with position and tilt support.""" device = ( "cover.test_semantics", "open", @@ -2697,50 +2748,57 @@ async def test_cover_semantics(hass): appliance, "Alexa.RangeController", "Alexa.EndpointHealth", "Alexa" ) - for range_instance in ("cover.position", "cover.tilt_position"): - range_capability = get_capability( - capabilities, "Alexa.RangeController", range_instance - ) - semantics = range_capability["semantics"] - assert semantics is not None + # Assert for Position Semantics + position_capability = get_capability( + capabilities, "Alexa.RangeController", "cover.position" + ) + position_semantics = position_capability["semantics"] + assert position_semantics is not None - action_mappings = semantics["actionMappings"] - assert action_mappings is not None - if range_instance == "cover.position": - assert { - "@type": "ActionsToDirective", - "actions": ["Alexa.Actions.Lower"], - "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 0}}, - } in action_mappings - assert { - "@type": "ActionsToDirective", - "actions": ["Alexa.Actions.Raise"], - "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 100}}, - } in action_mappings - elif range_instance == "cover.position": - assert { - "@type": "ActionsToDirective", - "actions": ["Alexa.Actions.Close"], - "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 0}}, - } in action_mappings - assert { - "@type": "ActionsToDirective", - "actions": ["Alexa.Actions.Open"], - "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 100}}, - } in action_mappings + position_action_mappings = position_semantics["actionMappings"] + assert position_action_mappings is not None + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Lower"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 0}}, + } in position_action_mappings + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Raise"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 100}}, + } in position_action_mappings - state_mappings = semantics["stateMappings"] - assert state_mappings is not None - assert { - "@type": "StatesToValue", - "states": ["Alexa.States.Closed"], - "value": 0, - } in state_mappings - assert { - "@type": "StatesToRange", - "states": ["Alexa.States.Open"], - "range": {"minimumValue": 1, "maximumValue": 100}, - } in state_mappings + # Assert for Tilt Semantics + tilt_capability = get_capability( + capabilities, "Alexa.RangeController", "cover.tilt" + ) + tilt_semantics = tilt_capability["semantics"] + assert tilt_semantics is not None + tilt_action_mappings = tilt_semantics["actionMappings"] + assert tilt_action_mappings is not None + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Close"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 0}}, + } in tilt_action_mappings + assert { + "@type": "ActionsToDirective", + "actions": ["Alexa.Actions.Open"], + "directive": {"name": "SetRangeValue", "payload": {"rangeValue": 100}}, + } in tilt_action_mappings + + tilt_state_mappings = tilt_semantics["stateMappings"] + assert tilt_state_mappings is not None + assert { + "@type": "StatesToValue", + "states": ["Alexa.States.Closed"], + "value": 0, + } in tilt_state_mappings + assert { + "@type": "StatesToRange", + "states": ["Alexa.States.Open"], + "range": {"minimumValue": 1, "maximumValue": 100}, + } in tilt_state_mappings async def test_input_number(hass): From f9b48844e69dab8d1549f2b85144725bb83ed5e7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 17 Jan 2020 14:54:32 -0800 Subject: [PATCH 17/27] camera endpoint likes to timeout, catch it. (#30919) --- homeassistant/components/ring/camera.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ring/camera.py b/homeassistant/components/ring/camera.py index 07d87c85714..1526a915482 100644 --- a/homeassistant/components/ring/camera.py +++ b/homeassistant/components/ring/camera.py @@ -6,6 +6,7 @@ import logging from haffmpeg.camera import CameraMjpeg from haffmpeg.tools import IMAGE_JPEG, ImageFrame +import requests from homeassistant.components.camera import Camera from homeassistant.components.ffmpeg import DATA_FFMPEG @@ -146,9 +147,15 @@ class RingCam(RingEntityMixin, Camera): ): return - video_url = await self.hass.async_add_executor_job( - self._device.recording_url, self._last_event["id"] - ) + try: + video_url = await self.hass.async_add_executor_job( + self._device.recording_url, self._last_event["id"] + ) + except requests.Timeout: + _LOGGER.warning( + "Time out fetching recording url for camera %s", self.entity_id + ) + video_url = None if video_url: self._last_video_id = self._last_event["id"] From 07a0bc4abe5f3d337e90afd6421d2ce37d6f8000 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 18 Jan 2020 00:33:46 +0100 Subject: [PATCH 18/27] Fix service device refresh calling state update (#30920) --- .../components/deconz/binary_sensor.py | 8 ++++--- .../components/deconz/deconz_device.py | 5 +++- .../components/deconz/deconz_event.py | 24 +++++++++++-------- homeassistant/components/deconz/manifest.json | 10 +++++--- homeassistant/components/deconz/sensor.py | 18 ++++++++------ homeassistant/components/deconz/services.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 44 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 6261473bb0e..225a28f52f8 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -54,11 +54,13 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorDevice): """Representation of a deCONZ binary sensor.""" @callback - def async_update_callback(self, force_update=False): + def async_update_callback(self, force_update=False, ignore_update=False): """Update the sensor's state.""" - changed = set(self._device.changed_keys) + if ignore_update: + return + keys = {"on", "reachable", "state"} - if force_update or any(key in changed for key in keys): + if force_update or self._device.changed_keys.intersection(keys): self.async_schedule_update_ha_state() @property diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py index 68daee6cf26..4ac3e6cd379 100644 --- a/homeassistant/components/deconz/deconz_device.py +++ b/homeassistant/components/deconz/deconz_device.py @@ -91,8 +91,11 @@ class DeconzDevice(DeconzBase, Entity): unsub_dispatcher() @callback - def async_update_callback(self, force_update=False): + def async_update_callback(self, force_update=False, ignore_update=False): """Update the device's state.""" + if ignore_update: + return + self.async_schedule_update_ha_state() @property diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 3c2442994a5..527e8d2ab7a 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -39,17 +39,21 @@ class DeconzEvent(DeconzBase): self._device = None @callback - def async_update_callback(self, force_update=False): + def async_update_callback(self, force_update=False, ignore_update=False): """Fire the event if reason is that state is updated.""" - if "state" in self._device.changed_keys: - data = { - CONF_ID: self.event_id, - CONF_UNIQUE_ID: self.serial, - CONF_EVENT: self._device.state, - } - if self._device.gesture: - data[CONF_GESTURE] = self._device.gesture - self.gateway.hass.bus.async_fire(CONF_DECONZ_EVENT, data) + if ignore_update or "state" not in self._device.changed_keys: + return + + data = { + CONF_ID: self.event_id, + CONF_UNIQUE_ID: self.serial, + CONF_EVENT: self._device.state, + } + + if self._device.gesture: + data[CONF_GESTURE] = self._device.gesture + + self.gateway.hass.bus.async_fire(CONF_DECONZ_EVENT, data) async def async_update_device_registry(self): """Update device registry.""" diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index a327d7106fc..f448e9105c8 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,13 +3,17 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==67"], + "requirements": [ + "pydeconz==68" + ], "ssdp": [ { "manufacturer": "Royal Philips Electronics" } ], "dependencies": [], - "codeowners": ["@kane610"], + "codeowners": [ + "@kane610" + ], "quality_scale": "platinum" -} +} \ No newline at end of file diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 8194dd145dc..8261f03e902 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -97,11 +97,13 @@ class DeconzSensor(DeconzDevice): """Representation of a deCONZ sensor.""" @callback - def async_update_callback(self, force_update=False): + def async_update_callback(self, force_update=False, ignore_update=False): """Update the sensor's state.""" - changed = set(self._device.changed_keys) + if ignore_update: + return + keys = {"on", "reachable", "state"} - if force_update or any(key in changed for key in keys): + if force_update or self._device.changed_keys.intersection(keys): self.async_schedule_update_ha_state() @property @@ -155,11 +157,13 @@ class DeconzBattery(DeconzDevice): """Battery class for when a device is only represented as an event.""" @callback - def async_update_callback(self, force_update=False): + def async_update_callback(self, force_update=False, ignore_update=False): """Update the battery's state, if needed.""" - changed = set(self._device.changed_keys) + if ignore_update: + return + keys = {"battery", "reachable"} - if force_update or any(key in changed for key in keys): + if force_update or self._device.changed_keys.intersection(keys): self.async_schedule_update_ha_state() @property @@ -217,7 +221,7 @@ class DeconzSensorStateTracker: self.sensor = None @callback - def async_update_callback(self): + def async_update_callback(self, ignore_update=False): """Sensor state updated.""" if "battery" in self.sensor.changed_keys: async_dispatcher_send( diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index f20ff65c434..f893b9880fd 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -127,7 +127,7 @@ async def async_refresh_devices_service(hass, data): scenes = set(gateway.api.scenes.keys()) sensors = set(gateway.api.sensors.keys()) - await gateway.api.refresh_state() + await gateway.api.refresh_state(ignore_update=True) gateway.async_add_device_callback( NEW_GROUP, diff --git a/requirements_all.txt b/requirements_all.txt index f59edf2b4b8..508ff673ad0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1194,7 +1194,7 @@ pydaikin==1.6.1 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==67 +pydeconz==68 # homeassistant.components.delijn pydelijn==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d7f44aed82b..fcd7b1df821 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -414,7 +414,7 @@ pycoolmasternet==0.0.4 pydaikin==1.6.1 # homeassistant.components.deconz -pydeconz==67 +pydeconz==68 # homeassistant.components.zwave pydispatcher==2.0.5 From 353010712f63cb02d176ef186ed6901d915a3a2e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 17 Jan 2020 23:54:53 +0100 Subject: [PATCH 19/27] pdated frontend to 20200108.2 (#30921) --- homeassistant/components/frontend/manifest.json | 10 +++------- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 1bc1900ee94..bd732a7a0a1 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,9 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": [ - "home-assistant-frontend==20200108.0" - ], + "requirements": ["home-assistant-frontend==20200108.2"], "dependencies": [ "api", "auth", @@ -14,8 +12,6 @@ "system_log", "websocket_api" ], - "codeowners": [ - "@home-assistant/frontend" - ], + "codeowners": ["@home-assistant/frontend"], "quality_scale": "internal" -} \ No newline at end of file +} diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 2cc4e1c65d6..c3e7dcea692 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ cryptography==2.8 defusedxml==0.6.0 distro==1.4.0 hass-nabucasa==0.31 -home-assistant-frontend==20200108.0 +home-assistant-frontend==20200108.2 importlib-metadata==1.3.0 jinja2>=2.10.3 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 508ff673ad0..bddb1908b85 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -679,7 +679,7 @@ hole==0.5.0 holidays==0.9.12 # homeassistant.components.frontend -home-assistant-frontend==20200108.0 +home-assistant-frontend==20200108.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fcd7b1df821..cc9f3e97e85 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -244,7 +244,7 @@ hole==0.5.0 holidays==0.9.12 # homeassistant.components.frontend -home-assistant-frontend==20200108.0 +home-assistant-frontend==20200108.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.7 From 2b733917a4354124c22794c1dd60d451efa460a2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 17 Jan 2020 15:38:38 -0800 Subject: [PATCH 20/27] Fix hue accepting filename (#30924) --- homeassistant/components/hue/__init__.py | 7 +++++-- tests/components/hue/test_init.py | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index b294a811c61..7349f4fe6a6 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -36,6 +36,7 @@ BRIDGE_CONFIG_SCHEMA = vol.Schema( vol.Optional( CONF_ALLOW_HUE_GROUPS, default=DEFAULT_ALLOW_HUE_GROUPS ): cv.boolean, + vol.Optional("filename"): str, } ) @@ -46,8 +47,10 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_BRIDGES): vol.All( cv.ensure_list, [ - cv.deprecated("filename", invalidation_version="0.106.0"), - vol.All(BRIDGE_CONFIG_SCHEMA), + vol.All( + cv.deprecated("filename", invalidation_version="0.106.0"), + BRIDGE_CONFIG_SCHEMA, + ), ], ) } diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index 2e147fd097b..35e1ba689b4 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -29,12 +29,14 @@ async def test_setup_defined_hosts_known_auth(hass): hue.DOMAIN, { hue.DOMAIN: { - hue.CONF_BRIDGES: { - hue.CONF_HOST: "0.0.0.0", - hue.CONF_ALLOW_HUE_GROUPS: False, - hue.CONF_ALLOW_UNREACHABLE: True, - "filename": "bla", - } + hue.CONF_BRIDGES: [ + { + hue.CONF_HOST: "0.0.0.0", + hue.CONF_ALLOW_HUE_GROUPS: False, + hue.CONF_ALLOW_UNREACHABLE: True, + }, + {hue.CONF_HOST: "1.1.1.1", "filename": "bla"}, + ] } }, ) @@ -42,7 +44,7 @@ async def test_setup_defined_hosts_known_auth(hass): ) # Flow started for discovered bridge - assert len(hass.config_entries.flow.async_progress()) == 0 + assert len(hass.config_entries.flow.async_progress()) == 1 # Config stored for domain. assert hass.data[hue.DATA_CONFIGS] == { @@ -50,8 +52,13 @@ async def test_setup_defined_hosts_known_auth(hass): hue.CONF_HOST: "0.0.0.0", hue.CONF_ALLOW_HUE_GROUPS: False, hue.CONF_ALLOW_UNREACHABLE: True, + }, + "1.1.1.1": { + hue.CONF_HOST: "1.1.1.1", + hue.CONF_ALLOW_HUE_GROUPS: True, + hue.CONF_ALLOW_UNREACHABLE: False, "filename": "bla", - } + }, } From 2c915af348d182825141ab687d04e0be2515df9a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 17 Jan 2020 15:48:27 -0800 Subject: [PATCH 21/27] Bumped version to 0.104.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 77e681432db..e2dfdf5ef64 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 104 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From dead2fe57655f91c3c1d35102e4abe01f5ac7f51 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 19 Jan 2020 13:39:16 -0800 Subject: [PATCH 22/27] Catch all Ring timeout errors (#30960) * Catch more Ring errors * Fix comment & Disable wifi entities by default Signed-off-by: Franck Nijhof --- homeassistant/components/ring/__init__.py | 30 ++++++++++++++++++++--- homeassistant/components/ring/light.py | 9 ++++++- homeassistant/components/ring/sensor.py | 12 ++++----- homeassistant/components/ring/switch.py | 9 ++++++- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index 7b4fbb15b30..34aa9f6b0ec 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import Optional from oauthlib.oauth2 import AccessDeniedError +import requests from ring_doorbell import Auth, Ring import voluptuous as vol @@ -95,13 +96,19 @@ async def async_setup_entry(hass, entry): "api": ring, "devices": ring.devices(), "device_data": GlobalDataUpdater( - hass, entry.entry_id, ring, "update_devices", timedelta(minutes=1) + hass, "device", entry.entry_id, ring, "update_devices", timedelta(minutes=1) ), "dings_data": GlobalDataUpdater( - hass, entry.entry_id, ring, "update_dings", timedelta(seconds=10) + hass, + "active dings", + entry.entry_id, + ring, + "update_dings", + timedelta(seconds=5), ), "history_data": DeviceDataUpdater( hass, + "history", entry.entry_id, ring, lambda device: device.history(limit=10), @@ -109,6 +116,7 @@ async def async_setup_entry(hass, entry): ), "health_data": DeviceDataUpdater( hass, + "health", entry.entry_id, ring, lambda device: device.update_health_data(), @@ -168,6 +176,7 @@ class GlobalDataUpdater: def __init__( self, hass: HomeAssistant, + data_type: str, config_entry_id: str, ring: Ring, update_method: str, @@ -175,6 +184,7 @@ class GlobalDataUpdater: ): """Initialize global data updater.""" self.hass = hass + self.data_type = data_type self.config_entry_id = config_entry_id self.ring = ring self.update_method = update_method @@ -215,6 +225,11 @@ class GlobalDataUpdater: _LOGGER.error("Ring access token is no longer valid. Set up Ring again") await self.hass.config_entries.async_unload(self.config_entry_id) return + except requests.Timeout: + _LOGGER.warning( + "Time out fetching Ring %s data", self.data_type, + ) + return for update_callback in self.listeners: update_callback() @@ -226,12 +241,14 @@ class DeviceDataUpdater: def __init__( self, hass: HomeAssistant, + data_type: str, config_entry_id: str, ring: Ring, update_method: str, update_interval: timedelta, ): """Initialize device data updater.""" + self.data_type = data_type self.hass = hass self.config_entry_id = config_entry_id self.ring = ring @@ -282,7 +299,7 @@ class DeviceDataUpdater: def refresh_all(self, _=None): """Refresh all registered devices.""" - for info in self.devices.values(): + for device_id, info in self.devices.items(): try: data = info["data"] = self.update_method(info["device"]) except AccessDeniedError: @@ -291,6 +308,13 @@ class DeviceDataUpdater: self.hass.config_entries.async_unload(self.config_entry_id) ) return + except requests.Timeout: + _LOGGER.warning( + "Time out fetching Ring %s data for device %s", + self.data_type, + device_id, + ) + continue for update_callback in info["update_callbacks"]: self.hass.loop.call_soon_threadsafe(update_callback, data) diff --git a/homeassistant/components/ring/light.py b/homeassistant/components/ring/light.py index 86ef55af16d..fa47ac35ee3 100644 --- a/homeassistant/components/ring/light.py +++ b/homeassistant/components/ring/light.py @@ -2,6 +2,8 @@ from datetime import timedelta import logging +import requests + from homeassistant.components.light import Light from homeassistant.core import callback import homeassistant.util.dt as dt_util @@ -72,7 +74,12 @@ class RingLight(RingEntityMixin, Light): def _set_light(self, new_state): """Update light state, and causes Home Assistant to correctly update.""" - self._device.lights = new_state + try: + self._device.lights = new_state + except requests.Timeout: + _LOGGER.error("Time out setting %s light to %s", self.entity_id, new_state) + return + self._light_on = new_state == ON_STATE self._no_updates_until = dt_util.utcnow() + SKIP_UPDATES_DELAY self.async_schedule_update_ha_state() diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 2b921dddd2f..329077a18e7 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -15,9 +15,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a sensor for a Ring device.""" devices = hass.data[DOMAIN][config_entry.entry_id]["devices"] - # Makes a ton of requests. We will make this a config entry option in the future - wifi_enabled = False - sensors = [] for device_type in ("chimes", "doorbots", "authorized_doorbots", "stickup_cams"): @@ -25,9 +22,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if device_type not in SENSOR_TYPES[sensor_type][1]: continue - if not wifi_enabled and sensor_type.startswith("wifi_"): - continue - for device in devices[device_type]: if device_type == "battery" and device.battery_life is None: continue @@ -124,6 +118,12 @@ class HealthDataRingSensor(RingSensor): """Call update method.""" self.async_write_ha_state() + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + # These sensors are data hungry and not useful. Disable by default. + return False + @property def state(self): """Return the state of the sensor.""" diff --git a/homeassistant/components/ring/switch.py b/homeassistant/components/ring/switch.py index 65eed83d98e..e2f1882adf6 100644 --- a/homeassistant/components/ring/switch.py +++ b/homeassistant/components/ring/switch.py @@ -2,6 +2,8 @@ from datetime import timedelta import logging +import requests + from homeassistant.components.switch import SwitchDevice from homeassistant.core import callback import homeassistant.util.dt as dt_util @@ -74,7 +76,12 @@ class SirenSwitch(BaseRingSwitch): def _set_switch(self, new_state): """Update switch state, and causes Home Assistant to correctly update.""" - self._device.siren = new_state + try: + self._device.siren = new_state + except requests.Timeout: + _LOGGER.error("Time out setting %s siren to %s", self.entity_id, new_state) + return + self._siren_on = new_state > 0 self._no_updates_until = dt_util.utcnow() + SKIP_UPDATES_DELAY self.schedule_update_ha_state() From dde6220ca59f4fb2f07afc06e227279568906b82 Mon Sep 17 00:00:00 2001 From: Jan De Luyck <5451787+jdeluyck@users.noreply.github.com> Date: Sun, 19 Jan 2020 21:01:16 +0100 Subject: [PATCH 23/27] Update emulated_roku to 0.2.0 (#30974) --- homeassistant/components/emulated_roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/emulated_roku/manifest.json b/homeassistant/components/emulated_roku/manifest.json index 62d51d7d910..8b5925fd12f 100644 --- a/homeassistant/components/emulated_roku/manifest.json +++ b/homeassistant/components/emulated_roku/manifest.json @@ -3,7 +3,7 @@ "name": "Emulated Roku", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/emulated_roku", - "requirements": ["emulated_roku==0.1.9"], + "requirements": ["emulated_roku==0.2.0"], "dependencies": [], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index bddb1908b85..053bfbd9cf7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -477,7 +477,7 @@ eliqonline==1.2.2 elkm1-lib==0.7.15 # homeassistant.components.emulated_roku -emulated_roku==0.1.9 +emulated_roku==0.2.0 # homeassistant.components.enocean enocean==0.50 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cc9f3e97e85..f52cc125f92 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -171,7 +171,7 @@ eebrightbox==0.0.4 elgato==0.2.0 # homeassistant.components.emulated_roku -emulated_roku==0.1.9 +emulated_roku==0.2.0 # homeassistant.components.season ephem==3.7.7.0 From 6889816704817e584ccdbd98a41b7186e3d8f4ad Mon Sep 17 00:00:00 2001 From: steve-gombos <3118886+steve-gombos@users.noreply.github.com> Date: Sun, 19 Jan 2020 16:18:11 -0500 Subject: [PATCH 24/27] Ring camera fix (#30975) * Fix ring camera entities * Reverted test refresh interval * Fix black errors --- homeassistant/components/ring/camera.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/ring/camera.py b/homeassistant/components/ring/camera.py index 1526a915482..96b1a962a67 100644 --- a/homeassistant/components/ring/camera.py +++ b/homeassistant/components/ring/camera.py @@ -51,8 +51,7 @@ class RingCam(RingEntityMixin, Camera): self._last_event = None self._last_video_id = None self._video_url = None - self._utcnow = dt_util.utcnow() - self._expires_at = self._utcnow - FORCE_REFRESH_INTERVAL + self._expires_at = dt_util.utcnow() - FORCE_REFRESH_INTERVAL async def async_added_to_hass(self): """Register callbacks.""" @@ -80,7 +79,7 @@ class RingCam(RingEntityMixin, Camera): self._last_event = None self._last_video_id = None self._video_url = None - self._expires_at = self._utcnow + self._expires_at = dt_util.utcnow() self.async_write_ha_state() @property @@ -141,10 +140,8 @@ class RingCam(RingEntityMixin, Camera): if self._last_event["recording"]["status"] != "ready": return - if ( - self._last_video_id == self._last_event["id"] - and self._utcnow <= self._expires_at - ): + utcnow = dt_util.utcnow() + if self._last_video_id == self._last_event["id"] and utcnow <= self._expires_at: return try: @@ -160,4 +157,4 @@ class RingCam(RingEntityMixin, Camera): if video_url: self._last_video_id = self._last_event["id"] self._video_url = video_url - self._expires_at = FORCE_REFRESH_INTERVAL + self._utcnow + self._expires_at = FORCE_REFRESH_INTERVAL + utcnow From 392cf57750816901fa3892ef67c820f6cfe05247 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sun, 19 Jan 2020 23:06:35 -0800 Subject: [PATCH 25/27] Fix capability_attributes when supported_features is None (#30993) * Fix capability_attributes when supported_features is None (water_heater) * Fix capability_attributes when supported_features is None (media_player) --- homeassistant/components/media_player/__init__.py | 2 +- homeassistant/components/water_heater/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 83c117d6c05..b73208402f8 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -785,7 +785,7 @@ class MediaPlayerDevice(Entity): @property def capability_attributes(self): """Return capabilitiy attributes.""" - supported_features = self.supported_features + supported_features = self.supported_features or 0 data = {} if supported_features & SUPPORT_SELECT_SOURCE: diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 8da94ff1098..ecff3105ae0 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -146,7 +146,7 @@ class WaterHeaterDevice(Entity): @property def capability_attributes(self): """Return capabilitiy attributes.""" - supported_features = self.supported_features + supported_features = self.supported_features or 0 data = { ATTR_MIN_TEMP: show_temp( From ccf6ce71cfbc18ea618cd5700134b86699ff18bd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 20 Jan 2020 17:18:10 +0100 Subject: [PATCH 26/27] Fix deCONZ update entry from Hassio discovery (#31015) * Fix deCONZ update entry from Hassio discovery * Empty commit to re-trigger build --- homeassistant/components/deconz/config_flow.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 5a9ef232e61..43c6cee9193 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -216,15 +216,15 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): This flow is triggered by the discovery component. """ self.bridge_id = normalize_bridge_id(user_input[CONF_SERIAL]) - gateway = self.hass.data.get(DOMAIN, {}).get(self.bridge_id) - if gateway: - return self._update_entry( - gateway.config_entry, - user_input[CONF_HOST], - user_input[CONF_PORT], - user_input[CONF_API_KEY], - ) + for entry in self.hass.config_entries.async_entries(DOMAIN): + if self.bridge_id == entry.unique_id: + return self._update_entry( + entry, + user_input[CONF_HOST], + user_input[CONF_PORT], + user_input[CONF_API_KEY], + ) await self.async_set_unique_id(self.bridge_id) self._hassio_discovery = user_input From cfa74039bb91288de71cf0aa1e743ddee4fd25fc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 21 Jan 2020 12:18:07 +0100 Subject: [PATCH 27/27] Bumped version to 0.104.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e2dfdf5ef64..4f6d643b531 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 104 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0)