From 7a2f6a500646ed8835b7a2740edd640689be0a61 Mon Sep 17 00:00:00 2001 From: shred86 <32663154+shred86@users.noreply.github.com> Date: Sun, 26 Jul 2020 11:59:11 -0700 Subject: [PATCH 01/16] Add Abode camera on and off support (#35164) * Add Abode camera controls * Add tests for camera turn on and off service * Bump abodepy version * Bump abodepy version and updates to reflect changes * Update manifest --- homeassistant/components/abode/__init__.py | 1 + homeassistant/components/abode/camera.py | 13 +++++++++ homeassistant/components/abode/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/abode/test_camera.py | 30 ++++++++++++++++++++ 6 files changed, 47 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 85e05e89cc1..92665bc1890 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -261,6 +261,7 @@ def setup_abode_events(hass): TIMELINE.AUTOMATION_GROUP, TIMELINE.DISARM_GROUP, TIMELINE.ARM_GROUP, + TIMELINE.ARM_FAULT_GROUP, TIMELINE.TEST_GROUP, TIMELINE.CAPTURE_GROUP, TIMELINE.DEVICE_GROUP, diff --git a/homeassistant/components/abode/camera.py b/homeassistant/components/abode/camera.py index b7d5f1dbe4c..99d4fd433a7 100644 --- a/homeassistant/components/abode/camera.py +++ b/homeassistant/components/abode/camera.py @@ -82,8 +82,21 @@ class AbodeCamera(AbodeDevice, Camera): return None + def turn_on(self): + """Turn on camera.""" + self._device.privacy_mode(False) + + def turn_off(self): + """Turn off camera.""" + self._device.privacy_mode(True) + def _capture_callback(self, capture): """Update the image with the device then refresh device.""" self._device.update_image_location(capture) self.get_image() self.schedule_update_ha_state() + + @property + def is_on(self): + """Return true if on.""" + return self._device.is_on diff --git a/homeassistant/components/abode/manifest.json b/homeassistant/components/abode/manifest.json index d59ddd6217f..e9a871035e6 100644 --- a/homeassistant/components/abode/manifest.json +++ b/homeassistant/components/abode/manifest.json @@ -3,7 +3,7 @@ "name": "Abode", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/abode", - "requirements": ["abodepy==0.19.0"], + "requirements": ["abodepy==1.1.0"], "codeowners": ["@shred86"], "homekit": { "models": ["Abode", "Iota"] diff --git a/requirements_all.txt b/requirements_all.txt index ea30023f8b9..bf08943341d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -100,7 +100,7 @@ WazeRouteCalculator==0.12 YesssSMS==0.4.1 # homeassistant.components.abode -abodepy==0.19.0 +abodepy==1.1.0 # homeassistant.components.mcp23017 adafruit-blinka==3.9.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0efa7c3664f..6bdf6919659 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -43,7 +43,7 @@ WSDiscovery==2.0.0 YesssSMS==0.4.1 # homeassistant.components.abode -abodepy==0.19.0 +abodepy==1.1.0 # homeassistant.components.androidtv adb-shell[async]==0.2.0 diff --git a/tests/components/abode/test_camera.py b/tests/components/abode/test_camera.py index 0e843c59023..9db03d90222 100644 --- a/tests/components/abode/test_camera.py +++ b/tests/components/abode/test_camera.py @@ -38,3 +38,33 @@ async def test_capture_image(hass): ) await hass.async_block_till_done() mock_capture.assert_called_once() + + +async def test_camera_on(hass): + """Test the camera turn on service.""" + await setup_platform(hass, CAMERA_DOMAIN) + + with patch("abodepy.AbodeCamera.privacy_mode") as mock_capture: + await hass.services.async_call( + CAMERA_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: "camera.test_cam"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_capture.assert_called_once_with(False) + + +async def test_camera_off(hass): + """Test the camera turn off service.""" + await setup_platform(hass, CAMERA_DOMAIN) + + with patch("abodepy.AbodeCamera.privacy_mode") as mock_capture: + await hass.services.async_call( + CAMERA_DOMAIN, + "turn_off", + {ATTR_ENTITY_ID: "camera.test_cam"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_capture.assert_called_once_with(True) From 1b73bcbff78c510838a98b7cf63fd24c49ff3b9c Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Wed, 29 Jul 2020 07:49:43 +0800 Subject: [PATCH 02/16] Fix songpal already configured check in config flow (#37813) * Fix already configured check * Mark endpoint duplicate check as callback --- homeassistant/components/songpal/config_flow.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/songpal/config_flow.py b/homeassistant/components/songpal/config_flow.py index 96e1e7ed7df..9acbedd11c7 100644 --- a/homeassistant/components/songpal/config_flow.py +++ b/homeassistant/components/songpal/config_flow.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import ssdp from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.core import callback from .const import CONF_ENDPOINT, DOMAIN # pylint: disable=unused-import @@ -74,7 +75,7 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_init(self, user_input=None): """Handle a flow start.""" # Check if already configured - if self._endpoint_already_configured(): + if self._async_endpoint_already_configured(): return self.async_abort(reason="already_configured") if user_input is None: @@ -145,9 +146,10 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_init(user_input) - def _endpoint_already_configured(self): + @callback + def _async_endpoint_already_configured(self): """See if we already have an endpoint matching user input configured.""" - existing_endpoints = [ - entry.data[CONF_ENDPOINT] for entry in self._async_current_entries() - ] - return self.conf.endpoint in existing_endpoints + for entry in self._async_current_entries(): + if entry.data.get(CONF_ENDPOINT) == self.conf.endpoint: + return True + return False From d1fbcba7b6fe8ac8103597b52a8dd5e66946ef9b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 27 Jul 2020 19:43:42 -1000 Subject: [PATCH 03/16] Prevent kodi from blocking startup (#38257) * Prevent kodi from blocking startup * Update homeassistant/components/kodi/media_player.py * isort * ignore args * adjustments per review * asyncio --- homeassistant/components/kodi/media_player.py | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index ac31716b887..81f8696a31a 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -1,5 +1,7 @@ """Support for interfacing with the XBMC/Kodi JSON-RPC API.""" +import asyncio from collections import OrderedDict +from datetime import timedelta from functools import wraps import logging import re @@ -53,6 +55,7 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, script from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.template import Template import homeassistant.util.dt as dt_util from homeassistant.util.yaml import dump @@ -82,6 +85,8 @@ DEPRECATED_TURN_OFF_ACTIONS = { "shutdown": "System.Shutdown", } +WEBSOCKET_WATCHDOG_INTERVAL = timedelta(minutes=3) + # https://github.com/xbmc/xbmc/blob/master/xbmc/media/MediaType.h MEDIA_TYPES = { "music": MEDIA_TYPE_MUSIC, @@ -435,6 +440,26 @@ class KodiDevice(MediaPlayerEntity): # run until the websocket connection is closed. self.hass.loop.create_task(ws_loop_wrapper()) + async def async_added_to_hass(self): + """Connect the websocket if needed.""" + if not self._enable_websocket: + return + + asyncio.create_task(self.async_ws_connect()) + + self.async_on_remove( + async_track_time_interval( + self.hass, + self._async_connect_websocket_if_disconnected, + WEBSOCKET_WATCHDOG_INTERVAL, + ) + ) + + async def _async_connect_websocket_if_disconnected(self, *_): + """Reconnect the websocket if it fails.""" + if not self._ws_server.connected: + await self.async_ws_connect() + async def async_update(self): """Retrieve latest state.""" self._players = await self._get_players() @@ -445,9 +470,6 @@ class KodiDevice(MediaPlayerEntity): self._app_properties = {} return - if self._enable_websocket and not self._ws_server.connected: - self.hass.async_create_task(self.async_ws_connect()) - self._app_properties = await self.server.Application.GetProperties( ["volume", "muted"] ) From d65545964b8a2a9891475457351e4d9ed836c04b Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 29 Jul 2020 13:56:32 -0500 Subject: [PATCH 04/16] Ignore remote Plex clients during plex.tv lookup (#38327) --- homeassistant/components/plex/server.py | 2 +- tests/components/plex/mock_classes.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index db779d67bb0..146291bdbcf 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -115,7 +115,7 @@ class PlexServer: self._plextv_clients = [ x for x in self.account.resources() - if "player" in x.provides and x.presence + if "player" in x.provides and x.presence and x.publicAddressMatches ] _LOGGER.debug( "Current available clients from plex.tv: %s", self._plextv_clients diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index eacee6d9f98..dd8e9a93ab8 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -53,6 +53,7 @@ class MockResource: self.provides = ["player"] self.device = MockPlexClient(f"http://192.168.0.1{index}:32500", index + 10) self.presence = index == 0 + self.publicAddressMatches = True def connect(self, timeout): """Mock the resource connect method.""" From dd4e0511a3254d21d03f91953943c5e7bb15c05e Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Wed, 29 Jul 2020 08:16:24 -0700 Subject: [PATCH 05/16] Bump androidtv to 0.0.47 and adb-shell to 0.2.1 (#38344) --- homeassistant/components/androidtv/manifest.json | 4 ++-- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index 40e7575bbb9..7a76a618756 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -3,8 +3,8 @@ "name": "Android TV", "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ - "adb-shell[async]==0.2.0", - "androidtv[async]==0.0.46", + "adb-shell[async]==0.2.1", + "androidtv[async]==0.0.47", "pure-python-adb==0.2.2.dev0" ], "codeowners": ["@JeffLIrion"] diff --git a/requirements_all.txt b/requirements_all.txt index bf08943341d..3306f6ca887 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -112,7 +112,7 @@ adafruit-circuitpython-bmp280==3.1.1 adafruit-circuitpython-mcp230xx==2.2.2 # homeassistant.components.androidtv -adb-shell[async]==0.2.0 +adb-shell[async]==0.2.1 # homeassistant.components.alarmdecoder adext==0.3 @@ -231,7 +231,7 @@ ambiclimate==0.2.1 amcrest==1.7.0 # homeassistant.components.androidtv -androidtv[async]==0.0.46 +androidtv[async]==0.0.47 # homeassistant.components.anel_pwrctrl anel_pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6bdf6919659..0a8977d2a7a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -46,7 +46,7 @@ YesssSMS==0.4.1 abodepy==1.1.0 # homeassistant.components.androidtv -adb-shell[async]==0.2.0 +adb-shell[async]==0.2.1 # homeassistant.components.adguard adguardhome==0.4.2 @@ -132,7 +132,7 @@ airly==0.0.2 ambiclimate==0.2.1 # homeassistant.components.androidtv -androidtv[async]==0.0.46 +androidtv[async]==0.0.47 # homeassistant.components.apns apns2==0.3.0 From abad6dfdd7720cf7fd6b78e7da2d274901990763 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 29 Jul 2020 23:46:14 +0200 Subject: [PATCH 06/16] Bump pychromecast to 7.2.0 (#38351) --- homeassistant/components/cast/config_flow.py | 13 +++++++++++-- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cast/test_init.py | 12 +++++------- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/cast/config_flow.py b/homeassistant/components/cast/config_flow.py index 5c2b6dca932..80d4abc9796 100644 --- a/homeassistant/components/cast/config_flow.py +++ b/homeassistant/components/cast/config_flow.py @@ -1,16 +1,25 @@ """Config flow for Cast.""" -from pychromecast.discovery import discover_chromecasts +import functools + +from pychromecast.discovery import discover_chromecasts, stop_discovery from homeassistant import config_entries from homeassistant.helpers import config_entry_flow from .const import DOMAIN +from .helpers import ChromeCastZeroconf async def _async_has_devices(hass): """Return if there are devices that can be discovered.""" - return await hass.async_add_executor_job(discover_chromecasts) + casts, browser = await hass.async_add_executor_job( + functools.partial( + discover_chromecasts, zeroconf_instance=ChromeCastZeroconf.get_zeroconf() + ) + ) + stop_discovery(browser) + return casts config_entry_flow.register_discovery_flow( diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 5d807525226..1187887e864 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==7.1.2"], + "requirements": ["pychromecast==7.2.0"], "after_dependencies": ["cloud","zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 3306f6ca887..200f4591d42 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1241,7 +1241,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==7.1.2 +pychromecast==7.2.0 # homeassistant.components.cmus pycmus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0a8977d2a7a..f2092c69ffd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -577,7 +577,7 @@ pyblackbird==0.5 pybotvac==0.0.17 # homeassistant.components.cast -pychromecast==7.1.2 +pychromecast==7.2.0 # homeassistant.components.coolmaster pycoolmasternet==0.0.4 diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index 8f194668e56..24be4d53ee6 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -13,7 +13,9 @@ async def test_creating_entry_sets_up_media_player(hass): "homeassistant.components.cast.media_player.async_setup_entry", return_value=True, ) as mock_setup, patch( - "pychromecast.discovery.discover_chromecasts", return_value=True + "pychromecast.discovery.discover_chromecasts", return_value=(True, None) + ), patch( + "pychromecast.discovery.stop_discovery" ): result = await hass.config_entries.flow.async_init( cast.DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -34,9 +36,7 @@ async def test_configuring_cast_creates_entry(hass): """Test that specifying config will create an entry.""" with patch( "homeassistant.components.cast.async_setup_entry", return_value=True - ) as mock_setup, patch( - "pychromecast.discovery.discover_chromecasts", return_value=True - ): + ) as mock_setup: await async_setup_component( hass, cast.DOMAIN, {"cast": {"some_config": "to_trigger_import"}} ) @@ -49,9 +49,7 @@ async def test_not_configuring_cast_not_creates_entry(hass): """Test that no config will not create an entry.""" with patch( "homeassistant.components.cast.async_setup_entry", return_value=True - ) as mock_setup, patch( - "pychromecast.discovery.discover_chromecasts", return_value=True - ): + ) as mock_setup: await async_setup_component(hass, cast.DOMAIN, {}) await hass.async_block_till_done() From 9819d70941ebba48f763a17589a972f2f8053797 Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Wed, 29 Jul 2020 11:49:13 -0600 Subject: [PATCH 07/16] Update aioharmony to 0.2.6 (#38360) --- homeassistant/components/harmony/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/harmony/manifest.json b/homeassistant/components/harmony/manifest.json index 40f88ad19ef..4d8b83f4643 100644 --- a/homeassistant/components/harmony/manifest.json +++ b/homeassistant/components/harmony/manifest.json @@ -2,7 +2,7 @@ "domain": "harmony", "name": "Logitech Harmony Hub", "documentation": "https://www.home-assistant.io/integrations/harmony", - "requirements": ["aioharmony==0.2.5"], + "requirements": ["aioharmony==0.2.6"], "codeowners": ["@ehendrix23", "@bramkragten", "@bdraco"], "ssdp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 200f4591d42..f8fe07d1712 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -164,7 +164,7 @@ aioftp==0.12.0 aioguardian==1.0.1 # homeassistant.components.harmony -aioharmony==0.2.5 +aioharmony==0.2.6 # homeassistant.components.homekit_controller aiohomekit[IP]==0.2.45 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f2092c69ffd..359ff4ce433 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -89,7 +89,7 @@ aiofreepybox==0.0.8 aioguardian==1.0.1 # homeassistant.components.harmony -aioharmony==0.2.5 +aioharmony==0.2.6 # homeassistant.components.homekit_controller aiohomekit[IP]==0.2.45 From 0ecaab1a7dbe1fddb461e50a10604e7541ebd666 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Jul 2020 09:20:06 -1000 Subject: [PATCH 08/16] Avoid error with ignored harmony config entries (#38367) --- homeassistant/components/harmony/config_flow.py | 3 +++ tests/components/harmony/test_config_flow.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index 9142eed2dba..6d5adabe235 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -164,6 +164,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def _host_already_configured(self, host): """See if we already have a harmony entry matching the host.""" for entry in self._async_current_entries(): + if CONF_HOST not in entry.data: + continue + if entry.data[CONF_HOST] == host: return True return False diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index d159a0f025f..acf1ffd16f1 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -153,6 +153,9 @@ async def test_form_ssdp_aborts_before_checking_remoteid_if_host_known(hass): ) config_entry.add_to_hass(hass) + config_entry_without_host = MockConfigEntry(domain=DOMAIN, data={"name": "other"},) + config_entry_without_host.add_to_hass(hass) + harmonyapi = _get_mock_harmonyapi(connect=True) with patch( From 293655f9887bd48368a2ee497cb1d3690524f91b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Jul 2020 11:20:19 -1000 Subject: [PATCH 09/16] Prevent nut config flow error when checking ignored entries (#38372) --- homeassistant/components/nut/config_flow.py | 1 + tests/components/nut/test_config_flow.py | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 5d90d16f157..7cebf0c6759 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -216,6 +216,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): existing_host_port_aliases = { _format_host_port_alias(entry.data) for entry in self._async_current_entries() + if CONF_HOST in entry.data } return _format_host_port_alias(user_input) in existing_host_port_aliases diff --git a/tests/components/nut/test_config_flow.py b/tests/components/nut/test_config_flow.py index 5a2155441b5..d86c0d8eb88 100644 --- a/tests/components/nut/test_config_flow.py +++ b/tests/components/nut/test_config_flow.py @@ -247,7 +247,25 @@ async def test_form_import_dupe(hass): entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "import"}, data=VALID_CONFIG + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=VALID_CONFIG + ) + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + +async def test_form_import_with_ignored_entry(hass): + """Test we get abort on duplicate import when there is an ignored one.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + entry = MockConfigEntry(domain=DOMAIN, data=VALID_CONFIG) + entry.add_to_hass(hass) + ignored_entry = MockConfigEntry( + domain=DOMAIN, data={}, source=config_entries.SOURCE_IGNORE + ) + ignored_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=VALID_CONFIG ) assert result["type"] == "abort" assert result["reason"] == "already_configured" From 47a729495dcaae63beccdc91a3a2c73f5154bea9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 30 Jul 2020 21:37:34 +0200 Subject: [PATCH 10/16] Ensure Toon webhook ID isn't registered on re-registration (#38376) --- homeassistant/components/toon/coordinator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/toon/coordinator.py b/homeassistant/components/toon/coordinator.py index 8e9722316e2..640fa9bb04e 100644 --- a/homeassistant/components/toon/coordinator.py +++ b/homeassistant/components/toon/coordinator.py @@ -71,6 +71,9 @@ class ToonDataUpdateCoordinator(DataUpdateCoordinator): self.entry.data[CONF_WEBHOOK_ID] ) + # Ensure the webhook is not registered already + webhook_unregister(self.hass, self.entry.data[CONF_WEBHOOK_ID]) + webhook_register( self.hass, DOMAIN, From 2a3947f7cc0906c3034615c6b696459d6fb3ebe6 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Fri, 31 Jul 2020 14:38:49 +0200 Subject: [PATCH 11/16] Fix rmvtransport breaking when destinations don't match (#38401) --- .../components/rmvtransport/sensor.py | 34 ++++++++++++++++--- tests/components/rmvtransport/test_sensor.py | 8 ++--- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index 704bde67a5c..76e75d77a58 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -100,7 +100,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= tasks = [sensor.async_update() for sensor in sensors] if tasks: await asyncio.wait(tasks) - if not all(sensor.data.departures for sensor in sensors): + + if not any(sensor.data for sensor in sensors): raise PlatformNotReady async_add_entities(sensors) @@ -165,6 +166,7 @@ class RMVDepartureSensor(Entity): "minutes": self.data.departures[0].get("minutes"), "departure_time": self.data.departures[0].get("departure_time"), "product": self.data.departures[0].get("product"), + ATTR_ATTRIBUTION: ATTRIBUTION, } except IndexError: return {} @@ -183,13 +185,16 @@ class RMVDepartureSensor(Entity): """Get the latest data and update the state.""" await self.data.async_update() + if self._name == DEFAULT_NAME: + self._name = self.data.station + + self._station = self.data.station + if not self.data.departures: self._state = None self._icon = ICONS[None] return - if self._name == DEFAULT_NAME: - self._name = self.data.station - self._station = self.data.station + self._state = self.data.departures[0].get("minutes") self._icon = ICONS[self.data.departures[0].get("product")] @@ -220,6 +225,7 @@ class RMVDepartureData: self._max_journeys = max_journeys self.rmv = RMVtransport(session, timeout) self.departures = [] + self._error_notification = False @Throttle(SCAN_INTERVAL) async def async_update(self): @@ -231,31 +237,49 @@ class RMVDepartureData: direction_id=self._direction, max_journeys=50, ) + except RMVtransportApiConnectionError: self.departures = [] _LOGGER.warning("Could not retrieve data from rmv.de") return + self.station = _data.get("station") + _deps = [] + _deps_not_found = set(self._destinations) + for journey in _data["journeys"]: # find the first departure meeting the criteria - _nextdep = {ATTR_ATTRIBUTION: ATTRIBUTION} + _nextdep = {} if self._destinations: dest_found = False for dest in self._destinations: if dest in journey["stops"]: dest_found = True + if dest in _deps_not_found: + _deps_not_found.remove(dest) _nextdep["destination"] = dest + if not dest_found: continue + elif self._lines and journey["number"] not in self._lines: continue + elif journey["minutes"] < self._time_offset: continue + for attr in ["direction", "departure_time", "product", "minutes"]: _nextdep[attr] = journey.get(attr, "") + _nextdep["line"] = journey.get("number", "") _deps.append(_nextdep) + if len(_deps) > self._max_journeys: break + + if not self._error_notification and _deps_not_found: + self._error_notification = True + _LOGGER.info("Destination(s) %s not found", ", ".join(_deps_not_found)) + self.departures = _deps diff --git a/tests/components/rmvtransport/test_sensor.py b/tests/components/rmvtransport/test_sensor.py index 419eaafc5eb..b576f385173 100644 --- a/tests/components/rmvtransport/test_sensor.py +++ b/tests/components/rmvtransport/test_sensor.py @@ -48,7 +48,7 @@ VALID_CONFIG_DEST = { def get_departures_mock(): """Mock rmvtransport departures loading.""" - data = { + return { "station": "Frankfurt (Main) Hauptbahnhof", "stationId": "3000010", "filter": "11111111111", @@ -145,18 +145,16 @@ def get_departures_mock(): }, ], } - return data def get_no_departures_mock(): """Mock no departures in results.""" - data = { + return { "station": "Frankfurt (Main) Hauptbahnhof", "stationId": "3000010", "filter": "11111111111", "journeys": [], } - return data async def test_rmvtransport_min_config(hass): @@ -232,4 +230,4 @@ async def test_rmvtransport_no_departures(hass): await hass.async_block_till_done() state = hass.states.get("sensor.frankfurt_main_hauptbahnhof") - assert not state + assert state.state == "unavailable" From f4afa2dc689dfd4e42aa2675508713a3bc1dbfe0 Mon Sep 17 00:00:00 2001 From: Stefan Lehmann Date: Fri, 31 Jul 2020 13:59:32 +0200 Subject: [PATCH 12/16] Fix ads integration after 0.113 (#38402) --- homeassistant/components/ads/__init__.py | 20 +++++++++++++------- homeassistant/components/ads/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index 15d58eb4620..b17a066eba7 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -230,7 +230,13 @@ class AdsHub: hnotify = int(contents.hNotification) _LOGGER.debug("Received notification %d", hnotify) - data = contents.data + + # get dynamically sized data array + data_size = contents.cbSampleSize + data = (ctypes.c_ubyte * data_size).from_address( + ctypes.addressof(contents) + + pyads.structs.SAdsNotificationHeader.data.offset + ) try: with self._lock: @@ -241,17 +247,17 @@ class AdsHub: # Parse data to desired datatype if notification_item.plc_datatype == self.PLCTYPE_BOOL: - value = bool(struct.unpack(" Date: Fri, 31 Jul 2020 22:06:02 +0200 Subject: [PATCH 13/16] Pin yarl dependency to 1.4.2 as core dependency (#38428) --- homeassistant/package_constraints.txt | 1 + requirements.txt | 1 + setup.py | 1 + 3 files changed, 3 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 50e006c3d71..7642ef3480f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -27,6 +27,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.18 voluptuous-serialize==2.4.0 voluptuous==0.11.7 +yarl==1.4.2 zeroconf==0.27.1 pycryptodome>=3.6.6 diff --git a/requirements.txt b/requirements.txt index 93a95112658..efae1204e11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,4 @@ requests==2.24.0 ruamel.yaml==0.15.100 voluptuous==0.11.7 voluptuous-serialize==2.4.0 +yarl==1.4.2 diff --git a/setup.py b/setup.py index c2042ab2459..7cf06942f32 100755 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ REQUIRES = [ "ruamel.yaml==0.15.100", "voluptuous==0.11.7", "voluptuous-serialize==2.4.0", + "yarl==1.4.2", ] MIN_PY_VERSION = ".".join(map(str, hass_const.REQUIRED_PYTHON_VER)) From 82b3fe1ab62771f5021dded75be3c451962bd7f9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 31 Jul 2020 22:06:17 +0200 Subject: [PATCH 14/16] Fix double encoding issue in google_translate TTS (#38429) --- homeassistant/components/google_translate/tts.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/google_translate/tts.py b/homeassistant/components/google_translate/tts.py index 36543e0515e..a040da0dccd 100644 --- a/homeassistant/components/google_translate/tts.py +++ b/homeassistant/components/google_translate/tts.py @@ -8,7 +8,6 @@ from aiohttp.hdrs import REFERER, USER_AGENT import async_timeout from gtts_token import gtts_token import voluptuous as vol -import yarl from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider from homeassistant.const import HTTP_OK @@ -129,7 +128,7 @@ class GoogleProvider(Provider): url_param = { "ie": "UTF-8", "tl": language, - "q": yarl.URL(part).raw_path, + "q": part, "tk": part_token, "total": len(message_parts), "idx": idx, From b209c1a7b5d970550deb5289081930c975f95098 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 1 Aug 2020 02:02:48 +0000 Subject: [PATCH 15/16] Bumped version to 0.113.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 5c5877c5e7b..e4c5b66526c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 113 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From c4fcd8bd2e47b8456ef8673baaf590773a57f31e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 29 Jul 2020 15:31:29 +0200 Subject: [PATCH 16/16] Temporary lock pip to 20.1.1 to avoid build issue (#38358) --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a17a4dc318f..6a7708c1c5c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,7 +46,7 @@ jobs: run: | python -m venv venv . venv/bin/activate - pip install -U pip setuptools + pip install -U pip==20.1.1 setuptools pip install -r requirements.txt -r requirements_test.txt # Uninstalling typing as a workaround. Eventually we should make sure # all our dependencies drop typing. @@ -603,7 +603,7 @@ jobs: run: | python -m venv venv . venv/bin/activate - pip install -U pip setuptools wheel + pip install -U pip==20.1.1 setuptools wheel pip install -r requirements_all.txt pip install -r requirements_test.txt # Uninstalling typing as a workaround. Eventually we should make sure