From 7f2a2ed23b855b39c88c0cccf4c51d387deec665 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 24 Jul 2020 21:00:08 -1000 Subject: [PATCH 01/20] Bump netdisco to 2.8.1 (#38173) * Bump netdisco to 2.8.1 * bump ssdp --- homeassistant/components/discovery/manifest.json | 2 +- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/discovery/manifest.json b/homeassistant/components/discovery/manifest.json index 962ba9b8e8c..232237484d1 100644 --- a/homeassistant/components/discovery/manifest.json +++ b/homeassistant/components/discovery/manifest.json @@ -2,7 +2,7 @@ "domain": "discovery", "name": "Discovery", "documentation": "https://www.home-assistant.io/integrations/discovery", - "requirements": ["netdisco==2.8.0"], + "requirements": ["netdisco==2.8.1"], "after_dependencies": ["zeroconf"], "codeowners": [], "quality_scale": "internal" diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 3dde2e9002e..85ae4725e93 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["defusedxml==0.6.0", "netdisco==2.8.0"], + "requirements": ["defusedxml==0.6.0", "netdisco==2.8.1"], "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 31fdad0d54a..50e006c3d71 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -16,7 +16,7 @@ hass-nabucasa==0.34.7 home-assistant-frontend==20200716.0 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 -netdisco==2.8.0 +netdisco==2.8.1 paho-mqtt==1.5.0 pip>=8.0.3 python-slugify==4.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 3bcfc2e7596..2d0ed0cb07c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -936,7 +936,7 @@ netdata==0.2.0 # homeassistant.components.discovery # homeassistant.components.ssdp -netdisco==2.8.0 +netdisco==2.8.1 # homeassistant.components.neurio_energy neurio==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bc48e7f7e49..f1365d9d598 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -431,7 +431,7 @@ nessclient==0.9.15 # homeassistant.components.discovery # homeassistant.components.ssdp -netdisco==2.8.0 +netdisco==2.8.1 # homeassistant.components.nexia nexia==0.9.3 From 61fa5720680e73bdd90f071098288b585d2288d6 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Sat, 25 Jul 2020 05:19:55 -0500 Subject: [PATCH 02/20] Stop automation runs when turned off or reloaded (#38174) * Add automation turn off / reload test * Stop automation runs when turned off or reloaded --- .../components/automation/__init__.py | 2 + tests/components/automation/test_init.py | 57 ++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 3cbb98d85bd..2a05b1555c9 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -455,6 +455,8 @@ class AutomationEntity(ToggleEntity, RestoreEntity): self._async_detach_triggers() self._async_detach_triggers = None + await self.action_script.async_stop() + self.async_write_ha_state() async def _async_attach_triggers( diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 909493ddf53..bdae9a1f326 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,4 +1,6 @@ """The tests for the automation component.""" +import asyncio + import pytest from homeassistant.components import logbook @@ -12,10 +14,11 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_NAME, EVENT_HOMEASSISTANT_STARTED, + SERVICE_TURN_OFF, STATE_OFF, STATE_ON, ) -from homeassistant.core import Context, CoreState, State +from homeassistant.core import Context, CoreState, State, callback from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -553,6 +556,58 @@ async def test_reload_config_handles_load_fails(hass, calls): assert len(calls) == 2 +@pytest.mark.parametrize("service", ["turn_off", "reload"]) +async def test_automation_stops(hass, calls, service): + """Test that turning off / reloading an automation stops any running actions.""" + entity_id = "automation.hello" + test_entity = "test.entity" + + config = { + automation.DOMAIN: { + "alias": "hello", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "running"}, + {"wait_template": "{{ is_state('test.entity', 'goodbye') }}"}, + {"service": "test.automation"}, + ], + } + } + assert await async_setup_component(hass, automation.DOMAIN, config,) + + running = asyncio.Event() + + @callback + def running_cb(event): + running.set() + + hass.bus.async_listen_once("running", running_cb) + hass.states.async_set(test_entity, "hello") + + hass.bus.async_fire("test_event") + await running.wait() + + if service == "turn_off": + await hass.services.async_call( + automation.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + else: + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value=config, + ): + await common.async_reload(hass) + + hass.states.async_set(test_entity, "goodbye") + await hass.async_block_till_done() + + assert len(calls) == 0 + + async def test_automation_restore_state(hass): """Ensure states are restored on startup.""" time = dt_util.utcnow() From b82e64d9cbc3bcd71a7c34ef60e274a7c87b8134 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 28 Jul 2020 05:26:06 -1000 Subject: [PATCH 03/20] Bump tesla-powerwall to 0.2.12 to handle powerwall firmware 1.48+ (#38180) --- homeassistant/components/powerwall/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index 1ba9562c4b7..930da38edde 100644 --- a/homeassistant/components/powerwall/manifest.json +++ b/homeassistant/components/powerwall/manifest.json @@ -3,6 +3,6 @@ "name": "Tesla Powerwall", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/powerwall", - "requirements": ["tesla-powerwall==0.2.11"], + "requirements": ["tesla-powerwall==0.2.12"], "codeowners": ["@bdraco", "@jrester"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2d0ed0cb07c..672b1360af8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2080,7 +2080,7 @@ temperusb==1.5.3 # tensorflow==1.13.2 # homeassistant.components.powerwall -tesla-powerwall==0.2.11 +tesla-powerwall==0.2.12 # homeassistant.components.tesla teslajsonpy==0.9.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f1365d9d598..73a0ccf2060 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -909,7 +909,7 @@ sunwatcher==0.2.1 tellduslive==0.10.11 # homeassistant.components.powerwall -tesla-powerwall==0.2.11 +tesla-powerwall==0.2.12 # homeassistant.components.tesla teslajsonpy==0.9.3 From 2e89ec24f7cb9f63dfac5c4e4e29c4d958f4c1c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 24 Jul 2020 23:13:47 -1000 Subject: [PATCH 04/20] Ignore harmony hubs ips that are already configured during ssdp discovery (#38181) We would connect to the hub via discovery and via setup around the same time. This put additional load on the hub which can increase the risk of timeouts. --- .../components/harmony/config_flow.py | 10 ++++++++ tests/components/harmony/test_config_flow.py | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index 576451ef2d6..9142eed2dba 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -86,6 +86,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]) friendly_name = discovery_info[ssdp.ATTR_UPNP_FRIENDLY_NAME] + if self._host_already_configured(parsed_url.hostname): + return self.async_abort(reason="already_configured") + # pylint: disable=no-member self.context["title_placeholders"] = {"name": friendly_name} @@ -158,6 +161,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=validated[CONF_NAME], data=data) + 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 entry.data[CONF_HOST] == host: + return True + return False + def _options_from_user_input(user_input): options = {} diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index 0228983ef9d..d159a0f025f 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -145,6 +145,30 @@ async def test_form_ssdp(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_ssdp_aborts_before_checking_remoteid_if_host_known(hass): + """Test we abort without connecting if the host is already known.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + config_entry = MockConfigEntry( + domain=DOMAIN, data={"host": "2.2.2.2", "name": "any"}, + ) + config_entry.add_to_hass(hass) + + harmonyapi = _get_mock_harmonyapi(connect=True) + + with patch( + "homeassistant.components.harmony.util.HarmonyAPI", return_value=harmonyapi, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data={ + "friendlyName": "Harmony Hub", + "ssdp_location": "http://2.2.2.2:8088/description", + }, + ) + assert result["type"] == "abort" + + async def test_form_cannot_connect(hass): """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( From ee0c32cbb7e64c8210627952a7e14d86e47e2b00 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Sat, 25 Jul 2020 22:56:58 +0200 Subject: [PATCH 05/20] Make rfxtrx RfyDevices have sun automation switches (#38210) * RfyDevices have sun automation * We must accept sun automation commands for switch * Add test for Rfy sun automation --- homeassistant/components/rfxtrx/const.py | 2 ++ homeassistant/components/rfxtrx/switch.py | 1 + tests/components/rfxtrx/test_switch.py | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/homeassistant/components/rfxtrx/const.py b/homeassistant/components/rfxtrx/const.py index 7626c082f45..500fb3a1d1b 100644 --- a/homeassistant/components/rfxtrx/const.py +++ b/homeassistant/components/rfxtrx/const.py @@ -7,12 +7,14 @@ COMMAND_ON_LIST = [ "Stop", "Open (inline relay)", "Stop (inline relay)", + "Enable sun automation", ] COMMAND_OFF_LIST = [ "Off", "Down", "Close (inline relay)", + "Disable sun automation", ] ATTR_EVENT = "event" diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index 6cd9a484abd..52a55f79c01 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -37,6 +37,7 @@ async def async_setup_entry( isinstance(event.device, rfxtrxmod.LightingDevice) and not event.device.known_to_be_dimmable and not event.device.known_to_be_rollershutter + or isinstance(event.device, rfxtrxmod.RfyDevice) ) # Add switch from config file diff --git a/tests/components/rfxtrx/test_switch.py b/tests/components/rfxtrx/test_switch.py index 22f7a73c77c..1f43081a9b5 100644 --- a/tests/components/rfxtrx/test_switch.py +++ b/tests/components/rfxtrx/test_switch.py @@ -10,6 +10,9 @@ from . import _signal_event from tests.common import mock_restore_cache +EVENT_RFY_ENABLE_SUN_AUTO = "081a00000301010113" +EVENT_RFY_DISABLE_SUN_AUTO = "081a00000301010114" + async def test_one_switch(hass, rfxtrx): """Test with 1 switch.""" @@ -139,3 +142,18 @@ async def test_discover_switch(hass, rfxtrx): state = hass.states.get("switch.ac_118cdeb_2") assert state assert state.state == "on" + + +async def test_discover_rfy_sun_switch(hass, rfxtrx_automatic): + """Test with discovery of switches.""" + rfxtrx = rfxtrx_automatic + + await rfxtrx.signal(EVENT_RFY_DISABLE_SUN_AUTO) + state = hass.states.get("switch.rfy_030101_1") + assert state + assert state.state == "off" + + await rfxtrx.signal(EVENT_RFY_ENABLE_SUN_AUTO) + state = hass.states.get("switch.rfy_030101_1") + assert state + assert state.state == "on" From e89c475856b8fb000c882017f5b9308560381213 Mon Sep 17 00:00:00 2001 From: Jeroen Van den Keybus Date: Tue, 28 Jul 2020 01:40:21 +0200 Subject: [PATCH 06/20] Fix detection of zones 2 and 3 in Onkyo/Pioneer amplifiers (#38234) --- homeassistant/components/onkyo/media_player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 30f4ae0800a..da33ff5f018 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -120,7 +120,7 @@ def determine_zones(receiver): out = {"zone2": False, "zone3": False} try: _LOGGER.debug("Checking for zone 2 capability") - receiver.raw("ZPW") + receiver.raw("ZPWQSTN") out["zone2"] = True except ValueError as error: if str(error) != TIMEOUT_MESSAGE: @@ -128,7 +128,7 @@ def determine_zones(receiver): _LOGGER.debug("Zone 2 timed out, assuming no functionality") try: _LOGGER.debug("Checking for zone 3 capability") - receiver.raw("PW3") + receiver.raw("PW3QSTN") out["zone3"] = True except ValueError as error: if str(error) != TIMEOUT_MESSAGE: From bdab4375742f0f0db585f88c2e1341f3bff9ad23 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Mon, 27 Jul 2020 16:51:34 -0500 Subject: [PATCH 07/20] Fix repeat action when variables present (#38237) --- homeassistant/helpers/script.py | 38 +++++++++++++++++---- tests/helpers/test_script.py | 58 +++++++++++++++++++++------------ 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 1eb46a1eded..b485ebdea4e 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -4,7 +4,19 @@ from datetime import datetime from functools import partial import itertools import logging -from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple +from types import MappingProxyType +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + Sequence, + Set, + Tuple, + Union, + cast, +) from async_timeout import timeout import voluptuous as vol @@ -134,13 +146,13 @@ class _ScriptRun: self, hass: HomeAssistant, script: "Script", - variables: Optional[Sequence], + variables: Dict[str, Any], context: Optional[Context], log_exceptions: bool, ) -> None: self._hass = hass self._script = script - self._variables = variables or {} + self._variables = variables self._context = context self._log_exceptions = log_exceptions self._step = -1 @@ -595,6 +607,9 @@ async def _async_stop_scripts_at_shutdown(hass, event): ) +_VarsType = Union[Dict[str, Any], MappingProxyType] + + class Script: """Representation of a script.""" @@ -617,6 +632,7 @@ class Script: hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, partial(_async_stop_scripts_at_shutdown, hass) ) + self._top_level = top_level if top_level: all_scripts.append( {"instance": self, "started_before_shutdown": not hass.is_stopping} @@ -732,14 +748,16 @@ class Script: self._referenced_entities = referenced return referenced - def run(self, variables=None, context=None): + def run( + self, variables: Optional[_VarsType] = None, context: Optional[Context] = None + ) -> None: """Run script.""" asyncio.run_coroutine_threadsafe( self.async_run(variables, context), self._hass.loop ).result() async def async_run( - self, variables: Optional[Sequence] = None, context: Optional[Context] = None + self, variables: Optional[_VarsType] = None, context: Optional[Context] = None ) -> None: """Run script.""" if self.is_running: @@ -753,11 +771,19 @@ class Script: self._log("Maximum number of runs exceeded", level=logging.WARNING) return + # If this is a top level Script then make a copy of the variables in case they + # are read-only, but more importantly, so as not to leak any variables created + # during the run back to the caller. + if self._top_level: + variables = dict(variables) if variables is not None else {} + if self.script_mode != SCRIPT_MODE_QUEUED: cls = _ScriptRun else: cls = _QueuedScriptRun - run = cls(self._hass, self, variables, context, self._log_exceptions) + run = cls( + self._hass, self, cast(dict, variables), context, self._log_exceptions + ) self._runs.append(run) try: diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 7a458c49286..29211e03119 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -4,6 +4,7 @@ import asyncio from contextlib import contextmanager from datetime import timedelta import logging +from types import MappingProxyType from unittest import mock import pytest @@ -122,7 +123,7 @@ async def test_firing_event_template(hass): ) script_obj = script.Script(hass, sequence) - await script_obj.async_run({"is_world": "yes"}, context=context) + await script_obj.async_run(MappingProxyType({"is_world": "yes"}), context=context) await hass.async_block_till_done() assert len(events) == 1 @@ -175,7 +176,7 @@ async def test_calling_service_template(hass): ) script_obj = script.Script(hass, sequence) - await script_obj.async_run({"is_world": "yes"}, context=context) + await script_obj.async_run(MappingProxyType({"is_world": "yes"}), context=context) await hass.async_block_till_done() assert len(calls) == 1 @@ -235,7 +236,9 @@ async def test_multiple_runs_no_wait(hass): logger.debug("starting 1st script") hass.async_create_task( script_obj.async_run( - {"fire1": "1", "listen1": "2", "fire2": "3", "listen2": "4"} + MappingProxyType( + {"fire1": "1", "listen1": "2", "fire2": "3", "listen2": "4"} + ) ) ) await asyncio.wait_for(heard_event.wait(), 1) @@ -243,7 +246,7 @@ async def test_multiple_runs_no_wait(hass): logger.debug("starting 2nd script") await script_obj.async_run( - {"fire1": "2", "listen1": "3", "fire2": "4", "listen2": "4"} + MappingProxyType({"fire1": "2", "listen1": "3", "fire2": "4", "listen2": "4"}) ) await hass.async_block_till_done() @@ -670,7 +673,9 @@ async def test_wait_template_variables(hass): try: hass.states.async_set("switch.test", "on") - hass.async_create_task(script_obj.async_run({"data": "switch.test"})) + hass.async_create_task( + script_obj.async_run(MappingProxyType({"data": "switch.test"})) + ) await asyncio.wait_for(wait_started_flag.wait(), 1) assert script_obj.is_running @@ -882,7 +887,14 @@ async def test_repeat_var_in_condition(hass, condition): assert len(events) == 2 -async def test_repeat_nested(hass): +@pytest.mark.parametrize( + "variables,first_last,inside_x", + [ + (None, {"repeat": "None", "x": "None"}, "None"), + (MappingProxyType({"x": 1}), {"repeat": "None", "x": "1"}, "1"), + ], +) +async def test_repeat_nested(hass, variables, first_last, inside_x): """Test nested repeats.""" event = "test_event" events = async_capture_events(hass, event) @@ -892,7 +904,8 @@ async def test_repeat_nested(hass): { "event": event, "event_data_template": { - "repeat": "{{ None if repeat is not defined else repeat }}" + "repeat": "{{ None if repeat is not defined else repeat }}", + "x": "{{ None if x is not defined else x }}", }, }, { @@ -905,6 +918,7 @@ async def test_repeat_nested(hass): "first": "{{ repeat.first }}", "index": "{{ repeat.index }}", "last": "{{ repeat.last }}", + "x": "{{ None if x is not defined else x }}", }, }, { @@ -916,6 +930,7 @@ async def test_repeat_nested(hass): "first": "{{ repeat.first }}", "index": "{{ repeat.index }}", "last": "{{ repeat.last }}", + "x": "{{ None if x is not defined else x }}", }, }, } @@ -926,6 +941,7 @@ async def test_repeat_nested(hass): "first": "{{ repeat.first }}", "index": "{{ repeat.index }}", "last": "{{ repeat.last }}", + "x": "{{ None if x is not defined else x }}", }, }, ], @@ -934,7 +950,8 @@ async def test_repeat_nested(hass): { "event": event, "event_data_template": { - "repeat": "{{ None if repeat is not defined else repeat }}" + "repeat": "{{ None if repeat is not defined else repeat }}", + "x": "{{ None if x is not defined else x }}", }, }, ] @@ -945,21 +962,21 @@ async def test_repeat_nested(hass): "homeassistant.helpers.condition._LOGGER.error", side_effect=AssertionError("Template Error"), ): - await script_obj.async_run() + await script_obj.async_run(variables) assert len(events) == 10 - assert events[0].data == {"repeat": "None"} - assert events[-1].data == {"repeat": "None"} + assert events[0].data == first_last + assert events[-1].data == first_last for index, result in enumerate( ( - ("True", "1", "False"), - ("True", "1", "False"), - ("False", "2", "True"), - ("True", "1", "False"), - ("False", "2", "True"), - ("True", "1", "False"), - ("False", "2", "True"), - ("False", "2", "True"), + ("True", "1", "False", inside_x), + ("True", "1", "False", inside_x), + ("False", "2", "True", inside_x), + ("True", "1", "False", inside_x), + ("False", "2", "True", inside_x), + ("True", "1", "False", inside_x), + ("False", "2", "True", inside_x), + ("False", "2", "True", inside_x), ), 1, ): @@ -967,6 +984,7 @@ async def test_repeat_nested(hass): "first": result[0], "index": result[1], "last": result[2], + "x": result[3], } @@ -998,7 +1016,7 @@ async def test_choose(hass, var, result): ) script_obj = script.Script(hass, sequence) - await script_obj.async_run({"var": var}) + await script_obj.async_run(MappingProxyType({"var": var})) await hass.async_block_till_done() assert len(events) == 1 From 293db61b329c22eb090c1ded31f37b080ce6ffb8 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Mon, 27 Jul 2020 03:43:58 -0500 Subject: [PATCH 08/20] Fix parallel script containing repeat or choose action with max_runs > 10 (#38243) --- homeassistant/helpers/script.py | 3 +++ tests/helpers/test_script.py | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index b485ebdea4e..6929d8a3bd3 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -821,6 +821,7 @@ class Script: action[CONF_REPEAT][CONF_SEQUENCE], f"{self.name}: {step_name}", script_mode=SCRIPT_MODE_PARALLEL, + max_runs=self.max_runs, logger=self._logger, top_level=False, ) @@ -848,6 +849,7 @@ class Script: choice[CONF_SEQUENCE], f"{self.name}: {step_name}: choice {idx}", script_mode=SCRIPT_MODE_PARALLEL, + max_runs=self.max_runs, logger=self._logger, top_level=False, ) @@ -862,6 +864,7 @@ class Script: action[CONF_DEFAULT], f"{self.name}: {step_name}: default", script_mode=SCRIPT_MODE_PARALLEL, + max_runs=self.max_runs, logger=self._logger, top_level=False, ) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 29211e03119..33dec87ccd8 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1023,6 +1023,31 @@ async def test_choose(hass, var, result): assert events[0].data["choice"] == result +@pytest.mark.parametrize( + "action", + [ + {"repeat": {"count": 1, "sequence": {"event": "abc"}}}, + {"choose": {"conditions": [], "sequence": {"event": "abc"}}}, + {"choose": [], "default": {"event": "abc"}}, + ], +) +async def test_multiple_runs_repeat_choose(hass, caplog, action): + """Test parallel runs with repeat & choose actions & max_runs > default.""" + max_runs = script.DEFAULT_MAX + 1 + script_obj = script.Script( + hass, cv.SCRIPT_SCHEMA(action), script_mode="parallel", max_runs=max_runs + ) + + events = async_capture_events(hass, "abc") + for _ in range(max_runs): + hass.async_create_task(script_obj.async_run()) + await hass.async_block_till_done() + + assert "WARNING" not in caplog.text + assert "ERROR" not in caplog.text + assert len(events) == max_runs + + async def test_last_triggered(hass): """Test the last_triggered.""" event = "test_event" From c55c4159336f8ac3a65a5dec3020f452e957f6f4 Mon Sep 17 00:00:00 2001 From: Mister Wil <1091741+MisterWil@users.noreply.github.com> Date: Sun, 26 Jul 2020 23:08:01 -0700 Subject: [PATCH 09/20] Fix Skybell useragent (#38245) --- homeassistant/components/skybell/__init__.py | 15 +++++++++++++-- homeassistant/components/skybell/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/skybell/__init__.py b/homeassistant/components/skybell/__init__.py index a4e4263d360..9606d9bcf12 100644 --- a/homeassistant/components/skybell/__init__.py +++ b/homeassistant/components/skybell/__init__.py @@ -5,7 +5,12 @@ from requests.exceptions import ConnectTimeout, HTTPError from skybellpy import Skybell import voluptuous as vol -from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + ATTR_ATTRIBUTION, + CONF_PASSWORD, + CONF_USERNAME, + __version__, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -20,6 +25,8 @@ DOMAIN = "skybell" DEFAULT_CACHEDB = "./skybell_cache.pickle" DEFAULT_ENTITY_NAMESPACE = "skybell" +AGENT_IDENTIFIER = f"HomeAssistant/{__version__}" + CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( @@ -42,7 +49,11 @@ def setup(hass, config): try: cache = hass.config.path(DEFAULT_CACHEDB) skybell = Skybell( - username=username, password=password, get_devices=True, cache_path=cache + username=username, + password=password, + get_devices=True, + cache_path=cache, + agent_identifier=AGENT_IDENTIFIER, ) hass.data[DOMAIN] = skybell diff --git a/homeassistant/components/skybell/manifest.json b/homeassistant/components/skybell/manifest.json index 9e0a0be8905..1b97b800956 100644 --- a/homeassistant/components/skybell/manifest.json +++ b/homeassistant/components/skybell/manifest.json @@ -2,6 +2,6 @@ "domain": "skybell", "name": "SkyBell", "documentation": "https://www.home-assistant.io/integrations/skybell", - "requirements": ["skybellpy==0.4.0"], + "requirements": ["skybellpy==0.6.1"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 672b1360af8..f123e4f0f4d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1945,7 +1945,7 @@ simplisafe-python==9.2.1 sisyphus-control==2.2.1 # homeassistant.components.skybell -skybellpy==0.4.0 +skybellpy==0.6.1 # homeassistant.components.slack slackclient==2.5.0 From 37e029b2c1820d27d23c98d07a28fc426c66fcfb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 26 Jul 2020 17:01:29 -1000 Subject: [PATCH 10/20] Improve setup retry logic to handle inconsistent powerview hub availability (#38249) --- .../hunterdouglas_powerview/__init__.py | 26 ++++++++++++------- .../hunterdouglas_powerview/const.py | 3 ++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 3df895c94ce..3f78726bf30 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -112,22 +112,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): try: async with async_timeout.timeout(10): device_info = await async_get_device_info(pv_request) + + async with async_timeout.timeout(10): + rooms = Rooms(pv_request) + room_data = _async_map_data_by_id((await rooms.get_resources())[ROOM_DATA]) + + async with async_timeout.timeout(10): + scenes = Scenes(pv_request) + scene_data = _async_map_data_by_id( + (await scenes.get_resources())[SCENE_DATA] + ) + + async with async_timeout.timeout(10): + shades = Shades(pv_request) + shade_data = _async_map_data_by_id( + (await shades.get_resources())[SHADE_DATA] + ) except HUB_EXCEPTIONS: _LOGGER.error("Connection error to PowerView hub: %s", hub_address) raise ConfigEntryNotReady + if not device_info: _LOGGER.error("Unable to initialize PowerView hub: %s", hub_address) raise ConfigEntryNotReady - rooms = Rooms(pv_request) - room_data = _async_map_data_by_id((await rooms.get_resources())[ROOM_DATA]) - - scenes = Scenes(pv_request) - scene_data = _async_map_data_by_id((await scenes.get_resources())[SCENE_DATA]) - - shades = Shades(pv_request) - shade_data = _async_map_data_by_id((await shades.get_resources())[SHADE_DATA]) - async def async_update_data(): """Fetch data from shade endpoint.""" async with async_timeout.timeout(10): diff --git a/homeassistant/components/hunterdouglas_powerview/const.py b/homeassistant/components/hunterdouglas_powerview/const.py index e69fe319c0f..e83a9d8945b 100644 --- a/homeassistant/components/hunterdouglas_powerview/const.py +++ b/homeassistant/components/hunterdouglas_powerview/const.py @@ -2,6 +2,7 @@ import asyncio +from aiohttp.client_exceptions import ServerDisconnectedError from aiopvapi.helpers.aiorequest import PvApiConnectionError DOMAIN = "hunterdouglas_powerview" @@ -64,7 +65,7 @@ PV_SHADE_DATA = "pv_shade_data" PV_ROOM_DATA = "pv_room_data" COORDINATOR = "coordinator" -HUB_EXCEPTIONS = (asyncio.TimeoutError, PvApiConnectionError) +HUB_EXCEPTIONS = (ServerDisconnectedError, asyncio.TimeoutError, PvApiConnectionError) LEGACY_DEVICE_SUB_REVISION = 1 LEGACY_DEVICE_REVISION = 0 From 8768fe1652947240bef192a3e84869e2293e63c2 Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Mon, 27 Jul 2020 16:15:28 +0100 Subject: [PATCH 11/20] Don't set up callbacks until entity is created. (#38251) --- homeassistant/components/vera/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index b636477b16d..263f5f0025b 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -194,7 +194,9 @@ class VeraDevice(Entity): slugify(vera_device.name), vera_device.device_id ) - self.controller.register(vera_device, self._update_callback) + async def async_added_to_hass(self): + """Subscribe to updates.""" + self.controller.register(self.vera_device, self._update_callback) def _update_callback(self, _device): """Update the state.""" From bd1336cbdf475727716cb6463dd521fd30b7a392 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 26 Jul 2020 20:51:53 -1000 Subject: [PATCH 12/20] Prevent onvif from blocking startup (#38256) --- homeassistant/components/onvif/event.py | 43 +++++++++++++------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 9084a06e7db..60a92e56ac0 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -6,7 +6,7 @@ from aiohttp.client_exceptions import ServerDisconnectedError from onvif import ONVIFCamera, ONVIFService from zeep.exceptions import Fault -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, CoreState, HomeAssistant, callback from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util @@ -114,30 +114,31 @@ class EventManager: async def async_pull_messages(self, _now: dt = None) -> None: """Pull messages from device.""" - try: - pullpoint = self.device.create_pullpoint_service() - req = pullpoint.create_type("PullMessages") - req.MessageLimit = 100 - req.Timeout = dt.timedelta(seconds=60) - response = await pullpoint.PullMessages(req) + if self.hass.state == CoreState.running: + try: + pullpoint = self.device.create_pullpoint_service() + req = pullpoint.create_type("PullMessages") + req.MessageLimit = 100 + req.Timeout = dt.timedelta(seconds=60) + response = await pullpoint.PullMessages(req) - # Renew subscription if less than two hours is left - if ( - dt_util.as_utc(response.TerminationTime) - dt_util.utcnow() - ).total_seconds() < 7200: - await self.async_renew() + # Renew subscription if less than two hours is left + if ( + dt_util.as_utc(response.TerminationTime) - dt_util.utcnow() + ).total_seconds() < 7200: + await self.async_renew() - # Parse response - await self.async_parse_messages(response.NotificationMessage) + # Parse response + await self.async_parse_messages(response.NotificationMessage) - except ServerDisconnectedError: - pass - except Fault: - pass + except ServerDisconnectedError: + pass + except Fault: + pass - # Update entities - for update_callback in self._listeners: - update_callback() + # Update entities + for update_callback in self._listeners: + update_callback() # Reschedule another pull if self._listeners: From b5a64b3752e165004ec004e1d329c23733ff8504 Mon Sep 17 00:00:00 2001 From: Marcio Granzotto Rodrigues Date: Mon, 27 Jul 2020 15:56:39 -0300 Subject: [PATCH 13/20] Fix #38289 issue with xboxapi lib (#38293) --- homeassistant/components/xbox_live/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xbox_live/manifest.json b/homeassistant/components/xbox_live/manifest.json index 3ebffc425ad..937f33bd009 100644 --- a/homeassistant/components/xbox_live/manifest.json +++ b/homeassistant/components/xbox_live/manifest.json @@ -2,6 +2,6 @@ "domain": "xbox_live", "name": "Xbox Live", "documentation": "https://www.home-assistant.io/integrations/xbox_live", - "requirements": ["xboxapi==2.0.0"], + "requirements": ["xboxapi==2.0.1"], "codeowners": ["@MartinHjelmare"] } diff --git a/requirements_all.txt b/requirements_all.txt index f123e4f0f4d..a054e273181 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2204,7 +2204,7 @@ wled==0.4.3 xbee-helper==0.0.7 # homeassistant.components.xbox_live -xboxapi==2.0.0 +xboxapi==2.0.1 # homeassistant.components.xfinity xfinity-gateway==0.0.4 From c4038c8652eb1ad2ebfde6133bfef096c9f43b90 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Tue, 28 Jul 2020 04:26:29 +0200 Subject: [PATCH 14/20] Bump python-miio to 0.5.3 (#38300) --- homeassistant/components/xiaomi_miio/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index 471dc7290df..09719d720c0 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -3,7 +3,7 @@ "name": "Xiaomi Miio", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xiaomi_miio", - "requirements": ["construct==2.9.45", "python-miio==0.5.2.1"], + "requirements": ["construct==2.9.45", "python-miio==0.5.3"], "codeowners": ["@rytilahti", "@syssi"], "zeroconf": ["_miio._udp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index a054e273181..ea30023f8b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1701,7 +1701,7 @@ python-juicenet==1.0.1 # python-lirc==1.2.3 # homeassistant.components.xiaomi_miio -python-miio==0.5.2.1 +python-miio==0.5.3 # homeassistant.components.mpd python-mpd2==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 73a0ccf2060..0efa7c3664f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -767,7 +767,7 @@ python-izone==1.1.2 python-juicenet==1.0.1 # homeassistant.components.xiaomi_miio -python-miio==0.5.2.1 +python-miio==0.5.3 # homeassistant.components.nest python-nest==4.1.0 From b8d6b20c96f5421acd5a0542890af27c5237c943 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 27 Jul 2020 19:57:36 -1000 Subject: [PATCH 15/20] Prevent speedtest from blocking startup or causing other intergations to fail setup (#38305) When speedtest starts up, it would saturate the network interface and cause other integrations to randomly fail to setup. We now wait to do the first speed test until after the started event is fired. --- .../components/speedtestdotnet/__init__.py | 34 +++++++++++++------ .../components/speedtestdotnet/sensor.py | 8 ++--- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 2ef49877031..6fd2dec5efd 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -6,7 +6,12 @@ import speedtest import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT -from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_SCAN_INTERVAL +from homeassistant.const import ( + CONF_MONITORED_CONDITIONS, + CONF_SCAN_INTERVAL, + EVENT_HOMEASSISTANT_STARTED, +) +from homeassistant.core import CoreState from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -70,10 +75,25 @@ async def async_setup_entry(hass, config_entry): coordinator = SpeedTestDataCoordinator(hass, config_entry) await coordinator.async_setup() - if not config_entry.options[CONF_MANUAL]: + async def _enable_scheduled_speedtests(*_): + """Activate the data update coordinator.""" + coordinator.update_interval = timedelta( + minutes=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + ) await coordinator.async_refresh() - if not coordinator.last_update_success: - raise ConfigEntryNotReady + + if not config_entry.options[CONF_MANUAL]: + if hass.state == CoreState.running: + await _enable_scheduled_speedtests() + if not coordinator.last_update_success: + raise ConfigEntryNotReady + else: + # Running a speed test during startup can prevent + # integrations from being able to setup because it + # can saturate the network interface. + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, _enable_scheduled_speedtests + ) hass.data[DOMAIN] = coordinator @@ -107,12 +127,6 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator): super().__init__( self.hass, _LOGGER, name=DOMAIN, update_method=self.async_update, ) - if not self.config_entry.options.get(CONF_MANUAL): - self.update_interval = timedelta( - minutes=self.config_entry.options.get( - CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ) - ) def update_servers(self): """Update list of test servers.""" diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 0889d7da5b2..d071a226a05 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -12,7 +12,6 @@ from .const import ( ATTR_SERVER_ID, ATTR_SERVER_NAME, ATTRIBUTION, - CONF_MANUAL, DEFAULT_NAME, DOMAIN, ICON, @@ -97,10 +96,9 @@ class SpeedtestSensor(RestoreEntity): async def async_added_to_hass(self): """Handle entity which will be added.""" await super().async_added_to_hass() - if self.coordinator.config_entry.options[CONF_MANUAL]: - state = await self.async_get_last_state() - if state: - self._state = state.state + state = await self.async_get_last_state() + if state: + self._state = state.state @callback def update(): From f8fbe8dba301456dcdf8ecc71fb8efa514829015 Mon Sep 17 00:00:00 2001 From: Kyle Hendricks Date: Tue, 28 Jul 2020 06:03:56 -0400 Subject: [PATCH 16/20] Fix issue with certain Samsung TVs repeatedly showing auth dialog (#38308) Through some testing with the samsungtvws library, it was determined that the issue is related to the short read timeout (1s). Increasing the timeout to 10s should solve the issue. --- homeassistant/components/samsungtv/bridge.py | 2 +- tests/components/samsungtv/test_media_player.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index 472ce894e1a..83b8ea3d138 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -250,7 +250,7 @@ class SamsungTVWSBridge(SamsungTVBridge): host=self.host, port=self.port, token=self.token, - timeout=1, + timeout=10, name=VALUE_CONF_NAME, ) self._remote.open() diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 15ac13c64d5..5d6c36153c2 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -97,7 +97,7 @@ MOCK_CALLS_ENTRY_WS = { "host": "fake", "name": "HomeAssistant", "port": 8001, - "timeout": 1, + "timeout": 10, "token": "abcde", } From e984e0d4148d621e364f1a86cb3cb505cb43fd60 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 28 Jul 2020 06:24:29 -1000 Subject: [PATCH 17/20] Add debug logging for when a chain of tasks blocks startup (#38311) Co-authored-by: Franck Nijhof Co-authored-by: Martin Hjelmare --- homeassistant/core.py | 16 +++++++++++++ tests/test_core.py | 55 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 01bfa402348..a40bc18c804 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -392,12 +392,28 @@ class HomeAssistant: """Block until all pending work is done.""" # To flush out any call_soon_threadsafe await asyncio.sleep(0) + start_time: Optional[float] = None while self._pending_tasks: pending = [task for task in self._pending_tasks if not task.done()] self._pending_tasks.clear() if pending: await self._await_and_log_pending(pending) + + if start_time is None: + # Avoid calling monotonic() until we know + # we may need to start logging blocked tasks. + start_time = 0 + elif start_time == 0: + # If we have waited twice then we set the start + # time + start_time = monotonic() + elif monotonic() - start_time > BLOCK_LOG_TIMEOUT: + # We have waited at least three loops and new tasks + # continue to block. At this point we start + # logging all waiting tasks. + for task in pending: + _LOGGER.debug("Waiting for task: %s", task) else: await asyncio.sleep(0) diff --git a/tests/test_core.py b/tests/test_core.py index 3c0a9ee4fb8..a63f42af61b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1408,9 +1408,62 @@ async def test_log_blocking_events(hass, caplog): hass.async_create_task(_wait_a_bit_1()) await hass.async_block_till_done() - with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.00001): + with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.0001): hass.async_create_task(_wait_a_bit_2()) await hass.async_block_till_done() assert "_wait_a_bit_2" in caplog.text assert "_wait_a_bit_1" not in caplog.text + + +async def test_chained_logging_hits_log_timeout(hass, caplog): + """Ensure we log which task is blocking startup when there is a task chain and debug logging is on.""" + caplog.set_level(logging.DEBUG) + + created = 0 + + async def _task_chain_1(): + nonlocal created + created += 1 + if created > 10: + return + hass.async_create_task(_task_chain_2()) + + async def _task_chain_2(): + nonlocal created + created += 1 + if created > 10: + return + hass.async_create_task(_task_chain_1()) + + with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.0001): + hass.async_create_task(_task_chain_1()) + await hass.async_block_till_done() + + assert "_task_chain_" in caplog.text + + +async def test_chained_logging_misses_log_timeout(hass, caplog): + """Ensure we do not log which task is blocking startup if we do not hit the timeout.""" + caplog.set_level(logging.DEBUG) + + created = 0 + + async def _task_chain_1(): + nonlocal created + created += 1 + if created > 10: + return + hass.async_create_task(_task_chain_2()) + + async def _task_chain_2(): + nonlocal created + created += 1 + if created > 10: + return + hass.async_create_task(_task_chain_1()) + + hass.async_create_task(_task_chain_1()) + await hass.async_block_till_done() + + assert "_task_chain_" not in caplog.text From d9953a8c2f242d021d363dba69ce8ce62b70f45b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 28 Jul 2020 17:51:35 +0200 Subject: [PATCH 18/20] Remove AdGuard version check (#38326) --- homeassistant/components/adguard/__init__.py | 10 +--- .../components/adguard/config_flow.py | 25 ++------- homeassistant/components/adguard/const.py | 2 - homeassistant/components/adguard/strings.json | 2 - tests/components/adguard/test_config_flow.py | 51 +------------------ 5 files changed, 5 insertions(+), 85 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 84e86bfcaba..71dff2ab6ee 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -1,5 +1,4 @@ """Support for AdGuard Home.""" -from distutils.version import LooseVersion import logging from typing import Any, Dict @@ -11,7 +10,6 @@ from homeassistant.components.adguard.const import ( DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN, - MIN_ADGUARD_HOME_VERSION, SERVICE_ADD_URL, SERVICE_DISABLE_URL, SERVICE_ENABLE_URL, @@ -67,16 +65,10 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data.setdefault(DOMAIN, {})[DATA_ADGUARD_CLIENT] = adguard try: - version = await adguard.version() + await adguard.version() except AdGuardHomeConnectionError as exception: raise ConfigEntryNotReady from exception - if version and LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version): - _LOGGER.error( - "This integration requires AdGuard Home v0.99.0 or higher to work correctly" - ) - raise ConfigEntryNotReady - for component in "sensor", "switch": hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index ede01706c5d..a0ace623862 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -1,12 +1,11 @@ """Config flow to configure the AdGuard Home integration.""" -from distutils.version import LooseVersion import logging from adguardhome import AdGuardHome, AdGuardHomeConnectionError import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.adguard.const import DOMAIN, MIN_ADGUARD_HOME_VERSION +from homeassistant.components.adguard.const import DOMAIN from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_HOST, @@ -79,20 +78,11 @@ class AdGuardHomeFlowHandler(ConfigFlow): ) try: - version = await adguard.version() + await adguard.version() except AdGuardHomeConnectionError: errors["base"] = "connection_error" return await self._show_setup_form(errors) - if version and LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version): - return self.async_abort( - reason="adguard_home_outdated", - description_placeholders={ - "current_version": version, - "minimal_version": MIN_ADGUARD_HOME_VERSION, - }, - ) - return self.async_create_entry( title=user_input[CONF_HOST], data={ @@ -160,20 +150,11 @@ class AdGuardHomeFlowHandler(ConfigFlow): ) try: - version = await adguard.version() + await adguard.version() except AdGuardHomeConnectionError: errors["base"] = "connection_error" return await self._show_hassio_form(errors) - if LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version): - return self.async_abort( - reason="adguard_home_addon_outdated", - description_placeholders={ - "current_version": version, - "minimal_version": MIN_ADGUARD_HOME_VERSION, - }, - ) - return self.async_create_entry( title=self._hassio_discovery["addon"], data={ diff --git a/homeassistant/components/adguard/const.py b/homeassistant/components/adguard/const.py index eb12a9c163f..c77d76a70cf 100644 --- a/homeassistant/components/adguard/const.py +++ b/homeassistant/components/adguard/const.py @@ -7,8 +7,6 @@ DATA_ADGUARD_VERION = "adguard_version" CONF_FORCE = "force" -MIN_ADGUARD_HOME_VERSION = "v0.99.0" - SERVICE_ADD_URL = "add_url" SERVICE_DISABLE_URL = "disable_url" SERVICE_ENABLE_URL = "enable_url" diff --git a/homeassistant/components/adguard/strings.json b/homeassistant/components/adguard/strings.json index f5f780c70b5..f010f9e2ade 100644 --- a/homeassistant/components/adguard/strings.json +++ b/homeassistant/components/adguard/strings.json @@ -19,8 +19,6 @@ }, "error": { "connection_error": "Failed to connect." }, "abort": { - "adguard_home_outdated": "This integration requires AdGuard Home {minimal_version} or higher, you have {current_version}.", - "adguard_home_addon_outdated": "This integration requires AdGuard Home {minimal_version} or higher, you have {current_version}. Please update your Hass.io AdGuard Home add-on.", "existing_instance_updated": "Updated existing configuration.", "single_instance_allowed": "Only a single configuration of AdGuard Home is allowed." } diff --git a/tests/components/adguard/test_config_flow.py b/tests/components/adguard/test_config_flow.py index d0e874bacdc..69e1285889b 100644 --- a/tests/components/adguard/test_config_flow.py +++ b/tests/components/adguard/test_config_flow.py @@ -4,7 +4,7 @@ import aiohttp from homeassistant import config_entries, data_entry_flow from homeassistant.components.adguard import config_flow -from homeassistant.components.adguard.const import DOMAIN, MIN_ADGUARD_HOME_VERSION +from homeassistant.components.adguard.const import DOMAIN from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -229,52 +229,3 @@ async def test_hassio_connection_error(hass, aioclient_mock): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "hassio_confirm" assert result["errors"] == {"base": "connection_error"} - - -async def test_outdated_adguard_version(hass, aioclient_mock): - """Test we show abort when connecting with unsupported AdGuard version.""" - aioclient_mock.get( - f"{'https' if FIXTURE_USER_INPUT[CONF_SSL] else 'http'}" - f"://{FIXTURE_USER_INPUT[CONF_HOST]}" - f":{FIXTURE_USER_INPUT[CONF_PORT]}/control/status", - json={"version": "v0.98.0"}, - headers={"Content-Type": "application/json"}, - ) - - flow = config_flow.AdGuardHomeFlowHandler() - flow.hass = hass - result = await flow.async_step_user(user_input=None) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - result = await flow.async_step_user(user_input=FIXTURE_USER_INPUT) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "adguard_home_outdated" - assert result["description_placeholders"] == { - "current_version": "v0.98.0", - "minimal_version": MIN_ADGUARD_HOME_VERSION, - } - - -async def test_outdated_adguard_addon_version(hass, aioclient_mock): - """Test we show abort when connecting with unsupported AdGuard add-on version.""" - aioclient_mock.get( - "http://mock-adguard:3000/control/status", - json={"version": "v0.98.0"}, - headers={"Content-Type": "application/json"}, - ) - - result = await hass.config_entries.flow.async_init( - "adguard", - data={"addon": "AdGuard Home Addon", "host": "mock-adguard", "port": 3000}, - context={"source": "hassio"}, - ) - - result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "adguard_home_addon_outdated" - assert result["description_placeholders"] == { - "current_version": "v0.98.0", - "minimal_version": MIN_ADGUARD_HOME_VERSION, - } From 612e27b5ffd9c59de31b3c4242262aab2730ba42 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 28 Jul 2020 19:37:50 +0200 Subject: [PATCH 19/20] Bumped version to 0.113.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ddbc6ad91ed..5c5877c5e7b 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 = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 60cab62da51f71a22847d62e1f1dc5a03b358d3a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 28 Jul 2020 20:05:31 +0200 Subject: [PATCH 20/20] Revert "Make rfxtrx RfyDevices have sun automation switches (#38210)" This reverts commit ee0c32cbb7e64c8210627952a7e14d86e47e2b00. --- homeassistant/components/rfxtrx/const.py | 2 -- homeassistant/components/rfxtrx/switch.py | 1 - tests/components/rfxtrx/test_switch.py | 18 ------------------ 3 files changed, 21 deletions(-) diff --git a/homeassistant/components/rfxtrx/const.py b/homeassistant/components/rfxtrx/const.py index 500fb3a1d1b..7626c082f45 100644 --- a/homeassistant/components/rfxtrx/const.py +++ b/homeassistant/components/rfxtrx/const.py @@ -7,14 +7,12 @@ COMMAND_ON_LIST = [ "Stop", "Open (inline relay)", "Stop (inline relay)", - "Enable sun automation", ] COMMAND_OFF_LIST = [ "Off", "Down", "Close (inline relay)", - "Disable sun automation", ] ATTR_EVENT = "event" diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index 52a55f79c01..6cd9a484abd 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -37,7 +37,6 @@ async def async_setup_entry( isinstance(event.device, rfxtrxmod.LightingDevice) and not event.device.known_to_be_dimmable and not event.device.known_to_be_rollershutter - or isinstance(event.device, rfxtrxmod.RfyDevice) ) # Add switch from config file diff --git a/tests/components/rfxtrx/test_switch.py b/tests/components/rfxtrx/test_switch.py index 1f43081a9b5..22f7a73c77c 100644 --- a/tests/components/rfxtrx/test_switch.py +++ b/tests/components/rfxtrx/test_switch.py @@ -10,9 +10,6 @@ from . import _signal_event from tests.common import mock_restore_cache -EVENT_RFY_ENABLE_SUN_AUTO = "081a00000301010113" -EVENT_RFY_DISABLE_SUN_AUTO = "081a00000301010114" - async def test_one_switch(hass, rfxtrx): """Test with 1 switch.""" @@ -142,18 +139,3 @@ async def test_discover_switch(hass, rfxtrx): state = hass.states.get("switch.ac_118cdeb_2") assert state assert state.state == "on" - - -async def test_discover_rfy_sun_switch(hass, rfxtrx_automatic): - """Test with discovery of switches.""" - rfxtrx = rfxtrx_automatic - - await rfxtrx.signal(EVENT_RFY_DISABLE_SUN_AUTO) - state = hass.states.get("switch.rfy_030101_1") - assert state - assert state.state == "off" - - await rfxtrx.signal(EVENT_RFY_ENABLE_SUN_AUTO) - state = hass.states.get("switch.rfy_030101_1") - assert state - assert state.state == "on"