From 0b620116268411b06445065cd2ceda9d73b94d31 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 19 Mar 2020 01:13:19 +0100 Subject: [PATCH 01/35] Updated frontend to 20200318.1 (#32957) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index fc9cd188565..df484edf137 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20200318.0" + "home-assistant-frontend==20200318.1" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 36499440c19..1f36e6e7db5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.8 defusedxml==0.6.0 distro==1.4.0 hass-nabucasa==0.32.2 -home-assistant-frontend==20200318.0 +home-assistant-frontend==20200318.1 importlib-metadata==1.5.0 jinja2>=2.10.3 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index afe893dbc56..abfd541140f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -696,7 +696,7 @@ hole==0.5.0 holidays==0.10.1 # homeassistant.components.frontend -home-assistant-frontend==20200318.0 +home-assistant-frontend==20200318.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4234621441f..26bd4c22331 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -263,7 +263,7 @@ hole==0.5.0 holidays==0.10.1 # homeassistant.components.frontend -home-assistant-frontend==20200318.0 +home-assistant-frontend==20200318.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.9 From 8e917ccf737375bf336e5d36b4496de5ecb05a76 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 18 Mar 2020 18:15:23 -0700 Subject: [PATCH 02/35] Add device automation as frontend dependency (#32962) --- homeassistant/components/frontend/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index df484edf137..e5f6c3e2a26 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -8,6 +8,7 @@ "dependencies": [ "api", "auth", + "device_automation", "http", "lovelace", "onboarding", @@ -19,4 +20,4 @@ "@home-assistant/frontend" ], "quality_scale": "internal" -} \ No newline at end of file +} From 4e08aa8b055a50ed9e39c454fed247020eb53c95 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 18 Mar 2020 18:14:25 -0700 Subject: [PATCH 03/35] Fix zone config (#32963) * Fix zone config * Add zone as dependency again to device tracker * Fix tests --- .../components/device_tracker/manifest.json | 4 +-- homeassistant/components/zone/__init__.py | 26 ++++++++++++---- tests/components/unifi/test_device_tracker.py | 24 +++++++-------- tests/components/unifi/test_sensor.py | 4 +-- tests/components/unifi/test_switch.py | 30 +++++++++---------- tests/components/zone/test_init.py | 15 ++++++++++ 6 files changed, 67 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/device_tracker/manifest.json b/homeassistant/components/device_tracker/manifest.json index 2d0e9a82a53..4bd9846f76d 100644 --- a/homeassistant/components/device_tracker/manifest.json +++ b/homeassistant/components/device_tracker/manifest.json @@ -3,8 +3,8 @@ "name": "Device Tracker", "documentation": "https://www.home-assistant.io/integrations/device_tracker", "requirements": [], - "dependencies": [], - "after_dependencies": ["zone"], + "dependencies": ["zone"], + "after_dependencies": [], "codeowners": [], "quality_scale": "internal" } diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index c71026ea79c..b1d784a7acb 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -1,6 +1,6 @@ """Support for the definition of zones.""" import logging -from typing import Dict, Optional, cast +from typing import Any, Dict, Optional, cast import voluptuous as vol @@ -18,6 +18,7 @@ from homeassistant.const import ( CONF_RADIUS, EVENT_CORE_CONFIG_UPDATE, SERVICE_RELOAD, + STATE_UNAVAILABLE, ) from homeassistant.core import Event, HomeAssistant, ServiceCall, State, callback from homeassistant.helpers import ( @@ -65,8 +66,20 @@ UPDATE_FIELDS = { } +def empty_value(value: Any) -> Any: + """Test if the user has the default config value from adding "zone:".""" + if isinstance(value, dict) and len(value) == 0: + return [] + + raise vol.Invalid("Not a default value") + + CONFIG_SCHEMA = vol.Schema( - {vol.Optional(DOMAIN): vol.All(cv.ensure_list, [vol.Schema(CREATE_FIELDS)])}, + { + vol.Optional(DOMAIN, default=[]): vol.Any( + vol.All(cv.ensure_list, [vol.Schema(CREATE_FIELDS)]), empty_value, + ) + }, extra=vol.ALLOW_EXTRA, ) @@ -93,7 +106,7 @@ def async_active_zone( closest = None for zone in zones: - if zone.attributes.get(ATTR_PASSIVE): + if zone.state == STATE_UNAVAILABLE or zone.attributes.get(ATTR_PASSIVE): continue zone_dist = distance( @@ -126,6 +139,9 @@ def in_zone(zone: State, latitude: float, longitude: float, radius: float = 0) - Async friendly. """ + if zone.state == STATE_UNAVAILABLE: + return False + zone_dist = distance( latitude, longitude, @@ -180,7 +196,7 @@ async def async_setup(hass: HomeAssistant, config: Dict) -> bool: component, storage_collection, lambda conf: Zone(conf, True) ) - if DOMAIN in config: + if config[DOMAIN]: await yaml_collection.async_load(config[DOMAIN]) await storage_collection.async_load() @@ -206,7 +222,7 @@ async def async_setup(hass: HomeAssistant, config: Dict) -> bool: conf = await component.async_prepare_reload(skip_reset=True) if conf is None: return - await yaml_collection.async_load(conf.get(DOMAIN, [])) + await yaml_collection.async_load(conf[DOMAIN]) service.async_register_admin_service( hass, diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index f225ddb44a9..56f82dfd0f1 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -108,7 +108,7 @@ async def test_no_clients(hass): """Test the update_clients function when no clients are found.""" await setup_unifi_integration(hass) - assert len(hass.states.async_all()) == 0 + assert len(hass.states.async_entity_ids("device_tracker")) == 0 async def test_tracked_devices(hass): @@ -123,7 +123,7 @@ async def test_tracked_devices(hass): devices_response=[DEVICE_1, DEVICE_2], known_wireless_clients=(CLIENT_4["mac"],), ) - assert len(hass.states.async_all()) == 6 + assert len(hass.states.async_entity_ids("device_tracker")) == 5 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -184,7 +184,7 @@ async def test_controller_state_change(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids("device_tracker")) == 2 # Controller unavailable controller.async_unifi_signalling_callback( @@ -214,7 +214,7 @@ async def test_option_track_clients(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) - assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids("device_tracker")) == 3 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -259,7 +259,7 @@ async def test_option_track_wired_clients(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) - assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids("device_tracker")) == 3 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -304,7 +304,7 @@ async def test_option_track_devices(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) - assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids("device_tracker")) == 3 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -349,7 +349,7 @@ async def test_option_ssid_filter(hass): controller = await setup_unifi_integration( hass, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_3], ) - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("device_tracker")) == 0 # SSID filter active client_3 = hass.states.get("device_tracker.client_3") @@ -387,7 +387,7 @@ async def test_wireless_client_go_wired_issue(hass): client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) controller = await setup_unifi_integration(hass, clients_response=[client_1_client]) - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("device_tracker")) == 1 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -460,7 +460,7 @@ async def test_restoring_client(hass): clients_response=[CLIENT_2], clients_all_response=[CLIENT_1], ) - assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids("device_tracker")) == 2 device_1 = hass.states.get("device_tracker.client_1") assert device_1 is not None @@ -474,7 +474,7 @@ async def test_dont_track_clients(hass): clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("device_tracker")) == 1 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is None @@ -492,7 +492,7 @@ async def test_dont_track_devices(hass): clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("device_tracker")) == 1 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -509,7 +509,7 @@ async def test_dont_track_wired_clients(hass): options={unifi.controller.CONF_TRACK_WIRED_CLIENTS: False}, clients_response=[CLIENT_1, CLIENT_2], ) - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("device_tracker")) == 1 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index a858bc9a649..91531a9ee38 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -55,7 +55,7 @@ async def test_no_clients(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 0 + assert len(hass.states.async_entity_ids("sensor")) == 0 async def test_sensors(hass): @@ -71,7 +71,7 @@ async def test_sensors(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 4 + assert len(hass.states.async_entity_ids("sensor")) == 4 wired_client_rx = hass.states.get("sensor.wired_client_name_rx") assert wired_client_rx.state == "1234.0" diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index a06be14024b..a6b33c2aa34 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -209,7 +209,7 @@ async def test_no_clients(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 0 + assert len(hass.states.async_entity_ids("switch")) == 0 async def test_controller_not_client(hass): @@ -222,7 +222,7 @@ async def test_controller_not_client(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 0 + assert len(hass.states.async_entity_ids("switch")) == 0 cloudkey = hass.states.get("switch.cloud_key") assert cloudkey is None @@ -240,7 +240,7 @@ async def test_not_admin(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 0 + assert len(hass.states.async_entity_ids("switch")) == 0 async def test_switches(hass): @@ -258,7 +258,7 @@ async def test_switches(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids("switch")) == 3 switch_1 = hass.states.get("switch.poe_client_1") assert switch_1 is not None @@ -312,7 +312,7 @@ async def test_new_client_discovered_on_block_control(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 0 + assert len(hass.states.async_entity_ids("switch")) == 0 blocked = hass.states.get("switch.block_client_1") assert blocked is None @@ -324,7 +324,7 @@ async def test_new_client_discovered_on_block_control(hass): controller.api.session_handler("data") await hass.async_block_till_done() - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("switch")) == 1 blocked = hass.states.get("switch.block_client_1") assert blocked is not None @@ -336,7 +336,7 @@ async def test_option_block_clients(hass): options={CONF_BLOCK_CLIENT: [BLOCKED["mac"]]}, clients_all_response=[BLOCKED, UNBLOCKED], ) - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("switch")) == 1 # Add a second switch hass.config_entries.async_update_entry( @@ -344,28 +344,28 @@ async def test_option_block_clients(hass): options={CONF_BLOCK_CLIENT: [BLOCKED["mac"], UNBLOCKED["mac"]]}, ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids("switch")) == 2 # Remove the second switch again hass.config_entries.async_update_entry( controller.config_entry, options={CONF_BLOCK_CLIENT: [BLOCKED["mac"]]}, ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("switch")) == 1 # Enable one and remove another one hass.config_entries.async_update_entry( controller.config_entry, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]}, ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("switch")) == 1 # Remove one hass.config_entries.async_update_entry( controller.config_entry, options={CONF_BLOCK_CLIENT: []}, ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 0 + assert len(hass.states.async_entity_ids("switch")) == 0 async def test_new_client_discovered_on_poe_control(hass): @@ -378,7 +378,7 @@ async def test_new_client_discovered_on_poe_control(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids("switch")) == 1 controller.api.websocket._data = { "meta": {"message": "sta:sync"}, @@ -391,7 +391,7 @@ async def test_new_client_discovered_on_poe_control(hass): "switch", "turn_off", {"entity_id": "switch.poe_client_1"}, blocking=True ) assert len(controller.mock_requests) == 5 - assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids("switch")) == 2 assert controller.mock_requests[4] == { "json": { "port_overrides": [{"port_idx": 1, "portconf_id": "1a1", "poe_mode": "off"}] @@ -430,7 +430,7 @@ async def test_ignore_multiple_poe_clients_on_same_port(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids("device_tracker")) == 3 switch_1 = hass.states.get("switch.poe_client_1") switch_2 = hass.states.get("switch.poe_client_2") @@ -481,7 +481,7 @@ async def test_restoring_client(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids("switch")) == 2 device_1 = hass.states.get("switch.client_1") assert device_1 is not None diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 0835b77579a..bf92a6aa12a 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -487,3 +487,18 @@ async def test_import_config_entry(hass): assert state.attributes[zone.ATTR_RADIUS] == 3 assert state.attributes[zone.ATTR_PASSIVE] is False assert state.attributes[ATTR_ICON] == "mdi:from-config-entry" + + +async def test_zone_empty_setup(hass): + """Set up zone with empty config.""" + assert await setup.async_setup_component(hass, DOMAIN, {"zone": {}}) + + +async def test_unavailable_zone(hass): + """Test active zone with unavailable zones.""" + assert await setup.async_setup_component(hass, DOMAIN, {"zone": {}}) + hass.states.async_set("zone.bla", "unavailable", {"restored": True}) + + assert zone.async_active_zone(hass, 0.0, 0.01) is None + + assert zone.in_zone(hass.states.get("zone.bla"), 0, 0) is False From f973b35cef9624240290ad3678a3382cbb62fa5b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 18 Mar 2020 18:19:33 -0700 Subject: [PATCH 04/35] Bumped version to 0.107.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a8a8b898ebb..44a9c298368 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 107 -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 3aa1bcbb7733992f13c41d675b763bfde5347bf1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 18 Mar 2020 22:13:37 -0700 Subject: [PATCH 05/35] Fix mobile app test --- tests/components/mobile_app/test_webhook.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index 974fb577606..1e8441290bb 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -140,16 +140,7 @@ async def test_webhook_update_registration(webhook_client, authed_api_client): async def test_webhook_handle_get_zones(hass, create_registrations, webhook_client): """Test that we can get zones properly.""" await async_setup_component( - hass, - ZONE_DOMAIN, - { - ZONE_DOMAIN: { - "name": "test", - "latitude": 32.880837, - "longitude": -117.237561, - "radius": 250, - } - }, + hass, ZONE_DOMAIN, {ZONE_DOMAIN: {}}, ) resp = await webhook_client.post( @@ -160,10 +151,9 @@ async def test_webhook_handle_get_zones(hass, create_registrations, webhook_clie assert resp.status == 200 json = await resp.json() - assert len(json) == 2 + assert len(json) == 1 zones = sorted(json, key=lambda entry: entry["entity_id"]) assert zones[0]["entity_id"] == "zone.home" - assert zones[1]["entity_id"] == "zone.test" async def test_webhook_handle_get_config(hass, create_registrations, webhook_client): From 877eddf43d1f8b5de29e62def2c7e4ad076aae2b Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 19 Mar 2020 22:26:01 +0000 Subject: [PATCH 06/35] =?UTF-8?q?0.107.2=20-=20Bump=20aiohomekit=20to=20fi?= =?UTF-8?q?x=20Insignia=20NS-CH1XGO8=20and=20Lenno=E2=80=A6=20(#33016)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index a73d68227c7..b09cc198006 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit[IP]==0.2.29.1"], + "requirements": ["aiohomekit[IP]==0.2.29.2"], "dependencies": [], "zeroconf": ["_hap._tcp.local."], "codeowners": ["@Jc2k"] diff --git a/requirements_all.txt b/requirements_all.txt index abfd541140f..98b77c61446 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -163,7 +163,7 @@ aioftp==0.12.0 aioharmony==0.1.13 # homeassistant.components.homekit_controller -aiohomekit[IP]==0.2.29.1 +aiohomekit[IP]==0.2.29.2 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 26bd4c22331..1134c131447 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -62,7 +62,7 @@ aiobotocore==0.11.1 aioesphomeapi==2.6.1 # homeassistant.components.homekit_controller -aiohomekit[IP]==0.2.29.1 +aiohomekit[IP]==0.2.29.2 # homeassistant.components.emulated_hue # homeassistant.components.http From 29a9781bf7bf75af0413880940ae825664293b53 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Mar 2020 15:28:38 -0700 Subject: [PATCH 07/35] Fix unifi tests --- tests/components/unifi/test_device_tracker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 56f82dfd0f1..079bbd7d751 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -123,7 +123,7 @@ async def test_tracked_devices(hass): devices_response=[DEVICE_1, DEVICE_2], known_wireless_clients=(CLIENT_4["mac"],), ) - assert len(hass.states.async_entity_ids("device_tracker")) == 5 + assert len(hass.states.async_entity_ids("device_tracker")) == 6 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -349,7 +349,7 @@ async def test_option_ssid_filter(hass): controller = await setup_unifi_integration( hass, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_3], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 0 + assert len(hass.states.async_entity_ids("device_tracker")) == 1 # SSID filter active client_3 = hass.states.get("device_tracker.client_3") From 9af95e85779c1cfabc6cb779df10cab72c3e7a69 Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Fri, 13 Mar 2020 11:50:16 -0400 Subject: [PATCH 08/35] Fix camera.options to camera.stream_options. (#32767) --- homeassistant/components/camera/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 5d9bc99f945..6bbf30b000e 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -141,7 +141,7 @@ async def async_request_stream(hass, entity_id, fmt): source, fmt=fmt, keepalive=camera_prefs.preload_stream, - options=camera.options, + options=camera.stream_options, ) From 06a608e342fab119650750275326b1af1333e84b Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Thu, 19 Mar 2020 07:56:32 +0100 Subject: [PATCH 09/35] Fix velbus in the 107 release (#32936) velbus 2.0.41 introduced some data files in the python module. They are not copied when installing the velbus module. 2.0.43 fixes this problem --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 4179c3e89ba..fe3aee9a4cd 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["python-velbus==2.0.42"], + "requirements": ["python-velbus==2.0.43"], "config_flow": true, "dependencies": [], "codeowners": ["@Cereal2nd", "@brefra"] diff --git a/requirements_all.txt b/requirements_all.txt index 98b77c61446..ab8250b593a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1665,7 +1665,7 @@ python-telnet-vlc==1.0.4 python-twitch-client==0.6.0 # homeassistant.components.velbus -python-velbus==2.0.42 +python-velbus==2.0.43 # homeassistant.components.vlc python-vlc==1.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1134c131447..4c6a2ae6360 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -587,7 +587,7 @@ python-nest==4.1.0 python-twitch-client==0.6.0 # homeassistant.components.velbus -python-velbus==2.0.42 +python-velbus==2.0.43 # homeassistant.components.awair python_awair==0.0.4 From ae8cb0ccdf8be380a2f740cde0baefad37b79338 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 19 Mar 2020 12:37:47 -0400 Subject: [PATCH 10/35] Refactor ZHA setup (#32959) * Refactor ZHA setup. Catch errors and raise if needed. * Cleanup. --- homeassistant/components/zha/__init__.py | 32 ++++++++++---------- homeassistant/components/zha/core/gateway.py | 26 ++++++++++++++-- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 0d4ceed829b..ac5648e097b 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -89,9 +89,19 @@ async def async_setup_entry(hass, config_entry): Will automatically load components to support devices found on the network. """ - hass.data[DATA_ZHA] = hass.data.get(DATA_ZHA, {}) - hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS] = [] - hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED] = asyncio.Event() + zha_data = hass.data.setdefault(DATA_ZHA, {}) + config = zha_data.get(DATA_ZHA_CONFIG, {}) + + if config.get(CONF_ENABLE_QUIRKS, True): + # needs to be done here so that the ZHA module is finished loading + # before zhaquirks is imported + import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error + + zha_gateway = ZHAGateway(hass, config, config_entry) + await zha_gateway.async_initialize() + + zha_data[DATA_ZHA_DISPATCHERS] = [] + zha_data[DATA_ZHA_PLATFORM_LOADED] = asyncio.Event() platforms = [] for component in COMPONENTS: platforms.append( @@ -102,20 +112,10 @@ async def async_setup_entry(hass, config_entry): async def _platforms_loaded(): await asyncio.gather(*platforms) - hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED].set() + zha_data[DATA_ZHA_PLATFORM_LOADED].set() hass.async_create_task(_platforms_loaded()) - config = hass.data[DATA_ZHA].get(DATA_ZHA_CONFIG, {}) - - if config.get(CONF_ENABLE_QUIRKS, True): - # needs to be done here so that the ZHA module is finished loading - # before zhaquirks is imported - import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error - - zha_gateway = ZHAGateway(hass, config, config_entry) - await zha_gateway.async_initialize() - device_registry = await hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, @@ -130,8 +130,8 @@ async def async_setup_entry(hass, config_entry): async def async_zha_shutdown(event): """Handle shutdown tasks.""" - await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].shutdown() - await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].async_update_device_storage() + await zha_data[DATA_ZHA_GATEWAY].shutdown() + await zha_data[DATA_ZHA_GATEWAY].async_update_device_storage() hass.bus.async_listen_once(ha_const.EVENT_HOMEASSISTANT_STOP, async_zha_shutdown) hass.async_create_task(zha_gateway.async_load_devices()) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 6c0681d9eca..1ad10710c60 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -7,10 +7,12 @@ import logging import os import traceback +from serial import SerialException import zigpy.device as zigpy_dev from homeassistant.components.system_log import LogEntry, _figure_out_source from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.device_registry import ( CONNECTION_ZIGBEE, async_get_registry as get_dev_reg, @@ -98,7 +100,6 @@ class ZHAGateway: self.ha_entity_registry = None self.application_controller = None self.radio_description = None - hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self self._log_levels = { DEBUG_LEVEL_ORIGINAL: async_capture_log_levels(), DEBUG_LEVEL_CURRENT: async_capture_log_levels(), @@ -122,7 +123,11 @@ class ZHAGateway: radio_details = RADIO_TYPES[radio_type] radio = radio_details[ZHA_GW_RADIO]() self.radio_description = radio_details[ZHA_GW_RADIO_DESCRIPTION] - await radio.connect(usb_path, baudrate) + try: + await radio.connect(usb_path, baudrate) + except (SerialException, OSError) as exception: + _LOGGER.error("Couldn't open serial port for ZHA: %s", str(exception)) + raise ConfigEntryNotReady if CONF_DATABASE in self._config: database = self._config[CONF_DATABASE] @@ -133,7 +138,22 @@ class ZHAGateway: apply_application_controller_patch(self) self.application_controller.add_listener(self) self.application_controller.groups.add_listener(self) - await self.application_controller.startup(auto_form=True) + + try: + res = await self.application_controller.startup(auto_form=True) + if res is False: + await self.application_controller.shutdown() + raise ConfigEntryNotReady + except asyncio.TimeoutError as exception: + _LOGGER.error( + "Couldn't start %s coordinator", + radio_details[ZHA_GW_RADIO_DESCRIPTION], + exc_info=exception, + ) + radio.close() + raise ConfigEntryNotReady from exception + + self._hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str( self.application_controller.ieee ) From a28091e94a04a86a130f0c02e3bb69d74945406a Mon Sep 17 00:00:00 2001 From: tetienne Date: Fri, 20 Mar 2020 00:09:14 +0100 Subject: [PATCH 11/35] Fix somfy optimistic mode when missing in conf (#32995) * Fix optimistic mode when missing in conf #32971 * Ease code using a default value * Client id and secret are now inclusive --- homeassistant/components/somfy/__init__.py | 34 ++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index 1b2722882e6..619e5a72602 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -27,7 +27,7 @@ DOMAIN = "somfy" CONF_CLIENT_ID = "client_id" CONF_CLIENT_SECRET = "client_secret" -CONF_OPTIMISTIC = "optimisitic" +CONF_OPTIMISTIC = "optimistic" SOMFY_AUTH_CALLBACK_PATH = "/auth/somfy/callback" SOMFY_AUTH_START = "/auth/somfy" @@ -36,8 +36,8 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, + vol.Inclusive(CONF_CLIENT_ID, "oauth"): cv.string, + vol.Inclusive(CONF_CLIENT_SECRET, "oauth"): cv.string, vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, } ) @@ -51,23 +51,21 @@ SOMFY_COMPONENTS = ["cover", "switch"] async def async_setup(hass, config): """Set up the Somfy component.""" hass.data[DOMAIN] = {} + domain_config = config.get(DOMAIN, {}) + hass.data[DOMAIN][CONF_OPTIMISTIC] = domain_config.get(CONF_OPTIMISTIC, False) - if DOMAIN not in config: - return True - - hass.data[DOMAIN][CONF_OPTIMISTIC] = config[DOMAIN][CONF_OPTIMISTIC] - - config_flow.SomfyFlowHandler.async_register_implementation( - hass, - config_entry_oauth2_flow.LocalOAuth2Implementation( + if CONF_CLIENT_ID in domain_config: + config_flow.SomfyFlowHandler.async_register_implementation( hass, - DOMAIN, - config[DOMAIN][CONF_CLIENT_ID], - config[DOMAIN][CONF_CLIENT_SECRET], - "https://accounts.somfy.com/oauth/oauth/v2/auth", - "https://accounts.somfy.com/oauth/oauth/v2/token", - ), - ) + config_entry_oauth2_flow.LocalOAuth2Implementation( + hass, + DOMAIN, + config[DOMAIN][CONF_CLIENT_ID], + config[DOMAIN][CONF_CLIENT_SECRET], + "https://accounts.somfy.com/oauth/oauth/v2/auth", + "https://accounts.somfy.com/oauth/oauth/v2/token", + ), + ) return True From 209025293665af851a7fe7d418b2d4dce807977f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 20 Mar 2020 00:30:38 +0100 Subject: [PATCH 12/35] Axis - Fix char in stream url (#33004) * An unwanted character had found its way into a stream string, reverting f-string work to remove duplication of code and improve readability * Fix failing tests --- homeassistant/components/axis/camera.py | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 3cf84ce2288..c914319aa42 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -21,6 +21,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .axis_base import AxisEntityBase from .const import DOMAIN as AXIS_DOMAIN +AXIS_IMAGE = "http://{host}:{port}/axis-cgi/jpg/image.cgi" +AXIS_VIDEO = "http://{host}:{port}/axis-cgi/mjpg/video.cgi" +AXIS_STREAM = "rtsp://{user}:{password}@{host}/axis-media/media.amp?videocodec=h264" + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Axis camera video stream.""" @@ -32,13 +36,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): CONF_NAME: config_entry.data[CONF_NAME], CONF_USERNAME: config_entry.data[CONF_USERNAME], CONF_PASSWORD: config_entry.data[CONF_PASSWORD], - CONF_MJPEG_URL: ( - f"http://{config_entry.data[CONF_HOST]}" - f":{config_entry.data[CONF_PORT]}/axis-cgi/mjpg/video.cgi" + CONF_MJPEG_URL: AXIS_VIDEO.format( + host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT], ), - CONF_STILL_IMAGE_URL: ( - f"http://{config_entry.data[CONF_HOST]}" - f":{config_entry.data[CONF_PORT]}/axis-cgi/jpg/image.cgi" + CONF_STILL_IMAGE_URL: AXIS_IMAGE.format( + host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT], ), CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, } @@ -70,19 +72,17 @@ class AxisCamera(AxisEntityBase, MjpegCamera): async def stream_source(self): """Return the stream source.""" - return ( - f"rtsp://{self.device.config_entry.data[CONF_USERNAME]}´" - f":{self.device.config_entry.data[CONF_PASSWORD]}" - f"@{self.device.host}/axis-media/media.amp?videocodec=h264" + return AXIS_STREAM.format( + user=self.device.config_entry.data[CONF_USERNAME], + password=self.device.config_entry.data[CONF_PASSWORD], + host=self.device.host, ) def _new_address(self): """Set new device address for video stream.""" port = self.device.config_entry.data[CONF_PORT] - self._mjpeg_url = (f"http://{self.device.host}:{port}/axis-cgi/mjpg/video.cgi",) - self._still_image_url = ( - f"http://{self.device.host}:{port}/axis-cgi/jpg/image.cgi" - ) + self._mjpeg_url = AXIS_VIDEO.format(host=self.device.host, port=port) + self._still_image_url = AXIS_IMAGE.format(host=self.device.host, port=port) @property def unique_id(self): From 346a4b399d69a28655ddc925f1d241cb129ebc8f Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 19 Mar 2020 19:34:15 +0000 Subject: [PATCH 13/35] Fix sighthound dependency issue (#33010) --- homeassistant/components/sighthound/manifest.json | 1 + requirements_all.txt | 1 + requirements_test_all.txt | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index 737aa01c21f..a891d807f57 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -3,6 +3,7 @@ "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", "requirements": [ + "pillow==7.0.0", "simplehound==0.3" ], "dependencies": [], diff --git a/requirements_all.txt b/requirements_all.txt index ab8250b593a..7880f8e3fc5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1017,6 +1017,7 @@ pilight==0.1.1 # homeassistant.components.proxy # homeassistant.components.qrcode # homeassistant.components.seven_segments +# homeassistant.components.sighthound # homeassistant.components.tensorflow pillow==7.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4c6a2ae6360..9952815d911 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -365,6 +365,14 @@ pexpect==4.6.0 # homeassistant.components.pilight pilight==0.1.1 +# homeassistant.components.doods +# homeassistant.components.proxy +# homeassistant.components.qrcode +# homeassistant.components.seven_segments +# homeassistant.components.sighthound +# homeassistant.components.tensorflow +pillow==7.0.0 + # homeassistant.components.plex plexapi==3.3.0 From ca12db9271530cab4542716ce115e95ca5b48c08 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 19 Mar 2020 16:39:24 -0600 Subject: [PATCH 14/35] Bump simplisafe-python to 9.0.3 (#33013) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index b5f89a65fea..0fdcdcfcf5e 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.0.2"], + "requirements": ["simplisafe-python==9.0.3"], "dependencies": [], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7880f8e3fc5..11d241bbc9f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1856,7 +1856,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.0.2 +simplisafe-python==9.0.3 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9952815d911..f21ef66187e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -649,7 +649,7 @@ sentry-sdk==0.13.5 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.0.2 +simplisafe-python==9.0.3 # homeassistant.components.sleepiq sleepyq==0.7 From d707a1b0727e75d8d2bf23dd864d092106f015bf Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 19 Mar 2020 22:26:01 +0000 Subject: [PATCH 15/35] =?UTF-8?q?0.107.2=20-=20Bump=20aiohomekit=20to=20fi?= =?UTF-8?q?x=20Insignia=20NS-CH1XGO8=20and=20Lenno=E2=80=A6=20(#33016)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From d2b0c35319874afef6f6b38d56feb86276122df0 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 19 Mar 2020 22:47:08 -0400 Subject: [PATCH 16/35] Handle zigpy clusters without ep_attribute attribute. (#33028) --- homeassistant/components/zha/core/channels/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/core/channels/base.py b/homeassistant/components/zha/core/channels/base.py index dca0bbe09f3..dfe564ec2c1 100644 --- a/homeassistant/components/zha/core/channels/base.py +++ b/homeassistant/components/zha/core/channels/base.py @@ -85,11 +85,11 @@ class ZigbeeChannel(LogMixin): self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType ) -> None: """Initialize ZigbeeChannel.""" - self._channel_name = cluster.ep_attribute + self._generic_id = f"channel_0x{cluster.cluster_id:04x}" + self._channel_name = getattr(cluster, "ep_attribute", self._generic_id) if self.CHANNEL_NAME: self._channel_name = self.CHANNEL_NAME self._ch_pool = ch_pool - self._generic_id = f"channel_0x{cluster.cluster_id:04x}" self._cluster = cluster self._id = f"{ch_pool.id}:0x{cluster.cluster_id:04x}" unique_id = ch_pool.unique_id.replace("-", ":") From 6a6037790f6a3d1dd9344d3a2d3665974c34c99d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Mar 2020 20:50:46 -0700 Subject: [PATCH 17/35] Bumped version to 0.107.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 44a9c298368..3d4e380439c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 107 -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 5f0816ea25d2716edd61f7d823f8157cb5fba0de Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Mar 2020 20:45:26 -0700 Subject: [PATCH 18/35] Fix zones in packages (#33027) --- homeassistant/config.py | 18 ++++++++++++++++-- tests/test_config.py | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index abb8511cab0..27aff8ca36b 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -565,9 +565,23 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) -> def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional[Dict]]: """Extract the schema and identify list or dict based.""" try: - schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] # type: ignore - except (AttributeError, KeyError): + key = next(k for k in module.CONFIG_SCHEMA.schema if k == module.DOMAIN) # type: ignore + except (AttributeError, StopIteration): return None, None + + schema = module.CONFIG_SCHEMA.schema[key] # type: ignore + + if hasattr(key, "default"): + default_value = schema(key.default()) + + if isinstance(default_value, dict): + return "dict", schema + + if isinstance(default_value, list): + return "list", schema + + return None, None + t_schema = str(schema) if t_schema.startswith("{") or "schema_with_slug_keys" in t_schema: return ("dict", schema) diff --git a/tests/test_config.py b/tests/test_config.py index fc5ec43093b..43f1263e581 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,6 +10,7 @@ from unittest.mock import Mock import asynctest from asynctest import CoroutineMock, patch import pytest +import voluptuous as vol from voluptuous import Invalid, MultipleInvalid import yaml @@ -989,3 +990,20 @@ async def test_component_config_exceptions(hass, caplog): "Unknown error validating config for test_platform platform for test_domain component with PLATFORM_SCHEMA" in caplog.text ) + + +@pytest.mark.parametrize( + "domain, schema, expected", + [ + ("zone", vol.Schema({vol.Optional("zone", default=[]): list}), "list"), + ("zone", vol.Schema({vol.Optional("zone", default=dict): dict}), "dict"), + ], +) +def test_identify_config_schema(domain, schema, expected): + """Test identify config schema.""" + assert ( + config_util._identify_config_schema(Mock(DOMAIN=domain, CONFIG_SCHEMA=schema))[ + 0 + ] + == expected + ) From aee5c16803e8361280f298d8ee81f09ea10ed751 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 19 Mar 2020 21:54:41 -0600 Subject: [PATCH 19/35] Fix RainMachine not properly storing data in the config entry (#33002) * Fix bug related to RainMachine's default config flow * A * Fix tests * Code review --- .../components/rainmachine/__init__.py | 20 +++++------ .../components/rainmachine/config_flow.py | 34 ++++++++++++++++--- homeassistant/components/rainmachine/const.py | 6 ++++ .../rainmachine/test_config_flow.py | 4 ++- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index 4844a9e68c8..d2c0281fa80 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -25,6 +25,7 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.service import verify_domain_control from .const import ( + CONF_ZONE_RUN_TIME, DATA_CLIENT, DATA_PROGRAMS, DATA_PROVISION_SETTINGS, @@ -33,6 +34,8 @@ from .const import ( DATA_ZONES, DATA_ZONES_DETAILS, DEFAULT_PORT, + DEFAULT_SCAN_INTERVAL, + DEFAULT_ZONE_RUN, DOMAIN, PROGRAM_UPDATE_TOPIC, SENSOR_UPDATE_TOPIC, @@ -41,19 +44,14 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -DATA_LISTENER = "listener" - CONF_CONTROLLERS = "controllers" CONF_PROGRAM_ID = "program_id" CONF_SECONDS = "seconds" CONF_ZONE_ID = "zone_id" -CONF_ZONE_RUN_TIME = "zone_run_time" DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC" DEFAULT_ICON = "mdi:water" -DEFAULT_SCAN_INTERVAL = timedelta(seconds=60) DEFAULT_SSL = True -DEFAULT_ZONE_RUN = 60 * 10 SERVICE_ALTER_PROGRAM = vol.Schema({vol.Required(CONF_PROGRAM_ID): cv.positive_int}) @@ -109,7 +107,6 @@ async def async_setup(hass, config): """Set up the RainMachine component.""" hass.data[DOMAIN] = {} hass.data[DOMAIN][DATA_CLIENT] = {} - hass.data[DOMAIN][DATA_LISTENER] = {} if DOMAIN not in config: return True @@ -143,7 +140,7 @@ async def async_setup_entry(hass, config_entry): config_entry.data[CONF_IP_ADDRESS], config_entry.data[CONF_PASSWORD], port=config_entry.data[CONF_PORT], - ssl=config_entry.data[CONF_SSL], + ssl=config_entry.data.get(CONF_SSL, DEFAULT_SSL), ) except RainMachineError as err: _LOGGER.error("An error occurred: %s", err) @@ -156,8 +153,10 @@ async def async_setup_entry(hass, config_entry): rainmachine = RainMachine( hass, controller, - config_entry.data[CONF_ZONE_RUN_TIME], - config_entry.data[CONF_SCAN_INTERVAL], + config_entry.data.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN), + config_entry.data.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL.total_seconds() + ), ) # Update the data object, which at this point (prior to any sensors registering @@ -260,9 +259,6 @@ async def async_unload_entry(hass, config_entry): """Unload an OpenUV config entry.""" hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) - remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id) - remove_listener() - tasks = [ hass.config_entries.async_forward_entry_unload(config_entry, component) for component in ("binary_sensor", "sensor", "switch") diff --git a/homeassistant/components/rainmachine/config_flow.py b/homeassistant/components/rainmachine/config_flow.py index ffa46cc2c15..dc1ee16d05f 100644 --- a/homeassistant/components/rainmachine/config_flow.py +++ b/homeassistant/components/rainmachine/config_flow.py @@ -4,10 +4,22 @@ from regenmaschine.errors import RainMachineError import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT +from homeassistant.const import ( + CONF_IP_ADDRESS, + CONF_PASSWORD, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_SSL, +) from homeassistant.helpers import aiohttp_client -from .const import DEFAULT_PORT, DOMAIN # pylint: disable=unused-import +from .const import ( # pylint: disable=unused-import + CONF_ZONE_RUN_TIME, + DEFAULT_PORT, + DEFAULT_SCAN_INTERVAL, + DEFAULT_ZONE_RUN, + DOMAIN, +) class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -53,8 +65,8 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): user_input[CONF_IP_ADDRESS], user_input[CONF_PASSWORD], websession, - port=user_input.get(CONF_PORT, DEFAULT_PORT), - ssl=True, + port=user_input[CONF_PORT], + ssl=user_input.get(CONF_SSL, True), ) except RainMachineError: return await self._show_form({CONF_PASSWORD: "invalid_credentials"}) @@ -63,5 +75,17 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # access token without using the IP address and password, so we have to # store it: return self.async_create_entry( - title=user_input[CONF_IP_ADDRESS], data=user_input + title=user_input[CONF_IP_ADDRESS], + data={ + CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_PORT: user_input[CONF_PORT], + CONF_SSL: user_input.get(CONF_SSL, True), + CONF_SCAN_INTERVAL: user_input.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL.total_seconds() + ), + CONF_ZONE_RUN_TIME: user_input.get( + CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN + ), + }, ) diff --git a/homeassistant/components/rainmachine/const.py b/homeassistant/components/rainmachine/const.py index 855ff5d5df5..a88573476ba 100644 --- a/homeassistant/components/rainmachine/const.py +++ b/homeassistant/components/rainmachine/const.py @@ -1,6 +1,10 @@ """Define constants for the SimpliSafe component.""" +from datetime import timedelta + DOMAIN = "rainmachine" +CONF_ZONE_RUN_TIME = "zone_run_time" + DATA_CLIENT = "client" DATA_PROGRAMS = "programs" DATA_PROVISION_SETTINGS = "provision.settings" @@ -10,6 +14,8 @@ DATA_ZONES = "zones" DATA_ZONES_DETAILS = "zones_details" DEFAULT_PORT = 8080 +DEFAULT_SCAN_INTERVAL = timedelta(seconds=60) +DEFAULT_ZONE_RUN = 60 * 10 PROGRAM_UPDATE_TOPIC = f"{DOMAIN}_program_update" SENSOR_UPDATE_TOPIC = f"{DOMAIN}_data_update" diff --git a/tests/components/rainmachine/test_config_flow.py b/tests/components/rainmachine/test_config_flow.py index 38dafdda986..fca0f624a29 100644 --- a/tests/components/rainmachine/test_config_flow.py +++ b/tests/components/rainmachine/test_config_flow.py @@ -4,7 +4,7 @@ from unittest.mock import patch from regenmaschine.errors import RainMachineError from homeassistant import data_entry_flow -from homeassistant.components.rainmachine import DOMAIN, config_flow +from homeassistant.components.rainmachine import CONF_ZONE_RUN_TIME, DOMAIN, config_flow from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( CONF_IP_ADDRESS, @@ -98,6 +98,7 @@ async def test_step_import(hass): CONF_PORT: 8080, CONF_SSL: True, CONF_SCAN_INTERVAL: 60, + CONF_ZONE_RUN_TIME: 600, } @@ -129,4 +130,5 @@ async def test_step_user(hass): CONF_PORT: 8080, CONF_SSL: True, CONF_SCAN_INTERVAL: 60, + CONF_ZONE_RUN_TIME: 600, } From fa650b648cab9c168802b596bc88b35265188ea9 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Fri, 20 Mar 2020 15:22:27 +0100 Subject: [PATCH 20/35] Fix netatmo webhook registration issue (#32994) * Wait for cloud connection * Just wait * Remove redundant entry * Drop webhook before unloading other platforms * Add missing scope * Update homeassistant/components/netatmo/__init__.py Co-Authored-By: Paulus Schoutsen * Fix test Co-authored-by: Paulus Schoutsen --- homeassistant/components/netatmo/__init__.py | 18 ++++++++++++------ homeassistant/components/netatmo/camera.py | 12 ------------ .../components/netatmo/config_flow.py | 1 + homeassistant/components/netatmo/const.py | 3 ++- tests/components/netatmo/test_config_flow.py | 1 + 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index 776e63bef7d..b7d439b4a74 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -28,6 +28,7 @@ from . import api, config_flow from .const import ( AUTH, CONF_CLOUDHOOK_URL, + DATA_DEVICE_IDS, DATA_PERSONS, DOMAIN, OAUTH2_AUTHORIZE, @@ -65,6 +66,7 @@ async def async_setup(hass: HomeAssistant, config: dict): """Set up the Netatmo component.""" hass.data[DOMAIN] = {} hass.data[DOMAIN][DATA_PERSONS] = {} + hass.data[DOMAIN][DATA_DEVICE_IDS] = {} if DOMAIN not in config: return True @@ -104,7 +106,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID]) async def register_webhook(event): - # Wait for the could integration to be ready + # Wait for the cloud integration to be ready await asyncio.sleep(WAIT_FOR_CLOUD) if CONF_WEBHOOK_ID not in entry.data: @@ -112,6 +114,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.config_entries.async_update_entry(entry, data=data) if hass.components.cloud.async_active_subscription(): + # Wait for cloud connection to be established + await asyncio.sleep(WAIT_FOR_CLOUD) + if CONF_CLOUDHOOK_URL not in entry.data: webhook_url = await hass.components.cloud.async_create_cloudhook( entry.data[CONF_WEBHOOK_ID] @@ -144,6 +149,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + if CONF_WEBHOOK_ID in entry.data: + await hass.async_add_executor_job( + hass.data[DOMAIN][entry.entry_id][AUTH].dropwebhook + ) + unload_ok = all( await asyncio.gather( *[ @@ -152,14 +162,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ] ) ) + if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) - if CONF_WEBHOOK_ID in entry.data: - await hass.async_add_executor_job( - hass.data[DOMAIN][entry.entry_id][AUTH].dropwebhook() - ) - return unload_ok diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 616d2a620c5..30f209625f6 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -84,21 +84,11 @@ class NetatmoCamera(Camera): self._unique_id = f"{self._camera_id}-{self._camera_type}" self._verify_ssl = verify_ssl self._quality = quality - - # URLs self._vpnurl = None self._localurl = None - - # Monitoring status self._status = None - - # SD Card status self._sd_status = None - - # Power status self._alim_status = None - - # Is local self._is_local = None def camera_image(self): @@ -219,8 +209,6 @@ class NetatmoCamera(Camera): def update(self): """Update entity status.""" - - # Refresh camera data self._data.update() camera = self._data.camera_data.get_camera(cid=self._camera_id) diff --git a/homeassistant/components/netatmo/config_flow.py b/homeassistant/components/netatmo/config_flow.py index dce87fb7931..6d524ab9f29 100644 --- a/homeassistant/components/netatmo/config_flow.py +++ b/homeassistant/components/netatmo/config_flow.py @@ -33,6 +33,7 @@ class NetatmoFlowHandler( "read_station", "read_thermostat", "write_camera", + "write_presence", "write_thermostat", ] diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index 0a0c9575600..4e4ff308755 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -14,12 +14,12 @@ MODELS = { "NOC": "Smart Outdoor Camera", "NSD": "Smart Smoke Alarm", "NACamDoorTag": "Smart Door and Window Sensors", + "NHC": "Smart Indoor Air Quality Monitor", "NAMain": "Smart Home Weather station – indoor module", "NAModule1": "Smart Home Weather station – outdoor module", "NAModule4": "Smart Additional Indoor module", "NAModule3": "Smart Rain Gauge", "NAModule2": "Smart Anemometer", - "NHC": "Home Coach", } AUTH = "netatmo_auth" @@ -32,6 +32,7 @@ CONF_CLOUDHOOK_URL = "cloudhook_url" OAUTH2_AUTHORIZE = "https://api.netatmo.com/oauth2/authorize" OAUTH2_TOKEN = "https://api.netatmo.com/oauth2/token" +DATA_DEVICE_IDS = "netatmo_device_ids" DATA_PERSONS = "netatmo_persons" NETATMO_WEBHOOK_URL = None diff --git a/tests/components/netatmo/test_config_flow.py b/tests/components/netatmo/test_config_flow.py index c9a663991cb..29a1d4f53d5 100644 --- a/tests/components/netatmo/test_config_flow.py +++ b/tests/components/netatmo/test_config_flow.py @@ -65,6 +65,7 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): "read_station", "read_thermostat", "write_camera", + "write_presence", "write_thermostat", ] ) From e36bdd717ac7878e3cd14d464ac4f97978ca9d4a Mon Sep 17 00:00:00 2001 From: cgtobi Date: Fri, 20 Mar 2020 15:20:42 +0100 Subject: [PATCH 21/35] Fix discovery issue with netatmo climate devices (#33040) --- homeassistant/components/netatmo/climate.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index aa269cfeb49..1f1b7088b29 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -441,6 +441,11 @@ class ThermostatData: except TypeError: _LOGGER.error("ThermostatData::setup() got error") return False + except pyatmo.exceptions.NoDevice: + _LOGGER.debug( + "No climate devices for %s (%s)", self.home_name, self.home_id + ) + return False return True @Throttle(MIN_TIME_BETWEEN_UPDATES) From 7b37dcd8ed84e8fdfe97de1626f03d58c6148287 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 20 Mar 2020 16:17:43 +0100 Subject: [PATCH 22/35] Fix packages for schemas without a default (#33045) --- homeassistant/components/person/__init__.py | 6 +++++- homeassistant/config.py | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 9cd3e882c48..006929c7345 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -77,7 +77,11 @@ PERSON_SCHEMA = vol.Schema( ) CONFIG_SCHEMA = vol.Schema( - {vol.Optional(DOMAIN): vol.All(cv.ensure_list, cv.remove_falsy, [PERSON_SCHEMA])}, + { + vol.Optional(DOMAIN, default=[]): vol.All( + cv.ensure_list, cv.remove_falsy, [PERSON_SCHEMA] + ) + }, extra=vol.ALLOW_EXTRA, ) diff --git a/homeassistant/config.py b/homeassistant/config.py index 27aff8ca36b..b1cd49b0852 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -571,7 +571,9 @@ def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional schema = module.CONFIG_SCHEMA.schema[key] # type: ignore - if hasattr(key, "default"): + if hasattr(key, "default") and not isinstance( + key.default, vol.schema_builder.Undefined + ): default_value = schema(key.default()) if isinstance(default_value, dict): From 55be5bf8800e1196614da5adc3fa708c4f2aeb47 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 20 Mar 2020 16:29:10 +0000 Subject: [PATCH 23/35] Bump version to 0.107.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 3d4e380439c..86ef567be6d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 107 -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) From 0ae5c325fe6394711bb000bfb7daf4044ba45b6f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 20 Mar 2020 13:34:56 -0700 Subject: [PATCH 24/35] Add negative tests for identify schema for packages (#33050) --- homeassistant/config.py | 19 +++++++++---------- tests/test_config.py | 11 +++++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index b1cd49b0852..bd956846886 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -562,12 +562,12 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) -> _LOGGER.error(message) -def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional[Dict]]: +def _identify_config_schema(module: ModuleType) -> Optional[str]: """Extract the schema and identify list or dict based.""" try: key = next(k for k in module.CONFIG_SCHEMA.schema if k == module.DOMAIN) # type: ignore except (AttributeError, StopIteration): - return None, None + return None schema = module.CONFIG_SCHEMA.schema[key] # type: ignore @@ -577,19 +577,19 @@ def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional default_value = schema(key.default()) if isinstance(default_value, dict): - return "dict", schema + return "dict" if isinstance(default_value, list): - return "list", schema + return "list" - return None, None + return None t_schema = str(schema) if t_schema.startswith("{") or "schema_with_slug_keys" in t_schema: - return ("dict", schema) + return "dict" if t_schema.startswith(("[", "All( Union[bool, str]: @@ -642,8 +642,7 @@ async def merge_packages_config( merge_list = hasattr(component, "PLATFORM_SCHEMA") if not merge_list and hasattr(component, "CONFIG_SCHEMA"): - merge_type, _ = _identify_config_schema(component) - merge_list = merge_type == "list" + merge_list = _identify_config_schema(component) == "list" if merge_list: config[comp_name] = cv.remove_falsy( diff --git a/tests/test_config.py b/tests/test_config.py index 43f1263e581..ba0153e7a7d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -722,7 +722,7 @@ async def test_merge_id_schema(hass): for domain, expected_type in types.items(): integration = await async_get_integration(hass, domain) module = integration.get_component() - typ, _ = config_util._identify_config_schema(module) + typ = config_util._identify_config_schema(module) assert typ == expected_type, f"{domain} expected {expected_type}, got {typ}" @@ -997,13 +997,16 @@ async def test_component_config_exceptions(hass, caplog): [ ("zone", vol.Schema({vol.Optional("zone", default=[]): list}), "list"), ("zone", vol.Schema({vol.Optional("zone", default=dict): dict}), "dict"), + ("zone", vol.Schema({vol.Optional("zone"): int}), None), + ("zone", vol.Schema({"zone": int}), None), + ("not_existing", vol.Schema({vol.Optional("zone", default=dict): dict}), None,), + ("non_existing", vol.Schema({"zone": int}), None), + ("zone", vol.Schema({}), None), ], ) def test_identify_config_schema(domain, schema, expected): """Test identify config schema.""" assert ( - config_util._identify_config_schema(Mock(DOMAIN=domain, CONFIG_SCHEMA=schema))[ - 0 - ] + config_util._identify_config_schema(Mock(DOMAIN=domain, CONFIG_SCHEMA=schema)) == expected ) From 312903025d3be3c180745c8271b8ff7e0ebfc9d1 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 20 Mar 2020 14:59:01 -0600 Subject: [PATCH 25/35] Bump simplisafe-python to 9.0.4 (#33059) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 0fdcdcfcf5e..1d010c67692 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.0.3"], + "requirements": ["simplisafe-python==9.0.4"], "dependencies": [], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 11d241bbc9f..36ca24a0792 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1856,7 +1856,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.0.3 +simplisafe-python==9.0.4 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f21ef66187e..5252667985a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -649,7 +649,7 @@ sentry-sdk==0.13.5 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.0.3 +simplisafe-python==9.0.4 # homeassistant.components.sleepiq sleepyq==0.7 From edbb995fff913b67440ebc1033cf5b514fc52591 Mon Sep 17 00:00:00 2001 From: Knapoc Date: Fri, 20 Mar 2020 21:34:17 +0100 Subject: [PATCH 26/35] Bump aioasuswrt to 1.2.3 and fix asuswrt sensor (#33064) * Bump aioasuswrt to 1.2.3 * Fix asuswrt connection setup parameters * fix typo --- homeassistant/components/asuswrt/__init__.py | 4 ++-- homeassistant/components/asuswrt/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index f2d7a72e54d..a0afbed69f1 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -73,8 +73,8 @@ async def async_setup(hass, config): conf.get("ssh_key", conf.get("pub_key", "")), conf[CONF_MODE], conf[CONF_REQUIRE_IP], - conf[CONF_INTERFACE], - conf[CONF_DNSMASQ], + interface=conf[CONF_INTERFACE], + dnsmasq=conf[CONF_DNSMASQ], ) await api.connection.async_connect() diff --git a/homeassistant/components/asuswrt/manifest.json b/homeassistant/components/asuswrt/manifest.json index c161dc4f536..2e032dedfe7 100644 --- a/homeassistant/components/asuswrt/manifest.json +++ b/homeassistant/components/asuswrt/manifest.json @@ -2,7 +2,7 @@ "domain": "asuswrt", "name": "ASUSWRT", "documentation": "https://www.home-assistant.io/integrations/asuswrt", - "requirements": ["aioasuswrt==1.2.2"], + "requirements": ["aioasuswrt==1.2.3"], "dependencies": [], "codeowners": ["@kennedyshead"] } diff --git a/requirements_all.txt b/requirements_all.txt index 36ca24a0792..27f7277b373 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -139,7 +139,7 @@ aio_georss_gdacs==0.3 aioambient==1.0.4 # homeassistant.components.asuswrt -aioasuswrt==1.2.2 +aioasuswrt==1.2.3 # homeassistant.components.automatic aioautomatic==0.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5252667985a..8d22e748a16 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -50,7 +50,7 @@ aio_georss_gdacs==0.3 aioambient==1.0.4 # homeassistant.components.asuswrt -aioasuswrt==1.2.2 +aioasuswrt==1.2.3 # homeassistant.components.automatic aioautomatic==0.6.5 From 57998f6f0f6317338fb65b10d3f524c4ce509207 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 20 Mar 2020 20:27:37 -0700 Subject: [PATCH 27/35] Fix package default extraction (#33071) --- homeassistant/config.py | 4 +++- tests/test_config.py | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index bd956846886..b63acf4ab4c 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -574,7 +574,9 @@ def _identify_config_schema(module: ModuleType) -> Optional[str]: if hasattr(key, "default") and not isinstance( key.default, vol.schema_builder.Undefined ): - default_value = schema(key.default()) + default_value = module.CONFIG_SCHEMA({module.DOMAIN: key.default()})[ # type: ignore + module.DOMAIN # type: ignore + ] if isinstance(default_value, dict): return "dict" diff --git a/tests/test_config.py b/tests/test_config.py index ba0153e7a7d..f226c72f7ad 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -995,8 +995,20 @@ async def test_component_config_exceptions(hass, caplog): @pytest.mark.parametrize( "domain, schema, expected", [ - ("zone", vol.Schema({vol.Optional("zone", default=[]): list}), "list"), - ("zone", vol.Schema({vol.Optional("zone", default=dict): dict}), "dict"), + ("zone", vol.Schema({vol.Optional("zone", default=list): [int]}), "list"), + ("zone", vol.Schema({vol.Optional("zone", default=[]): [int]}), "list"), + ( + "zone", + vol.Schema({vol.Optional("zone", default={}): {vol.Optional("hello"): 1}}), + "dict", + ), + ( + "zone", + vol.Schema( + {vol.Optional("zone", default=dict): {vol.Optional("hello"): 1}} + ), + "dict", + ), ("zone", vol.Schema({vol.Optional("zone"): int}), None), ("zone", vol.Schema({"zone": int}), None), ("not_existing", vol.Schema({vol.Optional("zone", default=dict): dict}), None,), From 663db747e9acd0aea65e1275cbbc4d69af291656 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 20 Mar 2020 23:02:50 -0700 Subject: [PATCH 28/35] Bumped version to 0.107.4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 86ef567be6d..3b88f9710bd 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 107 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 197736f66b9086d29a5e2a7e6ef56e5d8c6c8e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 14 Mar 2020 12:37:44 +0200 Subject: [PATCH 29/35] Upgrade huawei-lte-api to 1.4.11 (#32791) https://github.com/Salamek/huawei-lte-api/releases/tag/1.4.11 --- homeassistant/components/huawei_lte/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/huawei_lte/manifest.json b/homeassistant/components/huawei_lte/manifest.json index 795b33485b6..262ee118e0f 100644 --- a/homeassistant/components/huawei_lte/manifest.json +++ b/homeassistant/components/huawei_lte/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/huawei_lte", "requirements": [ "getmac==0.8.1", - "huawei-lte-api==1.4.10", + "huawei-lte-api==1.4.11", "stringcase==1.2.0", "url-normalize==1.4.1" ], diff --git a/requirements_all.txt b/requirements_all.txt index 27f7277b373..79e8a1154fa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -712,7 +712,7 @@ horimote==0.4.1 httplib2==0.10.3 # homeassistant.components.huawei_lte -huawei-lte-api==1.4.10 +huawei-lte-api==1.4.11 # homeassistant.components.hydrawise hydrawiser==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8d22e748a16..b7500916925 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -276,7 +276,7 @@ homematicip==0.10.17 httplib2==0.10.3 # homeassistant.components.huawei_lte -huawei-lte-api==1.4.10 +huawei-lte-api==1.4.11 # homeassistant.components.iaqualink iaqualink==0.3.1 From a129bc05aea7d09fa4f9a09ff2c3087e4ae052ed Mon Sep 17 00:00:00 2001 From: escoand Date: Sat, 21 Mar 2020 16:50:18 +0100 Subject: [PATCH 30/35] Try all Samsung TV websocket ports (#33001) * Update bridge.py * add test * silence pylint * correct pylint * add some tests * Update test_media_player.py --- homeassistant/components/samsungtv/bridge.py | 7 +- .../components/samsungtv/test_config_flow.py | 76 +++++++++++++------ .../components/samsungtv/test_media_player.py | 76 ++++++++++++++++++- 3 files changed, 133 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index b582f6269e4..a0f16e91cf5 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -206,6 +206,7 @@ class SamsungTVWSBridge(SamsungTVBridge): CONF_TIMEOUT: 31, } + result = None try: LOGGER.debug("Try config: %s", config) with SamsungTVWS( @@ -223,9 +224,13 @@ class SamsungTVWSBridge(SamsungTVBridge): return RESULT_SUCCESS except WebSocketException: LOGGER.debug("Working but unsupported config: %s", config) - return RESULT_NOT_SUPPORTED + result = RESULT_NOT_SUPPORTED except (OSError, ConnectionFailure) as err: LOGGER.debug("Failing config: %s, error: %s", config, err) + # pylint: disable=useless-else-on-loop + else: + if result: + return result return RESULT_NOT_SUCCESSFUL diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 5485ee95827..65807602f09 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -1,5 +1,5 @@ """Tests for Samsung TV config flow.""" -from unittest.mock import call, patch +from unittest.mock import Mock, PropertyMock, call, patch from asynctest import mock import pytest @@ -19,7 +19,7 @@ from homeassistant.components.ssdp import ( ATTR_UPNP_MODEL_NAME, ATTR_UPNP_UDN, ) -from homeassistant.const import CONF_HOST, CONF_ID, CONF_METHOD, CONF_NAME +from homeassistant.const import CONF_HOST, CONF_ID, CONF_METHOD, CONF_NAME, CONF_TOKEN MOCK_USER_DATA = {CONF_HOST: "fake_host", CONF_NAME: "fake_name"} MOCK_SSDP_DATA = { @@ -46,6 +46,20 @@ AUTODETECT_LEGACY = { "host": "fake_host", "timeout": 31, } +AUTODETECT_WEBSOCKET_PLAIN = { + "host": "fake_host", + "name": "HomeAssistant", + "port": 8001, + "timeout": 31, + "token": None, +} +AUTODETECT_WEBSOCKET_SSL = { + "host": "fake_host", + "name": "HomeAssistant", + "port": 8002, + "timeout": 31, + "token": None, +} @pytest.fixture(name="remote") @@ -446,20 +460,48 @@ async def test_autodetect_websocket(hass, remote, remotews): with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remotews: + enter = Mock() + type(enter).token = PropertyMock(return_value="123456789") + remote = Mock() + remote.__enter__ = Mock(return_value=enter) + remote.__exit__ = Mock(return_value=False) + remotews.return_value = remote + result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA ) assert result["type"] == "create_entry" assert result["data"][CONF_METHOD] == "websocket" + assert result["data"][CONF_TOKEN] == "123456789" assert remotews.call_count == 1 + assert remotews.call_args_list == [call(**AUTODETECT_WEBSOCKET_PLAIN)] + + +async def test_autodetect_websocket_ssl(hass, remote, remotews): + """Test for send key with autodetection of protocol.""" + with patch( + "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), + ), patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWS", + side_effect=[WebSocketProtocolException("Boom"), mock.DEFAULT], + ) as remotews: + enter = Mock() + type(enter).token = PropertyMock(return_value="123456789") + remote = Mock() + remote.__enter__ = Mock(return_value=enter) + remote.__exit__ = Mock(return_value=False) + remotews.return_value = remote + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA + ) + assert result["type"] == "create_entry" + assert result["data"][CONF_METHOD] == "websocket" + assert result["data"][CONF_TOKEN] == "123456789" + assert remotews.call_count == 2 assert remotews.call_args_list == [ - call( - host="fake_host", - name="HomeAssistant", - port=8001, - timeout=31, - token=None, - ) + call(**AUTODETECT_WEBSOCKET_PLAIN), + call(**AUTODETECT_WEBSOCKET_SSL), ] @@ -524,18 +566,6 @@ async def test_autodetect_none(hass, remote, remotews): ] assert remotews.call_count == 2 assert remotews.call_args_list == [ - call( - host="fake_host", - name="HomeAssistant", - port=8001, - timeout=31, - token=None, - ), - call( - host="fake_host", - name="HomeAssistant", - port=8002, - timeout=31, - token=None, - ), + call(**AUTODETECT_WEBSOCKET_PLAIN), + call(**AUTODETECT_WEBSOCKET_SSL), ] diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index dff7525d980..b7881d2ddaa 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -34,8 +34,11 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_SUPPORTED_FEATURES, CONF_HOST, + CONF_IP_ADDRESS, + CONF_METHOD, CONF_NAME, CONF_PORT, + CONF_TOKEN, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, @@ -51,7 +54,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed ENTITY_ID = f"{DOMAIN}.fake" MOCK_CONFIG = { @@ -64,17 +67,40 @@ MOCK_CONFIG = { } ] } - MOCK_CONFIGWS = { SAMSUNGTV_DOMAIN: [ { CONF_HOST: "fake", CONF_NAME: "fake", CONF_PORT: 8001, + CONF_TOKEN: "123456789", CONF_ON_ACTION: [{"delay": "00:00:01"}], } ] } +MOCK_CALLS_WS = { + "host": "fake", + "port": 8001, + "token": None, + "timeout": 31, + "name": "HomeAssistant", +} + +MOCK_ENTRY_WS = { + CONF_IP_ADDRESS: "test", + CONF_HOST: "fake", + CONF_METHOD: "websocket", + CONF_NAME: "fake", + CONF_PORT: 8001, + CONF_TOKEN: "abcde", +} +MOCK_CALLS_ENTRY_WS = { + "host": "fake", + "name": "HomeAssistant", + "port": 8001, + "timeout": 1, + "token": "abcde", +} ENTITY_ID_NOTURNON = f"{DOMAIN}.fake_noturnon" MOCK_CONFIG_NOTURNON = { @@ -155,6 +181,52 @@ async def test_setup_without_turnon(hass, remote): assert hass.states.get(ENTITY_ID_NOTURNON) +async def test_setup_websocket(hass, remotews, mock_now): + """Test setup of platform.""" + with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: + enter = mock.Mock() + type(enter).token = mock.PropertyMock(return_value="987654321") + remote = mock.Mock() + remote.__enter__ = mock.Mock(return_value=enter) + remote.__exit__ = mock.Mock() + remote_class.return_value = remote + + await setup_samsungtv(hass, MOCK_CONFIGWS) + + assert remote_class.call_count == 1 + assert remote_class.call_args_list == [call(**MOCK_CALLS_WS)] + assert hass.states.get(ENTITY_ID) + + +async def test_setup_websocket_2(hass, mock_now): + """Test setup of platform from config entry.""" + entity_id = f"{DOMAIN}.fake" + + entry = MockConfigEntry( + domain=SAMSUNGTV_DOMAIN, data=MOCK_ENTRY_WS, unique_id=entity_id, + ) + entry.add_to_hass(hass) + + config_entries = hass.config_entries.async_entries(SAMSUNGTV_DOMAIN) + assert len(config_entries) == 1 + assert entry is config_entries[0] + + assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {}) + await hass.async_block_till_done() + + next_update = mock_now + timedelta(minutes=5) + with patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWS" + ) as remote, patch("homeassistant.util.dt.utcnow", return_value=next_update): + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert remote.call_count == 1 + assert remote.call_args_list == [call(**MOCK_CALLS_ENTRY_WS)] + + async def test_update_on(hass, remote, mock_now): """Testing update tv on.""" await setup_samsungtv(hass, MOCK_CONFIG) From 2785b067e35e9d74d1173fd9db68294bf63d1881 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 21 Mar 2020 13:25:43 -0400 Subject: [PATCH 31/35] Split ZHA device loading and entities adding (#33075) * Split ZHA device loading and entities adding. Load zha devices from ZHA gateway, but add entities after integration was setup. * Use hass.loop.create_task() * Split ZHA device initialization from loading. Restore and initialize ZHA devices separately. * Use hass.async_create_task() Load devices prior group initialization. --- homeassistant/components/zha/__init__.py | 45 ++++++++++++------- homeassistant/components/zha/binary_sensor.py | 2 +- .../components/zha/core/discovery.py | 10 +++++ homeassistant/components/zha/core/gateway.py | 27 ++++++----- homeassistant/components/zha/cover.py | 2 +- .../components/zha/device_tracker.py | 2 +- homeassistant/components/zha/fan.py | 2 +- homeassistant/components/zha/light.py | 2 +- homeassistant/components/zha/lock.py | 2 +- homeassistant/components/zha/sensor.py | 2 +- homeassistant/components/zha/switch.py | 2 +- 11 files changed, 62 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index ac5648e097b..63659a47e0d 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -8,6 +8,8 @@ import voluptuous as vol from homeassistant import config_entries, const as ha_const import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType from . import api from .core import ZHAGateway @@ -27,6 +29,7 @@ from .core.const import ( DEFAULT_BAUDRATE, DEFAULT_RADIO_TYPE, DOMAIN, + SIGNAL_ADD_ENTITIES, RadioType, ) @@ -90,8 +93,15 @@ async def async_setup_entry(hass, config_entry): """ zha_data = hass.data.setdefault(DATA_ZHA, {}) + zha_data[DATA_ZHA_PLATFORM_LOADED] = {} config = zha_data.get(DATA_ZHA_CONFIG, {}) + zha_data[DATA_ZHA_DISPATCHERS] = [] + for component in COMPONENTS: + zha_data[component] = [] + coro = hass.config_entries.async_forward_entry_setup(config_entry, component) + zha_data[DATA_ZHA_PLATFORM_LOADED][component] = hass.async_create_task(coro) + if config.get(CONF_ENABLE_QUIRKS, True): # needs to be done here so that the ZHA module is finished loading # before zhaquirks is imported @@ -100,22 +110,6 @@ async def async_setup_entry(hass, config_entry): zha_gateway = ZHAGateway(hass, config, config_entry) await zha_gateway.async_initialize() - zha_data[DATA_ZHA_DISPATCHERS] = [] - zha_data[DATA_ZHA_PLATFORM_LOADED] = asyncio.Event() - platforms = [] - for component in COMPONENTS: - platforms.append( - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) - ) - ) - - async def _platforms_loaded(): - await asyncio.gather(*platforms) - zha_data[DATA_ZHA_PLATFORM_LOADED].set() - - hass.async_create_task(_platforms_loaded()) - device_registry = await hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, @@ -134,7 +128,7 @@ async def async_setup_entry(hass, config_entry): await zha_data[DATA_ZHA_GATEWAY].async_update_device_storage() hass.bus.async_listen_once(ha_const.EVENT_HOMEASSISTANT_STOP, async_zha_shutdown) - hass.async_create_task(zha_gateway.async_load_devices()) + hass.async_create_task(async_load_entities(hass, config_entry)) return True @@ -152,3 +146,20 @@ async def async_unload_entry(hass, config_entry): await hass.config_entries.async_forward_entry_unload(config_entry, component) return True + + +async def async_load_entities( + hass: HomeAssistantType, config_entry: config_entries.ConfigEntry +) -> None: + """Load entities after integration was setup.""" + await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].async_prepare_entities() + to_setup = [ + hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED][comp] + for comp in COMPONENTS + if hass.data[DATA_ZHA][comp] + ] + results = await asyncio.gather(*to_setup, return_exceptions=True) + for res in results: + if isinstance(res, Exception): + _LOGGER.warning("Couldn't setup zha platform: %s", res) + async_dispatcher_send(hass, SIGNAL_ADD_ENTITIES) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 6c88f3e1013..9ed1bbfca16 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -49,7 +49,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Zigbee Home Automation binary sensor from config entry.""" - entities_to_create = hass.data[DATA_ZHA][DOMAIN] = [] + entities_to_create = hass.data[DATA_ZHA][DOMAIN] unsub = async_dispatcher_connect( hass, diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index b60357cf9f3..5f8f6b593f8 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -8,6 +8,16 @@ from homeassistant.core import callback from homeassistant.helpers.typing import HomeAssistantType from . import const as zha_const, registries as zha_regs, typing as zha_typing +from .. import ( # noqa: F401 pylint: disable=unused-import, + binary_sensor, + cover, + device_tracker, + fan, + light, + lock, + sensor, + switch, +) from .channels import base _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 1ad10710c60..78b5f939cae 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -36,7 +36,6 @@ from .const import ( DATA_ZHA, DATA_ZHA_BRIDGE_ID, DATA_ZHA_GATEWAY, - DATA_ZHA_PLATFORM_LOADED, DEBUG_COMP_BELLOWS, DEBUG_COMP_ZHA, DEBUG_COMP_ZIGPY, @@ -157,34 +156,40 @@ class ZHAGateway: self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str( self.application_controller.ieee ) + await self.async_load_devices() self._initialize_groups() async def async_load_devices(self) -> None: """Restore ZHA devices from zigpy application state.""" - await self._hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED].wait() + zigpy_devices = self.application_controller.devices.values() + for zigpy_device in zigpy_devices: + self._async_get_or_create_device(zigpy_device, restored=True) + async def async_prepare_entities(self) -> None: + """Prepare entities by initializing device channels.""" semaphore = asyncio.Semaphore(2) - async def _throttle(device: zha_typing.ZigpyDeviceType): + async def _throttle(zha_device: zha_typing.ZhaDeviceType, cached: bool): async with semaphore: - await self.async_device_restored(device) + await zha_device.async_initialize(from_cache=cached) - zigpy_devices = self.application_controller.devices.values() _LOGGER.debug("Loading battery powered devices") await asyncio.gather( *[ - _throttle(dev) - for dev in zigpy_devices - if not dev.node_desc.is_mains_powered + _throttle(dev, cached=True) + for dev in self.devices.values() + if not dev.is_mains_powered ] ) - async_dispatcher_send(self._hass, SIGNAL_ADD_ENTITIES) _LOGGER.debug("Loading mains powered devices") await asyncio.gather( - *[_throttle(dev) for dev in zigpy_devices if dev.node_desc.is_mains_powered] + *[ + _throttle(dev, cached=False) + for dev in self.devices.values() + if dev.is_mains_powered + ] ) - async_dispatcher_send(self._hass, SIGNAL_ADD_ENTITIES) def device_joined(self, device): """Handle device joined. diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index 46f7dd0e031..571741da7c3 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -29,7 +29,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Zigbee Home Automation cover from config entry.""" - entities_to_create = hass.data[DATA_ZHA][DOMAIN] = [] + entities_to_create = hass.data[DATA_ZHA][DOMAIN] unsub = async_dispatcher_connect( hass, diff --git a/homeassistant/components/zha/device_tracker.py b/homeassistant/components/zha/device_tracker.py index 5fe1dbc0060..2a53fc3bf3c 100644 --- a/homeassistant/components/zha/device_tracker.py +++ b/homeassistant/components/zha/device_tracker.py @@ -26,7 +26,7 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Zigbee Home Automation device tracker from config entry.""" - entities_to_create = hass.data[DATA_ZHA][DOMAIN] = [] + entities_to_create = hass.data[DATA_ZHA][DOMAIN] unsub = async_dispatcher_connect( hass, diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index 234566267f6..d04453cd675 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -53,7 +53,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Zigbee Home Automation fan from config entry.""" - entities_to_create = hass.data[DATA_ZHA][DOMAIN] = [] + entities_to_create = hass.data[DATA_ZHA][DOMAIN] unsub = async_dispatcher_connect( hass, diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 435f8940032..8775cb2b4d8 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -52,7 +52,7 @@ _REFRESH_INTERVAL = (45, 75) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Zigbee Home Automation light from config entry.""" - entities_to_create = hass.data[DATA_ZHA][light.DOMAIN] = [] + entities_to_create = hass.data[DATA_ZHA][light.DOMAIN] unsub = async_dispatcher_connect( hass, diff --git a/homeassistant/components/zha/lock.py b/homeassistant/components/zha/lock.py index 5c0d54430e0..ba802120044 100644 --- a/homeassistant/components/zha/lock.py +++ b/homeassistant/components/zha/lock.py @@ -36,7 +36,7 @@ VALUE_TO_STATE = dict(enumerate(STATE_LIST)) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Zigbee Home Automation Door Lock from config entry.""" - entities_to_create = hass.data[DATA_ZHA][DOMAIN] = [] + entities_to_create = hass.data[DATA_ZHA][DOMAIN] unsub = async_dispatcher_connect( hass, diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 8182fdcabcf..5e2e8bf4a0d 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -68,7 +68,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Zigbee Home Automation sensor from config entry.""" - entities_to_create = hass.data[DATA_ZHA][DOMAIN] = [] + entities_to_create = hass.data[DATA_ZHA][DOMAIN] unsub = async_dispatcher_connect( hass, diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 156183ce95d..6be3a9b3347 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -26,7 +26,7 @@ STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Zigbee Home Automation switch from config entry.""" - entities_to_create = hass.data[DATA_ZHA][DOMAIN] = [] + entities_to_create = hass.data[DATA_ZHA][DOMAIN] unsub = async_dispatcher_connect( hass, From 64556f6f69b8646434842eba7c1814db6a92db2f Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Sat, 21 Mar 2020 15:14:19 -0700 Subject: [PATCH 32/35] Fix totalconnect AttributeError introduced in 0.107 (#33079) * Fit git * Remove period from loggging message * Remove tests for now * Add const.py * Fix lint error --- .../totalconnect/alarm_control_panel.py | 41 +++++++------------ .../components/totalconnect/const.py | 3 ++ 2 files changed, 18 insertions(+), 26 deletions(-) create mode 100644 homeassistant/components/totalconnect/const.py diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index b255132a365..2ab06e2f6bd 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -18,7 +18,7 @@ from homeassistant.const import ( STATE_ALARM_TRIGGERED, ) -from . import DOMAIN as TOTALCONNECT_DOMAIN +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -30,7 +30,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): alarms = [] - client = hass.data[TOTALCONNECT_DOMAIN].client + client = hass.data[DOMAIN].client for location_id, location in client.locations.items(): location_name = location.location_name @@ -71,7 +71,7 @@ class TotalConnectAlarm(alarm.AlarmControlPanel): def update(self): """Return the state of the device.""" - status = self._client.get_armed_status(self._location_id) + self._client.get_armed_status(self._location_id) attr = { "location_name": self._name, "location_id": self._location_id, @@ -79,47 +79,36 @@ class TotalConnectAlarm(alarm.AlarmControlPanel): "low_battery": self._client.locations[self._location_id].low_battery, "cover_tampered": self._client.locations[ self._location_id - ].is_cover_tampered, + ].is_cover_tampered(), "triggered_source": None, "triggered_zone": None, } - if status in (self._client.DISARMED, self._client.DISARMED_BYPASS): + if self._client.locations[self._location_id].is_disarmed(): state = STATE_ALARM_DISARMED - elif status in ( - self._client.ARMED_STAY, - self._client.ARMED_STAY_INSTANT, - self._client.ARMED_STAY_INSTANT_BYPASS, - ): + elif self._client.locations[self._location_id].is_armed_home(): state = STATE_ALARM_ARMED_HOME - elif status == self._client.ARMED_STAY_NIGHT: + elif self._client.locations[self._location_id].is_armed_night(): state = STATE_ALARM_ARMED_NIGHT - elif status in ( - self._client.ARMED_AWAY, - self._client.ARMED_AWAY_BYPASS, - self._client.ARMED_AWAY_INSTANT, - self._client.ARMED_AWAY_INSTANT_BYPASS, - ): + elif self._client.locations[self._location_id].is_armed_away(): state = STATE_ALARM_ARMED_AWAY - elif status == self._client.ARMED_CUSTOM_BYPASS: + elif self._client.locations[self._location_id].is_armed_custom_bypass(): state = STATE_ALARM_ARMED_CUSTOM_BYPASS - elif status == self._client.ARMING: + elif self._client.locations[self._location_id].is_arming(): state = STATE_ALARM_ARMING - elif status == self._client.DISARMING: + elif self._client.locations[self._location_id].is_disarming(): state = STATE_ALARM_DISARMING - elif status == self._client.ALARMING: + elif self._client.locations[self._location_id].is_triggered_police(): state = STATE_ALARM_TRIGGERED attr["triggered_source"] = "Police/Medical" - elif status == self._client.ALARMING_FIRE_SMOKE: + elif self._client.locations[self._location_id].is_triggered_fire(): state = STATE_ALARM_TRIGGERED attr["triggered_source"] = "Fire/Smoke" - elif status == self._client.ALARMING_CARBON_MONOXIDE: + elif self._client.locations[self._location_id].is_triggered_gas(): state = STATE_ALARM_TRIGGERED attr["triggered_source"] = "Carbon Monoxide" else: - logging.info( - "Total Connect Client returned unknown status code: %s", status - ) + logging.info("Total Connect Client returned unknown status") state = None self._state = state diff --git a/homeassistant/components/totalconnect/const.py b/homeassistant/components/totalconnect/const.py new file mode 100644 index 00000000000..6c19bf0a217 --- /dev/null +++ b/homeassistant/components/totalconnect/const.py @@ -0,0 +1,3 @@ +"""TotalConnect constants.""" + +DOMAIN = "totalconnect" From 470537bc5feae3cb43d6974241f9b630345287c8 Mon Sep 17 00:00:00 2001 From: guillempages Date: Sat, 21 Mar 2020 23:12:14 +0100 Subject: [PATCH 33/35] Fix tankerkoenig with more than 10 stations (#33098) * Fix tankerkoenig with more than 10 stations There seemed to be a problem, if more than 10 fuel stations were tracked. Added a warning in this case, and split the calls to the API in chunks of 10, so that the data can be fetched anyway. * Update homeassistant/components/tankerkoenig/__init__.py Co-Authored-By: Martin Hjelmare Co-authored-by: Martin Hjelmare --- .../components/tankerkoenig/__init__.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/tankerkoenig/__init__.py b/homeassistant/components/tankerkoenig/__init__.py index fde2f1c57cd..e8b4e92327c 100755 --- a/homeassistant/components/tankerkoenig/__init__.py +++ b/homeassistant/components/tankerkoenig/__init__.py @@ -1,6 +1,7 @@ """Ask tankerkoenig.de for petrol price information.""" from datetime import timedelta import logging +from math import ceil import pytankerkoenig import voluptuous as vol @@ -164,27 +165,41 @@ class TankerkoenigData: ) return False self.add_station(additional_station_data["station"]) + if len(self.stations) > 10: + _LOGGER.warning( + "Found more than 10 stations to check. " + "This might invalidate your api-key on the long run. " + "Try using a smaller radius" + ) return True async def fetch_data(self): """Get the latest data from tankerkoenig.de.""" _LOGGER.debug("Fetching new data from tankerkoenig.de") station_ids = list(self.stations) - data = await self._hass.async_add_executor_job( - pytankerkoenig.getPriceList, self._api_key, station_ids - ) - if data["ok"]: + prices = {} + + # The API seems to only return at most 10 results, so split the list in chunks of 10 + # and merge it together. + for index in range(ceil(len(station_ids) / 10)): + data = await self._hass.async_add_executor_job( + pytankerkoenig.getPriceList, + self._api_key, + station_ids[index * 10 : (index + 1) * 10], + ) + _LOGGER.debug("Received data: %s", data) + if not data["ok"]: + _LOGGER.error( + "Error fetching data from tankerkoenig.de: %s", data["message"] + ) + raise TankerkoenigError(data["message"]) if "prices" not in data: _LOGGER.error("Did not receive price information from tankerkoenig.de") raise TankerkoenigError("No prices in data") - else: - _LOGGER.error( - "Error fetching data from tankerkoenig.de: %s", data["message"] - ) - raise TankerkoenigError(data["message"]) - return data["prices"] + prices.update(data["prices"]) + return prices def add_station(self, station: dict): """Add fuel station to the entity list.""" From 78e5878247d40e9d4a6deb2ac10834950f5a08cc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 21 Mar 2020 19:36:35 +0100 Subject: [PATCH 34/35] Fix Extend ONVIF unique ID with profile index (#33103) --- homeassistant/components/onvif/camera.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index ce241f779b1..cb518d6c5ee 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -505,4 +505,6 @@ class ONVIFHassCamera(Camera): @property def unique_id(self) -> Optional[str]: """Return a unique ID.""" + if self._profile_index: + return f"{self._mac}_{self._profile_index}" return self._mac From ca1c696f54390d0ac9d4b41339ea68a10b92c305 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 21 Mar 2020 16:34:11 -0700 Subject: [PATCH 35/35] Bumped version to 0.107.5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 3b88f9710bd..c758ad4dd5f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 107 -PATCH_VERSION = "4" +PATCH_VERSION = "5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0)