From 08ebc4ce623e18c237eae64fb6bb4d5faaf651cd Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 3 Jul 2020 20:01:22 +0200 Subject: [PATCH 01/43] Don't print MQTT credentials to log (#37364) --- homeassistant/components/mqtt/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index bb2ec7a8bcb..5f82fa76dfa 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -476,10 +476,14 @@ async def async_setup_entry(hass, entry): if conf is None: conf = CONFIG_SCHEMA({DOMAIN: dict(entry.data)})[DOMAIN] elif any(key in conf for key in entry.data): - _LOGGER.warning( + shared_keys = conf.keys() & entry.data.keys() + override = {k: entry.data[k] for k in shared_keys} + if CONF_PASSWORD in override: + override[CONF_PASSWORD] = "********" + _LOGGER.info( "Data in your configuration entry is going to override your " "configuration.yaml: %s", - entry.data, + override, ) conf = _merge_config(entry, conf) From ed086e5200d599e157bcd7e154f4c1b289d597c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 3 Jul 2020 17:35:02 -0500 Subject: [PATCH 02/43] Handle index already existing on db migration with MySQLdb backend (#37384) _create_index needed the same check as _add_columns since the MySQLdb backend throws OperationalError instead of InternalError in this case --- homeassistant/components/recorder/migration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 1cff6d178ad..ece0e36402e 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -81,7 +81,9 @@ def _create_index(engine, table_name, index_name): try: index.create(engine) except OperationalError as err: - if "already exists" not in str(err).lower(): + lower_err_str = str(err).lower() + + if "already exists" not in lower_err_str and "duplicate" not in lower_err_str: raise _LOGGER.warning( From d7ecbb8ebeffd705258e6ce194b141a14bd553f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 2 Jul 2020 19:53:28 -0500 Subject: [PATCH 03/43] Ensure logbook entries appear when the logbook.log (#37388) service without a domain or entity_id --- homeassistant/components/logbook/__init__.py | 6 +++++ tests/components/logbook/test_init.py | 28 +++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 28d6c7fcd48..35c0361265f 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -117,6 +117,12 @@ async def async_setup(hass, config): domain = service.data.get(ATTR_DOMAIN) entity_id = service.data.get(ATTR_ENTITY_ID) + if entity_id is None and domain is None: + # If there is no entity_id or + # domain, the event will get filtered + # away so we use the "logbook" domain + domain = DOMAIN + message.hass = hass message = message.async_render() async_log_entry(hass, name, message, domain, entity_id) diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 03ef09b438d..93ac6c68140 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -77,7 +77,15 @@ class TestComponentLogbook(unittest.TestCase): }, True, ) - + self.hass.services.call( + logbook.DOMAIN, + "log", + { + logbook.ATTR_NAME: "This entry", + logbook.ATTR_MESSAGE: "has no domain or entity_id", + }, + True, + ) # Logbook entry service call results in firing an event. # Our service call will unblock when the event listeners have been # scheduled. This means that they may not have been processed yet. @@ -92,15 +100,21 @@ class TestComponentLogbook(unittest.TestCase): dt_util.utcnow() + timedelta(hours=1), ) ) - assert len(events) == 1 + assert len(events) == 2 + + assert len(calls) == 2 + first_call = calls[-2] + + assert first_call.data.get(logbook.ATTR_NAME) == "Alarm" + assert first_call.data.get(logbook.ATTR_MESSAGE) == "is triggered" + assert first_call.data.get(logbook.ATTR_DOMAIN) == "switch" + assert first_call.data.get(logbook.ATTR_ENTITY_ID) == "switch.test_switch" - assert len(calls) == 1 last_call = calls[-1] - assert last_call.data.get(logbook.ATTR_NAME) == "Alarm" - assert last_call.data.get(logbook.ATTR_MESSAGE) == "is triggered" - assert last_call.data.get(logbook.ATTR_DOMAIN) == "switch" - assert last_call.data.get(logbook.ATTR_ENTITY_ID) == "switch.test_switch" + assert last_call.data.get(logbook.ATTR_NAME) == "This entry" + assert last_call.data.get(logbook.ATTR_MESSAGE) == "has no domain or entity_id" + assert last_call.data.get(logbook.ATTR_DOMAIN) == "logbook" def test_service_call_create_log_book_entry_no_message(self): """Test if service call create log book entry without message.""" From 032a6f31439328513125d71da3ce23c6685e796a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 2 Jul 2020 23:07:54 -0600 Subject: [PATCH 04/43] Bump pytile to 4.0.0 (#37398) --- homeassistant/components/tile/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tile/manifest.json b/homeassistant/components/tile/manifest.json index a43a0e229c2..0f2c84c12e1 100644 --- a/homeassistant/components/tile/manifest.json +++ b/homeassistant/components/tile/manifest.json @@ -3,6 +3,6 @@ "name": "Tile", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tile", - "requirements": ["pytile==3.0.6"], + "requirements": ["pytile==4.0.0"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9b2133e9d34..3202b202673 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1778,7 +1778,7 @@ python_opendata_transport==0.2.1 pythonegardia==1.0.40 # homeassistant.components.tile -pytile==3.0.6 +pytile==4.0.0 # homeassistant.components.touchline pytouchline==0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8ec86122a41..8a028c13b92 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -775,7 +775,7 @@ python-velbus==2.0.43 python_awair==0.1.1 # homeassistant.components.tile -pytile==3.0.6 +pytile==4.0.0 # homeassistant.components.traccar pytraccar==0.9.0 From e3d3b87f2eda99492d1f46edaa00a2eb5702ea3c Mon Sep 17 00:00:00 2001 From: Teemu R Date: Sat, 4 Jul 2020 00:32:23 +0200 Subject: [PATCH 05/43] Bump python-miio to 0.5.2.1 (#37422) --- homeassistant/components/xiaomi_miio/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index c9e948a1300..471dc7290df 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -3,7 +3,7 @@ "name": "Xiaomi Miio", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xiaomi_miio", - "requirements": ["construct==2.9.45", "python-miio==0.5.1"], + "requirements": ["construct==2.9.45", "python-miio==0.5.2.1"], "codeowners": ["@rytilahti", "@syssi"], "zeroconf": ["_miio._udp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 3202b202673..cd079c3bf4a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1712,7 +1712,7 @@ python-juicenet==1.0.1 # python-lirc==1.2.3 # homeassistant.components.xiaomi_miio -python-miio==0.5.1 +python-miio==0.5.2.1 # homeassistant.components.mpd python-mpd2==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8a028c13b92..8c0f3d1f7cc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -748,7 +748,7 @@ python-izone==1.1.2 python-juicenet==1.0.1 # homeassistant.components.xiaomi_miio -python-miio==0.5.1 +python-miio==0.5.2.1 # homeassistant.components.nest python-nest==4.1.0 From 30e980d389f459fe48dbccf2fa20a4d4b8da8a87 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 3 Jul 2020 15:29:11 -0700 Subject: [PATCH 06/43] Bump teslajsonpy to 0.9.2 (#37434) * Bump teslajsonpy to 0.9.1 closes #37340 * Bump teslajsonpy to 0.9.2 --- homeassistant/components/tesla/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json index 9a0d80f9a05..e8f8cfa6caa 100644 --- a/homeassistant/components/tesla/manifest.json +++ b/homeassistant/components/tesla/manifest.json @@ -3,6 +3,6 @@ "name": "Tesla", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tesla", - "requirements": ["teslajsonpy==0.9.0"], + "requirements": ["teslajsonpy==0.9.2"], "codeowners": ["@zabuldon", "@alandtse"] } diff --git a/requirements_all.txt b/requirements_all.txt index cd079c3bf4a..87d4c01104e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2094,7 +2094,7 @@ temperusb==1.5.3 tesla-powerwall==0.2.11 # homeassistant.components.tesla -teslajsonpy==0.9.0 +teslajsonpy==0.9.2 # homeassistant.components.thermoworks_smoke thermoworks_smoke==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8c0f3d1f7cc..e5fbd63e993 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -890,7 +890,7 @@ tellduslive==0.10.11 tesla-powerwall==0.2.11 # homeassistant.components.tesla -teslajsonpy==0.9.0 +teslajsonpy==0.9.2 # homeassistant.components.toon toonapi==0.1.0 From 18c16c464e43b468839e1cd39ab67939e6f037e9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 3 Jul 2020 22:43:02 +0000 Subject: [PATCH 07/43] Bumped version to 0.112.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index dffaf87e1c2..8e5ff9c941d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -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 4ca643342e36241526e17240a0709d3c77d86ec6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Jun 2020 09:12:50 -0500 Subject: [PATCH 08/43] Move logbook continuous domain filtering to sql (#37115) * Move logbook continuous domain filtering to sql sensors tend to generate a significant amount of states that are filtered out by logbook. In testing 75% of states can be filtered away in sql to avoid the sqlalchemy ORM overhead of creating objects that will be discarded. * remove un-needed nesting --- homeassistant/components/logbook/__init__.py | 19 ++++--- tests/components/logbook/test_init.py | 52 ++++++++++++++------ 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 35c0361265f..3148a857039 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -5,6 +5,7 @@ import json import logging import time +import sqlalchemy from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import aliased import voluptuous as vol @@ -28,7 +29,6 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_NAME, - ATTR_UNIT_OF_MEASUREMENT, CONF_EXCLUDE, CONF_INCLUDE, EVENT_HOMEASSISTANT_START, @@ -66,6 +66,8 @@ DOMAIN = "logbook" GROUP_BY_MINUTES = 15 EMPTY_JSON_OBJECT = "{}" +UNIT_OF_MEASUREMENT_JSON = '"unit_of_measurement":' + CONFIG_SCHEMA = vol.Schema( {DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA}, extra=vol.ALLOW_EXTRA ) @@ -420,6 +422,15 @@ def _get_events(hass, config, start_day, end_day, entity_id=None): & (States.state != old_state.state) ) ) + # + # Prefilter out continuous domains that have + # ATTR_UNIT_OF_MEASUREMENT as its much faster in sql. + # + .filter( + (Events.event_type != EVENT_STATE_CHANGED) + | sqlalchemy.not_(States.domain.in_(CONTINUOUS_DOMAINS)) + | sqlalchemy.not_(States.attributes.contains(UNIT_OF_MEASUREMENT_JSON)) + ) .filter( Events.event_type.in_(ALL_EVENT_TYPES + list(hass.data.get(DOMAIN, {}))) ) @@ -455,12 +466,6 @@ def _keep_event(hass, event, entities_filter, entity_attr_cache): # Do not report on entity removal if not event.has_old_and_new_state: return False - - if event.domain in CONTINUOUS_DOMAINS and entity_attr_cache.get( - entity_id, ATTR_UNIT_OF_MEASUREMENT, event - ): - # Don't show continuous sensor value changes in the logbook - return False elif event.event_type in HOMEASSISTANT_EVENTS: entity_id = f"{HA_DOMAIN}." elif event.event_type in hass.data[DOMAIN] and ATTR_ENTITY_ID not in event.data: diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 93ac6c68140..ea9e1054bb0 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -163,22 +163,6 @@ class TestComponentLogbook(unittest.TestCase): entries[1], pointC, "bla", domain="sensor", entity_id=entity_id ) - def test_filter_continuous_sensor_values(self): - """Test remove continuous sensor events from logbook.""" - entity_id = "sensor.bla" - pointA = dt_util.utcnow() - entity_attr_cache = logbook.EntityAttributeCache(self.hass) - attributes = {"unit_of_measurement": "foo"} - eventA = self.create_state_changed_event(pointA, entity_id, 10, attributes) - - entities_filter = convert_include_exclude_filter( - logbook.CONFIG_SCHEMA({logbook.DOMAIN: {}})[logbook.DOMAIN] - ) - assert ( - logbook._keep_event(self.hass, eventA, entities_filter, entity_attr_cache) - is False - ) - def test_exclude_new_entities(self): """Test if events are excluded on first update.""" entity_id = "sensor.bla" @@ -1820,6 +1804,42 @@ async def test_logbook_entity_filter_with_automations(hass, hass_client): assert json_dict[0]["entity_id"] == entity_id_second +async def test_filter_continuous_sensor_values(hass, hass_client): + """Test remove continuous sensor events from logbook.""" + await hass.async_add_executor_job(init_recorder_component, hass) + await async_setup_component(hass, "logbook", {}) + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + entity_id_test = "switch.test" + hass.states.async_set(entity_id_test, STATE_OFF) + hass.states.async_set(entity_id_test, STATE_ON) + entity_id_second = "sensor.bla" + hass.states.async_set(entity_id_second, STATE_OFF, {"unit_of_measurement": "foo"}) + hass.states.async_set(entity_id_second, STATE_ON, {"unit_of_measurement": "foo"}) + entity_id_third = "light.bla" + hass.states.async_set(entity_id_third, STATE_OFF, {"unit_of_measurement": "foo"}) + hass.states.async_set(entity_id_third, STATE_ON, {"unit_of_measurement": "foo"}) + + await hass.async_add_job(partial(trigger_db_commit, hass)) + await hass.async_block_till_done() + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + client = await hass_client() + + # Today time 00:00:00 + start = dt_util.utcnow().date() + start_date = datetime(start.year, start.month, start.day) + + # Test today entries without filters + response = await client.get(f"/api/logbook/{start_date.isoformat()}") + assert response.status == 200 + response_json = await response.json() + + assert len(response_json) == 2 + assert response_json[0]["entity_id"] == entity_id_test + assert response_json[1]["entity_id"] == entity_id_third + + class MockLazyEventPartialState(ha.Event): """Minimal mock of a Lazy event.""" From 2e824f3fa5e181818282dbc5e282cd120d1111b4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 2 Jul 2020 11:12:27 -0500 Subject: [PATCH 09/43] Ensure logbook performs well when filtering is configured (#37292) --- homeassistant/components/history/__init__.py | 63 +++++++++----- homeassistant/components/logbook/__init__.py | 88 +++++++++----------- tests/components/logbook/test_init.py | 31 +++---- 3 files changed, 92 insertions(+), 90 deletions(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index f943c126d3e..7c9f054aa27 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -394,16 +394,9 @@ def get_state(hass, utc_point_in_time, entity_id, run=None): async def async_setup(hass, config): """Set up the history hooks.""" - filters = Filters() conf = config.get(DOMAIN, {}) - exclude = conf.get(CONF_EXCLUDE) - if exclude: - filters.excluded_entities = exclude.get(CONF_ENTITIES, []) - filters.excluded_domains = exclude.get(CONF_DOMAINS, []) - include = conf.get(CONF_INCLUDE) - if include: - filters.included_entities = include.get(CONF_ENTITIES, []) - filters.included_domains = include.get(CONF_DOMAINS, []) + + filters = sqlalchemy_filter_from_include_exclude_conf(conf) use_include_order = conf.get(CONF_ORDER) hass.http.register_view(HistoryPeriodView(filters, use_include_order)) @@ -530,6 +523,20 @@ class HistoryPeriodView(HomeAssistantView): return self.json(result) +def sqlalchemy_filter_from_include_exclude_conf(conf): + """Build a sql filter from config.""" + filters = Filters() + exclude = conf.get(CONF_EXCLUDE) + if exclude: + filters.excluded_entities = exclude.get(CONF_ENTITIES, []) + filters.excluded_domains = exclude.get(CONF_DOMAINS, []) + include = conf.get(CONF_INCLUDE) + if include: + filters.included_entities = include.get(CONF_ENTITIES, []) + filters.included_domains = include.get(CONF_DOMAINS, []) + return filters + + class Filters: """Container for the configured include and exclude filters.""" @@ -556,26 +563,34 @@ class Filters: return query.filter(States.entity_id.in_(entity_ids)) query = query.filter(~States.domain.in_(IGNORE_DOMAINS)) - filter_query = None + entity_filter = self.entity_filter() + if entity_filter is not None: + query = query.filter(entity_filter) + + return query + + def entity_filter(self): + """Generate the entity filter query.""" + entity_filter = None # filter if only excluded domain is configured if self.excluded_domains and not self.included_domains: - filter_query = ~States.domain.in_(self.excluded_domains) + entity_filter = ~States.domain.in_(self.excluded_domains) if self.included_entities: - filter_query &= States.entity_id.in_(self.included_entities) + entity_filter &= States.entity_id.in_(self.included_entities) # filter if only included domain is configured elif not self.excluded_domains and self.included_domains: - filter_query = States.domain.in_(self.included_domains) + entity_filter = States.domain.in_(self.included_domains) if self.included_entities: - filter_query |= States.entity_id.in_(self.included_entities) + entity_filter |= States.entity_id.in_(self.included_entities) # filter if included and excluded domain is configured elif self.excluded_domains and self.included_domains: - filter_query = ~States.domain.in_(self.excluded_domains) + entity_filter = ~States.domain.in_(self.excluded_domains) if self.included_entities: - filter_query &= States.domain.in_( + entity_filter &= States.domain.in_( self.included_domains ) | States.entity_id.in_(self.included_entities) else: - filter_query &= States.domain.in_( + entity_filter &= States.domain.in_( self.included_domains ) & ~States.domain.in_(self.excluded_domains) # no domain filter just included entities @@ -584,13 +599,17 @@ class Filters: and not self.included_domains and self.included_entities ): - filter_query = States.entity_id.in_(self.included_entities) - if filter_query is not None: - query = query.filter(filter_query) + entity_filter = States.entity_id.in_(self.included_entities) # finally apply excluded entities filter if configured if self.excluded_entities: - query = query.filter(~States.entity_id.in_(self.excluded_entities)) - return query + if entity_filter is not None: + entity_filter = (entity_filter) & ~States.entity_id.in_( + self.excluded_entities + ) + else: + entity_filter = ~States.entity_id.in_(self.excluded_entities) + + return entity_filter class LazyState(State): diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 3148a857039..4bfbc65413c 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -3,14 +3,13 @@ from datetime import timedelta from itertools import groupby import json import logging -import time import sqlalchemy -from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import aliased import voluptuous as vol from homeassistant.components import sun +from homeassistant.components.history import sqlalchemy_filter_from_include_exclude_conf from homeassistant.components.http import HomeAssistantView from homeassistant.components.recorder.models import ( Events, @@ -18,19 +17,13 @@ from homeassistant.components.recorder.models import ( process_timestamp, process_timestamp_to_utc_isoformat, ) -from homeassistant.components.recorder.util import ( - QUERY_RETRY_WAIT, - RETRIES, - session_scope, -) +from homeassistant.components.recorder.util import session_scope from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_DOMAIN, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_NAME, - CONF_EXCLUDE, - CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_LOGBOOK_ENTRY, @@ -129,12 +122,21 @@ async def async_setup(hass, config): message = message.async_render() async_log_entry(hass, name, message, domain, entity_id) - hass.http.register_view(LogbookView(config.get(DOMAIN, {}))) - hass.components.frontend.async_register_built_in_panel( "logbook", "logbook", "hass:format-list-bulleted-type" ) + conf = config.get(DOMAIN, {}) + + if conf: + filters = sqlalchemy_filter_from_include_exclude_conf(conf) + entities_filter = convert_include_exclude_filter(conf) + else: + filters = None + entities_filter = None + + hass.http.register_view(LogbookView(conf, filters, entities_filter)) + hass.services.async_register(DOMAIN, "log", log_message, schema=LOG_MESSAGE_SCHEMA) await async_process_integration_platforms(hass, DOMAIN, _process_logbook_platform) @@ -160,9 +162,11 @@ class LogbookView(HomeAssistantView): name = "api:logbook" extra_urls = ["/api/logbook/{datetime}"] - def __init__(self, config): + def __init__(self, config, filters, entities_filter): """Initialize the logbook view.""" self.config = config + self.filters = filters + self.entities_filter = entities_filter async def get(self, request, datetime=None): """Retrieve logbook entries.""" @@ -197,7 +201,15 @@ class LogbookView(HomeAssistantView): def json_events(): """Fetch events and generate JSON.""" return self.json( - _get_events(hass, self.config, start_day, end_day, entity_id) + _get_events( + hass, + self.config, + start_day, + end_day, + entity_id, + self.filters, + self.entities_filter, + ) ) return await hass.async_add_job(json_events) @@ -333,38 +345,9 @@ def humanify(hass, events, entity_attr_cache, prev_states=None): } -def _get_related_entity_ids(session, entity_filter): - timer_start = time.perf_counter() - - query = session.query(States).with_entities(States.entity_id).distinct() - - for tryno in range(RETRIES): - try: - result = [row.entity_id for row in query if entity_filter(row.entity_id)] - - if _LOGGER.isEnabledFor(logging.DEBUG): - elapsed = time.perf_counter() - timer_start - _LOGGER.debug( - "fetching %d distinct domain/entity_id pairs took %fs", - len(result), - elapsed, - ) - - return result - except SQLAlchemyError as err: - _LOGGER.error("Error executing query: %s", err) - - if tryno == RETRIES - 1: - raise - time.sleep(QUERY_RETRY_WAIT) - - -def _all_entities_filter(_): - """Filter that accepts all entities.""" - return True - - -def _get_events(hass, config, start_day, end_day, entity_id=None): +def _get_events( + hass, config, start_day, end_day, entity_id=None, filters=None, entities_filter=None +): """Get events for a period of time.""" entity_attr_cache = EntityAttributeCache(hass) @@ -379,12 +362,10 @@ def _get_events(hass, config, start_day, end_day, entity_id=None): if entity_id is not None: entity_ids = [entity_id.lower()] entities_filter = generate_filter([], entity_ids, [], []) - elif config.get(CONF_EXCLUDE) or config.get(CONF_INCLUDE): - entities_filter = convert_include_exclude_filter(config) - entity_ids = _get_related_entity_ids(session, entities_filter) + apply_sql_entities_filter = False else: - entities_filter = _all_entities_filter entity_ids = None + apply_sql_entities_filter = True old_state = aliased(States, name="old_state") @@ -451,6 +432,13 @@ def _get_events(hass, config, start_day, end_day, entity_id=None): | (States.state_id.is_(None)) ) + if apply_sql_entities_filter and filters: + entity_filter = filters.entity_filter() + if entity_filter is not None: + query = query.filter( + entity_filter | (Events.event_type != EVENT_STATE_CHANGED) + ) + # When all data is schema v8 or later, prev_states can be removed prev_states = {} return list(humanify(hass, yield_events(query), entity_attr_cache, prev_states)) @@ -484,7 +472,7 @@ def _keep_event(hass, event, entities_filter, entity_attr_cache): return False entity_id = f"{domain}." - return entities_filter(entity_id) + return entities_filter is None or entities_filter(entity_id) def _entry_message_from_event(hass, entity_id, domain, event, entity_attr_cache): diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index ea9e1054bb0..2507fede46d 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -20,6 +20,8 @@ from homeassistant.const import ( ATTR_NAME, CONF_DOMAINS, CONF_ENTITIES, + CONF_EXCLUDE, + CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, @@ -254,7 +256,7 @@ class TestComponentLogbook(unittest.TestCase): config = logbook.CONFIG_SCHEMA( { ha.DOMAIN: {}, - logbook.DOMAIN: {logbook.CONF_EXCLUDE: {CONF_ENTITIES: [entity_id]}}, + logbook.DOMAIN: {CONF_EXCLUDE: {CONF_ENTITIES: [entity_id]}}, } ) entities_filter = convert_include_exclude_filter(config[logbook.DOMAIN]) @@ -291,9 +293,7 @@ class TestComponentLogbook(unittest.TestCase): config = logbook.CONFIG_SCHEMA( { ha.DOMAIN: {}, - logbook.DOMAIN: { - logbook.CONF_EXCLUDE: {CONF_DOMAINS: ["switch", "alexa"]} - }, + logbook.DOMAIN: {CONF_EXCLUDE: {CONF_DOMAINS: ["switch", "alexa"]}}, } ) entities_filter = convert_include_exclude_filter(config[logbook.DOMAIN]) @@ -335,7 +335,7 @@ class TestComponentLogbook(unittest.TestCase): { ha.DOMAIN: {}, logbook.DOMAIN: { - logbook.CONF_EXCLUDE: { + CONF_EXCLUDE: { CONF_DOMAINS: ["switch", "alexa"], CONF_ENTITY_GLOBS: "*.excluded", } @@ -379,7 +379,7 @@ class TestComponentLogbook(unittest.TestCase): { ha.DOMAIN: {}, logbook.DOMAIN: { - logbook.CONF_INCLUDE: { + CONF_INCLUDE: { CONF_DOMAINS: ["homeassistant"], CONF_ENTITIES: [entity_id2], } @@ -427,9 +427,7 @@ class TestComponentLogbook(unittest.TestCase): { ha.DOMAIN: {}, logbook.DOMAIN: { - logbook.CONF_INCLUDE: { - CONF_DOMAINS: ["homeassistant", "sensor", "alexa"] - } + CONF_INCLUDE: {CONF_DOMAINS: ["homeassistant", "sensor", "alexa"]} }, } ) @@ -479,7 +477,7 @@ class TestComponentLogbook(unittest.TestCase): { ha.DOMAIN: {}, logbook.DOMAIN: { - logbook.CONF_INCLUDE: { + CONF_INCLUDE: { CONF_DOMAINS: ["homeassistant", "sensor", "alexa"], CONF_ENTITY_GLOBS: ["*.included"], } @@ -531,11 +529,11 @@ class TestComponentLogbook(unittest.TestCase): { ha.DOMAIN: {}, logbook.DOMAIN: { - logbook.CONF_INCLUDE: { + CONF_INCLUDE: { CONF_DOMAINS: ["sensor", "homeassistant"], CONF_ENTITIES: ["switch.bla"], }, - logbook.CONF_EXCLUDE: { + CONF_EXCLUDE: { CONF_DOMAINS: ["switch"], CONF_ENTITIES: ["sensor.bli"], }, @@ -600,12 +598,12 @@ class TestComponentLogbook(unittest.TestCase): { ha.DOMAIN: {}, logbook.DOMAIN: { - logbook.CONF_INCLUDE: { + CONF_INCLUDE: { CONF_DOMAINS: ["sensor", "homeassistant"], CONF_ENTITIES: ["switch.bla"], CONF_ENTITY_GLOBS: ["*.included"], }, - logbook.CONF_EXCLUDE: { + CONF_EXCLUDE: { CONF_DOMAINS: ["switch"], CONF_ENTITY_GLOBS: ["*.excluded"], CONF_ENTITIES: ["sensor.bli"], @@ -1631,10 +1629,7 @@ async def test_exclude_described_event(hass, hass_client): logbook.DOMAIN, { logbook.DOMAIN: { - logbook.CONF_EXCLUDE: { - CONF_DOMAINS: ["sensor"], - CONF_ENTITIES: [entity_id], - } + CONF_EXCLUDE: {CONF_DOMAINS: ["sensor"], CONF_ENTITIES: [entity_id]} } }, ) From 891640972b4413922aa41d6a6bb7e86ac888d4de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 4 Jul 2020 01:08:46 -0500 Subject: [PATCH 10/43] Ensure removed entities are not displayed in logbook (#37395) --- homeassistant/components/logbook/__init__.py | 17 +- homeassistant/components/recorder/__init__.py | 5 +- homeassistant/scripts/benchmark/__init__.py | 2 +- tests/components/logbook/test_init.py | 172 +++++++++--------- tests/components/recorder/test_init.py | 23 ++- 5 files changed, 121 insertions(+), 98 deletions(-) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 4bfbc65413c..ac9c6b1ba3e 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -355,7 +355,7 @@ def _get_events( """Yield Events that are not filtered away.""" for row in query.yield_per(1000): event = LazyEventPartialState(row) - if _keep_event(hass, event, entities_filter, entity_attr_cache): + if _keep_event(hass, event, entities_filter): yield event with session_scope(hass=hass) as session: @@ -375,12 +375,10 @@ def _get_events( Events.event_data, Events.time_fired, Events.context_user_id, - States.state_id, States.state, States.entity_id, States.domain, States.attributes, - old_state.state_id.label("old_state_id"), ) .order_by(Events.time_fired) .outerjoin(States, (Events.event_id == States.event_id)) @@ -400,6 +398,7 @@ def _get_events( | ( (States.state_id.isnot(None)) & (old_state.state_id.isnot(None)) + & (States.state.isnot(None)) & (States.state != old_state.state) ) ) @@ -444,12 +443,9 @@ def _get_events( return list(humanify(hass, yield_events(query), entity_attr_cache, prev_states)) -def _keep_event(hass, event, entities_filter, entity_attr_cache): +def _keep_event(hass, event, entities_filter): if event.event_type == EVENT_STATE_CHANGED: entity_id = event.entity_id - if entity_id is None: - return False - # Do not report on new entities # Do not report on entity removal if not event.has_old_and_new_state: @@ -650,9 +646,12 @@ class LazyEventPartialState: # Delete this check once all states are saved in the v8 schema # format or later (they have the old_state_id column). - # New events in v8 schema format + # New events in v8+ schema format if self._row.event_data == EMPTY_JSON_OBJECT: - return self._row.state_id is not None and self._row.old_state_id is not None + # Events are already pre-filtered in sql + # to exclude missing old and new state + # if they are in v8+ format + return True # Old events not in v8 schema format return ( diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index aadc8e61fa1..0d40cc0212f 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -386,11 +386,14 @@ class Recorder(threading.Thread): if dbevent and event.event_type == EVENT_STATE_CHANGED: try: dbstate = States.from_event(event) + has_new_state = event.data.get("new_state") dbstate.old_state_id = self._old_state_ids.get(dbstate.entity_id) + if not has_new_state: + dbstate.state = None dbstate.event_id = dbevent.event_id self.event_session.add(dbstate) self.event_session.flush() - if "new_state" in event.data: + if has_new_state: self._old_state_ids[dbstate.entity_id] = dbstate.state_id elif dbstate.entity_id in self._old_state_ids: del self._old_state_ids[dbstate.entity_id] diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index eaba6f52c02..0fab42c10bb 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -186,7 +186,7 @@ async def _logbook_filtering(hass, last_changed, last_updated): def yield_events(event): for _ in range(10 ** 5): # pylint: disable=protected-access - if logbook._keep_event(hass, event, entities_filter, entity_attr_cache): + if logbook._keep_event(hass, event, entities_filter): yield event start = timer() diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 2507fede46d..8b527219818 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -165,83 +165,6 @@ class TestComponentLogbook(unittest.TestCase): entries[1], pointC, "bla", domain="sensor", entity_id=entity_id ) - def test_exclude_new_entities(self): - """Test if events are excluded on first update.""" - entity_id = "sensor.bla" - entity_id2 = "sensor.blu" - pointA = dt_util.utcnow() - pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) - entity_attr_cache = logbook.EntityAttributeCache(self.hass) - - state_on = ha.State( - entity_id, "on", {"brightness": 200}, pointA, pointA - ).as_dict() - - eventA = self.create_state_changed_event_from_old_new( - entity_id, pointA, None, state_on - ) - eventB = self.create_state_changed_event(pointB, entity_id2, 20) - - entities_filter = convert_include_exclude_filter( - logbook.CONFIG_SCHEMA({logbook.DOMAIN: {}})[logbook.DOMAIN] - ) - events = [ - e - for e in ( - MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP), - eventA, - eventB, - ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) - ] - entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) - - assert len(entries) == 2 - self.assert_entry( - entries[0], name="Home Assistant", message="stopped", domain=ha.DOMAIN - ) - self.assert_entry( - entries[1], pointB, "blu", domain="sensor", entity_id=entity_id2 - ) - - def test_exclude_removed_entities(self): - """Test if events are excluded on last update.""" - entity_id = "sensor.bla" - entity_id2 = "sensor.blu" - pointA = dt_util.utcnow() - pointB = pointA + timedelta(minutes=logbook.GROUP_BY_MINUTES) - entity_attr_cache = logbook.EntityAttributeCache(self.hass) - - state_on = ha.State( - entity_id, "on", {"brightness": 200}, pointA, pointA - ).as_dict() - eventA = self.create_state_changed_event_from_old_new( - None, pointA, state_on, None, - ) - eventB = self.create_state_changed_event(pointB, entity_id2, 20) - - entities_filter = convert_include_exclude_filter( - logbook.CONFIG_SCHEMA({logbook.DOMAIN: {}})[logbook.DOMAIN] - ) - events = [ - e - for e in ( - MockLazyEventPartialState(EVENT_HOMEASSISTANT_STOP), - eventA, - eventB, - ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) - ] - entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) - - assert len(entries) == 2 - self.assert_entry( - entries[0], name="Home Assistant", message="stopped", domain=ha.DOMAIN - ) - self.assert_entry( - entries[1], pointB, "blu", domain="sensor", entity_id=entity_id2 - ) - def test_exclude_events_entity(self): """Test if events are filtered if entity is excluded in config.""" entity_id = "sensor.bla" @@ -267,7 +190,7 @@ class TestComponentLogbook(unittest.TestCase): eventA, eventB, ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -305,7 +228,7 @@ class TestComponentLogbook(unittest.TestCase): eventA, eventB, ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -352,7 +275,7 @@ class TestComponentLogbook(unittest.TestCase): eventB, eventC, ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -394,7 +317,7 @@ class TestComponentLogbook(unittest.TestCase): eventA, eventB, ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -440,7 +363,7 @@ class TestComponentLogbook(unittest.TestCase): eventA, eventB, ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -494,7 +417,7 @@ class TestComponentLogbook(unittest.TestCase): eventB, eventC, ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -551,7 +474,7 @@ class TestComponentLogbook(unittest.TestCase): eventB1, eventB2, ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -625,7 +548,7 @@ class TestComponentLogbook(unittest.TestCase): eventC2, eventC3, ) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -677,7 +600,7 @@ class TestComponentLogbook(unittest.TestCase): events = [ e for e in (eventA, eventB) - if logbook._keep_event(self.hass, e, entities_filter, entity_attr_cache) + if logbook._keep_event(self.hass, e, entities_filter) ] entries = list(logbook.humanify(self.hass, events, entity_attr_cache)) @@ -1835,6 +1758,83 @@ async def test_filter_continuous_sensor_values(hass, hass_client): assert response_json[1]["entity_id"] == entity_id_third +async def test_exclude_new_entities(hass, hass_client): + """Test if events are excluded on first update.""" + await hass.async_add_executor_job(init_recorder_component, hass) + await async_setup_component(hass, "logbook", {}) + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + entity_id = "climate.bla" + entity_id2 = "climate.blu" + + hass.states.async_set(entity_id, STATE_OFF) + hass.states.async_set(entity_id2, STATE_ON) + hass.states.async_set(entity_id2, STATE_OFF) + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + + await hass.async_add_job(partial(trigger_db_commit, hass)) + await hass.async_block_till_done() + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + client = await hass_client() + + # Today time 00:00:00 + start = dt_util.utcnow().date() + start_date = datetime(start.year, start.month, start.day) + + # Test today entries without filters + response = await client.get(f"/api/logbook/{start_date.isoformat()}") + assert response.status == 200 + response_json = await response.json() + + assert len(response_json) == 2 + assert response_json[0]["entity_id"] == entity_id2 + assert response_json[1]["domain"] == "homeassistant" + assert response_json[1]["message"] == "started" + + +async def test_exclude_removed_entities(hass, hass_client): + """Test if events are excluded on last update.""" + await hass.async_add_executor_job(init_recorder_component, hass) + await async_setup_component(hass, "logbook", {}) + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + entity_id = "climate.bla" + entity_id2 = "climate.blu" + + hass.states.async_set(entity_id, STATE_ON) + hass.states.async_set(entity_id, STATE_OFF) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + + hass.states.async_set(entity_id2, STATE_ON) + hass.states.async_set(entity_id2, STATE_OFF) + + hass.states.async_remove(entity_id) + hass.states.async_remove(entity_id2) + + await hass.async_add_job(partial(trigger_db_commit, hass)) + await hass.async_block_till_done() + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + client = await hass_client() + + # Today time 00:00:00 + start = dt_util.utcnow().date() + start_date = datetime(start.year, start.month, start.day) + + # Test today entries without filters + response = await client.get(f"/api/logbook/{start_date.isoformat()}") + assert response.status == 200 + response_json = await response.json() + + assert len(response_json) == 3 + assert response_json[0]["entity_id"] == entity_id + assert response_json[1]["domain"] == "homeassistant" + assert response_json[1]["message"] == "started" + assert response_json[2]["entity_id"] == entity_id2 + + class MockLazyEventPartialState(ha.Event): """Minimal mock of a Lazy event.""" diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 843609cf308..46db4782628 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -16,7 +16,7 @@ from homeassistant.components.recorder import ( from homeassistant.components.recorder.const import DATA_INSTANCE from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.util import session_scope -from homeassistant.const import MATCH_ALL +from homeassistant.const import MATCH_ALL, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import ATTR_NOW, EVENT_TIME_CHANGED, Context, callback from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -261,6 +261,27 @@ def test_saving_state_include_domain_glob_exclude_entity(hass_recorder): assert _state_empty_context(hass, "test.ok").state == "state2" +def test_saving_state_and_removing_entity(hass, hass_recorder): + """Test saving the state of a removed entity.""" + hass = hass_recorder() + entity_id = "lock.mine" + hass.states.set(entity_id, STATE_LOCKED) + hass.states.set(entity_id, STATE_UNLOCKED) + hass.states.async_remove(entity_id) + + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + states = list(session.query(States)) + assert len(states) == 3 + assert states[0].entity_id == entity_id + assert states[0].state == STATE_LOCKED + assert states[1].entity_id == entity_id + assert states[1].state == STATE_UNLOCKED + assert states[2].entity_id == entity_id + assert states[2].state is None + + def test_recorder_setup_failure(): """Test some exceptions.""" hass = get_test_home_assistant() From ddb049e884cead7364ddd3c28842fd77ad85bda0 Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Tue, 7 Jul 2020 01:18:56 +0300 Subject: [PATCH 11/43] Stop Speedtest sensors update on startup if manual option is enabled (#37403) Co-authored-by: Paulus Schoutsen --- .../components/speedtestdotnet/__init__.py | 28 ++++++++---- .../components/speedtestdotnet/config_flow.py | 4 +- .../components/speedtestdotnet/sensor.py | 44 +++++++++++++------ 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 3cad15a0967..1b50516f340 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -70,9 +70,10 @@ async def async_setup_entry(hass, config_entry): coordinator = SpeedTestDataCoordinator(hass, config_entry) await coordinator.async_setup() - await coordinator.async_refresh() - if not coordinator.last_update_success: - raise ConfigEntryNotReady + if not config_entry.options[CONF_MANUAL]: + await coordinator.async_refresh() + if not coordinator.last_update_success: + raise ConfigEntryNotReady hass.data[DOMAIN] = coordinator @@ -115,9 +116,12 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator): ), ) - def update_data(self): - """Get the latest data from speedtest.net.""" - server_list = self.api.get_servers() + def update_servers(self): + """Update list of test servers.""" + try: + server_list = self.api.get_servers() + except speedtest.ConfigRetrievalError: + return self.servers[DEFAULT_SERVER] = {} for server in sorted( @@ -125,14 +129,20 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator): ): self.servers[f"{server[0]['country']} - {server[0]['sponsor']}"] = server[0] + def update_data(self): + """Get the latest data from speedtest.net.""" + self.update_servers() + + self.api.closest.clear() if self.config_entry.options.get(CONF_SERVER_ID): server_id = self.config_entry.options.get(CONF_SERVER_ID) - self.api.closest.clear() self.api.get_servers(servers=[server_id]) + + self.api.get_best_server() _LOGGER.debug( "Executing speedtest.net speed test with server_id: %s", self.api.best["id"] ) - self.api.get_best_server() + self.api.download() self.api.upload() return self.api.results.dict() @@ -170,6 +180,8 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator): await self.async_set_options() + await self.hass.async_add_executor_job(self.update_servers) + self.hass.services.async_register(DOMAIN, SPEED_TEST_SERVICE, request_update) self.config_entry.add_update_listener(options_updated_listener) diff --git a/homeassistant/components/speedtestdotnet/config_flow.py b/homeassistant/components/speedtestdotnet/config_flow.py index 1d8f3cf189b..57076c2a90b 100644 --- a/homeassistant/components/speedtestdotnet/config_flow.py +++ b/homeassistant/components/speedtestdotnet/config_flow.py @@ -85,7 +85,7 @@ class SpeedTestOptionsFlowHandler(config_entries.OptionsFlow): self._servers = self.hass.data[DOMAIN].servers - server_name = DEFAULT_SERVER + server = [] if self.config_entry.options.get( CONF_SERVER_ID ) and not self.config_entry.options.get(CONF_SERVER_NAME): @@ -94,7 +94,7 @@ class SpeedTestOptionsFlowHandler(config_entries.OptionsFlow): for (key, value) in self._servers.items() if value.get("id") == self.config_entry.options[CONF_SERVER_ID] ] - server_name = server[0] if server else "" + server_name = server[0] if server else DEFAULT_SERVER options = { vol.Optional( diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 06868dc1437..0889d7da5b2 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -2,7 +2,8 @@ import logging from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.helpers.entity import Entity +from homeassistant.core import callback +from homeassistant.helpers.restore_state import RestoreEntity from .const import ( ATTR_BYTES_RECEIVED, @@ -11,6 +12,7 @@ from .const import ( ATTR_SERVER_ID, ATTR_SERVER_NAME, ATTRIBUTION, + CONF_MANUAL, DEFAULT_NAME, DOMAIN, ICON, @@ -32,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class SpeedtestSensor(Entity): +class SpeedtestSensor(RestoreEntity): """Implementation of a speedtest.net sensor.""" def __init__(self, coordinator, sensor_type): @@ -41,6 +43,7 @@ class SpeedtestSensor(Entity): self.coordinator = coordinator self.type = sensor_type self._unit_of_measurement = SENSOR_TYPES[self.type][1] + self._state = None @property def name(self): @@ -55,14 +58,7 @@ class SpeedtestSensor(Entity): @property def state(self): """Return the state of the device.""" - state = None - if self.type == "ping": - state = self.coordinator.data["ping"] - elif self.type == "download": - state = round(self.coordinator.data["download"] / 10 ** 6, 2) - elif self.type == "upload": - state = round(self.coordinator.data["upload"] / 10 ** 6, 2) - return state + return self._state @property def unit_of_measurement(self): @@ -82,6 +78,8 @@ class SpeedtestSensor(Entity): @property def device_state_attributes(self): """Return the state attributes.""" + if not self.coordinator.data: + return None attributes = { ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_SERVER_NAME: self.coordinator.data["server"]["name"], @@ -98,10 +96,30 @@ class SpeedtestSensor(Entity): async def async_added_to_hass(self): """Handle entity which will be added.""" + await super().async_added_to_hass() + if self.coordinator.config_entry.options[CONF_MANUAL]: + state = await self.async_get_last_state() + if state: + self._state = state.state - self.async_on_remove( - self.coordinator.async_add_listener(self.async_write_ha_state) - ) + @callback + def update(): + """Update state.""" + self._update_state() + self.async_write_ha_state() + + self.async_on_remove(self.coordinator.async_add_listener(update)) + self._update_state() + + def _update_state(self): + """Update sensors state.""" + if self.coordinator.data: + if self.type == "ping": + self._state = self.coordinator.data["ping"] + elif self.type == "download": + self._state = round(self.coordinator.data["download"] / 10 ** 6, 2) + elif self.type == "upload": + self._state = round(self.coordinator.data["upload"] / 10 ** 6, 2) async def async_update(self): """Request coordinator to update data.""" From 050a558243cdf1eb61b2e540433cd0317c79149f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 7 Jul 2020 00:17:13 +0200 Subject: [PATCH 12/43] Fix base topic for 'topic' (#37475) --- homeassistant/components/mqtt/discovery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 281172b6332..aff66954968 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -104,9 +104,9 @@ async def async_start( base = payload.pop(TOPIC_BASE) for key, value in payload.items(): if isinstance(value, str) and value: - if value[0] == TOPIC_BASE and key.endswith("_topic"): + if value[0] == TOPIC_BASE and key.endswith("topic"): payload[key] = f"{base}{value[1:]}" - if value[-1] == TOPIC_BASE and key.endswith("_topic"): + if value[-1] == TOPIC_BASE and key.endswith("topic"): payload[key] = f"{value[:-1]}{base}" # If present, the node_id will be included in the discovered object id From 16b59220f378eab99dc5b0c7886f1fdd2af299d1 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 4 Jul 2020 19:39:45 +0200 Subject: [PATCH 13/43] Fix base class for ViCare binary sensor to remove warning (#37478) --- homeassistant/components/vicare/binary_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/vicare/binary_sensor.py b/homeassistant/components/vicare/binary_sensor.py index a35339c12a9..9ae615a6367 100644 --- a/homeassistant/components/vicare/binary_sensor.py +++ b/homeassistant/components/vicare/binary_sensor.py @@ -5,7 +5,7 @@ import requests from homeassistant.components.binary_sensor import ( DEVICE_CLASS_POWER, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME @@ -77,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ViCareBinarySensor(BinarySensorDevice): +class ViCareBinarySensor(BinarySensorEntity): """Representation of a ViCare sensor.""" def __init__(self, name, api, sensor_type): From 7c3e64673f77d138de8952debfc698d95acc669e Mon Sep 17 00:00:00 2001 From: David Nielsen Date: Sun, 5 Jul 2020 06:02:45 -0400 Subject: [PATCH 14/43] Fix braviatv authentication refresh (#37482) - Bumps bravia-tv lib to 1.0.6 which fixes is_connected() to actually return True only when API is connected, instead of just returning whether or not cookies are cached (regardless if they actually worked). - Wrap is_connected() because it now performs io. - Remove unnecessary logic to refresh cookies. Now that is_connected() works, the bravia instance only needs to be reconnected when is_connected is False and TV is not off. --- .../components/braviatv/config_flow.py | 6 ++++-- .../components/braviatv/manifest.json | 4 ++-- .../components/braviatv/media_player.py | 18 ++++++++---------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/braviatv/config_flow.py b/homeassistant/components/braviatv/config_flow.py index d7db38c5c2a..d8831dd1494 100644 --- a/homeassistant/components/braviatv/config_flow.py +++ b/homeassistant/components/braviatv/config_flow.py @@ -55,7 +55,8 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.braviarc.connect, pin, CLIENTID_PREFIX, NICKNAME ) - if not self.braviarc.is_connected(): + connected = await self.hass.async_add_executor_job(self.braviarc.is_connected) + if not connected: raise CannotConnect() system_info = await self.hass.async_add_executor_job( @@ -161,7 +162,8 @@ class BraviaTVOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): """Manage the options.""" self.braviarc = self.hass.data[DOMAIN][self.config_entry.entry_id][BRAVIARC] - if not self.braviarc.is_connected(): + connected = await self.hass.async_add_executor_job(self.braviarc.is_connected) + if not connected: await self.hass.async_add_executor_job( self.braviarc.connect, self.pin, CLIENTID_PREFIX, NICKNAME ) diff --git a/homeassistant/components/braviatv/manifest.json b/homeassistant/components/braviatv/manifest.json index 7ed09ee018d..079da14ca87 100644 --- a/homeassistant/components/braviatv/manifest.json +++ b/homeassistant/components/braviatv/manifest.json @@ -2,7 +2,7 @@ "domain": "braviatv", "name": "Sony Bravia TV", "documentation": "https://www.home-assistant.io/integrations/braviatv", - "requirements": ["bravia-tv==1.0.5"], - "codeowners": ["@robbiet480", "@bieniu"], + "requirements": ["bravia-tv==1.0.6"], + "codeowners": ["@bieniu"], "config_flow": true } diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py index f6c023481c0..32a051f4e98 100644 --- a/homeassistant/components/braviatv/media_player.py +++ b/homeassistant/components/braviatv/media_player.py @@ -148,33 +148,31 @@ class BraviaTVDevice(MediaPlayerEntity): self._device_info = device_info self._ignored_sources = ignored_sources self._state_lock = asyncio.Lock() - self._need_refresh = True async def async_update(self): """Update TV info.""" if self._state_lock.locked(): return - if self._state == STATE_OFF: - self._need_refresh = True - power_status = await self.hass.async_add_executor_job( self._braviarc.get_power_status ) - if power_status == "active": - if self._need_refresh: + + if power_status != "off": + connected = await self.hass.async_add_executor_job( + self._braviarc.is_connected + ) + if not connected: try: connected = await self.hass.async_add_executor_job( self._braviarc.connect, self._pin, CLIENTID_PREFIX, NICKNAME ) except NoIPControl: _LOGGER.error("IP Control is disabled in the TV settings") - self._need_refresh = False - else: - connected = self._braviarc.is_connected() if not connected: - return + power_status = "off" + if power_status == "active": self._state = STATE_ON if ( await self._async_refresh_volume() diff --git a/requirements_all.txt b/requirements_all.txt index 87d4c01104e..e732234787e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -374,7 +374,7 @@ bomradarloop==0.1.4 boto3==1.9.252 # homeassistant.components.braviatv -bravia-tv==1.0.5 +bravia-tv==1.0.6 # homeassistant.components.broadlink broadlink==0.14.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e5fbd63e993..e4d23975387 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -177,7 +177,7 @@ blinkpy==0.15.0 bomradarloop==0.1.4 # homeassistant.components.braviatv -bravia-tv==1.0.5 +bravia-tv==1.0.6 # homeassistant.components.broadlink broadlink==0.14.0 From 37bef01f679db4fb6721ea289890ed2c8202a772 Mon Sep 17 00:00:00 2001 From: Sean Mooney Date: Sun, 5 Jul 2020 05:28:05 -0400 Subject: [PATCH 15/43] Fix default icon for Withings sleep sensor (#37502) The icon for Withings sleep sensor was using `mdi:bed` (which is correct for MDI v5.0.45 and later). However Home Assistant still uses an older version of Material Design Icons (4.9.95), so this `mdi:bed` icon was not displaying at all. It should be`mdi:hotel` instead, which you can see here: https://cdn.materialdesignicons.com/4.9.95/ --- homeassistant/components/withings/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 5d8d5799fc1..30b67fac566 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -450,7 +450,7 @@ WITHINGS_ATTRIBUTES = [ NotifyAppli.BED_IN, "In bed", "", - "mdi:bed", + "mdi:hotel", BINARY_SENSOR_DOMAIN, True, UpdateType.WEBHOOK, From a88ac1e1fcf37fe5918448c099096c1c78d2da29 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 6 Jul 2020 19:24:22 +0200 Subject: [PATCH 16/43] Update frontend to 20200702.1 (#37566) --- 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 8100d3afd7e..f3d293377e3 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200702.0"], + "requirements": ["home-assistant-frontend==20200702.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 79cc585d735..2b7ff35ae17 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.34.7 -home-assistant-frontend==20200702.0 +home-assistant-frontend==20200702.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 netdisco==2.7.1 diff --git a/requirements_all.txt b/requirements_all.txt index e732234787e..4ad1c559626 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200702.0 +home-assistant-frontend==20200702.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e4d23975387..b564c9369e1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -343,7 +343,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200702.0 +home-assistant-frontend==20200702.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 82693d9dca02cb50af7a0c7613f96b5e78b42345 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 6 Jul 2020 16:28:26 -0500 Subject: [PATCH 17/43] Suppress spurious homekit warning about media player sources when the device is off (#37567) --- homeassistant/components/homekit/type_media_players.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 886c15a5fb9..eaf9a7c7207 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -431,7 +431,7 @@ class TelevisionMediaPlayer(HomeAccessory): index = self.sources.index(source_name) if self.char_input_source.value != index: self.char_input_source.set_value(index) - else: + elif hk_state: _LOGGER.warning( "%s: Sources out of sync. Restart Home Assistant", self.entity_id, ) From 38d2410156b599cae77e0b0a17df7a14124d8385 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 6 Jul 2020 16:28:55 -0500 Subject: [PATCH 18/43] Ensure homekit tv names can be saved (#37571) --- homeassistant/components/homekit/type_media_players.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index eaf9a7c7207..c62bd5d69d5 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -280,7 +280,6 @@ class TelevisionMediaPlayer(HomeAccessory): serv_tv = self.add_preload_service(SERV_TELEVISION, self.chars_tv) self.set_primary_service(serv_tv) - serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name) serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True) self.char_active = serv_tv.configure_char( CHAR_ACTIVE, setter_callback=self.set_on_off From 0f3b7b73af5fe7a7d1da20a2057085d889c1fce7 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 6 Jul 2020 17:16:41 -0500 Subject: [PATCH 19/43] Fix Plex client controls when connected via plex.tv resource (#37572) --- homeassistant/components/plex/server.py | 2 ++ tests/components/plex/mock_classes.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 94ba9b6950d..fdacf48f29c 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -326,6 +326,8 @@ class PlexServer: _LOGGER.debug("plex.tv resource connection successful: %s", client) except NotFound: _LOGGER.error("plex.tv resource connection failed: %s", resource.name) + else: + client.proxyThroughServer(value=False, server=self._plex_server) self._plextv_device_cache[client_id] = client return client diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index 93cb2c5bbee..3812e9c87b9 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -229,6 +229,10 @@ class MockPlexClient: """Mock the version attribute.""" return "1.0" + def proxyThroughServer(self, value=True, server=None): + """Mock the proxyThroughServer method.""" + pass + def playMedia(self, item): """Mock the playMedia method.""" pass From 3bd5d83c9cdfe11d13ef3a56af20fa6199a062e4 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 7 Jul 2020 00:16:21 +0200 Subject: [PATCH 20/43] Ignore HEOS 1, 3, 5 and 7 for DenonAvr ssdp discovery (#37579) --- .../components/denonavr/config_flow.py | 4 ++++ tests/components/denonavr/test_config_flow.py | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/homeassistant/components/denonavr/config_flow.py b/homeassistant/components/denonavr/config_flow.py index 595f958ce01..05ea21e7227 100644 --- a/homeassistant/components/denonavr/config_flow.py +++ b/homeassistant/components/denonavr/config_flow.py @@ -20,6 +20,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "denonavr" SUPPORTED_MANUFACTURERS = ["Denon", "DENON", "Marantz"] +IGNORED_MODELS = ["HEOS 1", "HEOS 3", "HEOS 5", "HEOS 7"] CONF_SHOW_ALL_SOURCES = "show_all_sources" CONF_ZONE2 = "zone2" @@ -217,6 +218,9 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.serial_number = discovery_info[ssdp.ATTR_UPNP_SERIAL] self.host = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname + if self.model_name in IGNORED_MODELS: + return self.async_abort(reason="not_denonavr_manufacturer") + unique_id = self.construct_unique_id(self.model_name, self.serial_number) await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) diff --git a/tests/components/denonavr/test_config_flow.py b/tests/components/denonavr/test_config_flow.py index d7ab51ff029..f88ac38c46c 100644 --- a/tests/components/denonavr/test_config_flow.py +++ b/tests/components/denonavr/test_config_flow.py @@ -23,6 +23,7 @@ TEST_MAC = "ab:cd:ef:gh" TEST_HOST2 = "5.6.7.8" TEST_NAME = "Test_Receiver" TEST_MODEL = "model5" +TEST_IGNORED_MODEL = "HEOS 7" TEST_RECEIVER_TYPE = "avr-x" TEST_SERIALNUMBER = "123456789" TEST_MANUFACTURER = "Denon" @@ -470,6 +471,27 @@ async def test_config_flow_ssdp_missing_info(hass): assert result["reason"] == "not_denonavr_missing" +async def test_config_flow_ssdp_ignored_model(hass): + """ + Failed flow initialized by ssdp discovery. + + Model in the ignored models list. + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data={ + ssdp.ATTR_UPNP_MANUFACTURER: TEST_MANUFACTURER, + ssdp.ATTR_UPNP_MODEL_NAME: TEST_IGNORED_MODEL, + ssdp.ATTR_UPNP_SERIAL: TEST_SERIALNUMBER, + ssdp.ATTR_SSDP_LOCATION: TEST_SSDP_LOCATION, + }, + ) + + assert result["type"] == "abort" + assert result["reason"] == "not_denonavr_manufacturer" + + async def test_options_flow(hass): """Test specifying non default settings using options flow.""" config_entry = MockConfigEntry( From 7ba9bcebeda7ed380bc29e7fcc2a60ad68a8c7b4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 6 Jul 2020 22:35:38 +0000 Subject: [PATCH 21/43] Fix CODEOWNERS --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index d860a0f57ed..f29b66f1233 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -59,7 +59,7 @@ homeassistant/components/blink/* @fronzbot homeassistant/components/bmp280/* @belidzs homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe homeassistant/components/bom/* @maddenp -homeassistant/components/braviatv/* @robbiet480 @bieniu +homeassistant/components/braviatv/* @bieniu homeassistant/components/broadlink/* @danielhiversen @felipediel homeassistant/components/brother/* @bieniu homeassistant/components/brunt/* @eavanvalkenburg From e5a2b0d2d86bba7a422ecd1217d8c21940989e9c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 6 Jul 2020 22:36:05 +0000 Subject: [PATCH 22/43] Bumped version to 0.112.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 8e5ff9c941d..13cf3b14a14 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -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 af87168ca3289f60577bd2dcb9a6102287c1e1a1 Mon Sep 17 00:00:00 2001 From: Sava Tshontikidis <45082385+stshontikidis@users.noreply.github.com> Date: Wed, 8 Jul 2020 17:25:21 -0400 Subject: [PATCH 23/43] Add kwargs to send_magic_packet() service individually (#37387) --- .../components/wake_on_lan/__init__.py | 22 ++--- .../components/wake_on_lan/switch.py | 22 +++-- tests/components/wake_on_lan/test_init.py | 26 +++++- tests/components/wake_on_lan/test_switch.py | 86 ++++++++++++++++--- 4 files changed, 124 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/wake_on_lan/__init__.py b/homeassistant/components/wake_on_lan/__init__.py index 1600f70a15f..8262c461ec5 100644 --- a/homeassistant/components/wake_on_lan/__init__.py +++ b/homeassistant/components/wake_on_lan/__init__.py @@ -31,23 +31,23 @@ async def async_setup(hass, config): mac_address = call.data.get(CONF_MAC) broadcast_address = call.data.get(CONF_BROADCAST_ADDRESS) broadcast_port = call.data.get(CONF_BROADCAST_PORT) + + service_kwargs = {} + if broadcast_address is not None: + service_kwargs["ip_address"] = broadcast_address + if broadcast_port is not None: + service_kwargs["port"] = broadcast_port + _LOGGER.info( "Send magic packet to mac %s (broadcast: %s, port: %s)", mac_address, broadcast_address, broadcast_port, ) - if broadcast_address is not None: - await hass.async_add_job( - partial( - wakeonlan.send_magic_packet, - mac_address, - ip_address=broadcast_address, - port=broadcast_port, - ) - ) - else: - await hass.async_add_job(partial(wakeonlan.send_magic_packet, mac_address)) + + await hass.async_add_job( + partial(wakeonlan.send_magic_packet, mac_address, **service_kwargs) + ) hass.services.async_register( DOMAIN, diff --git a/homeassistant/components/wake_on_lan/switch.py b/homeassistant/components/wake_on_lan/switch.py index 8f26e19d2e0..dc6ea358acd 100644 --- a/homeassistant/components/wake_on_lan/switch.py +++ b/homeassistant/components/wake_on_lan/switch.py @@ -96,14 +96,20 @@ class WolSwitch(SwitchEntity): def turn_on(self, **kwargs): """Turn the device on.""" - if self._broadcast_address: - wakeonlan.send_magic_packet( - self._mac_address, - ip_address=self._broadcast_address, - port=self._broadcast_port, - ) - else: - wakeonlan.send_magic_packet(self._mac_address) + service_kwargs = {} + if self._broadcast_address is not None: + service_kwargs["ip_address"] = self._broadcast_address + if self._broadcast_port is not None: + service_kwargs["port"] = self._broadcast_port + + _LOGGER.info( + "Send magic packet to mac %s (broadcast: %s, port: %s)", + self._mac_address, + self._broadcast_address, + self._broadcast_port, + ) + + wakeonlan.send_magic_packet(self._mac_address, **service_kwargs) def turn_off(self, **kwargs): """Turn the device off if an off action is present.""" diff --git a/tests/components/wake_on_lan/test_init.py b/tests/components/wake_on_lan/test_init.py index 331f66b03d8..60a725a7f9e 100644 --- a/tests/components/wake_on_lan/test_init.py +++ b/tests/components/wake_on_lan/test_init.py @@ -28,6 +28,28 @@ async def test_send_magic_packet(hass): assert mocked_wakeonlan.mock_calls[-1][2]["ip_address"] == bc_ip assert mocked_wakeonlan.mock_calls[-1][2]["port"] == bc_port + await hass.services.async_call( + DOMAIN, + SERVICE_SEND_MAGIC_PACKET, + {"mac": mac, "broadcast_address": bc_ip}, + blocking=True, + ) + assert len(mocked_wakeonlan.mock_calls) == 2 + assert mocked_wakeonlan.mock_calls[-1][1][0] == mac + assert mocked_wakeonlan.mock_calls[-1][2]["ip_address"] == bc_ip + assert "port" not in mocked_wakeonlan.mock_calls[-1][2] + + await hass.services.async_call( + DOMAIN, + SERVICE_SEND_MAGIC_PACKET, + {"mac": mac, "broadcast_port": bc_port}, + blocking=True, + ) + assert len(mocked_wakeonlan.mock_calls) == 3 + assert mocked_wakeonlan.mock_calls[-1][1][0] == mac + assert mocked_wakeonlan.mock_calls[-1][2]["port"] == bc_port + assert "ip_address" not in mocked_wakeonlan.mock_calls[-1][2] + with pytest.raises(vol.Invalid): await hass.services.async_call( DOMAIN, @@ -35,11 +57,11 @@ async def test_send_magic_packet(hass): {"broadcast_address": bc_ip}, blocking=True, ) - assert len(mocked_wakeonlan.mock_calls) == 1 + assert len(mocked_wakeonlan.mock_calls) == 3 await hass.services.async_call( DOMAIN, SERVICE_SEND_MAGIC_PACKET, {"mac": mac}, blocking=True ) - assert len(mocked_wakeonlan.mock_calls) == 2 + assert len(mocked_wakeonlan.mock_calls) == 4 assert mocked_wakeonlan.mock_calls[-1][1][0] == mac assert not mocked_wakeonlan.mock_calls[-1][2] diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index ce5dbca9585..b6747062ce5 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -5,18 +5,13 @@ import homeassistant.components.switch as switch from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.setup import setup_component -from tests.async_mock import patch +from tests.async_mock import Mock, patch from tests.common import get_test_home_assistant, mock_service from tests.components.switch import common TEST_STATE = None -def send_magic_packet(*macs, **kwargs): - """Fake call for sending magic packets.""" - return - - def call(cmd, stdout, stderr): """Return fake subprocess return codes.""" if cmd[5] == "validhostname" and TEST_STATE: @@ -32,6 +27,8 @@ def system(): class TestWolSwitch(unittest.TestCase): """Test the wol switch.""" + send_magic_packet = Mock(return_value=None) + def setUp(self): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() @@ -116,17 +113,22 @@ class TestWolSwitch(unittest.TestCase): @patch("wakeonlan.send_magic_packet", new=send_magic_packet) @patch("subprocess.call", new=call) - def test_broadcast_config(self): - """Test with broadcast address config.""" + def test_broadcast_config_ip_and_port(self): + """Test with broadcast address and broadcast port config.""" + + mac = "00-01-02-03-04-05" + broadcast_address = "255.255.255.255" + port = 999 + assert setup_component( self.hass, switch.DOMAIN, { "switch": { "platform": "wake_on_lan", - "mac": "00-01-02-03-04-05", - "broadcast_address": "255.255.255.255", - "broadcast_port": 999, + "mac": mac, + "broadcast_address": broadcast_address, + "broadcast_port": port, } }, ) @@ -138,6 +140,68 @@ class TestWolSwitch(unittest.TestCase): common.turn_on(self.hass, "switch.wake_on_lan") self.hass.block_till_done() + self.send_magic_packet.assert_called_with( + mac, ip_address=broadcast_address, port=port + ) + + @patch("wakeonlan.send_magic_packet", new=send_magic_packet) + @patch("subprocess.call", new=call) + def test_broadcast_config_ip(self): + """Test with only broadcast address.""" + + mac = "00-01-02-03-04-05" + broadcast_address = "255.255.255.255" + + assert setup_component( + self.hass, + switch.DOMAIN, + { + "switch": { + "platform": "wake_on_lan", + "mac": mac, + "broadcast_address": broadcast_address, + } + }, + ) + self.hass.block_till_done() + + state = self.hass.states.get("switch.wake_on_lan") + assert STATE_OFF == state.state + + common.turn_on(self.hass, "switch.wake_on_lan") + self.hass.block_till_done() + + self.send_magic_packet.assert_called_with(mac, ip_address=broadcast_address) + + @patch("wakeonlan.send_magic_packet", new=send_magic_packet) + @patch("subprocess.call", new=call) + def test_broadcast_config_port(self): + """Test with only broadcast port config.""" + + mac = "00-01-02-03-04-05" + port = 999 + + assert setup_component( + self.hass, + switch.DOMAIN, + { + "switch": { + "platform": "wake_on_lan", + "mac": mac, + "broadcast_port": port, + } + }, + ) + self.hass.block_till_done() + + state = self.hass.states.get("switch.wake_on_lan") + assert STATE_OFF == state.state + + common.turn_on(self.hass, "switch.wake_on_lan") + self.hass.block_till_done() + + self.send_magic_packet.assert_called_with(mac, port=port) + @patch("wakeonlan.send_magic_packet", new=send_magic_packet) @patch("subprocess.call", new=call) def test_off_script(self): From aac65416ffa2aa4e7e7b38dc08919f18f3ce04a7 Mon Sep 17 00:00:00 2001 From: therealryanbonham <33603723+therealryanbonham@users.noreply.github.com> Date: Fri, 3 Jul 2020 07:59:30 -0400 Subject: [PATCH 24/43] Upgrade sonar to 0.2.3 (#37392) Co-authored-by: Ryan Bonham --- homeassistant/components/sonarr/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json index c1edb8ec521..3cd6e88913b 100644 --- a/homeassistant/components/sonarr/manifest.json +++ b/homeassistant/components/sonarr/manifest.json @@ -3,7 +3,7 @@ "name": "Sonarr", "documentation": "https://www.home-assistant.io/integrations/sonarr", "codeowners": ["@ctalkington"], - "requirements": ["sonarr==0.2.2"], + "requirements": ["sonarr==0.2.3"], "config_flow": true, "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index 4ad1c559626..48519dd44c9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2003,7 +2003,7 @@ somecomfort==0.5.2 somfy-mylink-synergy==1.0.6 # homeassistant.components.sonarr -sonarr==0.2.2 +sonarr==0.2.3 # homeassistant.components.marytts speak2mary==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b564c9369e1..629d48acad0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -853,7 +853,7 @@ solaredge==0.0.2 somecomfort==0.5.2 # homeassistant.components.sonarr -sonarr==0.2.2 +sonarr==0.2.3 # homeassistant.components.marytts speak2mary==1.4.0 From a30b5690f84fad6154f07f3ccf6f454ee17e3053 Mon Sep 17 00:00:00 2001 From: bsmappee <58250533+bsmappee@users.noreply.github.com> Date: Tue, 7 Jul 2020 16:15:52 +0200 Subject: [PATCH 25/43] Place smappee supported configurations after device class (#37595) --- homeassistant/components/smappee/sensor.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index 77578533749..61f7af05690 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -88,48 +88,48 @@ VOLTAGE_SENSORS = { "mdi:flash", VOLT, "phase_voltage_a", - ["ONE", "TWO", "THREE_STAR", "THREE_DELTA"], None, + ["ONE", "TWO", "THREE_STAR", "THREE_DELTA"], ], "phase_voltages_b": [ "Phase voltages - B", "mdi:flash", VOLT, "phase_voltage_b", - ["TWO", "THREE_STAR", "THREE_DELTA"], None, + ["TWO", "THREE_STAR", "THREE_DELTA"], ], "phase_voltages_c": [ "Phase voltages - C", "mdi:flash", VOLT, "phase_voltage_c", - ["THREE_STAR"], None, + ["THREE_STAR"], ], "line_voltages_a": [ "Line voltages - A", "mdi:flash", VOLT, "line_voltage_a", - ["ONE", "TWO", "THREE_STAR", "THREE_DELTA"], None, + ["ONE", "TWO", "THREE_STAR", "THREE_DELTA"], ], "line_voltages_b": [ "Line voltages - B", "mdi:flash", VOLT, "line_voltage_b", - ["TWO", "THREE_STAR", "THREE_DELTA"], None, + ["TWO", "THREE_STAR", "THREE_DELTA"], ], "line_voltages_c": [ "Line voltages - C", "mdi:flash", VOLT, "line_voltage_c", - ["THREE_STAR", "THREE_DELTA"], None, + ["THREE_STAR", "THREE_DELTA"], ], } @@ -182,7 +182,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # Add phase- and line voltages for sensor_name, sensor in VOLTAGE_SENSORS.items(): - if service_location.phase_type in sensor[4]: + if service_location.phase_type in sensor[5]: entities.append( SmappeeSensor( smappee_base=smappee_base, From cdb283319df4c6f3c275bd84664db83aabe73fcc Mon Sep 17 00:00:00 2001 From: bsmappee <58250533+bsmappee@users.noreply.github.com> Date: Wed, 8 Jul 2020 22:45:01 +0200 Subject: [PATCH 26/43] Update Smappee integration with proper solar, voltage and reactive entities (#37407) --- .../components/smappee/manifest.json | 2 +- homeassistant/components/smappee/sensor.py | 48 ++++++++++++------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/smappee/manifest.json b/homeassistant/components/smappee/manifest.json index 3ca8dc4b1b6..1c760376e04 100644 --- a/homeassistant/components/smappee/manifest.json +++ b/homeassistant/components/smappee/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/smappee", "dependencies": ["http"], "requirements": [ - "pysmappee==0.1.2" + "pysmappee==0.1.4" ], "codeowners": [ "@bsmappee" diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index 61f7af05690..1b0b5af8564 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -16,13 +16,6 @@ TREND_SENSORS = { "total_power", DEVICE_CLASS_POWER, ], - "total_reactive_power": [ - "Total consumption - Reactive power", - None, - POWER_WATT, - "total_reactive_power", - DEVICE_CLASS_POWER, - ], "alwayson": [ "Always on - Active power", None, @@ -59,6 +52,15 @@ TREND_SENSORS = { None, ], } +REACTIVE_SENSORS = { + "total_reactive_power": [ + "Total consumption - Reactive power", + None, + POWER_WATT, + "total_reactive_power", + DEVICE_CLASS_POWER, + ] +} SOLAR_SENSORS = { "solar_power": [ "Total production - Active power", @@ -151,6 +153,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) ) + if service_location.has_reactive_value: + for reactive_sensor in REACTIVE_SENSORS: + entities.append( + SmappeeSensor( + smappee_base=smappee_base, + service_location=service_location, + sensor=reactive_sensor, + attributes=REACTIVE_SENSORS[reactive_sensor], + ) + ) + # Add solar sensors if service_location.has_solar_production: for sensor in SOLAR_SENSORS: @@ -180,17 +193,18 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) ) - # Add phase- and line voltages - for sensor_name, sensor in VOLTAGE_SENSORS.items(): - if service_location.phase_type in sensor[5]: - entities.append( - SmappeeSensor( - smappee_base=smappee_base, - service_location=service_location, - sensor=sensor_name, - attributes=sensor, + # Add phase- and line voltages if available + if service_location.has_voltage_values: + for sensor_name, sensor in VOLTAGE_SENSORS.items(): + if service_location.phase_type in sensor[5]: + entities.append( + SmappeeSensor( + smappee_base=smappee_base, + service_location=service_location, + sensor=sensor_name, + attributes=sensor, + ) ) - ) # Add Gas and Water sensors for sensor_id, sensor in service_location.sensors.items(): diff --git a/requirements_all.txt b/requirements_all.txt index 48519dd44c9..efb3e95819c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1610,7 +1610,7 @@ pysignalclirestapi==0.3.4 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.1.2 +pysmappee==0.1.4 # homeassistant.components.smartthings pysmartapp==0.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 629d48acad0..ad1da1a1fc2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -715,7 +715,7 @@ pysignalclirestapi==0.3.4 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.1.2 +pysmappee==0.1.4 # homeassistant.components.smartthings pysmartapp==0.3.2 From 8f77cc2f7b0d17c0481554855a6b3ae23c070fc3 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 7 Jul 2020 16:28:20 +0200 Subject: [PATCH 27/43] Fix DenonAvr discovery of Denon DN-500AV (#37529) * fix discovery of Denon DN-500AV * fix discovery of Denon DN-500AV * fix discovery of Denon DN-500AV * fix typo * bump denonavr to 0.9.4 * add ignored model test * fix mistake in branch --- homeassistant/components/denonavr/config_flow.py | 2 +- homeassistant/components/denonavr/manifest.json | 14 +++++++++++++- homeassistant/generated/ssdp.py | 12 ++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/denonavr/config_flow.py b/homeassistant/components/denonavr/config_flow.py index 05ea21e7227..af2b7b03103 100644 --- a/homeassistant/components/denonavr/config_flow.py +++ b/homeassistant/components/denonavr/config_flow.py @@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "denonavr" -SUPPORTED_MANUFACTURERS = ["Denon", "DENON", "Marantz"] +SUPPORTED_MANUFACTURERS = ["Denon", "DENON", "DENON PROFESSIONAL", "Marantz"] IGNORED_MODELS = ["HEOS 1", "HEOS 3", "HEOS 5", "HEOS 7"] CONF_SHOW_ALL_SOURCES = "show_all_sources" diff --git a/homeassistant/components/denonavr/manifest.json b/homeassistant/components/denonavr/manifest.json index 4ea844ef060..afc5a7ef72f 100644 --- a/homeassistant/components/denonavr/manifest.json +++ b/homeassistant/components/denonavr/manifest.json @@ -3,7 +3,7 @@ "name": "Denon AVR Network Receivers", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/denonavr", - "requirements": ["denonavr==0.9.3", "getmac==0.8.2"], + "requirements": ["denonavr==0.9.4", "getmac==0.8.2"], "codeowners": ["@scarface-4711", "@starkillerOG"], "ssdp": [ { @@ -14,6 +14,10 @@ "manufacturer": "DENON", "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1" }, + { + "manufacturer": "DENON PROFESSIONAL", + "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1" + }, { "manufacturer": "Marantz", "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1" @@ -26,6 +30,10 @@ "manufacturer": "DENON", "deviceType": "urn:schemas-upnp-org:device:MediaServer:1" }, + { + "manufacturer": "DENON PROFESSIONAL", + "deviceType": "urn:schemas-upnp-org:device:MediaServer:1" + }, { "manufacturer": "Marantz", "deviceType": "urn:schemas-upnp-org:device:MediaServer:1" @@ -38,6 +46,10 @@ "manufacturer": "DENON", "deviceType": "urn:schemas-denon-com:device:AiosDevice:1" }, + { + "manufacturer": "DENON PROFESSIONAL", + "deviceType": "urn:schemas-denon-com:device:AiosDevice:1" + }, { "manufacturer": "Marantz", "deviceType": "urn:schemas-denon-com:device:AiosDevice:1" diff --git a/homeassistant/generated/ssdp.py b/homeassistant/generated/ssdp.py index 1cbade276fe..0bffc716aea 100644 --- a/homeassistant/generated/ssdp.py +++ b/homeassistant/generated/ssdp.py @@ -26,6 +26,10 @@ SSDP = { "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1", "manufacturer": "DENON" }, + { + "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1", + "manufacturer": "DENON PROFESSIONAL" + }, { "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1", "manufacturer": "Marantz" @@ -38,6 +42,10 @@ SSDP = { "deviceType": "urn:schemas-upnp-org:device:MediaServer:1", "manufacturer": "DENON" }, + { + "deviceType": "urn:schemas-upnp-org:device:MediaServer:1", + "manufacturer": "DENON PROFESSIONAL" + }, { "deviceType": "urn:schemas-upnp-org:device:MediaServer:1", "manufacturer": "Marantz" @@ -50,6 +58,10 @@ SSDP = { "deviceType": "urn:schemas-denon-com:device:AiosDevice:1", "manufacturer": "DENON" }, + { + "deviceType": "urn:schemas-denon-com:device:AiosDevice:1", + "manufacturer": "DENON PROFESSIONAL" + }, { "deviceType": "urn:schemas-denon-com:device:AiosDevice:1", "manufacturer": "Marantz" diff --git a/requirements_all.txt b/requirements_all.txt index efb3e95819c..16db1c09ac4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -475,7 +475,7 @@ defusedxml==0.6.0 deluge-client==1.7.1 # homeassistant.components.denonavr -denonavr==0.9.3 +denonavr==0.9.4 # homeassistant.components.devolo_home_control devolo-home-control-api==0.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ad1da1a1fc2..58d74766d19 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -227,7 +227,7 @@ debugpy==1.0.0b11 defusedxml==0.6.0 # homeassistant.components.denonavr -denonavr==0.9.3 +denonavr==0.9.4 # homeassistant.components.devolo_home_control devolo-home-control-api==0.11.0 From 3b56e75e1da734a8bdafaa5694a5cc70c29803a1 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 7 Jul 2020 09:09:13 +0200 Subject: [PATCH 28/43] Bump backend library for Dune HD integration (#37594) --- homeassistant/components/dunehd/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/dunehd/manifest.json b/homeassistant/components/dunehd/manifest.json index 61972b1b4d5..96a497f1f96 100644 --- a/homeassistant/components/dunehd/manifest.json +++ b/homeassistant/components/dunehd/manifest.json @@ -2,7 +2,7 @@ "domain": "dunehd", "name": "Dune HD", "documentation": "https://www.home-assistant.io/integrations/dunehd", - "requirements": ["pdunehd==1.3.1"], + "requirements": ["pdunehd==1.3.2"], "codeowners": ["@bieniu"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 16db1c09ac4..10f28c19c99 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1050,7 +1050,7 @@ panasonic_viera==0.3.5 pcal9535a==0.7 # homeassistant.components.dunehd -pdunehd==1.3.1 +pdunehd==1.3.2 # homeassistant.components.pencom pencompy==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 58d74766d19..e12244eac93 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -461,7 +461,7 @@ paho-mqtt==1.5.0 panasonic_viera==0.3.5 # homeassistant.components.dunehd -pdunehd==1.3.1 +pdunehd==1.3.2 # homeassistant.components.aruba # homeassistant.components.cisco_ios From 3ede54def9827a01abfe5bcaaf3eac699d8d0591 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 7 Jul 2020 16:30:20 +0200 Subject: [PATCH 29/43] Bump aiokef to v0.2.13 which fixes the device from becoming "Unavailable" (#37607) --- homeassistant/components/kef/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/kef/manifest.json b/homeassistant/components/kef/manifest.json index 586629765f6..496d7bae0f2 100644 --- a/homeassistant/components/kef/manifest.json +++ b/homeassistant/components/kef/manifest.json @@ -3,5 +3,5 @@ "name": "KEF", "documentation": "https://www.home-assistant.io/integrations/kef", "codeowners": ["@basnijholt"], - "requirements": ["aiokef==0.2.12", "getmac==0.8.2"] + "requirements": ["aiokef==0.2.13", "getmac==0.8.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 10f28c19c99..37d159549be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -200,7 +200,7 @@ aioimaplib==0.7.15 aiokafka==0.5.1 # homeassistant.components.kef -aiokef==0.2.12 +aiokef==0.2.13 # homeassistant.components.lifx aiolifx==0.6.7 From 2da64b03f3e8435592aefb31f3c21ddd485a0d97 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Tue, 7 Jul 2020 10:17:04 -0700 Subject: [PATCH 30/43] Remove withings use of deprecated classes (#37611) Adjusting in bed device class to occupancy. --- homeassistant/components/withings/binary_sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/withings/binary_sensor.py b/homeassistant/components/withings/binary_sensor.py index 0fb8d8411fd..136a12a0e1d 100644 --- a/homeassistant/components/withings/binary_sensor.py +++ b/homeassistant/components/withings/binary_sensor.py @@ -2,9 +2,9 @@ from typing import Callable, List from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_PRESENCE, + DEVICE_CLASS_OCCUPANCY, DOMAIN as BINARY_SENSOR_DOMAIN, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -26,7 +26,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class WithingsHealthBinarySensor(BaseWithingsSensor, BinarySensorDevice): +class WithingsHealthBinarySensor(BaseWithingsSensor, BinarySensorEntity): """Implementation of a Withings sensor.""" @property @@ -37,4 +37,4 @@ class WithingsHealthBinarySensor(BaseWithingsSensor, BinarySensorDevice): @property def device_class(self) -> str: """Provide the device class.""" - return DEVICE_CLASS_PRESENCE + return DEVICE_CLASS_OCCUPANCY From 9be06d8be4999d7d504282d8c39a797315bf3204 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Jul 2020 08:57:07 -0500 Subject: [PATCH 31/43] Increase slow setup logging to warning level (#37635) When I fixed verbose logging in #36444, I did not realize this would mean almost nobody would see this now. --- homeassistant/bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 94ec33f4e1a..952991228c9 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -351,7 +351,7 @@ async def _async_log_pending_setups( remaining = [domain for domain in domains if domain in setup_started] if remaining: - _LOGGER.info( + _LOGGER.warning( "Waiting on integrations to complete setup: %s", ", ".join(remaining), ) From 81af0e2adac75120d50fdb7d8aa87fc31a952def Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 9 Jul 2020 00:18:38 +0000 Subject: [PATCH 32/43] Bumped version to 0.112.4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 13cf3b14a14..47f271789e2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -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 39d5fb82e5289f6c4e95d01d77c964987d84cad1 Mon Sep 17 00:00:00 2001 From: bsmappee <58250533+bsmappee@users.noreply.github.com> Date: Thu, 9 Jul 2020 13:03:23 +0200 Subject: [PATCH 33/43] Smappee dependency update (#37680) --- homeassistant/components/smappee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smappee/manifest.json b/homeassistant/components/smappee/manifest.json index 1c760376e04..4f9a33b74da 100644 --- a/homeassistant/components/smappee/manifest.json +++ b/homeassistant/components/smappee/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/smappee", "dependencies": ["http"], "requirements": [ - "pysmappee==0.1.4" + "pysmappee==0.1.5" ], "codeowners": [ "@bsmappee" diff --git a/requirements_all.txt b/requirements_all.txt index 37d159549be..bfd590f3171 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1610,7 +1610,7 @@ pysignalclirestapi==0.3.4 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.1.4 +pysmappee==0.1.5 # homeassistant.components.smartthings pysmartapp==0.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e12244eac93..1687252fe27 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -715,7 +715,7 @@ pysignalclirestapi==0.3.4 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.1.4 +pysmappee==0.1.5 # homeassistant.components.smartthings pysmartapp==0.3.2 From 0d58048cea3f835f53b8dbf7019ca3bee48b8909 Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Mon, 13 Jul 2020 22:32:22 +0300 Subject: [PATCH 34/43] Properly set update_interval during Speedtest setup (#37708) * Properly set update_interval during setup * implement new update_interval method --- .../components/speedtestdotnet/__init__.py | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 1b50516f340..2ef49877031 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -105,16 +105,14 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator): self.api = None self.servers = {} super().__init__( - self.hass, - _LOGGER, - name=DOMAIN, - update_method=self.async_update, - update_interval=timedelta( + self.hass, _LOGGER, name=DOMAIN, update_method=self.async_update, + ) + if not self.config_entry.options.get(CONF_MANUAL): + self.update_interval = timedelta( minutes=self.config_entry.options.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL ) - ), - ) + ) def update_servers(self): """Update list of test servers.""" @@ -189,12 +187,11 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator): async def options_updated_listener(hass, entry): """Handle options update.""" - if not entry.options[CONF_MANUAL]: - hass.data[DOMAIN].update_interval = timedelta( - minutes=entry.options[CONF_SCAN_INTERVAL] - ) - await hass.data[DOMAIN].async_request_refresh() + if entry.options[CONF_MANUAL]: + hass.data[DOMAIN].update_interval = None return - # set the update interval to a very long time - # if the user wants to disable auto update - hass.data[DOMAIN].update_interval = timedelta(days=7) + + hass.data[DOMAIN].update_interval = timedelta( + minutes=entry.options[CONF_SCAN_INTERVAL] + ) + await hass.data[DOMAIN].async_request_refresh() From 5d26f5d01d2820fce9ab2be0637060e7fbbc3c66 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 4 Jul 2020 17:48:34 +0200 Subject: [PATCH 35/43] Support multiple MQTT availability topics (#37418) * Support multiple MQTT availability topics * Make availability list and availability_topic exclusive * Make availability list and availability_topic exclusive * Add missing abbreviation --- homeassistant/components/mqtt/__init__.py | 79 ++++++-- .../components/mqtt/abbreviations.py | 1 + tests/components/mqtt/test_common.py | 174 ++++++++++++++++++ tests/components/mqtt/test_sensor.py | 24 +++ 4 files changed, 260 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 5f82fa76dfa..b743626ebbb 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -88,6 +88,8 @@ CONF_TLS_INSECURE = "tls_insecure" CONF_TLS_VERSION = "tls_version" CONF_COMMAND_TOPIC = "command_topic" +CONF_TOPIC = "topic" +CONF_AVAILABILITY = "availability" CONF_AVAILABILITY_TOPIC = "availability_topic" CONF_PAYLOAD_AVAILABLE = "payload_available" CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available" @@ -203,9 +205,9 @@ CONFIG_SCHEMA = vol.Schema( SCHEMA_BASE = {vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA} -MQTT_AVAILABILITY_SCHEMA = vol.Schema( +MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema( { - vol.Optional(CONF_AVAILABILITY_TOPIC): valid_subscribe_topic, + vol.Exclusive(CONF_AVAILABILITY_TOPIC, "availability"): valid_subscribe_topic, vol.Optional( CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE ): cv.string, @@ -215,6 +217,30 @@ MQTT_AVAILABILITY_SCHEMA = vol.Schema( } ) +MQTT_AVAILABILITY_LIST_SCHEMA = vol.Schema( + { + vol.Exclusive(CONF_AVAILABILITY, "availability"): vol.All( + cv.ensure_list, + [ + { + vol.Optional(CONF_TOPIC): valid_subscribe_topic, + vol.Optional( + CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE + ): cv.string, + vol.Optional( + CONF_PAYLOAD_NOT_AVAILABLE, + default=DEFAULT_PAYLOAD_NOT_AVAILABLE, + ): cv.string, + } + ], + ), + } +) + +MQTT_AVAILABILITY_SCHEMA = MQTT_AVAILABILITY_SINGLE_SCHEMA.extend( + MQTT_AVAILABILITY_LIST_SCHEMA.schema +) + MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All( cv.deprecated(CONF_DEPRECATED_VIA_HUB, CONF_VIA_DEVICE), vol.Schema( @@ -989,8 +1015,7 @@ class MqttAvailability(Entity): """Initialize the availability mixin.""" self._availability_sub_state = None self._available = False - - self._avail_config = config + self._availability_setup_from_config(config) async def async_added_to_hass(self) -> None: """Subscribe MQTT events.""" @@ -1004,9 +1029,27 @@ class MqttAvailability(Entity): async def availability_discovery_update(self, config: dict): """Handle updated discovery message.""" - self._avail_config = config + self._availability_setup_from_config(config) await self._availability_subscribe_topics() + def _availability_setup_from_config(self, config): + """(Re)Setup.""" + self._avail_topics = {} + if CONF_AVAILABILITY_TOPIC in config: + self._avail_topics[config[CONF_AVAILABILITY_TOPIC]] = { + CONF_PAYLOAD_AVAILABLE: config[CONF_PAYLOAD_AVAILABLE], + CONF_PAYLOAD_NOT_AVAILABLE: config[CONF_PAYLOAD_NOT_AVAILABLE], + } + + if CONF_AVAILABILITY in config: + for avail in config[CONF_AVAILABILITY]: + self._avail_topics[avail[CONF_TOPIC]] = { + CONF_PAYLOAD_AVAILABLE: avail[CONF_PAYLOAD_AVAILABLE], + CONF_PAYLOAD_NOT_AVAILABLE: avail[CONF_PAYLOAD_NOT_AVAILABLE], + } + + self._avail_config = config + async def _availability_subscribe_topics(self): """(Re)Subscribe to topics.""" @@ -1014,23 +1057,24 @@ class MqttAvailability(Entity): @log_messages(self.hass, self.entity_id) def availability_message_received(msg: Message) -> None: """Handle a new received MQTT availability message.""" - if msg.payload == self._avail_config[CONF_PAYLOAD_AVAILABLE]: + topic = msg.topic + if msg.payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]: self._available = True - elif msg.payload == self._avail_config[CONF_PAYLOAD_NOT_AVAILABLE]: + elif msg.payload == self._avail_topics[topic][CONF_PAYLOAD_NOT_AVAILABLE]: self._available = False self.async_write_ha_state() + topics = {} + for topic in self._avail_topics: + topics[f"availability_{topic}"] = { + "topic": topic, + "msg_callback": availability_message_received, + "qos": self._avail_config[CONF_QOS], + } + self._availability_sub_state = await async_subscribe_topics( - self.hass, - self._availability_sub_state, - { - "availability_topic": { - "topic": self._avail_config.get(CONF_AVAILABILITY_TOPIC), - "msg_callback": availability_message_received, - "qos": self._avail_config[CONF_QOS], - } - }, + self.hass, self._availability_sub_state, topics, ) @callback @@ -1048,10 +1092,9 @@ class MqttAvailability(Entity): @property def available(self) -> bool: """Return if the device is available.""" - availability_topic = self._avail_config.get(CONF_AVAILABILITY_TOPIC) if not self.hass.data[DATA_MQTT].connected: return False - return availability_topic is None or self._available + return not self._avail_topics or self._available async def cleanup_device_registry(hass, device_id): diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 2ec0ea0d203..c3f6b55e0fe 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -7,6 +7,7 @@ ABBREVIATIONS = { "aux_cmd_t": "aux_command_topic", "aux_stat_tpl": "aux_state_template", "aux_stat_t": "aux_state_topic", + "avty": "availability", "avty_t": "availability_topic", "away_mode_cmd_t": "away_mode_command_topic", "away_mode_stat_tpl": "away_mode_state_template", diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 4275cc36e13..31566885a37 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -104,6 +104,98 @@ async def help_test_default_availability_payload( assert state.state != STATE_UNAVAILABLE +async def help_test_default_availability_list_payload( + hass, + mqtt_mock, + domain, + config, + no_assumed_state=False, + state_topic=None, + state_message=None, +): + """Test availability by default payload with defined topic. + + This is a test helper for the MqttAvailability mixin. + """ + # Add availability settings to config + config = copy.deepcopy(config) + config[domain]["availability"] = [ + {"topic": "availability-topic1"}, + {"topic": "availability-topic2"}, + ] + assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() + + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + async_fire_mqtt_message(hass, "availability-topic1", "online") + + state = hass.states.get(f"{domain}.test") + assert state.state != STATE_UNAVAILABLE + if no_assumed_state: + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "availability-topic1", "offline") + + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + async_fire_mqtt_message(hass, "availability-topic2", "online") + + state = hass.states.get(f"{domain}.test") + assert state.state != STATE_UNAVAILABLE + if no_assumed_state: + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "availability-topic2", "offline") + + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + if state_topic: + async_fire_mqtt_message(hass, state_topic, state_message) + + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + async_fire_mqtt_message(hass, "availability-topic1", "online") + + state = hass.states.get(f"{domain}.test") + assert state.state != STATE_UNAVAILABLE + + +async def help_test_default_availability_list_single( + hass, + mqtt_mock, + caplog, + domain, + config, + no_assumed_state=False, + state_topic=None, + state_message=None, +): + """Test availability list and availability_topic are mutually exclusive. + + This is a test helper for the MqttAvailability mixin. + """ + # Add availability settings to config + config = copy.deepcopy(config) + config[domain]["availability"] = [ + {"topic": "availability-topic1"}, + ] + config[domain]["availability_topic"] = "availability-topic" + assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() + + state = hass.states.get(f"{domain}.test") + assert state is None + assert ( + "Invalid config for [sensor.mqtt]: two or more values in the same group of exclusion 'availability'" + in caplog.text + ) + + async def help_test_custom_availability_payload( hass, mqtt_mock, @@ -152,6 +244,88 @@ async def help_test_custom_availability_payload( assert state.state != STATE_UNAVAILABLE +async def help_test_discovery_update_availability( + hass, + mqtt_mock, + domain, + config, + no_assumed_state=False, + state_topic=None, + state_message=None, +): + """Test update of discovered MQTTAvailability. + + This is a test helper for the MQTTAvailability mixin. + """ + # Add availability settings to config + config1 = copy.deepcopy(config) + config1[domain]["availability_topic"] = "availability-topic1" + config2 = copy.deepcopy(config) + config2[domain]["availability"] = [ + {"topic": "availability-topic2"}, + {"topic": "availability-topic3"}, + ] + config3 = copy.deepcopy(config) + config3[domain]["availability_topic"] = "availability-topic4" + data1 = json.dumps(config1[domain]) + data2 = json.dumps(config2[domain]) + data3 = json.dumps(config3[domain]) + + entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] + await async_start(hass, "homeassistant", entry) + async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data1) + await hass.async_block_till_done() + + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + async_fire_mqtt_message(hass, "availability-topic1", "online") + state = hass.states.get(f"{domain}.test") + assert state.state != STATE_UNAVAILABLE + + async_fire_mqtt_message(hass, "availability-topic1", "offline") + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + # Change availability_topic + async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data2) + await hass.async_block_till_done() + + # Verify we are no longer subscribing to the old topic + async_fire_mqtt_message(hass, "availability-topic1", "online") + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + # Verify we are subscribing to the new topic + async_fire_mqtt_message(hass, "availability-topic2", "online") + state = hass.states.get(f"{domain}.test") + assert state.state != STATE_UNAVAILABLE + + # Verify we are subscribing to the new topic + async_fire_mqtt_message(hass, "availability-topic3", "offline") + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + # Change availability_topic + async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data3) + await hass.async_block_till_done() + + # Verify we are no longer subscribing to the old topic + async_fire_mqtt_message(hass, "availability-topic2", "online") + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + # Verify we are no longer subscribing to the old topic + async_fire_mqtt_message(hass, "availability-topic3", "online") + state = hass.states.get(f"{domain}.test") + assert state.state == STATE_UNAVAILABLE + + # Verify we are subscribing to the new topic + async_fire_mqtt_message(hass, "availability-topic4", "online") + state = hass.states.get(f"{domain}.test") + assert state.state != STATE_UNAVAILABLE + + async def help_test_setting_attribute_via_mqtt_json_message( hass, mqtt_mock, domain, config ): diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index f54a27e8805..4d1209614eb 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -16,11 +16,14 @@ from .test_common import ( help_test_availability_when_connection_lost, help_test_availability_without_topic, help_test_custom_availability_payload, + help_test_default_availability_list_payload, + help_test_default_availability_list_single, help_test_default_availability_payload, help_test_discovery_broken, help_test_discovery_removal, help_test_discovery_update, help_test_discovery_update_attr, + help_test_discovery_update_availability, help_test_entity_debug_info, help_test_entity_debug_info_max_messages, help_test_entity_debug_info_message, @@ -250,6 +253,20 @@ async def test_default_availability_payload(hass, mqtt_mock): ) +async def test_default_availability_list_payload(hass, mqtt_mock): + """Test availability by default payload with defined topic.""" + await help_test_default_availability_list_payload( + hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG + ) + + +async def test_default_availability_list_single(hass, mqtt_mock, caplog): + """Test availability list and availability_topic are mutually exclusive.""" + await help_test_default_availability_list_single( + hass, mqtt_mock, caplog, sensor.DOMAIN, DEFAULT_CONFIG + ) + + async def test_custom_availability_payload(hass, mqtt_mock): """Test availability by custom payload with defined topic.""" await help_test_custom_availability_payload( @@ -257,6 +274,13 @@ async def test_custom_availability_payload(hass, mqtt_mock): ) +async def test_discovery_update_availability(hass, mqtt_mock): + """Test availability discovery update.""" + await help_test_discovery_update_availability( + hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG + ) + + async def test_invalid_device_class(hass, mqtt_mock): """Test device_class option with invalid value.""" assert await async_setup_component( From 114fbb1278e9391ca32e7c71a68ad77c5d3f8670 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 10 Jul 2020 18:39:18 +0200 Subject: [PATCH 36/43] Tweak MQTT availability (#37719) --- homeassistant/components/mqtt/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index b743626ebbb..5742281f89d 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1021,11 +1021,14 @@ class MqttAvailability(Entity): """Subscribe MQTT events.""" await super().async_added_to_hass() await self._availability_subscribe_topics() - async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect) - async_dispatcher_connect(self.hass, MQTT_DISCONNECTED, self.async_mqtt_connect) self.async_on_remove( async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect) ) + self.async_on_remove( + async_dispatcher_connect( + self.hass, MQTT_DISCONNECTED, self.async_mqtt_connect + ) + ) async def availability_discovery_update(self, config: dict): """Handle updated discovery message.""" @@ -1092,7 +1095,7 @@ class MqttAvailability(Entity): @property def available(self) -> bool: """Return if the device is available.""" - if not self.hass.data[DATA_MQTT].connected: + if not self.hass.data[DATA_MQTT].connected and not self.hass.is_stopping: return False return not self._avail_topics or self._available From 502f0cd2cea7b5f78960174583eb91a217c3a8af Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 10 Jul 2020 18:40:15 +0200 Subject: [PATCH 37/43] Fix MQTT availability startup race (#37718) --- homeassistant/components/mqtt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 5742281f89d..49ac83b2162 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1083,7 +1083,7 @@ class MqttAvailability(Entity): @callback def async_mqtt_connect(self): """Update state on connection/disconnection to MQTT broker.""" - if self.hass.is_running: + if not self.hass.is_stopping: self.async_write_ha_state() async def async_will_remove_from_hass(self): From 6eca0b2a399b29080be2ad31b919b12bbde4a809 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Jul 2020 14:45:05 -1000 Subject: [PATCH 38/43] Fix homekit_controller discovery via zeroconf (#37725) --- homeassistant/components/zeroconf/__init__.py | 5 +++-- tests/components/zeroconf/test_init.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 9f0d203d2b3..084cc9c2186 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -200,7 +200,7 @@ def setup(hass, config): # If we can handle it as a HomeKit discovery, we do that here. if service_type == HOMEKIT_TYPE: - handle_homekit(hass, info) + discovery_was_forwarded = handle_homekit(hass, info) # Continue on here as homekit_controller # still needs to get updates on devices # so it can see when the 'c#' field is updated. @@ -209,7 +209,8 @@ def setup(hass, config): # if the device is already paired in order to avoid # offering a second discovery for the same device if ( - HOMEKIT_PROPERTIES in info + discovery_was_forwarded + and HOMEKIT_PROPERTIES in info and HOMEKIT_PAIRED_STATUS_FLAG in info[HOMEKIT_PROPERTIES] ): try: diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 45b1d9b1171..dcde788e050 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -215,6 +215,25 @@ async def test_homekit_invalid_paring_status(hass, mock_zeroconf): assert mock_config_flow.mock_calls[0][1][0] == "tado" +async def test_homekit_not_paired(hass, mock_zeroconf): + """Test that an not paired device is sent to homekit_controller.""" + with patch.dict( + zc_gen.ZEROCONF, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True + ), patch.object( + hass.config_entries.flow, "async_init" + ) as mock_config_flow, patch.object( + zeroconf, "HaServiceBrowser", side_effect=service_update_mock + ) as mock_service_browser: + mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock( + "this_will_not_match_any_integration", HOMEKIT_STATUS_UNPAIRED + ) + assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) + + assert len(mock_service_browser.mock_calls) == 1 + assert len(mock_config_flow.mock_calls) == 1 + assert mock_config_flow.mock_calls[0][1][0] == "homekit_controller" + + async def test_info_from_service_non_utf8(hass): """Test info_from_service handles non UTF-8 property keys and values correctly.""" service_type = "_test._tcp.local." From d0e26c3deee437cb19dd7777f9aededf91b1acb1 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Fri, 10 Jul 2020 15:48:20 -0700 Subject: [PATCH 39/43] Add support for the DataUpdateCoordinator to not automatically update (#37734) --- homeassistant/helpers/update_coordinator.py | 5 +- tests/helpers/test_update_coordinator.py | 67 +++++++++++++++++++-- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 7fb96fdf7c8..5998d428d6a 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -30,7 +30,7 @@ class DataUpdateCoordinator: logger: logging.Logger, *, name: str, - update_interval: timedelta, + update_interval: Optional[timedelta] = None, update_method: Optional[Callable[[], Awaitable]] = None, request_refresh_debouncer: Optional[Debouncer] = None, ): @@ -91,6 +91,9 @@ class DataUpdateCoordinator: @callback def _schedule_refresh(self) -> None: """Schedule a refresh.""" + if self.update_interval is None: + return + if self._unsub_refresh: self._unsub_refresh() self._unsub_refresh = None diff --git a/tests/helpers/test_update_coordinator.py b/tests/helpers/test_update_coordinator.py index 8d4f6934d78..99399fee30f 100644 --- a/tests/helpers/test_update_coordinator.py +++ b/tests/helpers/test_update_coordinator.py @@ -15,9 +15,8 @@ from tests.common import async_fire_time_changed LOGGER = logging.getLogger(__name__) -@pytest.fixture -def crd(hass): - """Coordinator mock.""" +def get_crd(hass, update_interval): + """Make coordinator mocks.""" calls = 0 async def refresh(): @@ -30,11 +29,26 @@ def crd(hass): LOGGER, name="test", update_method=refresh, - update_interval=timedelta(seconds=10), + update_interval=update_interval, ) return crd +DEFAULT_UPDATE_INTERVAL = timedelta(seconds=10) + + +@pytest.fixture +def crd(hass): + """Coordinator mock with default update interval.""" + return get_crd(hass, DEFAULT_UPDATE_INTERVAL) + + +@pytest.fixture +def crd_without_update_interval(hass): + """Coordinator mock that never automatically updates.""" + return get_crd(hass, None) + + async def test_async_refresh(crd): """Test async_refresh for update coordinator.""" assert crd.data is None @@ -79,6 +93,20 @@ async def test_request_refresh(crd): assert crd.last_update_success is True +async def test_request_refresh_no_auto_update(crd_without_update_interval): + """Test request refresh for update coordinator without automatic update.""" + crd = crd_without_update_interval + assert crd.data is None + await crd.async_request_refresh() + assert crd.data == 1 + assert crd.last_update_success is True + + # Second time we hit the debonuce + await crd.async_request_refresh() + assert crd.data == 1 + assert crd.last_update_success is True + + @pytest.mark.parametrize( "err_msg", [ @@ -151,6 +179,37 @@ async def test_update_interval(hass, crd): assert crd.data == 2 +async def test_update_interval_not_present(hass, crd_without_update_interval): + """Test update never happens with no update interval.""" + crd = crd_without_update_interval + # Test we don't update without subscriber with no update interval + async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert crd.data is None + + # Add subscriber + update_callback = Mock() + crd.async_add_listener(update_callback) + + # Test twice we don't update with subscriber with no update interval + async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert crd.data is None + + async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert crd.data is None + + # Test removing listener + crd.async_remove_listener(update_callback) + + async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() + + # Test we stop don't update after we lose last subscriber + assert crd.data is None + + async def test_refresh_recover(crd, caplog): """Test recovery of freshing data.""" crd.last_update_success = False From 2c58d860b6ea7f6a73dd91087e5652b33b027474 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 11 Jul 2020 11:50:18 -0400 Subject: [PATCH 40/43] Bump blinkpy version to fix connection errors (#37755) * Bump blinkpy version to fix connection errors * Bump blinkpy version to fix connection errors * Rebased, re-ran gen_requirements_all --- homeassistant/components/blink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index ac42870fdb7..a42763e5843 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -2,7 +2,7 @@ "domain": "blink", "name": "Blink", "documentation": "https://www.home-assistant.io/integrations/blink", - "requirements": ["blinkpy==0.15.0"], + "requirements": ["blinkpy==0.15.1"], "codeowners": ["@fronzbot"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index bfd590f3171..2ffa1bf7869 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -348,7 +348,7 @@ bizkaibus==0.1.1 blebox_uniapi==1.3.2 # homeassistant.components.blink -blinkpy==0.15.0 +blinkpy==0.15.1 # homeassistant.components.blinksticklight blinkstick==1.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1687252fe27..5fb080310a3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -171,7 +171,7 @@ bellows==0.17.0 blebox_uniapi==1.3.2 # homeassistant.components.blink -blinkpy==0.15.0 +blinkpy==0.15.1 # homeassistant.components.bom bomradarloop==0.1.4 From 738d3a13e193ecae7e200bb27092bade9b71cfbb Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 12 Jul 2020 09:47:26 +0200 Subject: [PATCH 41/43] UniFi - Handle session expiration (#37782) --- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 124c7241c30..9c1896f0c48 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -3,7 +3,7 @@ "name": "Ubiquiti UniFi", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifi", - "requirements": ["aiounifi==22"], + "requirements": ["aiounifi==23"], "codeowners": ["@Kane610"], "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index 2ffa1bf7869..d1476a89d69 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -230,7 +230,7 @@ aiopylgtv==0.3.3 aioswitcher==1.2.0 # homeassistant.components.unifi -aiounifi==22 +aiounifi==23 # homeassistant.components.airly airly==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5fb080310a3..b2b6008d398 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -122,7 +122,7 @@ aiopylgtv==0.3.3 aioswitcher==1.2.0 # homeassistant.components.unifi -aiounifi==22 +aiounifi==23 # homeassistant.components.airly airly==0.0.2 From 5d1d113a25b87c9f1e66ece3d7060d3b2cf76e67 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 13 Jul 2020 01:16:40 +0200 Subject: [PATCH 42/43] deCONZ - don't let light "attr" events update group data (#37797) --- homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 149ea5f1bc5..2cba87f74d6 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==71"], + "requirements": ["pydeconz==72"], "ssdp": [ { "manufacturer": "Royal Philips Electronics" diff --git a/requirements_all.txt b/requirements_all.txt index d1476a89d69..d9d277f19cf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1282,7 +1282,7 @@ pydaikin==2.2.0 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==71 +pydeconz==72 # homeassistant.components.delijn pydelijn==0.6.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b2b6008d398..08a3ac61619 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -576,7 +576,7 @@ pycountry==19.8.18 pydaikin==2.2.0 # homeassistant.components.deconz -pydeconz==71 +pydeconz==72 # homeassistant.components.zwave pydispatcher==2.0.5 From fb484e87c0b70cffa26a1d05189842d1170db6e3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 14 Jul 2020 01:11:49 +0000 Subject: [PATCH 43/43] Bumped version to 0.112.5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 47f271789e2..d490e4c090b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -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)