From db0cd34b2422973ae598f195cd119bcabb95d9c6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 21 Oct 2020 20:57:05 +0200 Subject: [PATCH 001/118] Bumped version to 0.117.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1a09efa5c4..0b5b3f15a5 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0b0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 1ea103f090ed594b02c8d3e34ffb6c7a6647a38c Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 21 Oct 2020 15:35:20 -0600 Subject: [PATCH 002/118] Remove coordinator success from SimpliSafe availability calculation (#42180) --- homeassistant/components/simplisafe/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index dba17e6ef9..e81559bd1f 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -622,11 +622,7 @@ class SimpliSafeEntity(CoordinatorEntity): # the entity as available if: # 1. We can verify that the system is online (assuming True if we can't) # 2. We can verify that the entity is online - return ( - self.coordinator.last_update_success - and not (self._system.version == 3 and self._system.offline) - and self._online - ) + return not (self._system.version == 3 and self._system.offline) and self._online @property def device_info(self): From bfd943c5dc367c8a1cc233d63abbe6f4028bdb14 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 22 Oct 2020 00:17:13 +0200 Subject: [PATCH 003/118] Updated frontend to 20201021.1 (#42185) --- 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 17a65a86b9..e04e25520b 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==20201021.0"], + "requirements": ["home-assistant-frontend==20201021.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 059afd441f..287cd336d8 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.37.1 -home-assistant-frontend==20201021.0 +home-assistant-frontend==20201021.1 httpx==0.16.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.2 diff --git a/requirements_all.txt b/requirements_all.txt index 02fa4d96e8..e84eafb78b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -759,7 +759,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20201021.0 +home-assistant-frontend==20201021.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0f5dea0c51..a32f9ddfab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -385,7 +385,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20201021.0 +home-assistant-frontend==20201021.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From a83a5a26030b9ac2d2b8655b37602af19fa15cfe Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Thu, 22 Oct 2020 05:36:54 +0800 Subject: [PATCH 004/118] Remove duplicated finalize_stream code in stream (#42171) --- homeassistant/components/stream/worker.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 6893c69ee3..6769413baf 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -160,10 +160,6 @@ def _stream_worker_internal(hass, stream, quit_event): audio_stream = None except (av.AVError, StopIteration) as ex: - if not stream.keepalive: - # End of stream, clear listeners and stop thread - for fmt, _ in outputs.items(): - hass.loop.call_soon_threadsafe(stream.outputs[fmt].put, None) _LOGGER.error( "Error demuxing stream while finding first packet: %s", str(ex) ) From 66453429b07c9920420136908577969fa5d8524e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 21 Oct 2020 15:24:50 -0500 Subject: [PATCH 005/118] Avoid overloading the executor with service.yaml loads (#42172) --- homeassistant/helpers/service.py | 34 +++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 20f7aa2d2d..d03fb8c91c 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -38,7 +38,7 @@ from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, HomeAssistantType, TemplateVarsType -from homeassistant.loader import async_get_integration, bind_hass +from homeassistant.loader import Integration, async_get_integration, bind_hass from homeassistant.util.yaml import load_yaml from homeassistant.util.yaml.loader import JSON_TYPE @@ -252,21 +252,29 @@ async def async_extract_entity_ids( return extracted -async def _load_services_file(hass: HomeAssistantType, domain: str) -> JSON_TYPE: +def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JSON_TYPE: """Load services file for an integration.""" - integration = await async_get_integration(hass, domain) try: - return await hass.async_add_executor_job( - load_yaml, str(integration.file_path / "services.yaml") - ) + return load_yaml(str(integration.file_path / "services.yaml")) except FileNotFoundError: - _LOGGER.warning("Unable to find services.yaml for the %s integration", domain) + _LOGGER.warning( + "Unable to find services.yaml for the %s integration", integration.domain + ) return {} except HomeAssistantError: - _LOGGER.warning("Unable to parse services.yaml for the %s integration", domain) + _LOGGER.warning( + "Unable to parse services.yaml for the %s integration", integration.domain + ) return {} +def _load_services_files( + hass: HomeAssistantType, integrations: Iterable[Integration] +) -> List[JSON_TYPE]: + """Load service files for multiple intergrations.""" + return [_load_services_file(hass, integration) for integration in integrations] + + @bind_hass async def async_get_all_descriptions( hass: HomeAssistantType, @@ -289,8 +297,12 @@ async def async_get_all_descriptions( loaded = {} if missing: - contents = await asyncio.gather( - *(_load_services_file(hass, domain) for domain in missing) + integrations = await asyncio.gather( + *(async_get_integration(hass, domain) for domain in missing) + ) + + contents = await hass.async_add_executor_job( + _load_services_files, hass, integrations ) for domain, content in zip(missing, contents): @@ -308,7 +320,7 @@ async def async_get_all_descriptions( # Cache missing descriptions if description is None: domain_yaml = loaded[domain] - yaml_description = domain_yaml.get(service, {}) + yaml_description = domain_yaml.get(service, {}) # type: ignore # Don't warn for missing services, because it triggers false # positives for things like scripts, that register as a service From a8342d93954f6c7ba15d092dea58170634745ab7 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 21 Oct 2020 15:35:48 -0600 Subject: [PATCH 006/118] Bump simplisafe-python to 9.5.1 (#42179) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 613187ef74..0968235257 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.4.1"], + "requirements": ["simplisafe-python==9.5.1"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index e84eafb78b..bf515d1d99 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2017,7 +2017,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.4.1 +simplisafe-python==9.5.1 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a32f9ddfab..5e7e53ef33 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -959,7 +959,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.4.1 +simplisafe-python==9.5.1 # homeassistant.components.slack slackclient==2.5.0 From f1de6bf6b4c0e7e5fc8f66fb722fd9de3f7bd57a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 22 Oct 2020 00:24:25 +0200 Subject: [PATCH 007/118] Bumped version to 0.117.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 0b5b3f15a5..0eb828c5b1 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0b0" +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 3eece977f83466a717fdc4c2428cf01faaa861c9 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 22 Oct 2020 00:09:52 +0000 Subject: [PATCH 008/118] [ci skip] Translation update --- .../accuweather/translations/pl.json | 2 +- .../adguard/translations/zh-Hans.json | 3 ++ .../advantage_air/translations/zh-Hans.json | 11 ++++++ .../agent_dvr/translations/zh-Hans.json | 7 ++++ .../airvisual/translations/zh-Hans.json | 7 ++++ .../alarmdecoder/translations/de.json | 1 + .../alarmdecoder/translations/zh-Hans.json | 14 ++++++++ .../ambiclimate/translations/zh-Hans.json | 8 +++++ .../components/atag/translations/zh-Hans.json | 7 ++++ .../august/translations/zh-Hans.json | 11 ++++++ .../automation/translations/pl.json | 4 +-- .../azure_devops/translations/de.json | 15 ++++++++ .../binary_sensor/translations/pl.json | 4 +-- .../binary_sensor/translations/zh-Hans.json | 7 +++- .../blink/translations/zh-Hans.json | 11 ++++++ .../braviatv/translations/zh-Hans.json | 9 +++++ .../components/broadlink/translations/de.json | 9 ++++- .../brother/translations/zh-Hans.json | 8 +++++ .../bsblan/translations/zh-Hans.json | 7 ++++ .../components/calendar/translations/pl.json | 4 +-- .../canary/translations/zh-Hans.json | 12 +++++++ .../cloudflare/translations/ca.json | 35 +++++++++++++++++++ .../cloudflare/translations/cs.json | 34 ++++++++++++++++++ .../cloudflare/translations/en.json | 35 +++++++++++++++++++ .../cloudflare/translations/et.json | 34 ++++++++++++++++++ .../cloudflare/translations/pl.json | 35 +++++++++++++++++++ .../cloudflare/translations/ru.json | 35 +++++++++++++++++++ .../configurator/translations/pl.json | 2 +- .../control4/translations/zh-Hans.json | 11 ++++++ .../coolmaster/translations/zh-Hans.json | 14 ++++++++ .../cover/translations/zh-Hans.json | 8 +++++ .../daikin/translations/zh-Hans.json | 5 +++ .../deconz/translations/zh-Hans.json | 3 +- .../dexcom/translations/zh-Hans.json | 4 ++- .../doorbird/translations/zh-Hans.json | 11 ++++++ .../elgato/translations/zh-Hans.json | 10 ++++++ .../components/fan/translations/pl.json | 4 +-- .../components/fan/translations/zh-Hans.json | 2 +- .../components/flo/translations/zh-Hans.json | 11 ++++++ .../flume/translations/zh-Hans.json | 11 ++++++ .../fritzbox/translations/zh-Hans.json | 19 ++++++++++ .../garmin_connect/translations/zh-Hans.json | 11 ++++++ .../glances/translations/zh-Hans.json | 11 ++++++ .../gogogate2/translations/zh-Hans.json | 11 ++++++ .../components/gree/translations/zh-Hans.json | 3 ++ .../components/group/translations/pl.json | 4 +-- .../guardian/translations/zh-Hans.json | 7 ++++ .../hlk_sw16/translations/zh-Hans.json | 11 ++++++ .../components/homekit/translations/nl.json | 8 ++++- .../homematicip_cloud/translations/de.json | 1 + .../huawei_lte/translations/zh-Hans.json | 9 +++++ .../components/hue/translations/et.json | 2 +- .../components/hue/translations/zh-Hans.json | 5 +++ .../humidifier/translations/pl.json | 4 +-- .../humidifier/translations/zh-Hans.json | 7 ++++ .../hvv_departures/translations/zh-Hans.json | 11 ++++++ .../iaqualink/translations/zh-Hans.json | 12 +++++++ .../icloud/translations/zh-Hans.json | 8 ++++- .../input_boolean/translations/pl.json | 4 +-- .../components/insteon/translations/de.json | 3 ++ .../components/insteon/translations/et.json | 3 +- .../insteon/translations/zh-Hans.json | 16 +++++++++ .../components/ipp/translations/zh-Hans.json | 10 ++++++ .../isy994/translations/zh-Hans.json | 11 ++++++ .../components/kodi/translations/de.json | 9 +++-- .../components/kodi/translations/zh-Hans.json | 11 ++++++ .../components/konnected/translations/nl.json | 5 ++- .../life360/translations/zh-Hans.json | 4 +++ .../components/light/translations/pl.json | 4 +-- .../media_player/translations/pl.json | 4 +-- .../meteo_france/translations/de.json | 9 +++++ .../metoffice/translations/zh-Hans.json | 16 +++++++++ .../components/mill/translations/zh-Hans.json | 11 ++++++ .../components/mqtt/translations/zh-Hans.json | 9 +++++ .../components/myq/translations/zh-Hans.json | 11 ++++++ .../components/neato/translations/et.json | 4 ++- .../neato/translations/zh-Hans.json | 11 ++++++ .../components/nest/translations/ca.json | 6 ++++ .../components/nest/translations/cs.json | 6 ++++ .../components/nest/translations/en.json | 6 ++++ .../components/nest/translations/et.json | 3 ++ .../components/nest/translations/pl.json | 6 ++++ .../components/nest/translations/ru.json | 6 ++++ .../components/nest/translations/zh-Hans.json | 1 + .../components/nest/translations/zh-Hant.json | 6 ++++ .../components/netatmo/translations/et.json | 5 +++ .../nexia/translations/zh-Hans.json | 11 ++++++ .../nuheat/translations/zh-Hans.json | 11 ++++++ .../components/nut/translations/zh-Hans.json | 11 ++++++ .../components/nzbget/translations/de.json | 3 +- .../nzbget/translations/zh-Hans.json | 11 ++++++ .../omnilogic/translations/zh-Hans.json | 12 +++++++ .../onvif/translations/zh-Hans.json | 9 +++++ .../openweathermap/translations/de.json | 6 ++-- .../ovo_energy/translations/zh-Hans.json | 15 ++++++++ .../components/plex/translations/nl.json | 1 + .../components/plugwise/translations/de.json | 9 +++++ .../plugwise/translations/zh-Hans.json | 12 +++++++ .../plum_lightpad/translations/zh-Hans.json | 11 ++++++ .../components/point/translations/et.json | 2 +- .../components/ps4/translations/zh-Hans.json | 1 + .../rainmachine/translations/et.json | 3 +- .../components/remote/translations/pl.json | 4 +-- .../remote/translations/zh-Hans.json | 13 +++++++ .../components/rfxtrx/translations/de.json | 12 +++++++ .../rfxtrx/translations/zh-Hans.json | 11 ++++++ .../components/ring/translations/zh-Hans.json | 11 ++++++ .../risco/translations/zh-Hans.json | 11 ++++++ .../components/roon/translations/de.json | 5 +++ .../translations/zh-Hans.json | 15 ++++++++ .../components/script/translations/pl.json | 4 +-- .../components/sensor/translations/pl.json | 4 +-- .../sharkiq/translations/zh-Hans.json | 16 +++++++++ .../components/shelly/translations/ca.json | 2 +- .../components/shelly/translations/cs.json | 2 +- .../components/shelly/translations/pl.json | 2 +- .../components/shelly/translations/ru.json | 2 +- .../shelly/translations/zh-Hans.json | 11 ++++++ .../simplisafe/translations/et.json | 1 + .../simplisafe/translations/zh-Hans.json | 1 + .../components/smappee/translations/et.json | 4 +++ .../translations/zh-Hans.json | 11 ++++++ .../components/somfy/translations/et.json | 5 +++ .../spider/translations/zh-Hans.json | 11 ++++++ .../components/spotify/translations/et.json | 2 +- .../squeezebox/translations/zh-Hans.json | 11 ++++++ .../starline/translations/zh-Hans.json | 14 ++++++++ .../components/switch/translations/pl.json | 4 +-- .../switch/translations/zh-Hans.json | 12 +++++++ .../components/syncthru/translations/et.json | 1 + .../synology_dsm/translations/de.json | 3 +- .../components/tado/translations/zh-Hans.json | 11 ++++++ .../tibber/translations/zh-Hans.json | 7 ++++ .../components/tile/translations/zh-Hans.json | 11 ++++++ .../totalconnect/translations/zh-Hans.json | 14 ++++++++ .../transmission/translations/zh-Hans.json | 7 ++++ .../components/tuya/translations/zh-Hans.json | 11 ++++++ .../twentemilieu/translations/zh-Hans.json | 4 +++ .../components/unifi/translations/nl.json | 3 +- .../components/upcloud/translations/de.json | 9 +++++ .../upcloud/translations/zh-Hans.json | 15 ++++++++ .../components/vacuum/translations/pl.json | 4 +-- .../vesync/translations/zh-Hans.json | 8 +++++ .../water_heater/translations/zh-Hans.json | 8 +++++ .../components/wled/translations/zh-Hans.json | 10 ++++++ .../wolflink/translations/sensor.de.json | 7 ++++ .../wolflink/translations/sensor.pl.json | 2 +- .../wolflink/translations/zh-Hans.json | 11 ++++++ .../components/yeelight/translations/de.json | 2 ++ .../components/zha/translations/zh-Hans.json | 8 +++++ .../zoneminder/translations/zh-Hans.json | 11 ++++++ 151 files changed, 1249 insertions(+), 56 deletions(-) create mode 100644 homeassistant/components/advantage_air/translations/zh-Hans.json create mode 100644 homeassistant/components/agent_dvr/translations/zh-Hans.json create mode 100644 homeassistant/components/airvisual/translations/zh-Hans.json create mode 100644 homeassistant/components/alarmdecoder/translations/zh-Hans.json create mode 100644 homeassistant/components/ambiclimate/translations/zh-Hans.json create mode 100644 homeassistant/components/atag/translations/zh-Hans.json create mode 100644 homeassistant/components/august/translations/zh-Hans.json create mode 100644 homeassistant/components/azure_devops/translations/de.json create mode 100644 homeassistant/components/blink/translations/zh-Hans.json create mode 100644 homeassistant/components/braviatv/translations/zh-Hans.json create mode 100644 homeassistant/components/brother/translations/zh-Hans.json create mode 100644 homeassistant/components/bsblan/translations/zh-Hans.json create mode 100644 homeassistant/components/canary/translations/zh-Hans.json create mode 100644 homeassistant/components/cloudflare/translations/ca.json create mode 100644 homeassistant/components/cloudflare/translations/cs.json create mode 100644 homeassistant/components/cloudflare/translations/en.json create mode 100644 homeassistant/components/cloudflare/translations/et.json create mode 100644 homeassistant/components/cloudflare/translations/pl.json create mode 100644 homeassistant/components/cloudflare/translations/ru.json create mode 100644 homeassistant/components/control4/translations/zh-Hans.json create mode 100644 homeassistant/components/coolmaster/translations/zh-Hans.json create mode 100644 homeassistant/components/doorbird/translations/zh-Hans.json create mode 100644 homeassistant/components/elgato/translations/zh-Hans.json create mode 100644 homeassistant/components/flo/translations/zh-Hans.json create mode 100644 homeassistant/components/flume/translations/zh-Hans.json create mode 100644 homeassistant/components/fritzbox/translations/zh-Hans.json create mode 100644 homeassistant/components/garmin_connect/translations/zh-Hans.json create mode 100644 homeassistant/components/glances/translations/zh-Hans.json create mode 100644 homeassistant/components/gogogate2/translations/zh-Hans.json create mode 100644 homeassistant/components/gree/translations/zh-Hans.json create mode 100644 homeassistant/components/guardian/translations/zh-Hans.json create mode 100644 homeassistant/components/hlk_sw16/translations/zh-Hans.json create mode 100644 homeassistant/components/huawei_lte/translations/zh-Hans.json create mode 100644 homeassistant/components/hvv_departures/translations/zh-Hans.json create mode 100644 homeassistant/components/iaqualink/translations/zh-Hans.json create mode 100644 homeassistant/components/insteon/translations/zh-Hans.json create mode 100644 homeassistant/components/ipp/translations/zh-Hans.json create mode 100644 homeassistant/components/isy994/translations/zh-Hans.json create mode 100644 homeassistant/components/kodi/translations/zh-Hans.json create mode 100644 homeassistant/components/metoffice/translations/zh-Hans.json create mode 100644 homeassistant/components/mill/translations/zh-Hans.json create mode 100644 homeassistant/components/myq/translations/zh-Hans.json create mode 100644 homeassistant/components/neato/translations/zh-Hans.json create mode 100644 homeassistant/components/nexia/translations/zh-Hans.json create mode 100644 homeassistant/components/nuheat/translations/zh-Hans.json create mode 100644 homeassistant/components/nut/translations/zh-Hans.json create mode 100644 homeassistant/components/nzbget/translations/zh-Hans.json create mode 100644 homeassistant/components/omnilogic/translations/zh-Hans.json create mode 100644 homeassistant/components/ovo_energy/translations/zh-Hans.json create mode 100644 homeassistant/components/plugwise/translations/zh-Hans.json create mode 100644 homeassistant/components/plum_lightpad/translations/zh-Hans.json create mode 100644 homeassistant/components/rfxtrx/translations/zh-Hans.json create mode 100644 homeassistant/components/ring/translations/zh-Hans.json create mode 100644 homeassistant/components/risco/translations/zh-Hans.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/zh-Hans.json create mode 100644 homeassistant/components/sharkiq/translations/zh-Hans.json create mode 100644 homeassistant/components/shelly/translations/zh-Hans.json create mode 100644 homeassistant/components/smart_meter_texas/translations/zh-Hans.json create mode 100644 homeassistant/components/spider/translations/zh-Hans.json create mode 100644 homeassistant/components/squeezebox/translations/zh-Hans.json create mode 100644 homeassistant/components/starline/translations/zh-Hans.json create mode 100644 homeassistant/components/tado/translations/zh-Hans.json create mode 100644 homeassistant/components/tibber/translations/zh-Hans.json create mode 100644 homeassistant/components/tile/translations/zh-Hans.json create mode 100644 homeassistant/components/totalconnect/translations/zh-Hans.json create mode 100644 homeassistant/components/transmission/translations/zh-Hans.json create mode 100644 homeassistant/components/tuya/translations/zh-Hans.json create mode 100644 homeassistant/components/upcloud/translations/zh-Hans.json create mode 100644 homeassistant/components/water_heater/translations/zh-Hans.json create mode 100644 homeassistant/components/wled/translations/zh-Hans.json create mode 100644 homeassistant/components/wolflink/translations/zh-Hans.json create mode 100644 homeassistant/components/zoneminder/translations/zh-Hans.json diff --git a/homeassistant/components/accuweather/translations/pl.json b/homeassistant/components/accuweather/translations/pl.json index eb607d3819..2ac9aabfc9 100644 --- a/homeassistant/components/accuweather/translations/pl.json +++ b/homeassistant/components/accuweather/translations/pl.json @@ -27,7 +27,7 @@ "data": { "forecast": "Prognoza pogody" }, - "description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 64 minut zamiast co 32 minut.", + "description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 64 minuty zamiast co 32 minuty.", "title": "Opcje AccuWeather" } } diff --git a/homeassistant/components/adguard/translations/zh-Hans.json b/homeassistant/components/adguard/translations/zh-Hans.json index 4204beb526..64a68e9187 100644 --- a/homeassistant/components/adguard/translations/zh-Hans.json +++ b/homeassistant/components/adguard/translations/zh-Hans.json @@ -3,6 +3,9 @@ "abort": { "existing_instance_updated": "\u66f4\u65b0\u4e86\u73b0\u6709\u914d\u7f6e\u3002" }, + "error": { + "connection_error": "\u8fde\u63a5\u5931\u8d25\u3002" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/advantage_air/translations/zh-Hans.json b/homeassistant/components/advantage_air/translations/zh-Hans.json new file mode 100644 index 0000000000..a25ab477c7 --- /dev/null +++ b/homeassistant/components/advantage_air/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "ip_address": "IP\u5730\u5740" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/zh-Hans.json b/homeassistant/components/agent_dvr/translations/zh-Hans.json new file mode 100644 index 0000000000..2941dfd938 --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/zh-Hans.json b/homeassistant/components/airvisual/translations/zh-Hans.json new file mode 100644 index 0000000000..2941dfd938 --- /dev/null +++ b/homeassistant/components/airvisual/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/de.json b/homeassistant/components/alarmdecoder/translations/de.json index 69318f87b1..8e72173d15 100644 --- a/homeassistant/components/alarmdecoder/translations/de.json +++ b/homeassistant/components/alarmdecoder/translations/de.json @@ -6,6 +6,7 @@ "step": { "protocol": { "data": { + "device_path": "Ger\u00e4tepfad", "host": "Host", "port": "Port" } diff --git a/homeassistant/components/alarmdecoder/translations/zh-Hans.json b/homeassistant/components/alarmdecoder/translations/zh-Hans.json new file mode 100644 index 0000000000..1810fa0bcf --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/zh-Hans.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + }, + "step": { + "protocol": { + "data": { + "port": "\u7aef\u53e3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/zh-Hans.json b/homeassistant/components/ambiclimate/translations/zh-Hans.json new file mode 100644 index 0000000000..df2d0ac6af --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/zh-Hans.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "already_configured": "\u8d26\u53f7\u5df2\u7ecf\u8bbe\u7f6e\u5b8c\u6210", + "missing_configuration": "\u7ec4\u4ef6\u5c1a\u672a\u914d\u7f6e\u3002\u8bf7\u53c2\u89c2\u6587\u4ef6\u8bf4\u660e\u3002" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/zh-Hans.json b/homeassistant/components/atag/translations/zh-Hans.json new file mode 100644 index 0000000000..2941dfd938 --- /dev/null +++ b/homeassistant/components/atag/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/zh-Hans.json b/homeassistant/components/august/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/august/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/automation/translations/pl.json b/homeassistant/components/automation/translations/pl.json index b6cdef3db6..959fb48b35 100644 --- a/homeassistant/components/automation/translations/pl.json +++ b/homeassistant/components/automation/translations/pl.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "wy\u0142\u0105czona", - "on": "w\u0142\u0105czona" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Automatyzacja" diff --git a/homeassistant/components/azure_devops/translations/de.json b/homeassistant/components/azure_devops/translations/de.json new file mode 100644 index 0000000000..cd849b9f93 --- /dev/null +++ b/homeassistant/components/azure_devops/translations/de.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "reauth": { + "title": "Erneute Authentifizierung" + }, + "user": { + "data": { + "organization": "Organisation", + "project": "Projekt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/translations/pl.json b/homeassistant/components/binary_sensor/translations/pl.json index 67af6898c3..a3f578f1ce 100644 --- a/homeassistant/components/binary_sensor/translations/pl.json +++ b/homeassistant/components/binary_sensor/translations/pl.json @@ -91,8 +91,8 @@ }, "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." }, "battery": { "off": "na\u0142adowana", diff --git a/homeassistant/components/binary_sensor/translations/zh-Hans.json b/homeassistant/components/binary_sensor/translations/zh-Hans.json index d2edb26163..b13a3622ac 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hans.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hans.json @@ -48,7 +48,12 @@ "connected": "{entity_name} \u5df2\u8fde\u63a5", "gas": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u71c3\u6c14\u6cc4\u6f0f", "hot": "{entity_name} \u53d8\u70ed", - "light": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u5149\u7ebf" + "light": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u5149\u7ebf", + "motion": "{entity_name} \u68c0\u6d4b\u5230\u6709\u4eba", + "no_motion": "{entity_name} \u672a\u68c0\u6d4b\u5230\u6709\u4eba", + "not_opened": "{entity_name}\u5df2\u5173\u95ed", + "turned_off": "{entity_name} \u88ab\u5173\u95ed", + "turned_on": "{entity_name} \u88ab\u6253\u5f00" } }, "state": { diff --git a/homeassistant/components/blink/translations/zh-Hans.json b/homeassistant/components/blink/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/blink/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/zh-Hans.json b/homeassistant/components/braviatv/translations/zh-Hans.json new file mode 100644 index 0000000000..c60c3f800e --- /dev/null +++ b/homeassistant/components/braviatv/translations/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u8bbe\u7f6eSony Bravia\u7535\u89c6\u96c6\u6210\u3002\u5982\u679c\u60a8\u5728\u914d\u7f6e\u65b9\u9762\u9047\u5230\u95ee\u9898\uff0c\u8bf7\u8bbf\u95ee\uff1ahttps://www.home-assistant.io/integrations/braviatv\n\u786e\u4fdd\u7535\u89c6\u5df2\u6253\u5f00\u3002" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/de.json b/homeassistant/components/broadlink/translations/de.json index b0d7a55c78..99af347f1f 100644 --- a/homeassistant/components/broadlink/translations/de.json +++ b/homeassistant/components/broadlink/translations/de.json @@ -1,10 +1,17 @@ { "config": { + "abort": { + "not_supported": "Ger\u00e4t nicht unterst\u00fctzt" + }, "step": { + "auth": { + "title": "Authentifiziere dich beim Ger\u00e4t" + }, "finish": { "data": { "name": "Name" - } + }, + "title": "W\u00e4hle einen Namen f\u00fcr das Ger\u00e4t" }, "user": { "data": { diff --git a/homeassistant/components/brother/translations/zh-Hans.json b/homeassistant/components/brother/translations/zh-Hans.json new file mode 100644 index 0000000000..8f9e85e54a --- /dev/null +++ b/homeassistant/components/brother/translations/zh-Hans.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "snmp_error": "SNMP\u670d\u52a1\u5668\u5df2\u5173\u95ed\u6216\u4e0d\u652f\u6301\u6253\u5370\u3002" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/zh-Hans.json b/homeassistant/components/bsblan/translations/zh-Hans.json new file mode 100644 index 0000000000..2941dfd938 --- /dev/null +++ b/homeassistant/components/bsblan/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/calendar/translations/pl.json b/homeassistant/components/calendar/translations/pl.json index 94ac2fd244..b910624917 100644 --- a/homeassistant/components/calendar/translations/pl.json +++ b/homeassistant/components/calendar/translations/pl.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Kalendarz" diff --git a/homeassistant/components/canary/translations/zh-Hans.json b/homeassistant/components/canary/translations/zh-Hans.json new file mode 100644 index 0000000000..b0b26b0226 --- /dev/null +++ b/homeassistant/components/canary/translations/zh-Hans.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u5bc6\u7801", + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/ca.json b/homeassistant/components/cloudflare/translations/ca.json new file mode 100644 index 0000000000..edd31662e5 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/ca.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", + "unknown": "Error inesperat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_zone": "Zona inv\u00e0lida" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "Registres" + }, + "title": "Selecciona els registres a actualitzar" + }, + "user": { + "data": { + "api_token": "Token d'API" + }, + "description": "Aquesta integraci\u00f3 necessita un token d'API creat amb permisos d'edici\u00f3 Zone:Zone:Read i Zone:DNS:Edit per a totes les zones del teu compte.", + "title": "Connexi\u00f3 amb Cloudflare" + }, + "zone": { + "data": { + "zone": "Zona" + }, + "title": "Selecciona la zona a actualitzar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/cs.json b/homeassistant/components/cloudflare/translations/cs.json new file mode 100644 index 0000000000..d618c25c4f --- /dev/null +++ b/homeassistant/components/cloudflare/translations/cs.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace.", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "invalid_zone": "Neplatn\u00e1 z\u00f3na" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "Z\u00e1znamy" + }, + "title": "Vyberte z\u00e1znamy, kter\u00e9 chcete aktualizovat" + }, + "user": { + "data": { + "api_token": "API token" + }, + "title": "P\u0159ipojen\u00ed ke Cloudflare" + }, + "zone": { + "data": { + "zone": "Z\u00f3na" + }, + "title": "Vyberte z\u00f3nu, kter\u00e1 m\u00e1 b\u00fdt aktualizov\u00e1na" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/en.json b/homeassistant/components/cloudflare/translations/en.json new file mode 100644 index 0000000000..4016e22d8e --- /dev/null +++ b/homeassistant/components/cloudflare/translations/en.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible.", + "unknown": "Unexpected error" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "invalid_zone": "Invalid zone" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "Records" + }, + "title": "Choose the Records to Update" + }, + "user": { + "data": { + "api_token": "API Token" + }, + "description": "This integration requires an API Token created with Zone:Zone:Read and Zone:DNS:Edit permissions for all zones in your account.", + "title": "Connect to Cloudflare" + }, + "zone": { + "data": { + "zone": "Zone" + }, + "title": "Choose the Zone to Update" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/et.json b/homeassistant/components/cloudflare/translations/et.json new file mode 100644 index 0000000000..9c9ab666a8 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/et.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "unknown": "Tundmatu viga" + }, + "error": { + "cannot_connect": "\u00dchendus nurjus", + "invalid_auth": "Tuvastamise viga", + "invalid_zone": "Sobimatu ala" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "Kirjed" + }, + "title": "Vali v\u00e4rskendatavad kirjed" + }, + "user": { + "data": { + "api_token": "Api v\u00f5ti" + }, + "description": "Selle sidumise jaoks on vaja API tokenit, mis on loodud Zone: Zone: Read ja Zone: DNS: Edit k\u00f5igi oma konto alade \u00f5igusi.!?", + "title": "Loo \u00fchendus Cloudflare'iga" + }, + "zone": { + "data": { + "zone": "Ala" + }, + "title": "Vali v\u00e4rskendatav ala" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/pl.json b/homeassistant/components/cloudflare/translations/pl.json new file mode 100644 index 0000000000..70c7869937 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/pl.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "invalid_zone": "Nieprawid\u0142owa strefa" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "Rekordy" + }, + "title": "Wybierz rekordy do uaktualnienia" + }, + "user": { + "data": { + "api_token": "Token API" + }, + "description": "Ta integracja wymaga tokena API utworzonego z uprawnieniami \"Zone:Zone:Read\" oraz \"Zone:DNS:Edit\" dla wszystkich stref na Twoim koncie.", + "title": "Po\u0142\u0105czenie z Cloudflare" + }, + "zone": { + "data": { + "zone": "Strefa" + }, + "title": "Wybierz stref\u0119 do uaktualnienia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/ru.json b/homeassistant/components/cloudflare/translations/ru.json new file mode 100644 index 0000000000..fa4819d8c8 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/ru.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_zone": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0437\u043e\u043d\u0430" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "\u0417\u0430\u043f\u0438\u0441\u0438" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f" + }, + "user": { + "data": { + "api_token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u0414\u043b\u044f \u044d\u0442\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043a\u0435\u043d API, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0441 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f\u043c\u0438 Zone:Zone:Read \u0438 Zone:DNS:Edit \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0437\u043e\u043d \u0432 \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Cloudflare" + }, + "zone": { + "data": { + "zone": "\u0417\u043e\u043d\u0430" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u043e\u043d\u0443 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/configurator/translations/pl.json b/homeassistant/components/configurator/translations/pl.json index 45e5af4672..849a7e7bfe 100644 --- a/homeassistant/components/configurator/translations/pl.json +++ b/homeassistant/components/configurator/translations/pl.json @@ -1,7 +1,7 @@ { "state": { "_": { - "configure": "skonfiguruj", + "configure": "konfiguruj", "configured": "skonfigurowany" } }, diff --git a/homeassistant/components/control4/translations/zh-Hans.json b/homeassistant/components/control4/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/control4/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/zh-Hans.json b/homeassistant/components/coolmaster/translations/zh-Hans.json new file mode 100644 index 0000000000..c296a15219 --- /dev/null +++ b/homeassistant/components/coolmaster/translations/zh-Hans.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + }, + "step": { + "user": { + "data": { + "off": "\u53ef\u4ee5\u5173\u95ed" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cover/translations/zh-Hans.json b/homeassistant/components/cover/translations/zh-Hans.json index 2929a2cd33..0ff7340729 100644 --- a/homeassistant/components/cover/translations/zh-Hans.json +++ b/homeassistant/components/cover/translations/zh-Hans.json @@ -1,4 +1,12 @@ { + "device_automation": { + "condition_type": { + "is_closed": "{entity_name} \u5df2\u5173\u95ed" + }, + "trigger_type": { + "closed": "{entity_name}\u5df2\u5173\u95ed" + } + }, "state": { "_": { "closed": "\u5df2\u5173\u95ed", diff --git a/homeassistant/components/daikin/translations/zh-Hans.json b/homeassistant/components/daikin/translations/zh-Hans.json index 307fd15324..0acb5110fe 100644 --- a/homeassistant/components/daikin/translations/zh-Hans.json +++ b/homeassistant/components/daikin/translations/zh-Hans.json @@ -4,9 +4,14 @@ "already_configured": "\u8bbe\u5907\u5df2\u914d\u7f6e\u5b8c\u6210", "cannot_connect": "\u8fde\u63a5\u5931\u8d25" }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_auth": "\u9a8c\u8bc1\u7801\u9519\u8bef" + }, "step": { "user": { "data": { + "api_key": "API\u5bc6\u7801", "host": "\u4e3b\u673a" }, "description": "\u8f93\u5165\u60a8\u7684 Daikin \u7a7a\u8c03\u7684 IP \u5730\u5740\u3002", diff --git a/homeassistant/components/deconz/translations/zh-Hans.json b/homeassistant/components/deconz/translations/zh-Hans.json index 42b8067f64..5799323769 100644 --- a/homeassistant/components/deconz/translations/zh-Hans.json +++ b/homeassistant/components/deconz/translations/zh-Hans.json @@ -22,7 +22,8 @@ "side_3": "\u7b2c 3 \u9762", "side_4": "\u7b2c 4 \u9762", "side_5": "\u7b2c 5 \u9762", - "side_6": "\u7b2c 6 \u9762" + "side_6": "\u7b2c 6 \u9762", + "turn_off": "\u5173\u95ed" }, "trigger_type": { "remote_awakened": "\u8bbe\u5907\u5524\u9192", diff --git a/homeassistant/components/dexcom/translations/zh-Hans.json b/homeassistant/components/dexcom/translations/zh-Hans.json index 41e54c84b5..7fd407af3a 100644 --- a/homeassistant/components/dexcom/translations/zh-Hans.json +++ b/homeassistant/components/dexcom/translations/zh-Hans.json @@ -2,6 +2,7 @@ "config": { "error": { "account_error": "\u8ba4\u8bc1\u65e0\u6548", + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", "session_error": "\u8fde\u63a5\u5931\u8d25", "unknown": "\u672a\u77e5\u9519\u8bef" }, @@ -9,7 +10,8 @@ "user": { "data": { "password": "\u5bc6\u7801", - "server": "\u670d\u52a1\u5668" + "server": "\u670d\u52a1\u5668", + "username": "\u7528\u6237\u540d" } } } diff --git a/homeassistant/components/doorbird/translations/zh-Hans.json b/homeassistant/components/doorbird/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/doorbird/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/zh-Hans.json b/homeassistant/components/elgato/translations/zh-Hans.json new file mode 100644 index 0000000000..254f6df932 --- /dev/null +++ b/homeassistant/components/elgato/translations/zh-Hans.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fan/translations/pl.json b/homeassistant/components/fan/translations/pl.json index fcf925e60e..e99f92ae28 100644 --- a/homeassistant/components/fan/translations/pl.json +++ b/homeassistant/components/fan/translations/pl.json @@ -15,8 +15,8 @@ }, "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Wentylator" diff --git a/homeassistant/components/fan/translations/zh-Hans.json b/homeassistant/components/fan/translations/zh-Hans.json index 6dde9459c1..13b6917f4a 100644 --- a/homeassistant/components/fan/translations/zh-Hans.json +++ b/homeassistant/components/fan/translations/zh-Hans.json @@ -10,7 +10,7 @@ }, "trigger_type": { "turned_off": "{entity_name} \u88ab\u5173\u95ed", - "turned_on": "{entity_name} \u88ab\u5f00\u542f" + "turned_on": "{entity_name} \u88ab\u6253\u5f00" } }, "state": { diff --git a/homeassistant/components/flo/translations/zh-Hans.json b/homeassistant/components/flo/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/flo/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/zh-Hans.json b/homeassistant/components/flume/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/flume/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/zh-Hans.json b/homeassistant/components/fritzbox/translations/zh-Hans.json new file mode 100644 index 0000000000..183c748b2d --- /dev/null +++ b/homeassistant/components/fritzbox/translations/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "auth_failed": "\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef" + }, + "step": { + "confirm": { + "data": { + "username": "\u7528\u6237\u540d" + } + }, + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/zh-Hans.json b/homeassistant/components/garmin_connect/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/zh-Hans.json b/homeassistant/components/glances/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/glances/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/zh-Hans.json b/homeassistant/components/gogogate2/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/zh-Hans.json b/homeassistant/components/gree/translations/zh-Hans.json new file mode 100644 index 0000000000..c6d2c4c7cf --- /dev/null +++ b/homeassistant/components/gree/translations/zh-Hans.json @@ -0,0 +1,3 @@ +{ + "title": "\u683c\u529b\u7a7a\u8c03" +} \ No newline at end of file diff --git a/homeassistant/components/group/translations/pl.json b/homeassistant/components/group/translations/pl.json index b82492d83d..ce231ebaad 100644 --- a/homeassistant/components/group/translations/pl.json +++ b/homeassistant/components/group/translations/pl.json @@ -5,9 +5,9 @@ "home": "w domu", "locked": "Zablokowane", "not_home": "poza domem", - "off": "wy\u0142\u0105czony", + "off": "wy\u0142.", "ok": "ok", - "on": "w\u0142\u0105czony", + "on": "w\u0142.", "open": "otwarte", "problem": "problem", "unlocked": "Odblokowany" diff --git a/homeassistant/components/guardian/translations/zh-Hans.json b/homeassistant/components/guardian/translations/zh-Hans.json new file mode 100644 index 0000000000..6e842e66fa --- /dev/null +++ b/homeassistant/components/guardian/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/zh-Hans.json b/homeassistant/components/hlk_sw16/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index 75cb9e739b..3c3a019c3e 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -18,6 +18,10 @@ "options": { "step": { "advanced": { + "data": { + "safe_mode": "Veilige modus (alleen inschakelen als het koppelen mislukt)" + }, + "description": "Deze instellingen hoeven alleen te worden aangepast als HomeKit niet functioneert.", "title": "Geavanceerde configuratie" }, "exclude": { @@ -30,9 +34,11 @@ "init": { "data": { "include_domains": "Op te nemen domeinen" - } + }, + "title": "Selecteer domeinen om zichtbaar te maken." }, "yaml": { + "description": "Deze invoer wordt beheerd via YAML", "title": "Pas de HomeKit Bridge-opties aan" } } diff --git a/homeassistant/components/homematicip_cloud/translations/de.json b/homeassistant/components/homematicip_cloud/translations/de.json index c421620fd9..0718f62916 100644 --- a/homeassistant/components/homematicip_cloud/translations/de.json +++ b/homeassistant/components/homematicip_cloud/translations/de.json @@ -6,6 +6,7 @@ "unknown": "Ein unbekannter Fehler ist aufgetreten." }, "error": { + "invalid_pin": "Ung\u00fcltige PIN, bitte versuche es erneut.", "invalid_sgtin_or_pin": "Ung\u00fcltige PIN, bitte versuche es erneut.", "press_the_button": "Bitte dr\u00fccke die blaue Taste.", "register_failed": "Registrierung fehlgeschlagen, bitte versuche es erneut.", diff --git a/homeassistant/components/huawei_lte/translations/zh-Hans.json b/homeassistant/components/huawei_lte/translations/zh-Hans.json new file mode 100644 index 0000000000..2f49996410 --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "config": { + "error": { + "connection_failed": "\u8fde\u63a5\u5931\u8d25", + "incorrect_username": "\u7528\u6237\u540d\u9519\u8bef", + "incorrect_username_or_password": "\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/et.json b/homeassistant/components/hue/translations/et.json index 55b078a952..53695563f8 100644 --- a/homeassistant/components/hue/translations/et.json +++ b/homeassistant/components/hue/translations/et.json @@ -18,7 +18,7 @@ "data": { "host": "" }, - "title": "Valige Hue sild" + "title": "Vali Hue sild" }, "link": { "description": "Vajutage silla nuppu, et registreerida Philips Hue Home Assistant abil. \n\n ! [Nupu asukoht sillal] (/ static / images / config_philips_hue.jpg)", diff --git a/homeassistant/components/hue/translations/zh-Hans.json b/homeassistant/components/hue/translations/zh-Hans.json index f3b763c7b0..c34bd68aad 100644 --- a/homeassistant/components/hue/translations/zh-Hans.json +++ b/homeassistant/components/hue/translations/zh-Hans.json @@ -25,5 +25,10 @@ "title": "\u8fde\u63a5\u4e2d\u67a2" } } + }, + "device_automation": { + "trigger_subtype": { + "turn_off": "\u5173\u95ed" + } } } \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/pl.json b/homeassistant/components/humidifier/translations/pl.json index 9a2f14e5c7..af04941778 100644 --- a/homeassistant/components/humidifier/translations/pl.json +++ b/homeassistant/components/humidifier/translations/pl.json @@ -20,8 +20,8 @@ }, "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Nawil\u017cacz" diff --git a/homeassistant/components/humidifier/translations/zh-Hans.json b/homeassistant/components/humidifier/translations/zh-Hans.json index 0a6aa709bc..8fa6b0be0d 100644 --- a/homeassistant/components/humidifier/translations/zh-Hans.json +++ b/homeassistant/components/humidifier/translations/zh-Hans.json @@ -6,6 +6,13 @@ "toggle": "\u5207\u6362 {entity_name} \u5f00\u5173", "turn_off": "\u5173\u95ed {entity_name}", "turn_on": "\u6253\u5f00 {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} \u5df2\u5173\u95ed" + }, + "trigger_type": { + "turned_off": "{entity_name} \u88ab\u5173\u95ed", + "turned_on": "{entity_name} \u88ab\u6253\u5f00" } }, "state": { diff --git a/homeassistant/components/hvv_departures/translations/zh-Hans.json b/homeassistant/components/hvv_departures/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/zh-Hans.json b/homeassistant/components/iaqualink/translations/zh-Hans.json new file mode 100644 index 0000000000..d13c8a6b46 --- /dev/null +++ b/homeassistant/components/iaqualink/translations/zh-Hans.json @@ -0,0 +1,12 @@ +{ + "config": { + "error": { + "connection_failure": "\u65e0\u6cd5\u8fde\u63a5\u5230iAqualink\u3002\u68c0\u67e5\u60a8\u7684\u7528\u6237\u540d\u548c\u5bc6\u7801\u3002" + }, + "step": { + "user": { + "description": "\u8bf7\u8f93\u5165\u60a8\u7684iAqualink\u5e10\u6237\u7684\u7528\u6237\u540d\u548c\u5bc6\u7801\u3002" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/zh-Hans.json b/homeassistant/components/icloud/translations/zh-Hans.json index 0fed17b7ce..82927dc54b 100644 --- a/homeassistant/components/icloud/translations/zh-Hans.json +++ b/homeassistant/components/icloud/translations/zh-Hans.json @@ -9,6 +9,11 @@ "validate_verification_code": "\u65e0\u6cd5\u9a8c\u8bc1\u9a8c\u8bc1\u7801\uff0c\u8bf7\u9009\u62e9\u53d7\u4fe1\u4efb\u7684\u8bbe\u5907\u5e76\u91cd\u65b0\u5f00\u59cb\u9a8c\u8bc1" }, "step": { + "reauth": { + "data": { + "password": "\u5bc6\u7801" + } + }, "trusted_device": { "data": { "trusted_device": "\u53d7\u4fe1\u4efb\u7684\u8bbe\u5907" @@ -18,7 +23,8 @@ }, "user": { "data": { - "password": "\u5bc6\u7801" + "password": "\u5bc6\u7801", + "username": "\u90ae\u7bb1" }, "description": "\u8bf7\u8f93\u5165\u51ed\u636e", "title": "iCloud \u51ed\u636e" diff --git a/homeassistant/components/input_boolean/translations/pl.json b/homeassistant/components/input_boolean/translations/pl.json index 031c361aee..c50d198159 100644 --- a/homeassistant/components/input_boolean/translations/pl.json +++ b/homeassistant/components/input_boolean/translations/pl.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Pole warto\u015bci logicznej" diff --git a/homeassistant/components/insteon/translations/de.json b/homeassistant/components/insteon/translations/de.json index dfefff8c55..c7f2ad9431 100644 --- a/homeassistant/components/insteon/translations/de.json +++ b/homeassistant/components/insteon/translations/de.json @@ -33,6 +33,9 @@ } }, "options": { + "error": { + "select_single": "W\u00e4hle eine Option aus." + }, "step": { "add_override": { "title": "Insteon" diff --git a/homeassistant/components/insteon/translations/et.json b/homeassistant/components/insteon/translations/et.json index b7eab44fe3..33a6848f7e 100644 --- a/homeassistant/components/insteon/translations/et.json +++ b/homeassistant/components/insteon/translations/et.json @@ -60,7 +60,8 @@ "already_configured": "Insteoni modemi \u00fchendus on juba seadistatud" }, "error": { - "cannot_connect": "\u00dchendamine nurjus" + "cannot_connect": "\u00dchendamine nurjus", + "input_error": "Vigane sisestus, kontrolli andmeid" }, "step": { "add_override": { diff --git a/homeassistant/components/insteon/translations/zh-Hans.json b/homeassistant/components/insteon/translations/zh-Hans.json new file mode 100644 index 0000000000..eb9facef17 --- /dev/null +++ b/homeassistant/components/insteon/translations/zh-Hans.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "hub2": { + "data": { + "username": "\u7528\u6237\u540d" + } + }, + "hubv2": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/zh-Hans.json b/homeassistant/components/ipp/translations/zh-Hans.json new file mode 100644 index 0000000000..254f6df932 --- /dev/null +++ b/homeassistant/components/ipp/translations/zh-Hans.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/zh-Hans.json b/homeassistant/components/isy994/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/isy994/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/de.json b/homeassistant/components/kodi/translations/de.json index ac98f2016b..d8db0e8613 100644 --- a/homeassistant/components/kodi/translations/de.json +++ b/homeassistant/components/kodi/translations/de.json @@ -6,12 +6,17 @@ "data": { "password": "Passwort", "username": "Benutzername" - } + }, + "description": "Bitte gib deinen Kodi-Benutzernamen und Passwort ein. Diese findest du unter System/Einstellungen/Netzwerk/Dienste." + }, + "discovery_confirm": { + "description": "M\u00f6chtest du Kodi (` {name} `) zu Home Assistant hinzuf\u00fcgen?" }, "host": { "data": { "host": "Host", - "port": "Port" + "port": "Port", + "ssl": "\u00dcber SSL verbinden" } }, "user": { diff --git a/homeassistant/components/kodi/translations/zh-Hans.json b/homeassistant/components/kodi/translations/zh-Hans.json new file mode 100644 index 0000000000..6fe91b6e99 --- /dev/null +++ b/homeassistant/components/kodi/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "credentials": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 6d6d03d9af..43e8664272 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -52,7 +52,8 @@ "poll_interval": "Poll interval (minuten) (optioneel)", "type": "Type sensor" }, - "description": "{zone} opties" + "description": "{zone} opties", + "title": "Configureer digitale sensor" }, "options_io": { "data": { @@ -90,11 +91,13 @@ }, "options_switch": { "data": { + "momentary": "Pulsduur (ms) (optioneel)", "more_states": "Aanvullende statussen voor deze zone configureren", "name": "Naam (optioneel)", "pause": "Pauze tussen de pulsen (ms) (optioneel)", "repeat": "Aantal herhalingen (-1=oneindig) (optioneel)" }, + "description": "{zone} opties: status {state}", "title": "Schakelbare uitgang configureren" } } diff --git a/homeassistant/components/life360/translations/zh-Hans.json b/homeassistant/components/life360/translations/zh-Hans.json index 9495323aa1..14e6d90786 100644 --- a/homeassistant/components/life360/translations/zh-Hans.json +++ b/homeassistant/components/life360/translations/zh-Hans.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "invalid_auth": "\u65e0\u6548\u7684\u8eab\u4efd\u9a8c\u8bc1" + }, "error": { + "invalid_auth": "\u65e0\u6548\u7684\u8eab\u4efd\u9a8c\u8bc1", "invalid_credentials": "\u65e0\u6548\u7684\u8eab\u4efd\u8ba4\u8bc1", "invalid_username": "\u65e0\u6548\u7684\u7528\u6237\u540d" }, diff --git a/homeassistant/components/light/translations/pl.json b/homeassistant/components/light/translations/pl.json index 3bb887b8d6..3b26bfcafd 100644 --- a/homeassistant/components/light/translations/pl.json +++ b/homeassistant/components/light/translations/pl.json @@ -19,8 +19,8 @@ }, "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "\u015awiat\u0142o" diff --git a/homeassistant/components/media_player/translations/pl.json b/homeassistant/components/media_player/translations/pl.json index 8d4f42a1a6..865d2e1df2 100644 --- a/homeassistant/components/media_player/translations/pl.json +++ b/homeassistant/components/media_player/translations/pl.json @@ -11,8 +11,8 @@ "state": { "_": { "idle": "nieaktywny", - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony", + "off": "wy\u0142.", + "on": "w\u0142.", "paused": "wstrzymany", "playing": "odtwarzanie", "standby": "tryb czuwania" diff --git a/homeassistant/components/meteo_france/translations/de.json b/homeassistant/components/meteo_france/translations/de.json index 1c05beac36..65313f16c4 100644 --- a/homeassistant/components/meteo_france/translations/de.json +++ b/homeassistant/components/meteo_france/translations/de.json @@ -4,7 +4,16 @@ "already_configured": "Stadt bereits konfiguriert", "unknown": "Unbekannter Fehler: Bitte versuchen Sie es sp\u00e4ter erneut" }, + "error": { + "empty": "Kein Ergebnis bei der Stadtsuche: Bitte \u00fcberpr\u00fcfe das Stadtfeld" + }, "step": { + "cities": { + "data": { + "city": "Stadt" + }, + "description": "W\u00e4hle deine Stadt aus der Liste" + }, "user": { "data": { "city": "Stadt" diff --git a/homeassistant/components/metoffice/translations/zh-Hans.json b/homeassistant/components/metoffice/translations/zh-Hans.json new file mode 100644 index 0000000000..597bfc38a8 --- /dev/null +++ b/homeassistant/components/metoffice/translations/zh-Hans.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "unknown": "\u672a\u77e5\u9519\u8bef" + }, + "step": { + "user": { + "data": { + "latitude": "\u7eac\u5ea6", + "longitude": "\u7ecf\u5ea6" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/zh-Hans.json b/homeassistant/components/mill/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/mill/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/zh-Hans.json b/homeassistant/components/mqtt/translations/zh-Hans.json index 4a9b860f87..e508f2cb29 100644 --- a/homeassistant/components/mqtt/translations/zh-Hans.json +++ b/homeassistant/components/mqtt/translations/zh-Hans.json @@ -47,5 +47,14 @@ "button_short_release": "\"{subtype}\" \u91ca\u653e", "button_triple_press": "\"{subtype}\" \u4e09\u8fde\u51fb" } + }, + "options": { + "step": { + "broker": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/myq/translations/zh-Hans.json b/homeassistant/components/myq/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/myq/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/et.json b/homeassistant/components/neato/translations/et.json index e761939220..8eed7850b4 100644 --- a/homeassistant/components/neato/translations/et.json +++ b/homeassistant/components/neato/translations/et.json @@ -2,10 +2,12 @@ "config": { "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", - "invalid_auth": "Tuvastamise viga" + "invalid_auth": "Tuvastamise viga", + "invalid_credentials": "Sobimatu mandaat" }, "error": { "invalid_auth": "Tuvastamise viga", + "invalid_credentials": "Sobimatu mandaat", "unexpected_error": "Ootamatu t\u00f5rge", "unknown": "Ootamatu t\u00f5rge" }, diff --git a/homeassistant/components/neato/translations/zh-Hans.json b/homeassistant/components/neato/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/neato/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/ca.json b/homeassistant/components/nest/translations/ca.json index 8f96d0f635..ecb1532934 100644 --- a/homeassistant/components/nest/translations/ca.json +++ b/homeassistant/components/nest/translations/ca.json @@ -8,6 +8,9 @@ "no_flows": "Necessites configurar Nest abans de poder autenticar-t'hi. Llegeix les [instruccions](https://www.home-assistant.io/components/nest/).", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, "error": { "internal_error": "Error intern al validar el codi", "invalid_code": "Codi inv\u00e0lid", @@ -29,6 +32,9 @@ }, "description": "Per enlla\u00e7ar el teu compte de Nest, [autoritza el teu compte]({url}). \n\nDespr\u00e9s de l'autoritzaci\u00f3, copia i enganxa el codi pin que es mostra a sota.", "title": "Enlla\u00e7 amb el compte de Nest" + }, + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" } } } diff --git a/homeassistant/components/nest/translations/cs.json b/homeassistant/components/nest/translations/cs.json index 15a1e041ac..dd9240270a 100644 --- a/homeassistant/components/nest/translations/cs.json +++ b/homeassistant/components/nest/translations/cs.json @@ -8,6 +8,9 @@ "no_flows": "Pot\u0159ebujete nakonfigurovat Nest, abyste se s n\u00edm mohli autentizovat. [P\u0159e\u010dt\u011bte si pros\u00edm pokyny] (https://www.home-assistant.io/components/nest/).", "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." }, + "create_entry": { + "default": "\u00dasp\u011b\u0161n\u011b ov\u011b\u0159eno" + }, "error": { "internal_error": "Intern\u00ed chyba ov\u011b\u0159en\u00ed k\u00f3du", "invalid_code": "Neplatn\u00fd k\u00f3d", @@ -29,6 +32,9 @@ }, "description": "Chcete-li propojit \u00fa\u010det Nest, [autorizujte sv\u016fj \u00fa\u010det]({url}). \n\n Po autorizaci zkop\u00edrujte n\u00ed\u017ee uveden\u00fd k\u00f3d PIN.", "title": "Propojit s Nest \u00fa\u010dtem" + }, + "pick_implementation": { + "title": "Vyberte metodu ov\u011b\u0159en\u00ed" } } } diff --git a/homeassistant/components/nest/translations/en.json b/homeassistant/components/nest/translations/en.json index bbe7d9dad9..4243926a55 100644 --- a/homeassistant/components/nest/translations/en.json +++ b/homeassistant/components/nest/translations/en.json @@ -8,6 +8,9 @@ "no_flows": "You need to configure Nest before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/nest/).", "single_instance_allowed": "Already configured. Only a single configuration possible." }, + "create_entry": { + "default": "Successfully authenticated" + }, "error": { "internal_error": "Internal error validating code", "invalid_code": "Invalid code", @@ -29,6 +32,9 @@ }, "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided pin code below.", "title": "Link Nest Account" + }, + "pick_implementation": { + "title": "Pick Authentication Method" } } } diff --git a/homeassistant/components/nest/translations/et.json b/homeassistant/components/nest/translations/et.json index 3280e6e038..1389bbb5c1 100644 --- a/homeassistant/components/nest/translations/et.json +++ b/homeassistant/components/nest/translations/et.json @@ -29,6 +29,9 @@ }, "description": "Nest-i konto linkimiseks [authorize your account] ({url}).\n\nP\u00e4rast autoriseerimist kopeeri allolev PIN kood.", "title": "Lingi Nesti konto" + }, + "pick_implementation": { + "title": "Vali tuvastusmeetod" } } } diff --git a/homeassistant/components/nest/translations/pl.json b/homeassistant/components/nest/translations/pl.json index dbfcdb982c..3b9f87a3f7 100644 --- a/homeassistant/components/nest/translations/pl.json +++ b/homeassistant/components/nest/translations/pl.json @@ -8,6 +8,9 @@ "no_flows": "Musisz skonfigurowa\u0107 Nest, aby m\u00f3c si\u0119 z nim uwierzytelni\u0107. Zapoznaj si\u0119 z [instrukcj\u0105](https://www.home-assistant.io/components/nest/).", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, "error": { "internal_error": "Wewn\u0119trzny b\u0142\u0105d sprawdzania poprawno\u015bci kodu", "invalid_code": "Nieprawid\u0142owy kod", @@ -29,6 +32,9 @@ }, "description": "Aby po\u0142\u0105czy\u0107 z kontem Nest, [wykonaj autoryzacj\u0119]({url}). \n\n Po autoryzacji skopiuj i wklej podany kod PIN poni\u017cej.", "title": "Po\u0142\u0105czenie z kontem Nest" + }, + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" } } } diff --git a/homeassistant/components/nest/translations/ru.json b/homeassistant/components/nest/translations/ru.json index c9461ccbf3..82c50bab5e 100644 --- a/homeassistant/components/nest/translations/ru.json +++ b/homeassistant/components/nest/translations/ru.json @@ -8,6 +8,9 @@ "no_flows": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 Nest \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/nest/).", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, + "create_entry": { + "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, "error": { "internal_error": "\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u043e\u0434\u0430.", "invalid_code": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434.", @@ -29,6 +32,9 @@ }, "description": "[\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c]({url}), \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Nest. \n\n \u041f\u043e\u0441\u043b\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u0430\u0433\u0430\u0435\u043c\u044b\u0439 \u043f\u0438\u043d-\u043a\u043e\u0434.", "title": "\u041f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Nest" + }, + "pick_implementation": { + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" } } } diff --git a/homeassistant/components/nest/translations/zh-Hans.json b/homeassistant/components/nest/translations/zh-Hans.json index ce0824075b..55d610219a 100644 --- a/homeassistant/components/nest/translations/zh-Hans.json +++ b/homeassistant/components/nest/translations/zh-Hans.json @@ -9,6 +9,7 @@ "error": { "internal_error": "\u9a8c\u8bc1\u4ee3\u7801\u65f6\u53d1\u751f\u5185\u90e8\u9519\u8bef", "invalid_code": "\u9a8c\u8bc1\u7801\u65e0\u6548", + "invalid_pin": "PIN\u7801\u65e0\u6548", "timeout": "\u9a8c\u8bc1\u7801\u8d85\u65f6", "unknown": "\u9a8c\u8bc1\u7801\u672a\u77e5\u9519\u8bef" }, diff --git a/homeassistant/components/nest/translations/zh-Hant.json b/homeassistant/components/nest/translations/zh-Hant.json index 9bf75cc6fa..87f8b62427 100644 --- a/homeassistant/components/nest/translations/zh-Hant.json +++ b/homeassistant/components/nest/translations/zh-Hant.json @@ -8,6 +8,9 @@ "no_flows": "\u5fc5\u9808\u5148\u8a2d\u5b9a Nest \u65b9\u80fd\u9032\u884c\u8a8d\u8b49\u3002[\u8acb\u53c3\u95b1\u6559\u5b78\u6307\u5f15](https://www.home-assistant.io/components/nest/)\u3002", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, "error": { "internal_error": "\u8a8d\u8b49\u78bc\u5167\u90e8\u932f\u8aa4", "invalid_code": "\u8a8d\u8b49\u78bc\u7121\u6548", @@ -29,6 +32,9 @@ }, "description": "\u6b32\u9023\u7d50 Nest \u5e33\u865f\uff0c[\u8a8d\u8b49\u5e33\u865f]({url}).\n\n\u65bc\u8a8d\u8b49\u5f8c\uff0c\u8907\u88fd\u4e26\u8cbc\u4e0a\u4e0b\u65b9\u7684\u8a8d\u8b49\u78bc\u3002", "title": "\u9023\u7d50 Nest \u5e33\u865f" + }, + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" } } } diff --git a/homeassistant/components/netatmo/translations/et.json b/homeassistant/components/netatmo/translations/et.json index c0f672dd18..7e3a8ad5a8 100644 --- a/homeassistant/components/netatmo/translations/et.json +++ b/homeassistant/components/netatmo/translations/et.json @@ -3,6 +3,11 @@ "abort": { "no_url_available": "URL pole saadaval. Rohkem teavet [check the help section]({docs_url})", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } } }, "options": { diff --git a/homeassistant/components/nexia/translations/zh-Hans.json b/homeassistant/components/nexia/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/nexia/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/zh-Hans.json b/homeassistant/components/nuheat/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/nuheat/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/zh-Hans.json b/homeassistant/components/nut/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/nut/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/de.json b/homeassistant/components/nzbget/translations/de.json index 6aa89d2e62..2ebae1083e 100644 --- a/homeassistant/components/nzbget/translations/de.json +++ b/homeassistant/components/nzbget/translations/de.json @@ -9,7 +9,8 @@ "password": "Passwort", "port": "Port", "username": "Benutzername" - } + }, + "title": "Mit NZBGet verbinden" } } }, diff --git a/homeassistant/components/nzbget/translations/zh-Hans.json b/homeassistant/components/nzbget/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/nzbget/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/zh-Hans.json b/homeassistant/components/omnilogic/translations/zh-Hans.json new file mode 100644 index 0000000000..b0b26b0226 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/zh-Hans.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u5bc6\u7801", + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/zh-Hans.json b/homeassistant/components/onvif/translations/zh-Hans.json index c5269fccbd..b6ad5c30a7 100644 --- a/homeassistant/components/onvif/translations/zh-Hans.json +++ b/homeassistant/components/onvif/translations/zh-Hans.json @@ -1,4 +1,13 @@ { + "config": { + "step": { + "auth": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + }, "options": { "step": { "onvif_devices": { diff --git a/homeassistant/components/openweathermap/translations/de.json b/homeassistant/components/openweathermap/translations/de.json index 6582b2046b..964fdbb941 100644 --- a/homeassistant/components/openweathermap/translations/de.json +++ b/homeassistant/components/openweathermap/translations/de.json @@ -1,7 +1,8 @@ { "config": { "error": { - "auth": "Der API-Schl\u00fcssel ist nicht korrekt." + "auth": "Der API-Schl\u00fcssel ist nicht korrekt.", + "connection": "Es kann keine Verbindung zur OWM-API hergestellt werden" }, "step": { "user": { @@ -9,7 +10,8 @@ "language": "Sprache", "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", - "mode": "Modus" + "mode": "Modus", + "name": "Name der Integration" }, "title": "OpenWeatherMap" } diff --git a/homeassistant/components/ovo_energy/translations/zh-Hans.json b/homeassistant/components/ovo_energy/translations/zh-Hans.json new file mode 100644 index 0000000000..a7477e8c37 --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/zh-Hans.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_auth": "\u9a8c\u8bc1\u7801\u9519\u8bef" + }, + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/nl.json b/homeassistant/components/plex/translations/nl.json index 2450e51060..218e11ac76 100644 --- a/homeassistant/components/plex/translations/nl.json +++ b/homeassistant/components/plex/translations/nl.json @@ -38,6 +38,7 @@ "step": { "plex_mp_settings": { "data": { + "ignore_new_shared_users": "Negeer nieuwe beheerde/gedeelde gebruikers", "use_episode_art": "Gebruik aflevering kunst" }, "description": "Opties voor Plex-mediaspelers" diff --git a/homeassistant/components/plugwise/translations/de.json b/homeassistant/components/plugwise/translations/de.json index 02b46f7511..43ea3c8e3b 100644 --- a/homeassistant/components/plugwise/translations/de.json +++ b/homeassistant/components/plugwise/translations/de.json @@ -21,5 +21,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Scanintervall (Sekunden)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/zh-Hans.json b/homeassistant/components/plugwise/translations/zh-Hans.json new file mode 100644 index 0000000000..80c129af78 --- /dev/null +++ b/homeassistant/components/plugwise/translations/zh-Hans.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user_gateway": { + "data": { + "host": "IP\u5730\u5740", + "port": "\u7aef\u53e3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/zh-Hans.json b/homeassistant/components/plum_lightpad/translations/zh-Hans.json new file mode 100644 index 0000000000..6a9d4aa28c --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u90ae\u7bb1" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/et.json b/homeassistant/components/point/translations/et.json index b9573cda58..d019b68773 100644 --- a/homeassistant/components/point/translations/et.json +++ b/homeassistant/components/point/translations/et.json @@ -15,7 +15,7 @@ "step": { "user": { "description": "Kas alustan seadistamist?", - "title": "Vali tuvastsmeetod" + "title": "Vali tuvastusmeetod" } } } diff --git a/homeassistant/components/ps4/translations/zh-Hans.json b/homeassistant/components/ps4/translations/zh-Hans.json index 821cb0ed05..770a3d8953 100644 --- a/homeassistant/components/ps4/translations/zh-Hans.json +++ b/homeassistant/components/ps4/translations/zh-Hans.json @@ -8,6 +8,7 @@ "port_997_bind_error": "\u65e0\u6cd5\u7ed1\u5b9a\u7aef\u53e3 997\u3002" }, "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", "login_failed": "\u65e0\u6cd5\u4e0e PlayStation 4 \u914d\u5bf9\u3002\u8bf7\u786e\u8ba4 PIN \u662f\u5426\u6b63\u786e\u3002", "not_ready": "PlayStation 4 \u672a\u5f00\u673a\u6216\u672a\u8fde\u63a5\u5230\u7f51\u7edc\u3002" }, diff --git a/homeassistant/components/rainmachine/translations/et.json b/homeassistant/components/rainmachine/translations/et.json index 201733b2a6..1b17200b76 100644 --- a/homeassistant/components/rainmachine/translations/et.json +++ b/homeassistant/components/rainmachine/translations/et.json @@ -5,7 +5,8 @@ }, "error": { "identifier_exists": "Konto on juba seadistatud", - "invalid_auth": "Tuvastamise viga" + "invalid_auth": "Tuvastamise viga", + "invalid_credentials": "Sobimatu mandaat" }, "step": { "user": { diff --git a/homeassistant/components/remote/translations/pl.json b/homeassistant/components/remote/translations/pl.json index 608e80c30b..3c319ff548 100644 --- a/homeassistant/components/remote/translations/pl.json +++ b/homeassistant/components/remote/translations/pl.json @@ -16,8 +16,8 @@ }, "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Pilot" diff --git a/homeassistant/components/remote/translations/zh-Hans.json b/homeassistant/components/remote/translations/zh-Hans.json index 70ab64a957..ba1344fbb7 100644 --- a/homeassistant/components/remote/translations/zh-Hans.json +++ b/homeassistant/components/remote/translations/zh-Hans.json @@ -1,4 +1,17 @@ { + "device_automation": { + "action_type": { + "turn_off": "\u5173\u95ed {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} \u5df2\u5173\u95ed", + "is_on": "{entity_name} \u5df2\u6253\u5f00" + }, + "trigger_type": { + "turned_off": "{entity_name} \u88ab\u5173\u95ed", + "turned_on": "{entity_name} \u88ab\u6253\u5f00" + } + }, "state": { "_": { "off": "\u5173", diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json index cbd7d2f3ae..c4b378db21 100644 --- a/homeassistant/components/rfxtrx/translations/de.json +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -4,6 +4,9 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "step": { + "setup_network": { + "title": "Verbindungsadresse ausw\u00e4hlen" + }, "setup_serial": { "data": { "device": "Ger\u00e4t w\u00e4hlen" @@ -20,5 +23,14 @@ "title": "Verbindungstyp ausw\u00e4hlen" } } + }, + "options": { + "step": { + "prompt_options": { + "data": { + "debug": "Debugging aktivieren" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/zh-Hans.json b/homeassistant/components/rfxtrx/translations/zh-Hans.json new file mode 100644 index 0000000000..49e9260783 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "setup_network": { + "data": { + "port": "\u7aef\u53e3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/zh-Hans.json b/homeassistant/components/ring/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/ring/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/zh-Hans.json b/homeassistant/components/risco/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/risco/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/de.json b/homeassistant/components/roon/translations/de.json index b73dc4d644..00f85d6c1b 100644 --- a/homeassistant/components/roon/translations/de.json +++ b/homeassistant/components/roon/translations/de.json @@ -1,3 +1,8 @@ { + "config": { + "error": { + "duplicate_entry": "Dieser Host wurde bereits hinzugef\u00fcgt." + } + }, "title": "Roon" } \ No newline at end of file diff --git a/homeassistant/components/ruckus_unleashed/translations/zh-Hans.json b/homeassistant/components/ruckus_unleashed/translations/zh-Hans.json new file mode 100644 index 0000000000..b6a239d7b9 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/zh-Hans.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u7801", + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/script/translations/pl.json b/homeassistant/components/script/translations/pl.json index 8982527645..625b8cf9d9 100644 --- a/homeassistant/components/script/translations/pl.json +++ b/homeassistant/components/script/translations/pl.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Skrypt" diff --git a/homeassistant/components/sensor/translations/pl.json b/homeassistant/components/sensor/translations/pl.json index 8d355cc5d9..aa2d9f4ca1 100644 --- a/homeassistant/components/sensor/translations/pl.json +++ b/homeassistant/components/sensor/translations/pl.json @@ -33,8 +33,8 @@ }, "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Sensor" diff --git a/homeassistant/components/sharkiq/translations/zh-Hans.json b/homeassistant/components/sharkiq/translations/zh-Hans.json new file mode 100644 index 0000000000..7a170396ca --- /dev/null +++ b/homeassistant/components/sharkiq/translations/zh-Hans.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "reauth": { + "data": { + "username": "\u7528\u6237\u540d" + } + }, + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/ca.json b/homeassistant/components/shelly/translations/ca.json index 41ffcb34b8..6df9210269 100644 --- a/homeassistant/components/shelly/translations/ca.json +++ b/homeassistant/components/shelly/translations/ca.json @@ -10,7 +10,7 @@ "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "confirm_discovery": { "description": "Vols configurar {model} a {host}? \n\nAbans de configurar-lo, el dispositiu amb bateria ha d'estar despert, prem el bot\u00f3 del dispositiu per despertar-lo." diff --git a/homeassistant/components/shelly/translations/cs.json b/homeassistant/components/shelly/translations/cs.json index 3f477c3567..3f9397d5cc 100644 --- a/homeassistant/components/shelly/translations/cs.json +++ b/homeassistant/components/shelly/translations/cs.json @@ -10,7 +10,7 @@ "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "confirm_discovery": { "description": "Chcete nastavit {model} na {host}?\n\nP\u0159ed nastaven\u00edm mus\u00ed b\u00fdt za\u0159\u00edzen\u00ed nap\u00e1jen\u00e9 z baterie probuzeno stisknut\u00edm tla\u010d\u00edtka na dan\u00e9m za\u0159\u00edzen\u00ed." diff --git a/homeassistant/components/shelly/translations/pl.json b/homeassistant/components/shelly/translations/pl.json index 688ac6ed7e..c59ca67753 100644 --- a/homeassistant/components/shelly/translations/pl.json +++ b/homeassistant/components/shelly/translations/pl.json @@ -10,7 +10,7 @@ "invalid_auth": "Niepoprawne uwierzytelnienie", "unknown": "Nieoczekiwany b\u0142\u0105d" }, - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "confirm_discovery": { "description": "Czy chcesz skonfigurowa\u0107 {model} ({host})?\n\nPrzed konfiguracj\u0105 urz\u0105dzenie zasilane bateryjnie nale\u017cy wybudzi\u0107, naciskaj\u0105c przycisk na urz\u0105dzeniu." diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index 24478afe0a..47b4ad27c7 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/translations/ru.json @@ -10,7 +10,7 @@ "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "confirm_discovery": { "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c {model} ({host}) ?\n\n\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0435 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0438\u0437 \u0441\u043f\u044f\u0449\u0435\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430, \u043d\u0430\u0436\u0430\u0432 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435." diff --git a/homeassistant/components/shelly/translations/zh-Hans.json b/homeassistant/components/shelly/translations/zh-Hans.json new file mode 100644 index 0000000000..6fe91b6e99 --- /dev/null +++ b/homeassistant/components/shelly/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "credentials": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/et.json b/homeassistant/components/simplisafe/translations/et.json index 36178300e0..95a24352db 100644 --- a/homeassistant/components/simplisafe/translations/et.json +++ b/homeassistant/components/simplisafe/translations/et.json @@ -6,6 +6,7 @@ }, "error": { "invalid_auth": "Tuvastamise viga", + "invalid_credentials": "Sobimatu mandaat", "unknown": "Tundmatu viga" }, "step": { diff --git a/homeassistant/components/simplisafe/translations/zh-Hans.json b/homeassistant/components/simplisafe/translations/zh-Hans.json index 721ce3a94f..e4bb147aea 100644 --- a/homeassistant/components/simplisafe/translations/zh-Hans.json +++ b/homeassistant/components/simplisafe/translations/zh-Hans.json @@ -2,6 +2,7 @@ "config": { "error": { "identifier_exists": "\u8d26\u6237\u5df2\u6ce8\u518c", + "invalid_auth": "\u65e0\u6548\u7684\u8eab\u4efd\u9a8c\u8bc1", "invalid_credentials": "\u65e0\u6548\u7684\u8eab\u4efd\u8ba4\u8bc1" }, "step": { diff --git a/homeassistant/components/smappee/translations/et.json b/homeassistant/components/smappee/translations/et.json index 1a2b1dd95a..b57bd9b81f 100644 --- a/homeassistant/components/smappee/translations/et.json +++ b/homeassistant/components/smappee/translations/et.json @@ -5,6 +5,7 @@ "already_configured_local_device": "Kohalikud seadmed on juba seadistatud. Enne pilveseadme seadistamist eemaldage need.", "cannot_connect": "\u00dchendamine nurjus", "connection_error": "Smappee seadmega \u00fchenduse loomine nurjus.", + "invalid_mdns": "Seade ei toeta Smappee'ga sidumist.", "missing_configuration": "Osis pole seadistatud. Palun vaata dokumentatsiooni.", "no_url_available": "URL pole saadaval. Rohkem teavet [check the help section]({docs_url})" }, @@ -15,6 +16,9 @@ }, "description": "Smappee kohaliku sidumise algatamiseks sisestage hostinimi" }, + "pick_implementation": { + "title": "Vali tuvastusmeetod" + }, "zeroconf_confirm": { "description": "Kas soovid lisada Home Assistanti Smappee seadme seerianumbriga \" {serial_number} \"?" } diff --git a/homeassistant/components/smart_meter_texas/translations/zh-Hans.json b/homeassistant/components/smart_meter_texas/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/et.json b/homeassistant/components/somfy/translations/et.json index fe64d4d412..184faad8f4 100644 --- a/homeassistant/components/somfy/translations/et.json +++ b/homeassistant/components/somfy/translations/et.json @@ -4,6 +4,11 @@ "already_setup": "Saad h\u00e4\u00e4lestada ainult \u00fche Somfy konto.", "no_url_available": "URL pole saadaval. Rohkem teavet [check the help section]({docs_url})", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } } } } \ No newline at end of file diff --git a/homeassistant/components/spider/translations/zh-Hans.json b/homeassistant/components/spider/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/spider/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/et.json b/homeassistant/components/spotify/translations/et.json index 04d29ccd0a..3403292db0 100644 --- a/homeassistant/components/spotify/translations/et.json +++ b/homeassistant/components/spotify/translations/et.json @@ -12,7 +12,7 @@ }, "step": { "pick_implementation": { - "title": "Valige autentimismeetod" + "title": "Vali tuvastusmeetod" }, "reauth_confirm": { "description": "Spotify konto: {account} sidumine tuleb uuesti autentida", diff --git a/homeassistant/components/squeezebox/translations/zh-Hans.json b/homeassistant/components/squeezebox/translations/zh-Hans.json new file mode 100644 index 0000000000..56359f4930 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "edit": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/zh-Hans.json b/homeassistant/components/starline/translations/zh-Hans.json new file mode 100644 index 0000000000..980744e116 --- /dev/null +++ b/homeassistant/components/starline/translations/zh-Hans.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "error_auth_user": "\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef" + }, + "step": { + "auth_user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switch/translations/pl.json b/homeassistant/components/switch/translations/pl.json index 4b3aebf30b..0d0d7263bf 100644 --- a/homeassistant/components/switch/translations/pl.json +++ b/homeassistant/components/switch/translations/pl.json @@ -16,8 +16,8 @@ }, "state": { "_": { - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony" + "off": "wy\u0142.", + "on": "w\u0142." } }, "title": "Prze\u0142\u0105cznik" diff --git a/homeassistant/components/switch/translations/zh-Hans.json b/homeassistant/components/switch/translations/zh-Hans.json index eed1df4620..8820cb9e43 100644 --- a/homeassistant/components/switch/translations/zh-Hans.json +++ b/homeassistant/components/switch/translations/zh-Hans.json @@ -1,4 +1,16 @@ { + "device_automation": { + "action_type": { + "turn_off": "\u5173\u95ed {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} \u5df2\u5173\u95ed" + }, + "trigger_type": { + "turned_off": "{entity_name} \u88ab\u5173\u95ed", + "turned_on": "{entity_name} \u88ab\u6253\u5f00" + } + }, "state": { "_": { "off": "\u5173", diff --git a/homeassistant/components/syncthru/translations/et.json b/homeassistant/components/syncthru/translations/et.json index a6dd582004..71fe5cfb03 100644 --- a/homeassistant/components/syncthru/translations/et.json +++ b/homeassistant/components/syncthru/translations/et.json @@ -4,6 +4,7 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud" }, "error": { + "invalid_url": "Sobimatu URL", "unknown_state": "Printeri olek teadmata, kontrolli URL-i ja v\u00f5rgu\u00fchendust" } } diff --git a/homeassistant/components/synology_dsm/translations/de.json b/homeassistant/components/synology_dsm/translations/de.json index f54a7e2a37..205bf5197b 100644 --- a/homeassistant/components/synology_dsm/translations/de.json +++ b/homeassistant/components/synology_dsm/translations/de.json @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "Minuten zwischen den Scans" + "scan_interval": "Minuten zwischen den Scans", + "timeout": "Timeout (Sekunden)" } } } diff --git a/homeassistant/components/tado/translations/zh-Hans.json b/homeassistant/components/tado/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/tado/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/zh-Hans.json b/homeassistant/components/tibber/translations/zh-Hans.json new file mode 100644 index 0000000000..2941dfd938 --- /dev/null +++ b/homeassistant/components/tibber/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/zh-Hans.json b/homeassistant/components/tile/translations/zh-Hans.json new file mode 100644 index 0000000000..6a9d4aa28c --- /dev/null +++ b/homeassistant/components/tile/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u90ae\u7bb1" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/zh-Hans.json b/homeassistant/components/totalconnect/translations/zh-Hans.json new file mode 100644 index 0000000000..1d0c0eb107 --- /dev/null +++ b/homeassistant/components/totalconnect/translations/zh-Hans.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "login": "\u767b\u5f55\u9519\u8bef\uff1a\u8bf7\u68c0\u67e5\u7528\u6237\u540d\u548c\u5bc6\u7801" + }, + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/zh-Hans.json b/homeassistant/components/transmission/translations/zh-Hans.json new file mode 100644 index 0000000000..feebc8ea8a --- /dev/null +++ b/homeassistant/components/transmission/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "wrong_credentials": "\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/zh-Hans.json b/homeassistant/components/tuya/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/tuya/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/zh-Hans.json b/homeassistant/components/twentemilieu/translations/zh-Hans.json index 80301cfd57..507422c735 100644 --- a/homeassistant/components/twentemilieu/translations/zh-Hans.json +++ b/homeassistant/components/twentemilieu/translations/zh-Hans.json @@ -2,6 +2,10 @@ "config": { "abort": { "address_exists": "\u5730\u5740\u5df2\u7ecf\u8bbe\u7f6e\u597d\u4e86\u3002" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "connection_error": "\u8fde\u63a5\u5931\u8d25\u3002" } } } \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index f945e5c4d6..9c4804c09a 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -54,7 +54,8 @@ "data": { "block_client": "Cli\u00ebnten met netwerktoegang", "track_clients": "Volg netwerkclients" - } + }, + "description": "Configureer UniFi-integratie" }, "statistics_sensors": { "data": { diff --git a/homeassistant/components/upcloud/translations/de.json b/homeassistant/components/upcloud/translations/de.json index 6f39806287..ffdd1e0dd5 100644 --- a/homeassistant/components/upcloud/translations/de.json +++ b/homeassistant/components/upcloud/translations/de.json @@ -8,5 +8,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Aktualisierungsintervall in Sekunden, mindestens 30" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/upcloud/translations/zh-Hans.json b/homeassistant/components/upcloud/translations/zh-Hans.json new file mode 100644 index 0000000000..83be90bc29 --- /dev/null +++ b/homeassistant/components/upcloud/translations/zh-Hans.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "invalid_auth": "\u65e0\u6548\u7684\u8eab\u4efd\u9a8c\u8bc1" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u7801", + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vacuum/translations/pl.json b/homeassistant/components/vacuum/translations/pl.json index 6eef0f893f..27a1dfb150 100644 --- a/homeassistant/components/vacuum/translations/pl.json +++ b/homeassistant/components/vacuum/translations/pl.json @@ -19,8 +19,8 @@ "docked": "w stacji dokuj\u0105cej", "error": "b\u0142\u0105d", "idle": "nieaktywny", - "off": "wy\u0142\u0105czony", - "on": "w\u0142\u0105czony", + "off": "wy\u0142.", + "on": "w\u0142.", "paused": "wstrzymany", "returning": "powr\u00f3t do stacji dokuj\u0105cej" } diff --git a/homeassistant/components/vesync/translations/zh-Hans.json b/homeassistant/components/vesync/translations/zh-Hans.json index caa00f36c8..e256110ffa 100644 --- a/homeassistant/components/vesync/translations/zh-Hans.json +++ b/homeassistant/components/vesync/translations/zh-Hans.json @@ -2,6 +2,14 @@ "config": { "abort": { "already_setup": "\u53ea\u5141\u8bb8\u4e00\u4e2aVesync\u5b9e\u4f8b" + }, + "error": { + "invalid_login": "\u7528\u6237\u540d\u6216\u5bc6\u7801\u65e0\u6548" + }, + "step": { + "user": { + "title": "\u8f93\u5165\u7528\u6237\u540d\u548c\u5bc6\u7801" + } } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/zh-Hans.json b/homeassistant/components/water_heater/translations/zh-Hans.json new file mode 100644 index 0000000000..21a77facc9 --- /dev/null +++ b/homeassistant/components/water_heater/translations/zh-Hans.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "\u5173\u95ed {entity_name}", + "turn_on": "\u6253\u5f00 {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/zh-Hans.json b/homeassistant/components/wled/translations/zh-Hans.json new file mode 100644 index 0000000000..254f6df932 --- /dev/null +++ b/homeassistant/components/wled/translations/zh-Hans.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.de.json b/homeassistant/components/wolflink/translations/sensor.de.json index ef60c1c1ae..373e198957 100644 --- a/homeassistant/components/wolflink/translations/sensor.de.json +++ b/homeassistant/components/wolflink/translations/sensor.de.json @@ -1,6 +1,13 @@ { "state": { "wolflink__state": { + "solarbetrieb": "Solarmodus", + "sparbetrieb": "Sparmodus", + "sparen": "Sparen", + "stabilisierung": "Stabilisierung", + "standby": "Standby", + "start": "Start", + "storung": "Fehler", "test": "Test", "tpw": "TPW" } diff --git a/homeassistant/components/wolflink/translations/sensor.pl.json b/homeassistant/components/wolflink/translations/sensor.pl.json index a0894f3881..176f0c49b5 100644 --- a/homeassistant/components/wolflink/translations/sensor.pl.json +++ b/homeassistant/components/wolflink/translations/sensor.pl.json @@ -9,7 +9,7 @@ "antilegionellenfunktion": "funkcja antylegionella", "at_abschaltung": "granica wy\u0142\u0105czenia temperatura zewn\u0119trzna", "at_frostschutz": "ochrona antyzamro\u017ceniowa AT", - "aus": "wy\u0142\u0105czone", + "aus": "wy\u0142\u0105czony", "auto": "automatyczny", "auto_off_cool": "AutoOffCool", "auto_on_cool": "AutoOnCool", diff --git a/homeassistant/components/wolflink/translations/zh-Hans.json b/homeassistant/components/wolflink/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/wolflink/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/de.json b/homeassistant/components/yeelight/translations/de.json index 6930fca0a5..b282a4d8b8 100644 --- a/homeassistant/components/yeelight/translations/de.json +++ b/homeassistant/components/yeelight/translations/de.json @@ -18,6 +18,8 @@ "step": { "init": { "data": { + "save_on_change": "Status bei \u00c4nderung speichern", + "transition": "\u00dcbergangszeit (ms)", "use_music_mode": "Musik-Modus aktivieren" } } diff --git a/homeassistant/components/zha/translations/zh-Hans.json b/homeassistant/components/zha/translations/zh-Hans.json index 7a841edbb3..4df88473d0 100644 --- a/homeassistant/components/zha/translations/zh-Hans.json +++ b/homeassistant/components/zha/translations/zh-Hans.json @@ -11,5 +11,13 @@ "title": "ZHA" } } + }, + "device_automation": { + "trigger_subtype": { + "turn_off": "\u5173\u95ed" + }, + "trigger_type": { + "device_offline": "\u8bbe\u5907\u79bb\u7ebf" + } } } \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/zh-Hans.json b/homeassistant/components/zoneminder/translations/zh-Hans.json new file mode 100644 index 0000000000..a5f4ff11f0 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file From fcbe90cb80db8905a0177129b386bef79a908fdd Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Thu, 22 Oct 2020 10:06:16 +0200 Subject: [PATCH 009/118] Fix precision of climate devices in devolo Home Control (#41832) --- .../components/devolo_home_control/climate.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/devolo_home_control/climate.py b/homeassistant/components/devolo_home_control/climate.py index f243b5c508..0cc2340dc1 100644 --- a/homeassistant/components/devolo_home_control/climate.py +++ b/homeassistant/components/devolo_home_control/climate.py @@ -9,7 +9,7 @@ from homeassistant.components.climate import ( ClimateEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PRECISION_HALVES +from homeassistant.const import PRECISION_HALVES, PRECISION_TENTHS from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN @@ -60,9 +60,14 @@ class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntit @property def target_temperature(self) -> Optional[float]: - """Return the current temperature.""" + """Return the target temperature.""" return self._value + @property + def target_temperature_step(self) -> float: + """Return the precision of the target temperature.""" + return PRECISION_HALVES + @property def hvac_mode(self) -> str: """Return the supported HVAC mode.""" @@ -86,7 +91,7 @@ class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntit @property def precision(self) -> float: """Return the precision of the set temperature.""" - return PRECISION_HALVES + return PRECISION_TENTHS @property def supported_features(self): From 6c5e2af379f8a7818d7960a56973f9d24c45cf95 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Thu, 22 Oct 2020 10:01:43 +0200 Subject: [PATCH 010/118] Add Netatmo sensor enable default (#41928) --- homeassistant/components/netatmo/sensor.py | 62 ++++++++++++++-------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 9cecc19683..84e18ba724 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -48,39 +48,53 @@ SUPPORTED_PUBLIC_SENSOR_TYPES = [ ] SENSOR_TYPES = { - "temperature": ["Temperature", TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE], - "temp_trend": ["Temperature trend", None, "mdi:trending-up", None], - "co2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, "mdi:molecule-co2", None], - "pressure": ["Pressure", PRESSURE_MBAR, None, DEVICE_CLASS_PRESSURE], - "pressure_trend": ["Pressure trend", None, "mdi:trending-up", None], - "noise": ["Noise", "dB", "mdi:volume-high", None], - "humidity": ["Humidity", PERCENTAGE, None, DEVICE_CLASS_HUMIDITY], - "rain": ["Rain", LENGTH_MILLIMETERS, "mdi:weather-rainy", None], - "sum_rain_1": ["Rain last hour", LENGTH_MILLIMETERS, "mdi:weather-rainy", None], - "sum_rain_24": ["Rain today", LENGTH_MILLIMETERS, "mdi:weather-rainy", None], - "battery_percent": ["Battery Percent", PERCENTAGE, None, DEVICE_CLASS_BATTERY], - "windangle": ["Direction", None, "mdi:compass-outline", None], - "windangle_value": ["Angle", DEGREE, "mdi:compass-outline", None], + "temperature": ["Temperature", TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE, True], + "temp_trend": ["Temperature trend", None, "mdi:trending-up", None, False], + "co2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, "mdi:molecule-co2", None, True], + "pressure": ["Pressure", PRESSURE_MBAR, None, DEVICE_CLASS_PRESSURE, True], + "pressure_trend": ["Pressure trend", None, "mdi:trending-up", None, False], + "noise": ["Noise", "dB", "mdi:volume-high", None, True], + "humidity": ["Humidity", PERCENTAGE, None, DEVICE_CLASS_HUMIDITY, True], + "rain": ["Rain", LENGTH_MILLIMETERS, "mdi:weather-rainy", None, True], + "sum_rain_1": [ + "Rain last hour", + LENGTH_MILLIMETERS, + "mdi:weather-rainy", + None, + False, + ], + "sum_rain_24": ["Rain today", LENGTH_MILLIMETERS, "mdi:weather-rainy", None, True], + "battery_percent": [ + "Battery Percent", + PERCENTAGE, + None, + DEVICE_CLASS_BATTERY, + True, + ], + "windangle": ["Direction", None, "mdi:compass-outline", None, True], + "windangle_value": ["Angle", DEGREE, "mdi:compass-outline", None, False], "windstrength": [ "Wind Strength", SPEED_KILOMETERS_PER_HOUR, "mdi:weather-windy", None, + True, ], - "gustangle": ["Gust Direction", None, "mdi:compass-outline", None], - "gustangle_value": ["Gust Angle", DEGREE, "mdi:compass-outline", None], + "gustangle": ["Gust Direction", None, "mdi:compass-outline", None, False], + "gustangle_value": ["Gust Angle", DEGREE, "mdi:compass-outline", None, False], "guststrength": [ "Gust Strength", SPEED_KILOMETERS_PER_HOUR, "mdi:weather-windy", None, + False, ], - "reachable": ["Reachability", None, "mdi:signal", None], - "rf_status": ["Radio", None, "mdi:signal", None], - "rf_status_lvl": ["Radio Level", "", None, DEVICE_CLASS_SIGNAL_STRENGTH], - "wifi_status": ["Wifi", None, "mdi:wifi", None], - "wifi_status_lvl": ["Wifi Level", "dBm", None, DEVICE_CLASS_SIGNAL_STRENGTH], - "health_idx": ["Health", None, "mdi:cloud", None], + "reachable": ["Reachability", None, "mdi:signal", None, False], + "rf_status": ["Radio", None, "mdi:signal", None, False], + "rf_status_lvl": ["Radio Level", "", None, DEVICE_CLASS_SIGNAL_STRENGTH, False], + "wifi_status": ["Wifi", None, "mdi:wifi", None, False], + "wifi_status_lvl": ["Wifi Level", "dBm", None, DEVICE_CLASS_SIGNAL_STRENGTH, False], + "health_idx": ["Health", None, "mdi:cloud", None, True], } MODULE_TYPE_OUTDOOR = "NAModule1" @@ -257,6 +271,7 @@ class NetatmoSensor(NetatmoBase): self._unit_of_measurement = SENSOR_TYPES[self.type][1] self._model = device["type"] self._unique_id = f"{self._id}-{self.type}" + self._enabled_default = SENSOR_TYPES[self.type][4] @property def icon(self): @@ -283,6 +298,11 @@ class NetatmoSensor(NetatmoBase): """Return entity availability.""" return self._state is not None + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return self._enabled_default + @callback def async_update_callback(self): """Update the entity's state.""" From 1fb1bb0c08b2cd2db3cf743667ad933e9264e49b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 23 Oct 2020 01:22:51 +0200 Subject: [PATCH 011/118] Support reloading Tasmota config entries (#42097) --- homeassistant/components/tasmota/__init__.py | 51 +++++++++++++-- .../components/tasmota/binary_sensor.py | 6 +- homeassistant/components/tasmota/const.py | 9 +++ .../components/tasmota/device_automation.py | 5 +- homeassistant/components/tasmota/discovery.py | 64 ++++++------------- homeassistant/components/tasmota/light.py | 6 +- .../components/tasmota/manifest.json | 2 +- homeassistant/components/tasmota/mixins.py | 6 ++ homeassistant/components/tasmota/sensor.py | 6 +- homeassistant/components/tasmota/switch.py | 6 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/conftest.py | 1 + tests/components/tasmota/test_discovery.py | 25 ++++---- tests/components/tasmota/test_sensor.py | 63 +++++++++++++++++- 15 files changed, 179 insertions(+), 75 deletions(-) diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py index 1e11aff448..5754b98b71 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -1,4 +1,5 @@ """The Tasmota integration.""" +import asyncio import logging from hatasmota.const import ( @@ -21,8 +22,8 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType -from . import discovery -from .const import CONF_DISCOVERY_PREFIX +from . import device_automation, discovery +from .const import CONF_DISCOVERY_PREFIX, DATA_REMOVE_DISCOVER_COMPONENT, PLATFORMS from .discovery import TASMOTA_DISCOVERY_DEVICE _LOGGER = logging.getLogger(__name__) @@ -54,14 +55,52 @@ async def async_setup_entry(hass, entry): tasmota_mqtt = TasmotaMQTTClient(_publish, _subscribe_topics, _unsubscribe_topics) - discovery_prefix = entry.data[CONF_DISCOVERY_PREFIX] - await discovery.async_start(hass, discovery_prefix, entry, tasmota_mqtt) - async def async_discover_device(config, mac): """Discover and add a Tasmota device.""" await async_setup_device(hass, mac, config, entry, tasmota_mqtt) - async_dispatcher_connect(hass, TASMOTA_DISCOVERY_DEVICE, async_discover_device) + hass.data[ + DATA_REMOVE_DISCOVER_COMPONENT.format("device") + ] = async_dispatcher_connect(hass, TASMOTA_DISCOVERY_DEVICE, async_discover_device) + + async def start_platforms(): + await device_automation.async_setup_entry(hass, entry) + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_setup(entry, component) + for component in PLATFORMS + ] + ) + + discovery_prefix = entry.data[CONF_DISCOVERY_PREFIX] + await discovery.async_start(hass, discovery_prefix, entry, tasmota_mqtt) + + hass.async_create_task(start_platforms()) + return True + + +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + + # cleanup platforms + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if not unload_ok: + return False + + # disable discovery + await discovery.async_stop(hass) + hass.data.pop(DEVICE_MACS) + hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format("device")]() + hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format("device_automation"))() + for component in PLATFORMS: + hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format(component))() return True diff --git a/homeassistant/components/tasmota/binary_sensor.py b/homeassistant/components/tasmota/binary_sensor.py index 549452c194..ade309840c 100644 --- a/homeassistant/components/tasmota/binary_sensor.py +++ b/homeassistant/components/tasmota/binary_sensor.py @@ -6,7 +6,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.helpers.event as evt -from .const import DOMAIN as TASMOTA_DOMAIN +from .const import DATA_REMOVE_DISCOVER_COMPONENT, DOMAIN as TASMOTA_DOMAIN from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW from .mixins import TasmotaAvailability, TasmotaDiscoveryUpdate @@ -25,7 +25,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ] ) - async_dispatcher_connect( + hass.data[ + DATA_REMOVE_DISCOVER_COMPONENT.format(binary_sensor.DOMAIN) + ] = async_dispatcher_connect( hass, TASMOTA_DISCOVERY_ENTITY_NEW.format(binary_sensor.DOMAIN, TASMOTA_DOMAIN), async_discover, diff --git a/homeassistant/components/tasmota/const.py b/homeassistant/components/tasmota/const.py index 11f43ed38b..7cddeb7a60 100644 --- a/homeassistant/components/tasmota/const.py +++ b/homeassistant/components/tasmota/const.py @@ -1,8 +1,17 @@ """Constants used by multiple Tasmota modules.""" CONF_DISCOVERY_PREFIX = "discovery_prefix" +DATA_REMOVE_DISCOVER_COMPONENT = "tasmota_discover_{}" + DEFAULT_PREFIX = "tasmota/discovery" DOMAIN = "tasmota" +PLATFORMS = [ + "binary_sensor", + "light", + "sensor", + "switch", +] + TASMOTA_EVENT = "tasmota_event" diff --git a/homeassistant/components/tasmota/device_automation.py b/homeassistant/components/tasmota/device_automation.py index 6bbd8ce6d8..e921a186fe 100644 --- a/homeassistant/components/tasmota/device_automation.py +++ b/homeassistant/components/tasmota/device_automation.py @@ -6,6 +6,7 @@ from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import device_trigger +from .const import DATA_REMOVE_DISCOVER_COMPONENT from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW @@ -25,7 +26,9 @@ async def async_setup_entry(hass, config_entry): hass, tasmota_automation, config_entry, discovery_hash ) - async_dispatcher_connect( + hass.data[ + DATA_REMOVE_DISCOVER_COMPONENT.format("device_automation") + ] = async_dispatcher_connect( hass, TASMOTA_DISCOVERY_ENTITY_NEW.format("device_automation", "tasmota"), async_discover, diff --git a/homeassistant/components/tasmota/discovery.py b/homeassistant/components/tasmota/discovery.py index e137692445..6d224a9870 100644 --- a/homeassistant/components/tasmota/discovery.py +++ b/homeassistant/components/tasmota/discovery.py @@ -1,5 +1,4 @@ """Support for Tasmota device discovery.""" -import asyncio import logging from hatasmota.discovery import ( @@ -9,7 +8,6 @@ from hatasmota.discovery import ( get_entity as tasmota_get_entity, get_trigger as tasmota_get_trigger, get_triggers as tasmota_get_triggers, - has_entities_with_platform as tasmota_has_entities_with_platform, unique_id_from_hash, ) @@ -18,23 +16,15 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN +from .const import DOMAIN, PLATFORMS _LOGGER = logging.getLogger(__name__) -SUPPORTED_PLATFORMS = [ - "binary_sensor", - "light", - "sensor", - "switch", -] - ALREADY_DISCOVERED = "tasmota_discovered_components" -CONFIG_ENTRY_IS_SETUP = "tasmota_config_entry_is_setup" -DATA_CONFIG_ENTRY_LOCK = "tasmota_config_entry_lock" TASMOTA_DISCOVERY_DEVICE = "tasmota_discovery_device" TASMOTA_DISCOVERY_ENTITY_NEW = "tasmota_discovery_entity_new_{}" TASMOTA_DISCOVERY_ENTITY_UPDATED = "tasmota_discovery_entity_updated_{}_{}_{}_{}" +TASMOTA_DISCOVERY_INSTANCE = "tasmota_discovery_instance" def clear_discovery_hash(hass, discovery_hash): @@ -52,16 +42,6 @@ async def async_start( ) -> bool: """Start Tasmota device discovery.""" - async def _load_platform(platform): - """Load a Tasmota platform if not already done.""" - async with hass.data[DATA_CONFIG_ENTRY_LOCK]: - config_entries_key = f"{platform}.tasmota" - if config_entries_key not in hass.data[CONFIG_ENTRY_IS_SETUP]: - await hass.config_entries.async_forward_entry_setup( - config_entry, platform - ) - hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key) - async def _discover_entity(tasmota_entity_config, discovery_hash, platform): """Handle adding or updating a discovered entity.""" if not tasmota_entity_config: @@ -86,8 +66,13 @@ async def async_start( tasmota_entity_config, ) else: - _LOGGER.debug("Adding new entity: %s %s", platform, discovery_hash) tasmota_entity = tasmota_get_entity(tasmota_entity_config, tasmota_mqtt) + _LOGGER.debug( + "Adding new entity: %s %s %s", + platform, + discovery_hash, + tasmota_entity.unique_id, + ) hass.data[ALREADY_DISCOVERED][discovery_hash] = None @@ -102,7 +87,8 @@ async def async_start( """Process the received message.""" if ALREADY_DISCOVERED not in hass.data: - hass.data[ALREADY_DISCOVERED] = {} + # Discovery is shutting down + return _LOGGER.debug("Received discovery data for tasmota device: %s", mac) tasmota_device_config = tasmota_get_device_config(payload) @@ -114,17 +100,6 @@ async def async_start( return tasmota_triggers = tasmota_get_triggers(payload) - async with hass.data[DATA_CONFIG_ENTRY_LOCK]: - if any(trigger.is_active for trigger in tasmota_triggers): - config_entries_key = "device_automation.tasmota" - if config_entries_key not in hass.data[CONFIG_ENTRY_IS_SETUP]: - # Local import to avoid circular dependencies - # pylint: disable=import-outside-toplevel - from . import device_automation - - await device_automation.async_setup_entry(hass, config_entry) - hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key) - for trigger_config in tasmota_triggers: discovery_hash = (mac, "automation", "trigger", trigger_config.trigger_id) if discovery_hash in hass.data[ALREADY_DISCOVERED]: @@ -150,12 +125,7 @@ async def async_start( discovery_hash, ) - for platform in SUPPORTED_PLATFORMS: - if not tasmota_has_entities_with_platform(payload, platform): - continue - await _load_platform(platform) - - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: tasmota_entities = tasmota_get_entities_for_platform(payload, platform) for (tasmota_entity_config, discovery_hash) in tasmota_entities: await _discover_entity(tasmota_entity_config, discovery_hash, platform) @@ -163,7 +133,6 @@ async def async_start( async def async_sensors_discovered(sensors, mac): """Handle discovery of (additional) sensors.""" platform = sensor.DOMAIN - await _load_platform(platform) device_registry = await hass.helpers.device_registry.async_get_registry() entity_registry = await hass.helpers.entity_registry.async_get_registry() @@ -188,10 +157,17 @@ async def async_start( _LOGGER.debug("Removing entity: %s %s", platform, entity_id) entity_registry.async_remove(entity_id) - hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock() - hass.data[CONFIG_ENTRY_IS_SETUP] = set() + hass.data[ALREADY_DISCOVERED] = {} tasmota_discovery = TasmotaDiscovery(discovery_topic, tasmota_mqtt) await tasmota_discovery.start_discovery( async_device_discovered, async_sensors_discovered ) + hass.data[TASMOTA_DISCOVERY_INSTANCE] = tasmota_discovery + + +async def async_stop(hass: HomeAssistantType) -> bool: + """Stop Tasmota device discovery.""" + hass.data.pop(ALREADY_DISCOVERED) + tasmota_discovery = hass.data.pop(TASMOTA_DISCOVERY_INSTANCE) + await tasmota_discovery.stop_discovery() diff --git a/homeassistant/components/tasmota/light.py b/homeassistant/components/tasmota/light.py index 00cedc4a61..c761d3436d 100644 --- a/homeassistant/components/tasmota/light.py +++ b/homeassistant/components/tasmota/light.py @@ -27,7 +27,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.color as color_util -from .const import DOMAIN as TASMOTA_DOMAIN +from .const import DATA_REMOVE_DISCOVER_COMPONENT, DOMAIN as TASMOTA_DOMAIN from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW from .mixins import TasmotaAvailability, TasmotaDiscoveryUpdate @@ -45,7 +45,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): [TasmotaLight(tasmota_entity=tasmota_entity, discovery_hash=discovery_hash)] ) - async_dispatcher_connect( + hass.data[ + DATA_REMOVE_DISCOVER_COMPONENT.format(light.DOMAIN) + ] = async_dispatcher_connect( hass, TASMOTA_DISCOVERY_ENTITY_NEW.format(light.DOMAIN, TASMOTA_DOMAIN), async_discover, diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 4157c416ec..b64d10c107 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.0.19"], + "requirements": ["hatasmota==0.0.20"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/tasmota/mixins.py b/homeassistant/components/tasmota/mixins.py index 13e511e4a8..3b52fafd12 100644 --- a/homeassistant/components/tasmota/mixins.py +++ b/homeassistant/components/tasmota/mixins.py @@ -153,6 +153,12 @@ class TasmotaDiscoveryUpdate(TasmotaEntity): ) ) + @callback + def add_to_platform_abort(self) -> None: + """Abort adding an entity to a platform.""" + clear_discovery_hash(self.hass, self._discovery_hash) + super().add_to_platform_abort() + async def async_will_remove_from_hass(self) -> None: """Stop listening to signal and cleanup discovery data..""" if not self._removed_from_hass: diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 660c0efc1e..076ca457a2 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -58,7 +58,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from .const import DOMAIN as TASMOTA_DOMAIN +from .const import DATA_REMOVE_DISCOVER_COMPONENT, DOMAIN as TASMOTA_DOMAIN from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW from .mixins import TasmotaAvailability, TasmotaDiscoveryUpdate @@ -123,7 +123,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ] ) - async_dispatcher_connect( + hass.data[ + DATA_REMOVE_DISCOVER_COMPONENT.format(sensor.DOMAIN) + ] = async_dispatcher_connect( hass, TASMOTA_DISCOVERY_ENTITY_NEW.format(sensor.DOMAIN, TASMOTA_DOMAIN), async_discover_sensor, diff --git a/homeassistant/components/tasmota/switch.py b/homeassistant/components/tasmota/switch.py index fe8f4b9555..0a97a5e252 100644 --- a/homeassistant/components/tasmota/switch.py +++ b/homeassistant/components/tasmota/switch.py @@ -5,7 +5,7 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .const import DOMAIN as TASMOTA_DOMAIN +from .const import DATA_REMOVE_DISCOVER_COMPONENT, DOMAIN as TASMOTA_DOMAIN from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW from .mixins import TasmotaAvailability, TasmotaDiscoveryUpdate @@ -24,7 +24,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ] ) - async_dispatcher_connect( + hass.data[ + DATA_REMOVE_DISCOVER_COMPONENT.format(switch.DOMAIN) + ] = async_dispatcher_connect( hass, TASMOTA_DISCOVERY_ENTITY_NEW.format(switch.DOMAIN, TASMOTA_DOMAIN), async_discover, diff --git a/requirements_all.txt b/requirements_all.txt index bf515d1d99..56b17074a4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -732,7 +732,7 @@ hass-nabucasa==0.37.1 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.0.19 +hatasmota==0.0.20 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5e7e53ef33..8ca3780f66 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ hangups==0.4.11 hass-nabucasa==0.37.1 # homeassistant.components.tasmota -hatasmota==0.0.19 +hatasmota==0.0.20 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/tests/components/tasmota/conftest.py b/tests/components/tasmota/conftest.py index 456d157547..09a3bfa4d6 100644 --- a/tests/components/tasmota/conftest.py +++ b/tests/components/tasmota/conftest.py @@ -72,6 +72,7 @@ async def setup_tasmota_helper(hass): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() assert "tasmota" in hass.config.components diff --git a/tests/components/tasmota/test_discovery.py b/tests/components/tasmota/test_discovery.py index f337b06283..607676615a 100644 --- a/tests/components/tasmota/test_discovery.py +++ b/tests/components/tasmota/test_discovery.py @@ -27,40 +27,41 @@ async def test_valid_discovery_message(hass, mqtt_mock, caplog): config = copy.deepcopy(DEFAULT_CONFIG) with patch( - "homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform" - ) as mock_tasmota_has_entities: + "homeassistant.components.tasmota.discovery.tasmota_get_device_config", + return_value={}, + ) as mock_tasmota_get_device_config: await setup_tasmota_helper(hass) async_fire_mqtt_message( hass, f"{DEFAULT_PREFIX}/00000049A3BC/config", json.dumps(config) ) await hass.async_block_till_done() - assert mock_tasmota_has_entities.called + assert mock_tasmota_get_device_config.called async def test_invalid_topic(hass, mqtt_mock): """Test receiving discovery message on wrong topic.""" with patch( - "homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform" - ) as mock_tasmota_has_entities: + "homeassistant.components.tasmota.discovery.tasmota_get_device_config" + ) as mock_tasmota_get_device_config: await setup_tasmota_helper(hass) async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/123456/configuration", "{}") await hass.async_block_till_done() - assert not mock_tasmota_has_entities.called + assert not mock_tasmota_get_device_config.called async def test_invalid_message(hass, mqtt_mock, caplog): """Test receiving an invalid message.""" with patch( - "homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform" - ) as mock_tasmota_has_entities: + "homeassistant.components.tasmota.discovery.tasmota_get_device_config" + ) as mock_tasmota_get_device_config: await setup_tasmota_helper(hass) async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/123456/config", "asd") await hass.async_block_till_done() assert "Invalid discovery message" in caplog.text - assert not mock_tasmota_has_entities.called + assert not mock_tasmota_get_device_config.called async def test_invalid_mac(hass, mqtt_mock, caplog): @@ -68,8 +69,8 @@ async def test_invalid_mac(hass, mqtt_mock, caplog): config = copy.deepcopy(DEFAULT_CONFIG) with patch( - "homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform" - ) as mock_tasmota_has_entities: + "homeassistant.components.tasmota.discovery.tasmota_get_device_config" + ) as mock_tasmota_get_device_config: await setup_tasmota_helper(hass) async_fire_mqtt_message( @@ -77,7 +78,7 @@ async def test_invalid_mac(hass, mqtt_mock, caplog): ) await hass.async_block_till_done() assert "MAC mismatch" in caplog.text - assert not mock_tasmota_has_entities.called + assert not mock_tasmota_get_device_config.called async def test_correct_config_discovery( diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index 859c0f735e..ab8498ed04 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -1,5 +1,6 @@ """The tests for the Tasmota sensor platform.""" import copy +from datetime import timedelta import json from hatasmota.utils import ( @@ -9,9 +10,11 @@ from hatasmota.utils import ( ) import pytest +from homeassistant import config_entries from homeassistant.components import sensor from homeassistant.components.tasmota.const import DEFAULT_PREFIX from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNKNOWN +from homeassistant.util import dt from .test_common import ( DEFAULT_CONFIG, @@ -27,7 +30,7 @@ from .test_common import ( ) from tests.async_mock import patch -from tests.common import async_fire_mqtt_message +from tests.common import async_fire_mqtt_message, async_fire_time_changed DEFAULT_SENSOR_CONFIG = { "sn": { @@ -377,6 +380,64 @@ async def test_indexed_sensor_attributes(hass, mqtt_mock, setup_tasmota): assert state.attributes.get("unit_of_measurement") == "ppm" +@pytest.mark.parametrize("status_sensor_disabled", [False]) +async def test_enable_status_sensor(hass, mqtt_mock, setup_tasmota): + """Test enabling status sensor.""" + entity_reg = await hass.helpers.entity_registry.async_get_registry() + + config = copy.deepcopy(DEFAULT_CONFIG) + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get("sensor.tasmota_status") + assert state is None + entry = entity_reg.async_get("sensor.tasmota_status") + assert entry.disabled + assert entry.disabled_by == "integration" + + # Enable the status sensor + updated_entry = entity_reg.async_update_entity( + "sensor.tasmota_status", disabled_by=None + ) + assert updated_entry != entry + assert updated_entry.disabled is False + await hass.async_block_till_done() + + async_fire_time_changed( + hass, + dt.utcnow() + + timedelta( + seconds=config_entries.EntityRegistryDisabledHandler.RELOAD_AFTER_UPDATE_DELAY + + 1 + ), + ) + await hass.async_block_till_done() + + # Fake re-send of retained discovery message + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + state = hass.states.get("sensor.tasmota_status") + assert state.state == "unavailable" + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") + state = hass.states.get("sensor.tasmota_status") + assert state.state == STATE_UNKNOWN + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async def test_availability_when_connection_lost( hass, mqtt_client_mock, mqtt_mock, setup_tasmota ): From e0621d2e5f380c95849c4757fc26edd6222fef93 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 22 Oct 2020 03:20:17 -0400 Subject: [PATCH 012/118] Set Vizio unique ID for discovery flow early and abort if configured to prevent duplicate discovery flows (#42194) --- homeassistant/components/vizio/config_flow.py | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 74fc0d746e..f3b7720a33 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -206,29 +206,29 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: Dict[str, Any] = None ) -> Dict[str, Any]: """Handle a flow initialized by the user.""" + assert self.hass errors = {} if user_input is not None: # Store current values in case setup fails and user needs to edit self._user_schema = _get_config_schema(user_input) - unique_id = await VizioAsync.get_unique_id( - user_input[CONF_HOST], - user_input[CONF_DEVICE_CLASS], - session=async_get_clientsession(self.hass, False), - ) - - if not unique_id: - errors[CONF_HOST] = "cannot_connect" - else: - # Set unique ID and abort if a flow with the same unique ID is already in progress - existing_entry = await self.async_set_unique_id( - unique_id=unique_id, raise_on_progress=True + if self.unique_id is None: + unique_id = await VizioAsync.get_unique_id( + user_input[CONF_HOST], + user_input[CONF_DEVICE_CLASS], + session=async_get_clientsession(self.hass, False), ) - # If device was discovered, abort if existing entry found, otherwise display an error - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - if self.context["source"] == SOURCE_ZEROCONF: - self._abort_if_unique_id_configured() - elif existing_entry: + + # Check if unique ID was found, set unique ID, and abort if a flow with + # the same unique ID is already in progress + if not unique_id: + errors[CONF_HOST] = "cannot_connect" + elif ( + await self.async_set_unique_id( + unique_id=unique_id, raise_on_progress=True + ) + is not None + ): errors[CONF_HOST] = "existing_config_entry_found" if not errors: @@ -346,6 +346,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, discovery_info: Optional[DiscoveryInfoType] = None ) -> Dict[str, Any]: """Handle zeroconf discovery.""" + assert self.hass discovery_info[ CONF_HOST @@ -360,6 +361,15 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): discovery_info[CONF_HOST] ) + # Set unique ID early for discovery flow so we can abort if needed + unique_id = await VizioAsync.get_unique_id( + discovery_info[CONF_HOST], + discovery_info[CONF_DEVICE_CLASS], + session=async_get_clientsession(self.hass, False), + ) + await self.async_set_unique_id(unique_id=unique_id, raise_on_progress=True) + self._abort_if_unique_id_configured() + # Form must be shown after discovery so user can confirm/update configuration # before ConfigEntry creation. self._must_show_form = True From 3586e0c5e415e4796a0892a5265908b06e15ecc6 Mon Sep 17 00:00:00 2001 From: Niccolo Zapponi Date: Thu, 22 Oct 2020 17:01:10 +0100 Subject: [PATCH 013/118] Fix bug for SimpliSafe 2 systems repeatedly saying "your settings have been synchronised" (#42197) * Fix bug for SimpliSafe 2 systems * Improved loop and logging for SimpliSafe 2 --- .../components/simplisafe/__init__.py | 2 +- .../components/simplisafe/binary_sensor.py | 8 +++++-- homeassistant/components/simplisafe/sensor.py | 21 +++++++++++-------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index e81559bd1f..a3f56a9e1a 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -500,7 +500,7 @@ class SimpliSafe: async def async_update_system(system): """Update a system.""" - await system.update(cached=False) + await system.update(cached=system.version != 3) self._async_process_new_notifications(system) tasks = [async_update_system(system) for system in self.systems.values()] diff --git a/homeassistant/components/simplisafe/binary_sensor.py b/homeassistant/components/simplisafe/binary_sensor.py index cd6a446526..263bbf51b1 100644 --- a/homeassistant/components/simplisafe/binary_sensor.py +++ b/homeassistant/components/simplisafe/binary_sensor.py @@ -12,7 +12,7 @@ from homeassistant.components.binary_sensor import ( from homeassistant.core import callback from . import SimpliSafeEntity -from .const import DATA_CLIENT, DOMAIN +from .const import DATA_CLIENT, DOMAIN, LOGGER SUPPORTED_BATTERY_SENSOR_TYPES = [ EntityTypes.carbon_monoxide, @@ -48,9 +48,13 @@ SENSOR_MODELS = { async def async_setup_entry(hass, entry, async_add_entities): """Set up SimpliSafe binary sensors based on a config entry.""" simplisafe = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] - sensors = [] + for system in simplisafe.systems.values(): + if system.version == 2: + LOGGER.info("Skipping sensor setup for V2 system: %s", system.system_id) + continue + for sensor in system.sensors.values(): if sensor.type in SUPPORTED_SENSOR_TYPES: sensors.append(SimpliSafeBinarySensor(simplisafe, system, sensor)) diff --git a/homeassistant/components/simplisafe/sensor.py b/homeassistant/components/simplisafe/sensor.py index fd6f1121af..5909471c45 100644 --- a/homeassistant/components/simplisafe/sensor.py +++ b/homeassistant/components/simplisafe/sensor.py @@ -5,21 +5,24 @@ from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT from homeassistant.core import callback from . import SimpliSafeEntity -from .const import DATA_CLIENT, DOMAIN +from .const import DATA_CLIENT, DOMAIN, LOGGER async def async_setup_entry(hass, entry, async_add_entities): """Set up SimpliSafe freeze sensors based on a config entry.""" simplisafe = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] + sensors = [] - async_add_entities( - [ - SimplisafeFreezeSensor(simplisafe, system, sensor) - for system in simplisafe.systems.values() - for sensor in system.sensors.values() - if sensor.type == EntityTypes.temperature - ] - ) + for system in simplisafe.systems.values(): + if system.version == 2: + LOGGER.info("Skipping sensor setup for V2 system: %s", system.system_id) + continue + + for sensor in system.sensors.values(): + if sensor.type == EntityTypes.temperature: + sensors.append(SimplisafeFreezeSensor(simplisafe, system, sensor)) + + async_add_entities(sensors) class SimplisafeFreezeSensor(SimpliSafeEntity): From 65eb5ea55fdd0bbedf612bcfd240712a376d7949 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Thu, 22 Oct 2020 16:04:06 -0400 Subject: [PATCH 014/118] Fix issue in python > 3.8.2 (#42213) --- homeassistant/components/insteon/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index b0e6b8d10a..0188671e07 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -159,7 +159,7 @@ async def async_setup_entry(hass, entry): identifiers={(DOMAIN, str(devices.modem.address))}, manufacturer="Smart Home", name=f"{devices.modem.description} {devices.modem.address}", - model=f"{devices.modem.model} (0x{devices.modem.cat:02x}, 0x{devices.modem.subcat:02x})", + model=f"{devices.modem.model} ({devices.modem.cat!r}, 0x{devices.modem.subcat:02x})", sw_version=f"{devices.modem.firmware:02x} Engine Version: {devices.modem.engine_version}", ) From d00ca7bf039e197dba51e2b55ada5099eee6e924 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Thu, 22 Oct 2020 22:30:21 +0200 Subject: [PATCH 015/118] Bump bimmer-connected to 0.7.8 to fix login issue (#42215) Co-authored-by: rikroe --- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index c7cacfb6f6..bd76294c8e 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.7.7"], + "requirements": ["bimmer_connected==0.7.8"], "dependencies": [], "codeowners": ["@gerard33", "@rikroe"] } diff --git a/requirements_all.txt b/requirements_all.txt index 56b17074a4..869653d56c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -342,7 +342,7 @@ beautifulsoup4==4.9.1 bellows==0.20.3 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.7 +bimmer_connected==0.7.8 # homeassistant.components.bizkaibus bizkaibus==0.1.1 From 4f908bc58cded9ce85205abcbaced122c8bc5163 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 23 Oct 2020 00:11:02 +0200 Subject: [PATCH 016/118] Refresh now() templates on second=0 (#42225) --- homeassistant/helpers/event.py | 14 ++++++++------ tests/helpers/test_event.py | 12 +++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index e15d3ed90e..b4caccf531 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -792,11 +792,13 @@ class _TrackTemplateResultInfo: @callback def _setup_time_listener(self, template: Template, has_time: bool) -> None: - if template in self._time_listeners: - self._time_listeners.pop(template)() - - # now() or utcnow() has left the scope of the template if not has_time: + if template in self._time_listeners: + # now() or utcnow() has left the scope of the template + self._time_listeners.pop(template)() + return + + if template in self._time_listeners: return track_templates = [ @@ -809,8 +811,8 @@ class _TrackTemplateResultInfo: def _refresh_from_time(now: datetime) -> None: self._refresh(None, track_templates=track_templates) - self._time_listeners[template] = async_call_later( - self.hass, 60.45, _refresh_from_time + self._time_listeners[template] = async_track_utc_time_change( + self.hass, _refresh_from_time, second=0 ) @callback diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index bd90edb20e..9f6e66048f 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -2172,15 +2172,17 @@ async def test_track_template_with_time_that_leaves_scope(hass): "time": True, } - # Verify we do not update a second time - # if the state change happens + # Verify we do not update before the minute rolls over callback_count_before_time_change = len(specific_runs) - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=59)) + test_time = dt_util.utcnow().replace(second=1) + async_fire_time_changed(hass, test_time) + await hass.async_block_till_done() + async_fire_time_changed(hass, test_time + timedelta(seconds=58)) await hass.async_block_till_done() assert len(specific_runs) == callback_count_before_time_change - # Verify we do update on the next time change - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=61)) + # Verify we do update on the next change of minute + async_fire_time_changed(hass, test_time + timedelta(seconds=59)) await hass.async_block_till_done() assert len(specific_runs) == callback_count_before_time_change + 1 From 69cace1d41977e748a162061cbedc2c3aa58d7f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 22 Oct 2020 18:28:22 -0500 Subject: [PATCH 017/118] Ensure websocket event serializer cache is effective if subscription iden differs (#42226) Since someone websocket subscriptions will use an iden of 2 for state_changed event (most comment), and some will use another number for all events, the cache would not be used because the iden number was different. We now cache only the event and use a fast replace to insert the iden number into the serailized response. --- .../components/websocket_api/messages.py | 19 ++++++++-- .../components/websocket_api/test_messages.py | 37 ++++++++++++++++++- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index 52e97b60cc..f68beff592 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -12,6 +12,7 @@ from homeassistant.util.json import ( find_paths_unserializable_data, format_unserializable_data, ) +from homeassistant.util.yaml.loader import JSON_TYPE from . import const @@ -27,6 +28,9 @@ MINIMAL_MESSAGE_SCHEMA = vol.Schema( # Base schema to extend by message handlers BASE_COMMAND_MESSAGE_SCHEMA = vol.Schema({vol.Required("id"): cv.positive_int}) +IDEN_TEMPLATE = "__IDEN__" +IDEN_JSON_TEMPLATE = '"__IDEN__"' + def result_message(iden: int, result: Any = None) -> Dict: """Return a success result message.""" @@ -43,12 +47,11 @@ def error_message(iden: int, code: str, message: str) -> Dict: } -def event_message(iden: int, event: Any) -> Dict: +def event_message(iden: JSON_TYPE, event: Any) -> Dict: """Return an event message.""" return {"id": iden, "type": "event", "event": event} -@lru_cache(maxsize=128) def cached_event_message(iden: int, event: Event) -> str: """Return an event message. @@ -58,7 +61,17 @@ def cached_event_message(iden: int, event: Event) -> str: all getting many of the same events (mostly state changed) we can avoid serializing the same data for each connection. """ - return message_to_json(event_message(iden, event)) + return _cached_event_message(event).replace(IDEN_JSON_TEMPLATE, str(iden), 1) + + +@lru_cache(maxsize=128) +def _cached_event_message(event: Event) -> str: + """Cache and serialize the event to json. + + The IDEN_TEMPLATE is used which will be replaced + with the actual iden in cached_event_message + """ + return message_to_json(event_message(IDEN_TEMPLATE, event)) def message_to_json(message: Any) -> str: diff --git a/tests/components/websocket_api/test_messages.py b/tests/components/websocket_api/test_messages.py index 832b72c5c1..3ec156e694 100644 --- a/tests/components/websocket_api/test_messages.py +++ b/tests/components/websocket_api/test_messages.py @@ -1,6 +1,7 @@ """Test Websocket API messages module.""" from homeassistant.components.websocket_api.messages import ( + _cached_event_message as lru_event_cache, cached_event_message, message_to_json, ) @@ -24,6 +25,7 @@ async def test_cached_event_message(hass): await hass.async_block_till_done() assert len(events) == 2 + lru_event_cache.cache_clear() msg0 = cached_event_message(2, events[0]) assert msg0 == cached_event_message(2, events[0]) @@ -33,18 +35,49 @@ async def test_cached_event_message(hass): assert msg0 != msg1 - cache_info = cached_event_message.cache_info() + cache_info = lru_event_cache.cache_info() assert cache_info.hits == 2 assert cache_info.misses == 2 assert cache_info.currsize == 2 cached_event_message(2, events[1]) - cache_info = cached_event_message.cache_info() + cache_info = lru_event_cache.cache_info() assert cache_info.hits == 3 assert cache_info.misses == 2 assert cache_info.currsize == 2 +async def test_cached_event_message_with_different_idens(hass): + """Test that we cache event messages when the subscrition idens differ.""" + + events = [] + + @callback + def _event_listener(event): + events.append(event) + + hass.bus.async_listen(EVENT_STATE_CHANGED, _event_listener) + + hass.states.async_set("light.window", "on") + await hass.async_block_till_done() + + assert len(events) == 1 + + lru_event_cache.cache_clear() + + msg0 = cached_event_message(2, events[0]) + msg1 = cached_event_message(3, events[0]) + msg2 = cached_event_message(4, events[0]) + + assert msg0 != msg1 + assert msg0 != msg2 + + cache_info = lru_event_cache.cache_info() + assert cache_info.hits == 2 + assert cache_info.misses == 1 + assert cache_info.currsize == 1 + + async def test_message_to_json(caplog): """Test we can serialize websocket messages.""" From e660577e43f11f4042ec3a40b61fdc7caa57e79b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 23 Oct 2020 01:17:47 +0200 Subject: [PATCH 018/118] Update frontend to 20201021.2 (#42242) --- 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 e04e25520b..f02e9a4ec3 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==20201021.1"], + "requirements": ["home-assistant-frontend==20201021.2"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 287cd336d8..41211fafb7 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.37.1 -home-assistant-frontend==20201021.1 +home-assistant-frontend==20201021.2 httpx==0.16.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.2 diff --git a/requirements_all.txt b/requirements_all.txt index 869653d56c..3406bfd3a3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -759,7 +759,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20201021.1 +home-assistant-frontend==20201021.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8ca3780f66..5bf62d047e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -385,7 +385,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20201021.1 +home-assistant-frontend==20201021.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 868b14f15179969e996b71d1f0eda681453318f0 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 22 Oct 2020 19:30:32 -0400 Subject: [PATCH 019/118] Update ZHA dependencies (#42245) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index d2c2cca36d..01c24bdc6f 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -8,7 +8,7 @@ "pyserial==3.4", "zha-quirks==0.0.45", "zigpy-cc==0.5.2", - "zigpy-deconz==0.10.0", + "zigpy-deconz==0.11.0", "zigpy==0.26.0", "zigpy-xbee==0.13.0", "zigpy-zigate==0.6.2", diff --git a/requirements_all.txt b/requirements_all.txt index 3406bfd3a3..c02a23b986 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2343,7 +2343,7 @@ ziggo-mediabox-xl==1.1.0 zigpy-cc==0.5.2 # homeassistant.components.zha -zigpy-deconz==0.10.0 +zigpy-deconz==0.11.0 # homeassistant.components.zha zigpy-xbee==0.13.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5bf62d047e..5444e4fee6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1112,7 +1112,7 @@ zha-quirks==0.0.45 zigpy-cc==0.5.2 # homeassistant.components.zha -zigpy-deconz==0.10.0 +zigpy-deconz==0.11.0 # homeassistant.components.zha zigpy-xbee==0.13.0 From 8e4a325fdb809fb205d5b43d240c46ddd07183fa Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 22 Oct 2020 23:33:34 +0000 Subject: [PATCH 020/118] Bumped version to 0.117.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 0eb828c5b1..db8e92e2a4 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From e0466d4ac83ad2811323af0113721532c6911803 Mon Sep 17 00:00:00 2001 From: BrianWithAHat <19786223+BrianWithAHat@users.noreply.github.com> Date: Fri, 23 Oct 2020 03:25:55 -0400 Subject: [PATCH 021/118] Fix MaryTTS filename extensions (#42228) Co-authored-by: Brian Laferriere --- homeassistant/components/marytts/tts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/marytts/tts.py b/homeassistant/components/marytts/tts.py index a5bdee618e..0f75fbcee3 100644 --- a/homeassistant/components/marytts/tts.py +++ b/homeassistant/components/marytts/tts.py @@ -21,6 +21,8 @@ DEFAULT_VOICE = "cmu-slt-hsmm" DEFAULT_CODEC = "WAVE_FILE" DEFAULT_EFFECTS = {} +MAP_MARYTTS_CODEC = {"WAVE_FILE": "wav", "AIFF_FILE": "aiff", "AU_FILE": "au"} + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, @@ -81,5 +83,6 @@ class MaryTTSProvider(Provider): effects = options[CONF_EFFECT] data = self._mary.speak(message, effects) + audiotype = MAP_MARYTTS_CODEC[self._mary.codec] - return self._mary.codec, data + return audiotype, data From f6f95d04221c529fef4aef5326821030afa292c1 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Fri, 23 Oct 2020 15:14:37 +0200 Subject: [PATCH 022/118] Fix polling of color for ZHA lights not updating (#42248) --- homeassistant/components/zha/light.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index ba05d63df1..34c27f0af4 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -457,8 +457,8 @@ class Light(BaseLight, ZhaEntity): ): self._color_temp = results["color_temperature"] - color_x = results.get("color_x") - color_y = results.get("color_y") + color_x = results.get("current_x") + color_y = results.get("current_y") if color_x is not None and color_y is not None: self._hs_color = color_util.color_xy_to_hs( float(color_x / 65535), float(color_y / 65535) From aace9dce38ebf9e3c34f26705fba20d1e10b2d59 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Fri, 23 Oct 2020 04:17:10 -0700 Subject: [PATCH 023/118] Fix is_on test for Tesla Charger switch (#42251) closes #41755 --- homeassistant/components/tesla/switch.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/tesla/switch.py b/homeassistant/components/tesla/switch.py index cb57c1e3d5..9fcdbae0c1 100644 --- a/homeassistant/components/tesla/switch.py +++ b/homeassistant/components/tesla/switch.py @@ -2,7 +2,6 @@ import logging from homeassistant.components.switch import SwitchEntity -from homeassistant.const import STATE_ON from . import DOMAIN as TESLA_DOMAIN, TeslaDevice @@ -42,7 +41,7 @@ class ChargerSwitch(TeslaDevice, SwitchEntity): """Get whether the switch is in on state.""" if self.tesla_device.is_charging() is None: return None - return self.tesla_device.is_charging() == STATE_ON + return self.tesla_device.is_charging() class RangeSwitch(TeslaDevice, SwitchEntity): From 84ab40c57d0d4d5e26626381ad5e008834f89418 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 23 Oct 2020 17:40:43 +0200 Subject: [PATCH 024/118] Fix Tasmota relay acting as on/off light (#42259) --- .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_light.py | 76 ++++++++++++++++++- tests/components/tasmota/test_switch.py | 4 +- 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index b64d10c107..41fffd9ea8 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.0.20"], + "requirements": ["hatasmota==0.0.21"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index c02a23b986..10535ac8af 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -732,7 +732,7 @@ hass-nabucasa==0.37.1 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.0.20 +hatasmota==0.0.21 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5444e4fee6..371c491760 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ hangups==0.4.11 hass-nabucasa==0.37.1 # homeassistant.components.tasmota -hatasmota==0.0.20 +hatasmota==0.0.21 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index d2f14a151d..34164aa44f 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -277,7 +277,39 @@ async def test_attributes_rgbww_reduced(hass, mqtt_mock, setup_tasmota): ) -async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): +async def test_controlling_state_via_mqtt_on_off(hass, mqtt_mock, setup_tasmota): + """Test state update via MQTT.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config["rl"][0] = 1 + config["so"]["30"] = 1 # Enforce Home Assistant auto-discovery as light + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + state = hass.states.get("light.test") + assert state.state == "unavailable" + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON"}') + state = hass.states.get("light.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"OFF"}') + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + +async def test_controlling_state_via_mqtt_rgbww(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" config = copy.deepcopy(DEFAULT_CONFIG) config["rl"][0] = 2 @@ -344,7 +376,47 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): assert state.attributes.get("effect") == "Cycle down" -async def test_sending_mqtt_commands(hass, mqtt_mock, setup_tasmota): +async def test_sending_mqtt_commands_on_off(hass, mqtt_mock, setup_tasmota): + """Test the sending MQTT commands.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config["rl"][0] = 1 + config["so"]["30"] = 1 # Enforce Home Assistant auto-discovery as light + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") + state = hass.states.get("light.test") + assert state.state == STATE_OFF + await hass.async_block_till_done() + await hass.async_block_till_done() + mqtt_mock.async_publish.reset_mock() + + # Turn the light on and verify MQTT message is sent + await common.async_turn_on(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 ON", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Tasmota is not optimistic, the state should still be off + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + # Turn the light off and verify MQTT message is sent + await common.async_turn_off(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 OFF", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + +async def test_sending_mqtt_commands_rgbww(hass, mqtt_mock, setup_tasmota): """Test the sending MQTT commands.""" config = copy.deepcopy(DEFAULT_CONFIG) config["rl"][0] = 2 diff --git a/tests/components/tasmota/test_switch.py b/tests/components/tasmota/test_switch.py index 5757d16112..564dc2f286 100644 --- a/tests/components/tasmota/test_switch.py +++ b/tests/components/tasmota/test_switch.py @@ -80,7 +80,7 @@ async def test_sending_mqtt_commands(hass, mqtt_mock, setup_tasmota): # Turn the switch on and verify MQTT message is sent await common.async_turn_on(hass, "switch.test") mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/POWER1", "ON", 0, False + "tasmota_49A3BC/cmnd/Power1", "ON", 0, False ) mqtt_mock.async_publish.reset_mock() @@ -91,7 +91,7 @@ async def test_sending_mqtt_commands(hass, mqtt_mock, setup_tasmota): # Turn the switch off and verify MQTT message is sent await common.async_turn_off(hass, "switch.test") mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/POWER1", "OFF", 0, False + "tasmota_49A3BC/cmnd/Power1", "OFF", 0, False ) state = hass.states.get("switch.test") From d8fea1c5821bd30378ca91cf56f7ff1f2dfeb625 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 23 Oct 2020 10:38:46 -0500 Subject: [PATCH 025/118] Ensure event listener integrations do the queue insert as a callback (#42265) --- homeassistant/components/influxdb/__init__.py | 2 ++ homeassistant/components/watson_iot/__init__.py | 2 ++ homeassistant/components/zabbix/__init__.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index 5696424b40..16b6971b11 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -26,6 +26,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) +from homeassistant.core import callback from homeassistant.helpers import event as event_helper, state as state_helper import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_values import EntityValues @@ -500,6 +501,7 @@ class InfluxThread(threading.Thread): self.shutdown = False hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) + @callback def _event_listener(self, event): """Listen for new messages on the bus and queue them for Influx.""" item = (time.monotonic(), event) diff --git a/homeassistant/components/watson_iot/__init__.py b/homeassistant/components/watson_iot/__init__.py index ecb546f4a8..99f6b63ec9 100644 --- a/homeassistant/components/watson_iot/__init__.py +++ b/homeassistant/components/watson_iot/__init__.py @@ -21,6 +21,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) +from homeassistant.core import callback from homeassistant.helpers import state as state_helper import homeassistant.helpers.config_validation as cv @@ -165,6 +166,7 @@ class WatsonIOTThread(threading.Thread): self.shutdown = False hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) + @callback def _event_listener(self, event): """Listen for new messages on the bus and queue them for Watson IoT.""" item = (time.monotonic(), event) diff --git a/homeassistant/components/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py index 0afe8050d6..b9d0273157 100644 --- a/homeassistant/components/zabbix/__init__.py +++ b/homeassistant/components/zabbix/__init__.py @@ -22,6 +22,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) +from homeassistant.core import callback from homeassistant.helpers import event as event_helper, state as state_helper import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import ( @@ -186,6 +187,7 @@ class ZabbixThread(threading.Thread): self.queue.put(None) self.join() + @callback def _event_listener(self, event): """Listen for new messages on the bus and queue them for Zabbix.""" item = (time.monotonic(), event) From 9c601957808846511c7b0791f43aa2a8ad600144 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 23 Oct 2020 15:45:06 +0000 Subject: [PATCH 026/118] Bumped version to 0.117.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index db8e92e2a4..d9f7d4003a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 0c1c7d797c592ac2329d95ca9852c23c09c0ed55 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Sun, 25 Oct 2020 01:53:36 +0200 Subject: [PATCH 027/118] Only log xiaomi_miio update exceptions once (#41226) * xiaomi_miio: only log update exceptions once Replaces #37695 * add som more missed exception logger cases + do not change the control flow as pointed out by @cgtobi * Use patch&MagickMock from tests.async_mock * Fix linting for alarm_control_panel * update the test to verify that the warning on update is only logged when the device was previously available --- .../components/xiaomi_miio/air_quality.py | 10 ++- .../xiaomi_miio/alarm_control_panel.py | 12 +-- homeassistant/components/xiaomi_miio/fan.py | 21 +++-- .../components/xiaomi_miio/gateway.py | 7 +- homeassistant/components/xiaomi_miio/light.py | 48 ++++++++---- .../components/xiaomi_miio/sensor.py | 5 +- .../components/xiaomi_miio/switch.py | 26 ++++--- .../components/xiaomi_miio/vacuum.py | 8 +- tests/components/xiaomi_miio/test_vacuum.py | 77 ++++++++++--------- 9 files changed, 127 insertions(+), 87 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/air_quality.py b/homeassistant/components/xiaomi_miio/air_quality.py index baeb0bf39e..1e1e1b5863 100644 --- a/homeassistant/components/xiaomi_miio/air_quality.py +++ b/homeassistant/components/xiaomi_miio/air_quality.py @@ -195,8 +195,9 @@ class AirMonitorS1(AirMonitorB1): self._humidity = state.humidity self._available = True except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) class AirMonitorV1(AirMonitorB1): @@ -210,8 +211,9 @@ class AirMonitorV1(AirMonitorB1): self._air_quality_index = state.aqi self._available = True except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) @property def unit_of_measurement(self): diff --git a/homeassistant/components/xiaomi_miio/alarm_control_panel.py b/homeassistant/components/xiaomi_miio/alarm_control_panel.py index 89fcb45b86..6880202cbd 100644 --- a/homeassistant/components/xiaomi_miio/alarm_control_panel.py +++ b/homeassistant/components/xiaomi_miio/alarm_control_panel.py @@ -3,7 +3,7 @@ from functools import partial import logging -from miio.gateway import GatewayException +from miio import DeviceException from homeassistant.components.alarm_control_panel import ( SUPPORT_ALARM_ARM_AWAY, @@ -103,7 +103,7 @@ class XiaomiGatewayAlarm(AlarmControlPanelEntity): partial(func, *args, **kwargs) ) _LOGGER.debug("Response received from miio device: %s", result) - except GatewayException as exc: + except DeviceException as exc: _LOGGER.error(mask_error, exc) async def async_alarm_arm_away(self, code=None): @@ -122,9 +122,11 @@ class XiaomiGatewayAlarm(AlarmControlPanelEntity): """Fetch state from the device.""" try: state = await self.hass.async_add_executor_job(self._gateway.alarm.status) - except GatewayException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + except DeviceException as ex: + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return _LOGGER.debug("Got new state: %s", state) diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index b25f69f68c..8d971446a5 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -655,8 +655,10 @@ class XiaomiGenericDevice(FanEntity): return result == SUCCESS except DeviceException as exc: - _LOGGER.error(mask_error, exc) - self._available = False + if self._available: + _LOGGER.error(mask_error, exc) + self._available = False + return False async def async_turn_on(self, speed: str = None, **kwargs) -> None: @@ -785,8 +787,9 @@ class XiaomiAirPurifier(XiaomiGenericDevice): ) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) @property def speed_list(self) -> list: @@ -1029,8 +1032,9 @@ class XiaomiAirHumidifier(XiaomiGenericDevice): ) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) @property def speed_list(self) -> list: @@ -1138,8 +1142,9 @@ class XiaomiAirFresh(XiaomiGenericDevice): ) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) @property def speed_list(self) -> list: diff --git a/homeassistant/components/xiaomi_miio/gateway.py b/homeassistant/components/xiaomi_miio/gateway.py index 8c928cb36e..eb2f4cdf2e 100644 --- a/homeassistant/components/xiaomi_miio/gateway.py +++ b/homeassistant/components/xiaomi_miio/gateway.py @@ -65,7 +65,7 @@ class XiaomiGatewayDevice(Entity): self._entry = entry self._unique_id = sub_device.sid self._name = f"{sub_device.name} ({sub_device.sid})" - self._available = None + self._available = False @property def unique_id(self): @@ -100,5 +100,6 @@ class XiaomiGatewayDevice(Entity): await self.hass.async_add_executor_job(self._sub_device.update) self._available = True except gateway.GatewayException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 34ebb550ad..d1746fcd88 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -324,8 +324,10 @@ class XiaomiPhilipsAbstractLight(LightEntity): return result == SUCCESS except DeviceException as exc: - _LOGGER.error(mask_error, exc) - self._available = False + if self._available: + _LOGGER.error(mask_error, exc) + self._available = False + return False async def async_turn_on(self, **kwargs): @@ -356,8 +358,10 @@ class XiaomiPhilipsAbstractLight(LightEntity): try: state = await self.hass.async_add_executor_job(self._light.status) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return _LOGGER.debug("Got new state: %s", state) @@ -380,8 +384,10 @@ class XiaomiPhilipsGenericLight(XiaomiPhilipsAbstractLight): try: state = await self.hass.async_add_executor_job(self._light.status) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return _LOGGER.debug("Got new state: %s", state) @@ -536,8 +542,10 @@ class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): try: state = await self.hass.async_add_executor_job(self._light.status) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return _LOGGER.debug("Got new state: %s", state) @@ -593,8 +601,10 @@ class XiaomiPhilipsCeilingLamp(XiaomiPhilipsBulb): try: state = await self.hass.async_add_executor_job(self._light.status) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return _LOGGER.debug("Got new state: %s", state) @@ -637,8 +647,10 @@ class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight): try: state = await self.hass.async_add_executor_job(self._light.status) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return _LOGGER.debug("Got new state: %s", state) @@ -778,8 +790,10 @@ class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight): try: state = await self.hass.async_add_executor_job(self._light.status) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return _LOGGER.debug("Got new state: %s", state) @@ -932,8 +946,10 @@ class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): try: state = await self.hass.async_add_executor_job(self._light.status) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return _LOGGER.debug("Got new state: %s", state) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 55f6c26c56..d20c2dfac1 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -233,8 +233,9 @@ class XiaomiAirQualityMonitor(Entity): ) except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) class XiaomiGatewaySensor(XiaomiGatewayDevice): diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 781dd10a82..b9e90cc5c2 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -280,8 +280,10 @@ class XiaomiPlugGenericSwitch(SwitchEntity): return result == SUCCESS except DeviceException as exc: - _LOGGER.error(mask_error, exc) - self._available = False + if self._available: + _LOGGER.error(mask_error, exc) + self._available = False + return False async def async_turn_on(self, **kwargs): @@ -316,8 +318,9 @@ class XiaomiPlugGenericSwitch(SwitchEntity): self._state_attrs[ATTR_TEMPERATURE] = state.temperature except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) async def async_set_wifi_led_on(self): """Turn the wifi led on.""" @@ -402,8 +405,9 @@ class XiaomiPowerStripSwitch(XiaomiPlugGenericSwitch): self._state_attrs[ATTR_POWER_PRICE] = state.power_price except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) async def async_set_power_mode(self, mode: str): """Set the power mode.""" @@ -492,8 +496,9 @@ class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch): self._state_attrs[ATTR_LOAD_POWER] = state.load_power except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) class XiaomiAirConditioningCompanionSwitch(XiaomiPlugGenericSwitch): @@ -541,5 +546,6 @@ class XiaomiAirConditioningCompanionSwitch(XiaomiPlugGenericSwitch): self._state_attrs[ATTR_LOAD_POWER] = state.load_power except DeviceException as ex: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) + if self._available: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 53fc458c50..2a9ae1187b 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -485,10 +485,10 @@ class MiroboVacuum(StateVacuumEntity): self.dnd_state = self._vacuum.dnd_status() self._available = True - except OSError as exc: - _LOGGER.error("Got OSError while fetching the state: %s", exc) - except DeviceException as exc: - _LOGGER.warning("Got exception while fetching the state: %s", exc) + except (OSError, DeviceException) as exc: + if self._available: + self._available = False + _LOGGER.warning("Got exception while fetching the state: %s", exc) # Fetch timers separately, see #38285 try: diff --git a/tests/components/xiaomi_miio/test_vacuum.py b/tests/components/xiaomi_miio/test_vacuum.py index 3b1234622a..0fa241fb0b 100644 --- a/tests/components/xiaomi_miio/test_vacuum.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -2,6 +2,7 @@ from datetime import datetime, time, timedelta from unittest import mock +from miio import DeviceException import pytest from pytz import utc @@ -52,9 +53,12 @@ from homeassistant.const import ( CONF_PLATFORM, STATE_OFF, STATE_ON, + STATE_UNAVAILABLE, ) from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, patch + PLATFORM = "xiaomi_miio" # calls made when device status is requested @@ -70,7 +74,7 @@ STATUS_CALLS = [ @pytest.fixture(name="mock_mirobo_is_got_error") def mirobo_is_got_error_fixture(): """Mock mock_mirobo.""" - mock_vacuum = mock.MagicMock() + mock_vacuum = MagicMock() mock_vacuum.status().data = {"test": "raw"} mock_vacuum.status().is_on = False mock_vacuum.status().fanspeed = 38 @@ -98,21 +102,19 @@ def mirobo_is_got_error_fixture(): mock_vacuum.dnd_status().start = time(hour=22, minute=0) mock_vacuum.dnd_status().end = time(hour=6, minute=0) - mock_timer_1 = mock.MagicMock() + mock_timer_1 = MagicMock() mock_timer_1.enabled = True mock_timer_1.cron = "5 5 1 8 1" mock_timer_1.next_schedule = datetime(2020, 5, 23, 13, 21, 10, tzinfo=utc) - mock_timer_2 = mock.MagicMock() + mock_timer_2 = MagicMock() mock_timer_2.enabled = False mock_timer_2.cron = "5 5 1 8 2" mock_timer_2.next_schedule = datetime(2020, 5, 23, 13, 21, 10, tzinfo=utc) mock_vacuum.timer.return_value = [mock_timer_1, mock_timer_2] - with mock.patch( - "homeassistant.components.xiaomi_miio.vacuum.Vacuum" - ) as mock_vaccum_cls: + with patch("homeassistant.components.xiaomi_miio.vacuum.Vacuum") as mock_vaccum_cls: mock_vaccum_cls.return_value = mock_vacuum yield mock_vacuum @@ -135,14 +137,12 @@ new_fanspeeds = { @pytest.fixture(name="mock_mirobo_fanspeeds", params=[old_fanspeeds, new_fanspeeds]) def mirobo_old_speeds_fixture(request): """Fixture for testing both types of fanspeeds.""" - mock_vacuum = mock.MagicMock() + mock_vacuum = MagicMock() mock_vacuum.status().battery = 32 mock_vacuum.fan_speed_presets.return_value = request.param mock_vacuum.status().fanspeed = list(request.param.values())[0] - with mock.patch( - "homeassistant.components.xiaomi_miio.vacuum.Vacuum" - ) as mock_vaccum_cls: + with patch("homeassistant.components.xiaomi_miio.vacuum.Vacuum") as mock_vaccum_cls: mock_vaccum_cls.return_value = mock_vacuum yield mock_vacuum @@ -150,7 +150,7 @@ def mirobo_old_speeds_fixture(request): @pytest.fixture(name="mock_mirobo_is_on") def mirobo_is_on_fixture(): """Mock mock_mirobo.""" - mock_vacuum = mock.MagicMock() + mock_vacuum = MagicMock() mock_vacuum.status().data = {"test": "raw"} mock_vacuum.status().is_on = True mock_vacuum.status().fanspeed = 99 @@ -176,46 +176,53 @@ def mirobo_is_on_fixture(): mock_vacuum.status().state_code = 5 mock_vacuum.dnd_status().enabled = False - mock_timer_1 = mock.MagicMock() + mock_timer_1 = MagicMock() mock_timer_1.enabled = True mock_timer_1.cron = "5 5 1 8 1" mock_timer_1.next_schedule = datetime(2020, 5, 23, 13, 21, 10, tzinfo=utc) - mock_timer_2 = mock.MagicMock() + mock_timer_2 = MagicMock() mock_timer_2.enabled = False mock_timer_2.cron = "5 5 1 8 2" mock_timer_2.next_schedule = datetime(2020, 5, 23, 13, 21, 10, tzinfo=utc) mock_vacuum.timer.return_value = [mock_timer_1, mock_timer_2] - with mock.patch( - "homeassistant.components.xiaomi_miio.vacuum.Vacuum" - ) as mock_vaccum_cls: + with patch("homeassistant.components.xiaomi_miio.vacuum.Vacuum") as mock_vaccum_cls: mock_vaccum_cls.return_value = mock_vacuum yield mock_vacuum -@pytest.fixture(name="mock_mirobo_errors") -def mirobo_errors_fixture(): - """Mock mock_mirobo_errors to simulate a bad vacuum status request.""" - mock_vacuum = mock.MagicMock() - mock_vacuum.status.side_effect = OSError() - with mock.patch( - "homeassistant.components.xiaomi_miio.vacuum.Vacuum" - ) as mock_vaccum_cls: - mock_vaccum_cls.return_value = mock_vacuum - yield mock_vacuum - - -async def test_xiaomi_exceptions(hass, caplog, mock_mirobo_errors): - """Test vacuum supported features.""" +async def test_xiaomi_exceptions(hass, caplog, mock_mirobo_is_on): + """Test error logging on exceptions.""" entity_name = "test_vacuum_cleaner_error" - await setup_component(hass, entity_name) + entity_id = await setup_component(hass, entity_name) + def is_available(): + state = hass.states.get(entity_id) + return state.state != STATE_UNAVAILABLE + + # The initial setup has to be done successfully assert "Initializing with host 192.168.1.100 (token 12345...)" in caplog.text - assert mock_mirobo_errors.status.call_count == 1 - assert "ERROR" in caplog.text - assert "Got OSError while fetching the state" in caplog.text + assert "WARNING" not in caplog.text + assert is_available() + + # Second update causes an exception, which should be logged + mock_mirobo_is_on.status.side_effect = DeviceException("dummy exception") + await hass.helpers.entity_component.async_update_entity(entity_id) + assert "WARNING" in caplog.text + assert "Got exception while fetching the state" in caplog.text + assert not is_available() + + # Third update does not get logged as the device is already unavailable, + # so we clear the log and reset the status to test that + caplog.clear() + mock_mirobo_is_on.status.reset_mock() + + await hass.helpers.entity_component.async_update_entity(entity_id) + assert "Got exception while fetching the state" not in caplog.text + assert not is_available() + assert mock_mirobo_is_on.status.call_count == 1 async def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error): @@ -463,7 +470,7 @@ async def test_xiaomi_vacuum_fanspeeds(hass, caplog, mock_mirobo_fanspeeds): {"entity_id": entity_id, "fan_speed": "invent"}, blocking=True, ) - assert "ERROR" in caplog.text + assert "Fan speed step not recognized" in caplog.text async def test_xiaomi_vacuum_goto_service(hass, caplog, mock_mirobo_is_on): From b6994689b18ef2b0ff5b4a2b4463510e969d150c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 24 Oct 2020 11:48:28 -0700 Subject: [PATCH 028/118] Update nest integration with fixes from initial PR (#42250) --- homeassistant/components/nest/__init__.py | 10 +++- homeassistant/components/nest/config_flow.py | 10 +++- homeassistant/components/nest/sensor.py | 4 +- homeassistant/components/nest/sensor_sdm.py | 30 +++++----- tests/components/nest/sensor_sdm_test.py | 62 ++++++++++++++++++-- 5 files changed, 87 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index ae32527bdc..16ff05f909 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -30,7 +30,11 @@ from homeassistant.helpers import ( config_entry_oauth2_flow, config_validation as cv, ) -from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, + dispatcher_send, +) from homeassistant.helpers.entity import Entity from . import api, config_flow, local_auth @@ -176,7 +180,7 @@ class SignalUpdateCallback(EventCallback): # This event triggered an update to a device that changed some # properties which the DeviceManager should already have received. # Send a signal to refresh state of all listening devices. - dispatcher_send(self._hass, SIGNAL_NEST_UPDATE) + async_dispatcher_send(self._hass, SIGNAL_NEST_UPDATE) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): @@ -203,7 +207,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): auth, config[CONF_PROJECT_ID], config[CONF_SUBSCRIBER_ID] ) subscriber.set_update_callback(SignalUpdateCallback(hass)) - hass.loop.create_task(subscriber.start_async()) + asyncio.create_task(subscriber.start_async()) hass.data[DOMAIN][entry.entry_id] = subscriber for component in PLATFORMS: diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index aa4b7af7e1..e792b496da 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -61,6 +61,10 @@ class CodeInvalid(NestAuthError): """Raised when invalid authorization code.""" +class UnexpectedStateError(HomeAssistantError): + """Raised when the config flow is invoked in a 'should not happen' case.""" + + @config_entries.HANDLERS.register(DOMAIN) class NestFlowHandler( config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN @@ -111,7 +115,7 @@ class NestFlowHandler( async def async_step_init(self, user_input=None): """Handle a flow start.""" if self.is_sdm_api(): - return None + raise UnexpectedStateError("Step only supported for legacy API") flows = self.hass.data.get(DATA_FLOW_IMPL, {}) @@ -142,7 +146,7 @@ class NestFlowHandler( deliver the authentication code. """ if self.is_sdm_api(): - return None + raise UnexpectedStateError("Step only supported for legacy API") flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl] @@ -185,7 +189,7 @@ class NestFlowHandler( async def async_step_import(self, info): """Import existing auth from Nest.""" if self.is_sdm_api(): - return None + raise UnexpectedStateError("Step only supported for legacy API") if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/nest/sensor.py b/homeassistant/components/nest/sensor.py index b3df3dd393..794e8210f8 100644 --- a/homeassistant/components/nest/sensor.py +++ b/homeassistant/components/nest/sensor.py @@ -13,5 +13,5 @@ async def async_setup_entry( ) -> None: """Set up the sensors.""" if DATA_SDM not in entry.data: - return await async_setup_legacy_entry(hass, entry, async_add_entities) - return await async_setup_sdm_entry(hass, entry, async_add_entities) + await async_setup_legacy_entry(hass, entry, async_add_entities) + await async_setup_sdm_entry(hass, entry, async_add_entities) diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index 8c567c9b36..6c4e37e57b 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -18,6 +18,13 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN, SIGNAL_NEST_UPDATE +DEVICE_TYPE_MAP = { + "sdm.devices.types.CAMERA": "Camera", + "sdm.devices.types.DISPLAY": "Display", + "sdm.devices.types.DOORBELL": "Doorbell", + "sdm.devices.types.THERMOSTAT": "Thermostat", +} + async def async_setup_sdm_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities @@ -46,7 +53,7 @@ class SensorBase(Entity): self._device = device @property - def should_pool(self) -> bool: + def should_poll(self) -> bool: """Disable polling since entities have state pushed via pubsub.""" return False @@ -89,28 +96,19 @@ class SensorBase(Entity): # The API intentionally returns minimal information about specific # devices, instead relying on traits, but we can infer a generic model # name based on the type - if self._device.type == "sdm.devices.types.CAMERA": - return "Camera" - if self._device.type == "sdm.devices.types.DISPLAY": - return "Display" - if self._device.type == "sdm.devices.types.DOORBELL": - return "Doorbell" - if self._device.type == "sdm.devices.types.THERMOSTAT": - return "Thermostat" - return None + return DEVICE_TYPE_MAP.get(self._device.type) async def async_added_to_hass(self): """Run when entity is added to register update signal handler.""" - - async def async_update_state(): - """Update sensor state.""" - await self.async_update_ha_state(True) - # Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted # here to re-fresh the signals from _device. Unregister this callback # when the entity is removed. self.async_on_remove( - async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE, async_update_state) + async_dispatcher_connect( + self.hass, + SIGNAL_NEST_UPDATE, + self.async_write_ha_state, + ) ) diff --git a/tests/components/nest/sensor_sdm_test.py b/tests/components/nest/sensor_sdm_test.py index fbd1370126..ab2cae3d9f 100644 --- a/tests/components/nest/sensor_sdm_test.py +++ b/tests/components/nest/sensor_sdm_test.py @@ -41,6 +41,8 @@ CONFIG_ENTRY_DATA = { }, } +THERMOSTAT_TYPE = "sdm.devices.types.THERMOSTAT" + class FakeDeviceManager(DeviceManager): """Fake DeviceManager that can supply a list of devices and structures.""" @@ -104,7 +106,7 @@ async def setup_sensor(hass, devices={}, structures={}): "homeassistant.components.nest.GoogleNestSubscriber", return_value=subscriber ): assert await async_setup_component(hass, DOMAIN, CONFIG) - await hass.async_block_till_done() + await hass.async_block_till_done() return subscriber @@ -114,7 +116,7 @@ async def test_thermostat_device(hass): "some-device-id": Device.MakeDevice( { "name": "some-device-id", - "type": "sdm.devices.types.Thermostat", + "type": THERMOSTAT_TYPE, "traits": { "sdm.devices.traits.Info": { "customName": "My Sensor", @@ -140,6 +142,18 @@ async def test_thermostat_device(hass): assert humidity is not None assert humidity.state == "35.0" + registry = await hass.helpers.entity_registry.async_get_registry() + entry = registry.async_get("sensor.my_sensor_temperature") + assert entry.unique_id == "some-device-id-temperature" + assert entry.original_name == "My Sensor Temperature" + assert entry.domain == "sensor" + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + assert device.name == "My Sensor" + assert device.model == "Thermostat" + assert device.identifiers == {("nest", "some-device-id")} + async def test_no_devices(hass): """Test no devices returned by the api.""" @@ -158,7 +172,7 @@ async def test_device_no_sensor_traits(hass): "some-device-id": Device.MakeDevice( { "name": "some-device-id", - "type": "sdm.devices.types.Thermostat", + "type": THERMOSTAT_TYPE, "traits": {}, }, auth=None, @@ -179,7 +193,7 @@ async def test_device_name_from_structure(hass): "some-device-id": Device.MakeDevice( { "name": "some-device-id", - "type": "sdm.devices.types.Thermostat", + "type": THERMOSTAT_TYPE, "traits": { "sdm.devices.traits.Temperature": { "ambientTemperatureCelsius": 25.2, @@ -205,7 +219,7 @@ async def test_event_updates_sensor(hass): "some-device-id": Device.MakeDevice( { "name": "some-device-id", - "type": "sdm.devices.types.Thermostat", + "type": THERMOSTAT_TYPE, "traits": { "sdm.devices.traits.Info": { "customName": "My Sensor", @@ -246,3 +260,41 @@ async def test_event_updates_sensor(hass): temperature = hass.states.get("sensor.my_sensor_temperature") assert temperature is not None assert temperature.state == "26.2" + + +async def test_device_with_unknown_type(hass): + """Test a device without a custom name, inferring name from structure.""" + devices = { + "some-device-id": Device.MakeDevice( + { + "name": "some-device-id", + "type": "some-unknown-type", + "traits": { + "sdm.devices.traits.Info": { + "customName": "My Sensor", + }, + "sdm.devices.traits.Temperature": { + "ambientTemperatureCelsius": 25.1, + }, + }, + }, + auth=None, + ) + } + await setup_sensor(hass, devices) + + temperature = hass.states.get("sensor.my_sensor_temperature") + assert temperature is not None + assert temperature.state == "25.1" + + registry = await hass.helpers.entity_registry.async_get_registry() + entry = registry.async_get("sensor.my_sensor_temperature") + assert entry.unique_id == "some-device-id-temperature" + assert entry.original_name == "My Sensor Temperature" + assert entry.domain == "sensor" + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + assert device.name == "My Sensor" + assert device.model is None + assert device.identifiers == {("nest", "some-device-id")} From 21917e86c98b31596fe8876959eaaefb3f209962 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sat, 24 Oct 2020 12:13:40 -0700 Subject: [PATCH 029/118] Fix Tesla attribute refreshing (#42257) * Fix Tesla attribute refreshing * Remove extraneous copy --- homeassistant/components/tesla/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index a04c597588..51090d3427 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -282,7 +282,7 @@ class TeslaDevice(CoordinatorEntity): @property def device_state_attributes(self): """Return the state attributes of the device.""" - attr = self._attributes.copy() + attr = self._attributes if self.tesla_device.has_battery(): attr[ATTR_BATTERY_LEVEL] = self.tesla_device.battery_level() attr[ATTR_BATTERY_CHARGING] = self.tesla_device.battery_charging() @@ -310,4 +310,5 @@ class TeslaDevice(CoordinatorEntity): This assumes the coordinator has updated the controller. """ self.tesla_device.refresh() + self._attributes = self.tesla_device.attrs.copy() self.async_write_ha_state() From 00284a87d0ab8fca3405d8a1173cec3aa2fd5433 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Sun, 25 Oct 2020 10:55:12 +0800 Subject: [PATCH 030/118] Fix AVError and allow more missing DTS packets in stream (#42277) * Fix AVError and allow more missing DTS in stream init * Avoid recreating container demux iterator * Relax missing dts requirement * Fix spelling error * Fix error message count * Add timeout to av.open read * Increase STREAM_TIMEOUT to 30 --- homeassistant/components/stream/const.py | 3 ++ homeassistant/components/stream/worker.py | 59 ++++++++++++++--------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index b986cddaf6..9406c24eaf 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -20,3 +20,6 @@ MIN_SEGMENT_DURATION = 1.5 # Each segment is at least this many seconds PACKETS_TO_WAIT_FOR_AUDIO = 20 # Some streams have an audio stream with no audio MAX_TIMESTAMP_GAP = 10000 # seconds - anything from 10 to 50000 is probably reasonable + +MAX_MISSING_DTS = 6 # Number of packets missing DTS to allow +STREAM_TIMEOUT = 30 # Timeout for reading stream diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 6769413baf..4f972774fc 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -6,7 +6,13 @@ import time import av -from .const import MAX_TIMESTAMP_GAP, MIN_SEGMENT_DURATION, PACKETS_TO_WAIT_FOR_AUDIO +from .const import ( + MAX_MISSING_DTS, + MAX_TIMESTAMP_GAP, + MIN_SEGMENT_DURATION, + PACKETS_TO_WAIT_FOR_AUDIO, + STREAM_TIMEOUT, +) from .core import Segment, StreamBuffer _LOGGER = logging.getLogger(__name__) @@ -62,7 +68,7 @@ def stream_worker(hass, stream, quit_event): def _stream_worker_internal(hass, stream, quit_event): """Handle consuming streams.""" - container = av.open(stream.source, options=stream.options) + container = av.open(stream.source, options=stream.options, timeout=STREAM_TIMEOUT) try: video_stream = container.streams.video[0] except (KeyError, IndexError): @@ -81,13 +87,15 @@ def _stream_worker_internal(hass, stream, quit_event): if audio_stream and audio_stream.profile is None: audio_stream = None + # Iterator for demuxing + container_packets = None # The presentation timestamps of the first packet in each stream we receive # Use to adjust before muxing or outputting, but we don't adjust internally first_pts = {} # The decoder timestamps of the latest packet in each stream we processed last_dts = None # Keep track of consecutive packets without a dts to detect end of stream. - last_packet_was_without_dts = False + missing_dts = 0 # Holds the buffers for each stream provider outputs = None # Keep track of the number of segments we've processed @@ -102,8 +110,8 @@ def _stream_worker_internal(hass, stream, quit_event): # 2 - seeking can be problematic https://trac.ffmpeg.org/ticket/7815 def peek_first_pts(): - nonlocal first_pts, audio_stream - missing_dts = False + nonlocal first_pts, audio_stream, container_packets + missing_dts = 0 def empty_stream_dict(): return { @@ -112,17 +120,20 @@ def _stream_worker_internal(hass, stream, quit_event): } try: + container_packets = container.demux((video_stream, audio_stream)) first_packet = empty_stream_dict() first_pts = empty_stream_dict() # Get to first video keyframe while first_packet[video_stream] is None: - packet = next(container.demux()) + packet = next(container_packets) if ( packet.dts is None - ): # Allow single packet with no dts, raise error on second - if missing_dts: - raise av.AVError - missing_dts = True + ): # Allow MAX_MISSING_DTS packets with no dts, raise error on the next one + if missing_dts >= MAX_MISSING_DTS: + raise StopIteration( + f"Invalid data - got {MAX_MISSING_DTS+1} packets with missing DTS while initializing" + ) + missing_dts += 1 continue if packet.stream == video_stream and packet.is_keyframe: first_packet[video_stream] = packet @@ -131,13 +142,15 @@ def _stream_worker_internal(hass, stream, quit_event): while any( [pts is None for pts in {**first_packet, **first_pts}.values()] ) and (len(initial_packets) < PACKETS_TO_WAIT_FOR_AUDIO): - packet = next(container.demux((video_stream, audio_stream))) + packet = next(container_packets) if ( packet.dts is None - ): # Allow single packet with no dts, raise error on second - if missing_dts: - raise av.AVError - missing_dts = True + ): # Allow MAX_MISSING_DTS packet with no dts, raise error on the next one + if missing_dts >= MAX_MISSING_DTS: + raise StopIteration( + f"Invalid data - got {MAX_MISSING_DTS+1} packets with missing DTS while initializing" + ) + missing_dts += 1 continue if ( first_packet[packet.stream] is None @@ -223,16 +236,16 @@ def _stream_worker_internal(hass, stream, quit_event): if len(initial_packets) > 0: packet = initial_packets.popleft() else: - packet = next(container.demux((video_stream, audio_stream))) + packet = next(container_packets) if packet.dts is None: - _LOGGER.error("Stream packet without dts detected, skipping...") - # Allow a single packet without dts before terminating the stream. - if last_packet_was_without_dts: - # If we get a "flushing" packet, the stream is done - raise StopIteration("No dts in consecutive packets") - last_packet_was_without_dts = True + # Allow MAX_MISSING_DTS consecutive packets without dts. Terminate the stream on the next one. + if missing_dts >= MAX_MISSING_DTS: + raise StopIteration( + f"No dts in {MAX_MISSING_DTS+1} consecutive packets" + ) + missing_dts += 1 continue - last_packet_was_without_dts = False + missing_dts = 0 except (av.AVError, StopIteration) as ex: _LOGGER.error("Error demuxing stream: %s", str(ex)) finalize_stream() From 3032f9280cbcd4a41fde355a96aa53f6c57effbb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 24 Oct 2020 14:29:26 +0200 Subject: [PATCH 031/118] Fix Tasmota CT light (#42287) --- .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_light.py | 55 +++++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 41fffd9ea8..bdd25f4466 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.0.21"], + "requirements": ["hatasmota==0.0.22"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 10535ac8af..8258793ebf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -732,7 +732,7 @@ hass-nabucasa==0.37.1 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.0.21 +hatasmota==0.0.22 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 371c491760..4806de987c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ hangups==0.4.11 hass-nabucasa==0.37.1 # homeassistant.components.tasmota -hatasmota==0.0.21 +hatasmota==0.0.22 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index 34164aa44f..afb583a5a2 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -309,6 +309,61 @@ async def test_controlling_state_via_mqtt_on_off(hass, mqtt_mock, setup_tasmota) assert state.state == STATE_OFF +async def test_controlling_state_via_mqtt_ct(hass, mqtt_mock, setup_tasmota): + """Test state update via MQTT.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config["rl"][0] = 2 + config["lt_st"] = 2 # 2 channel light (CT) + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + state = hass.states.get("light.test") + assert state.state == "unavailable" + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON"}') + state = hass.states.get("light.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"OFF"}') + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + async_fire_mqtt_message( + hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON","Dimmer":50}' + ) + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("brightness") == 127.5 + + async_fire_mqtt_message( + hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON","CT":300}' + ) + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("color_temp") == 300 + + # Tasmota will send "Color" also for CT light, this should be ignored + async_fire_mqtt_message( + hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON","Color":"255,128"}' + ) + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("color_temp") == 300 + assert state.attributes.get("brightness") == 127.5 + + async def test_controlling_state_via_mqtt_rgbww(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" config = copy.deepcopy(DEFAULT_CONFIG) From 5c608eb1bc98ef9397c2cc770b1b6acaedad1275 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 24 Oct 2020 14:31:08 +0200 Subject: [PATCH 032/118] Fix reconfiguring of Tasmota lights (#42288) --- homeassistant/components/tasmota/light.py | 5 ++-- homeassistant/components/tasmota/mixins.py | 5 ++-- tests/components/tasmota/test_common.py | 2 +- tests/components/tasmota/test_light.py | 34 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/tasmota/light.py b/homeassistant/components/tasmota/light.py index c761d3436d..1a41e7373f 100644 --- a/homeassistant/components/tasmota/light.py +++ b/homeassistant/components/tasmota/light.py @@ -80,10 +80,11 @@ class TasmotaLight( self._setup_from_entity() - async def discovery_update(self, update): + async def discovery_update(self, update, write_state=True): """Handle updated discovery message.""" + await super().discovery_update(update, write_state=False) self._setup_from_entity() - await super().discovery_update(update) + self.async_write_ha_state() def _setup_from_entity(self): """(Re)Setup the entity.""" diff --git a/homeassistant/components/tasmota/mixins.py b/homeassistant/components/tasmota/mixins.py index 3b52fafd12..5a1c7a9f3d 100644 --- a/homeassistant/components/tasmota/mixins.py +++ b/homeassistant/components/tasmota/mixins.py @@ -38,11 +38,12 @@ class TasmotaEntity(Entity): await self._tasmota_entity.unsubscribe_topics() await super().async_will_remove_from_hass() - async def discovery_update(self, update): + async def discovery_update(self, update, write_state=True): """Handle updated discovery message.""" self._tasmota_entity.config_update(update) await self._subscribe_topics() - self.async_write_ha_state() + if write_state: + self.async_write_ha_state() async def _subscribe_topics(self): """(Re)Subscribe to topics.""" diff --git a/tests/components/tasmota/test_common.py b/tests/components/tasmota/test_common.py index ccd87b2e43..5c18f2f401 100644 --- a/tests/components/tasmota/test_common.py +++ b/tests/components/tasmota/test_common.py @@ -385,7 +385,7 @@ async def help_test_discovery_update_unchanged( entity_id="test", name="Test", ): - """Test update of discovered component without changes. + """Test update of discovered component with and without changes. This is a test helper for the MqttDiscoveryUpdate mixin. """ diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index afb583a5a2..871d55a4d7 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -2,6 +2,8 @@ import copy import json +from hatasmota.const import CONF_MAC + from homeassistant.components import light from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, @@ -771,6 +773,38 @@ async def test_unlinked_light2(hass, mqtt_mock, setup_tasmota): await _test_unlinked_light(hass, mqtt_mock, config, 2) +async def test_discovery_update_reconfigure_light( + hass, mqtt_mock, caplog, setup_tasmota +): + """Test reconfigure of discovered light.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config["rl"][0] = 2 + config["lt_st"] = 1 # 1 channel light (Dimmer) + config2 = copy.deepcopy(DEFAULT_CONFIG) + config2["rl"][0] = 2 + config2["lt_st"] = 3 # 3 channel light (RGB) + data1 = json.dumps(config) + data2 = json.dumps(config2) + + # Simple dimmer + async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{config[CONF_MAC]}/config", data1) + await hass.async_block_till_done() + state = hass.states.get("light.test") + assert ( + state.attributes.get("supported_features") + == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + ) + + # Reconfigure as RGB light + async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{config[CONF_MAC]}/config", data2) + await hass.async_block_till_done() + state = hass.states.get("light.test") + assert ( + state.attributes.get("supported_features") + == SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_EFFECT | SUPPORT_TRANSITION + ) + + async def test_availability_when_connection_lost( hass, mqtt_client_mock, mqtt_mock, setup_tasmota ): From b925ae39cbc1229b3f53ca64ddbef73dafb2de43 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 24 Oct 2020 18:19:57 +0200 Subject: [PATCH 033/118] Upgrade spotipy to 2.16.1 (#42293) --- homeassistant/components/spotify/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/spotify/manifest.json b/homeassistant/components/spotify/manifest.json index db2f35ded9..c4d7378c06 100644 --- a/homeassistant/components/spotify/manifest.json +++ b/homeassistant/components/spotify/manifest.json @@ -2,7 +2,7 @@ "domain": "spotify", "name": "Spotify", "documentation": "https://www.home-assistant.io/integrations/spotify", - "requirements": ["spotipy==2.16.0"], + "requirements": ["spotipy==2.16.1"], "zeroconf": ["_spotify-connect._tcp.local."], "dependencies": ["http"], "codeowners": ["@frenck"], diff --git a/requirements_all.txt b/requirements_all.txt index 8258793ebf..6f6173dae4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2088,7 +2088,7 @@ spiderpy==1.3.1 spotcrime==1.0.4 # homeassistant.components.spotify -spotipy==2.16.0 +spotipy==2.16.1 # homeassistant.components.recorder # homeassistant.components.sql diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4806de987c..6d010c5e21 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -995,7 +995,7 @@ speedtest-cli==2.1.2 spiderpy==1.3.1 # homeassistant.components.spotify -spotipy==2.16.0 +spotipy==2.16.1 # homeassistant.components.recorder # homeassistant.components.sql From 2e7ce63cbc036a4674c544f9a1c39b240039111c Mon Sep 17 00:00:00 2001 From: airthusiast <67556031+airthusiast@users.noreply.github.com> Date: Sun, 25 Oct 2020 13:06:45 +0100 Subject: [PATCH 034/118] Fix for Fibaro HC3 support, climate temp sensor and target temp (#42300) --- homeassistant/components/fibaro/climate.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py index eee1c08bd3..6a7a6f145d 100644 --- a/homeassistant/components/fibaro/climate.py +++ b/homeassistant/components/fibaro/climate.py @@ -132,7 +132,10 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): elif ( self._temp_sensor_device is None and "unit" in device.properties - and "value" in device.properties + and ( + "value" in device.properties + or "heatingThermostatSetpoint" in device.properties + ) and (device.properties.unit == "C" or device.properties.unit == "F") ): self._temp_sensor_device = FibaroDevice(device) @@ -141,6 +144,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): if ( "setTargetLevel" in device.actions or "setThermostatSetpoint" in device.actions + or "setHeatingThermostatSetpoint" in device.actions ): self._target_temp_device = FibaroDevice(device) self._support_flags |= SUPPORT_TARGET_TEMPERATURE @@ -317,6 +321,8 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): """Return the current temperature.""" if self._temp_sensor_device: device = self._temp_sensor_device.fibaro_device + if device.properties.heatingThermostatSetpoint: + return float(device.properties.heatingThermostatSetpoint) return float(device.properties.value) return None @@ -325,6 +331,8 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): """Return the temperature we try to reach.""" if self._target_temp_device: device = self._target_temp_device.fibaro_device + if device.properties.heatingThermostatSetpointFuture: + return float(device.properties.heatingThermostatSetpointFuture) return float(device.properties.targetLevel) return None @@ -335,5 +343,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): if temperature is not None: if "setThermostatSetpoint" in target.fibaro_device.actions: target.action("setThermostatSetpoint", self.fibaro_op_mode, temperature) + elif "setHeatingThermostatSetpoint" in target.fibaro_device.actions: + target.action("setHeatingThermostatSetpoint", temperature) else: target.action("setTargetLevel", temperature) From 883fb8c168a439c4e1439112be504b1e97bb6092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 25 Oct 2020 01:07:17 +0200 Subject: [PATCH 035/118] Fix iCloud matching accounts (#42303) `name` is no longer defined but `username`. Fixes #38393 --- homeassistant/components/icloud/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py index 723e89ed78..6a3897a54c 100644 --- a/homeassistant/components/icloud/__init__.py +++ b/homeassistant/components/icloud/__init__.py @@ -187,7 +187,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool icloud_account = hass.data[DOMAIN].get(account_identifier) if icloud_account is None: for account in hass.data[DOMAIN].values(): - if account.name == account_identifier: + if account.username == account_identifier: icloud_account = account if icloud_account is None: From a2c157b5e98c8cc43b0e7b2e7de33ee70497c164 Mon Sep 17 00:00:00 2001 From: mezz64 <2854333+mezz64@users.noreply.github.com> Date: Sun, 25 Oct 2020 08:32:41 -0400 Subject: [PATCH 036/118] Bump pyhik to 0.2.8 (#42322) --- homeassistant/components/hikvision/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hikvision/manifest.json b/homeassistant/components/hikvision/manifest.json index e6dec7b8e8..8abe451916 100644 --- a/homeassistant/components/hikvision/manifest.json +++ b/homeassistant/components/hikvision/manifest.json @@ -2,6 +2,6 @@ "domain": "hikvision", "name": "Hikvision", "documentation": "https://www.home-assistant.io/integrations/hikvision", - "requirements": ["pyhik==0.2.7"], + "requirements": ["pyhik==0.2.8"], "codeowners": ["@mezz64"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6f6173dae4..ba6d0edb54 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1412,7 +1412,7 @@ pyhaversion==3.4.2 pyheos==0.6.0 # homeassistant.components.hikvision -pyhik==0.2.7 +pyhik==0.2.8 # homeassistant.components.hive pyhiveapi==0.2.20.2 From 331d5ba7efc2dd6f2ee6ddb018b12007f1e2f6f7 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Sun, 25 Oct 2020 13:44:39 +0100 Subject: [PATCH 037/118] Fix parameter issue in LCN cover close/stop (#42342) --- homeassistant/components/lcn/cover.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index a4fa0a19b7..512a7978c8 100644 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -89,7 +89,7 @@ class LcnOutputsCover(LcnDevice, CoverEntity): self._is_opening = False self._is_closing = True state = pypck.lcn_defs.MotorStateModifier.DOWN - self.address_connection.control_motors_outputs(state) + self.address_connection.control_motors_outputs(state, self.reverse_time) self.async_write_ha_state() async def async_open_cover(self, **kwargs): @@ -106,7 +106,7 @@ class LcnOutputsCover(LcnDevice, CoverEntity): self._is_closing = False self._is_opening = False state = pypck.lcn_defs.MotorStateModifier.STOP - self.address_connection.control_motors_outputs(state, self.reverse_time) + self.address_connection.control_motors_outputs(state) self.async_write_ha_state() def input_received(self, input_obj): From 35d2badb2478cd52a062f430579173950bdd1edc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 25 Oct 2020 17:53:31 -0500 Subject: [PATCH 038/118] Ensure config entry platforms are excluded from reload (#42367) --- homeassistant/helpers/entity_platform.py | 4 ++++ homeassistant/helpers/reload.py | 10 +++++++--- tests/helpers/test_reload.py | 10 ++++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 2792debff5..693c01b982 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -79,6 +79,10 @@ class EntityPlatform: self.platform_name, [] ).append(self) + def __repr__(self): + """Represent an EntityPlatform.""" + return f"" + @callback def _get_parallel_updates_semaphore( self, entity_has_async_update: bool diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index 1c11afdb46..e596027b7e 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -80,7 +80,9 @@ async def _resetup_platform( # If its an entity platform, we use the entity_platform # async_reset method - platform = async_get_platform(hass, integration_name, integration_platform) + platform = async_get_platform_without_config_entry( + hass, integration_name, integration_platform + ) if platform: await _async_reconfig_platform(platform, root_config[integration_platform]) return @@ -137,11 +139,13 @@ async def async_integration_yaml_config( @callback -def async_get_platform( +def async_get_platform_without_config_entry( hass: HomeAssistantType, integration_name: str, integration_platform_name: str ) -> Optional[EntityPlatform]: - """Find an existing platform.""" + """Find an existing platform that is not a config entry.""" for integration_platform in async_get_platforms(hass, integration_name): + if integration_platform.config_entry is not None: + continue if integration_platform.domain == integration_platform_name: platform: EntityPlatform = integration_platform return platform diff --git a/tests/helpers/test_reload.py b/tests/helpers/test_reload.py index 2584415153..3ed8d17b3f 100644 --- a/tests/helpers/test_reload.py +++ b/tests/helpers/test_reload.py @@ -7,8 +7,9 @@ import pytest from homeassistant import config from homeassistant.const import SERVICE_RELOAD from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.entity_platform import async_get_platforms from homeassistant.helpers.reload import ( - async_get_platform, + async_get_platform_without_config_entry, async_integration_yaml_config, async_reload_integration_platforms, async_setup_reload_service, @@ -52,7 +53,7 @@ async def test_reload_platform(hass): assert f"{DOMAIN}.{PLATFORM}" in hass.config.components assert len(setup_called) == 1 - platform = async_get_platform(hass, PLATFORM, DOMAIN) + platform = async_get_platform_without_config_entry(hass, PLATFORM, DOMAIN) assert platform.platform_name == PLATFORM assert platform.domain == DOMAIN @@ -66,6 +67,11 @@ async def test_reload_platform(hass): assert len(setup_called) == 2 + existing_platforms = async_get_platforms(hass, PLATFORM) + for existing_platform in existing_platforms: + existing_platform.config_entry = "abc" + assert not async_get_platform_without_config_entry(hass, PLATFORM, DOMAIN) + async def test_setup_reload_service(hass): """Test setting up a reload service.""" From 4eacf3f6c0b911c282ea1ed2d03f255983a822f0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 25 Oct 2020 22:54:50 +0000 Subject: [PATCH 039/118] Bumped version to 0.117.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index d9f7d4003a..d51fa29a2f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 9c6351c1b397b9066d3ef44d3ce660db68c3235a Mon Sep 17 00:00:00 2001 From: Maksym D Date: Mon, 26 Oct 2020 08:38:37 +0100 Subject: [PATCH 040/118] Change AfterShip polling interval from 5m to 15m (#42360) This change will prevent reaching API requests limit of AfterShip free-tier account. --- homeassistant/components/aftership/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index 83ab52499d..07267c1185 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -31,7 +31,7 @@ UPDATE_TOPIC = f"{DOMAIN}_update" ICON = "mdi:package-variant-closed" -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) SERVICE_ADD_TRACKING = "add_tracking" SERVICE_REMOVE_TRACKING = "remove_tracking" From 1fb18580b2573f7997e786027502bce93d815deb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Oct 2020 11:30:58 +0100 Subject: [PATCH 041/118] Store original result on template results (#42391) * Store original result on template results * Fix shell command test --- homeassistant/helpers/config_validation.py | 6 +- homeassistant/helpers/template.py | 80 +++++++++++++++++---- tests/components/shell_command/test_init.py | 2 +- tests/helpers/test_config_validation.py | 17 ++++- tests/helpers/test_template.py | 22 ++++++ 5 files changed, 108 insertions(+), 19 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index e41e26e5e0..190cee5e05 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -486,7 +486,11 @@ def string(value: Any) -> str: """Coerce value to string, except for None.""" if value is None: raise vol.Invalid("string value is None") - if isinstance(value, (list, dict)): + + if isinstance(value, template_helper.ResultWrapper): + value = value.render_result + + elif isinstance(value, (list, dict)): raise vol.Invalid("value should be a string") return str(value) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 8920060d8e..9ffc659395 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -11,7 +11,7 @@ import math from operator import attrgetter import random import re -from typing import Any, Generator, Iterable, List, Optional, Union +from typing import Any, Dict, Generator, Iterable, List, Optional, Type, Union from urllib.parse import urlencode as urllib_urlencode import weakref @@ -124,6 +124,43 @@ def is_template_string(maybe_template: str) -> bool: return _RE_JINJA_DELIMITERS.search(maybe_template) is not None +class ResultWrapper: + """Result wrapper class to store render result.""" + + render_result: str + + +def gen_result_wrapper(kls): + """Generate a result wrapper.""" + + class Wrapper(kls, ResultWrapper): + """Wrapper of a kls that can store render_result.""" + + def __init__(self, value: kls, render_result: str) -> None: + super().__init__(value) + self.render_result = render_result + + return Wrapper + + +class TupleWrapper(tuple, ResultWrapper): + """Wrap a tuple.""" + + def __new__(cls, value: tuple, render_result: str) -> "TupleWrapper": + """Create a new tuple class.""" + return super().__new__(cls, tuple(value)) + + def __init__(self, value: tuple, render_result: str): + """Initialize a new tuple class.""" + self.render_result = render_result + + +RESULT_WRAPPERS: Dict[Type, Type] = { + kls: gen_result_wrapper(kls) for kls in (list, dict, set) +} +RESULT_WRAPPERS[tuple] = TupleWrapper + + def extract_entities( hass: HomeAssistantType, template: Optional[str], @@ -285,7 +322,7 @@ class Template: if not isinstance(template, str): raise TypeError("Expected template to be a string") - self.template: str = template + self.template: str = template.strip() self._compiled_code = None self._compiled = None self.hass = hass @@ -322,7 +359,9 @@ class Template: def render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any: """Render given template.""" if self.is_static: - return self.template.strip() + if self.hass.config.legacy_templates: + return self.template + return self._parse_result(self.template) if variables is not None: kwargs.update(variables) @@ -338,7 +377,9 @@ class Template: This method must be run in the event loop. """ if self.is_static: - return self.template.strip() + if self.hass.config.legacy_templates: + return self.template + return self._parse_result(self.template) compiled = self._compiled or self._ensure_compiled() @@ -352,18 +393,27 @@ class Template: render_result = render_result.strip() - if not self.hass.config.legacy_templates: - try: - result = literal_eval(render_result) + if self.hass.config.legacy_templates: + return render_result - # If the literal_eval result is a string, use the original - # render, by not returning right here. The evaluation of strings - # resulting in strings impacts quotes, to avoid unexpected - # output; use the original render instead of the evaluated one. - if not isinstance(result, str): - return result - except (ValueError, SyntaxError, MemoryError): - pass + return self._parse_result(render_result) + + def _parse_result(self, render_result: str) -> Any: + """Parse the result.""" + try: + result = literal_eval(render_result) + + if type(result) in RESULT_WRAPPERS: + result = RESULT_WRAPPERS[type(result)](result, render_result) + + # If the literal_eval result is a string, use the original + # render, by not returning right here. The evaluation of strings + # resulting in strings impacts quotes, to avoid unexpected + # output; use the original render instead of the evaluated one. + if not isinstance(result, str): + return result + except (ValueError, SyntaxError, MemoryError): + pass return render_result diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index f5ad37cc61..440375438c 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -174,7 +174,7 @@ async def test_do_no_run_forever(hass, caplog): assert await async_setup_component( hass, shell_command.DOMAIN, - {shell_command.DOMAIN: {"test_service": "sleep 10000"}}, + {shell_command.DOMAIN: {"test_service": "sleep 10000s"}}, ) await hass.async_block_till_done() diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index d8956c143d..693785f4ea 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -9,7 +9,7 @@ import pytest import voluptuous as vol import homeassistant -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, template from tests.async_mock import Mock, patch @@ -365,7 +365,7 @@ def test_slug(): schema(value) -def test_string(): +def test_string(hass): """Test string validation.""" schema = vol.Schema(cv.string) @@ -381,6 +381,19 @@ def test_string(): for value in (True, 1, "hello"): schema(value) + # Test template support + for text, native in ( + ("[1, 2]", [1, 2]), + ("{1, 2}", {1, 2}), + ("(1, 2)", (1, 2)), + ('{"hello": True}', {"hello": True}), + ): + tpl = template.Template(text, hass) + result = tpl.async_render() + assert isinstance(result, template.ResultWrapper) + assert result == native + assert schema(result) == text + def test_string_with_no_html(): """Test string with no html validation.""" diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 33f0e3f7f0..86e17f8cb0 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -2651,3 +2651,25 @@ async def test_legacy_templates(hass): template.Template("{{ states.sensor.temperature.state }}", hass).async_render() == "12" ) + + +async def test_is_static_still_ast_evals(hass): + """Test is_static still convers to native type.""" + tpl = template.Template("[1, 2]", hass) + assert tpl.is_static + assert tpl.async_render() == [1, 2] + + +async def test_result_wrappers(hass): + """Test result wrappers.""" + for text, native in ( + ("[1, 2]", [1, 2]), + ("{1, 2}", {1, 2}), + ("(1, 2)", (1, 2)), + ('{"hello": True}', {"hello": True}), + ): + tpl = template.Template(text, hass) + result = tpl.async_render() + assert isinstance(result, template.ResultWrapper) + assert result == native + assert result.render_result == text From c00ab762d3e57763217e11adc5ed5412ccbb4ac9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 26 Oct 2020 14:58:36 +0100 Subject: [PATCH 042/118] Bump hatasmota to 0.0.23 (#42394) --- .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_light.py | 32 ++++++++++++++++++- tests/components/tasmota/test_switch.py | 23 ++++++++++++- 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index bdd25f4466..8cc3006c63 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.0.22"], + "requirements": ["hatasmota==0.0.23"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index ba6d0edb54..5686eb6df6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -732,7 +732,7 @@ hass-nabucasa==0.37.1 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.0.22 +hatasmota==0.0.23 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6d010c5e21..af83a66436 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ hangups==0.4.11 hass-nabucasa==0.37.1 # homeassistant.components.tasmota -hatasmota==0.0.22 +hatasmota==0.0.23 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index 871d55a4d7..f9d839516e 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -3,6 +3,11 @@ import copy import json from hatasmota.const import CONF_MAC +from hatasmota.utils import ( + get_topic_stat_result, + get_topic_tele_state, + get_topic_tele_will, +) from homeassistant.components import light from homeassistant.components.light import ( @@ -310,6 +315,16 @@ async def test_controlling_state_via_mqtt_on_off(hass, mqtt_mock, setup_tasmota) state = hass.states.get("light.test") assert state.state == STATE_OFF + async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"POWER":"ON"}') + + state = hass.states.get("light.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"POWER":"OFF"}') + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + async def test_controlling_state_via_mqtt_ct(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" @@ -432,6 +447,16 @@ async def test_controlling_state_via_mqtt_rgbww(hass, mqtt_mock, setup_tasmota): assert state.state == STATE_ON assert state.attributes.get("effect") == "Cycle down" + async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"POWER":"ON"}') + + state = hass.states.get("light.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"POWER":"OFF"}') + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + async def test_sending_mqtt_commands_on_off(hass, mqtt_mock, setup_tasmota): """Test the sending MQTT commands.""" @@ -914,8 +939,13 @@ async def test_entity_id_update_subscriptions(hass, mqtt_mock, setup_tasmota): config = copy.deepcopy(DEFAULT_CONFIG) config["rl"][0] = 2 config["lt_st"] = 1 # 1 channel light (Dimmer) + topics = [ + get_topic_stat_result(config), + get_topic_tele_state(config), + get_topic_tele_will(config), + ] await help_test_entity_id_update_subscriptions( - hass, mqtt_mock, light.DOMAIN, config + hass, mqtt_mock, light.DOMAIN, config, topics ) diff --git a/tests/components/tasmota/test_switch.py b/tests/components/tasmota/test_switch.py index 564dc2f286..208026c2de 100644 --- a/tests/components/tasmota/test_switch.py +++ b/tests/components/tasmota/test_switch.py @@ -2,6 +2,12 @@ import copy import json +from hatasmota.utils import ( + get_topic_stat_result, + get_topic_tele_state, + get_topic_tele_will, +) + from homeassistant.components import switch from homeassistant.components.tasmota.const import DEFAULT_PREFIX from homeassistant.const import ATTR_ASSUMED_STATE, STATE_OFF, STATE_ON @@ -56,6 +62,16 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): state = hass.states.get("switch.test") assert state.state == STATE_OFF + async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"POWER":"ON"}') + + state = hass.states.get("switch.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"POWER":"OFF"}') + + state = hass.states.get("switch.test") + assert state.state == STATE_OFF + async def test_sending_mqtt_commands(hass, mqtt_mock, setup_tasmota): """Test the sending MQTT commands.""" @@ -211,8 +227,13 @@ async def test_entity_id_update_subscriptions(hass, mqtt_mock, setup_tasmota): """Test MQTT subscriptions are managed when entity_id is updated.""" config = copy.deepcopy(DEFAULT_CONFIG) config["rl"][0] = 1 + topics = [ + get_topic_stat_result(config), + get_topic_tele_state(config), + get_topic_tele_will(config), + ] await help_test_entity_id_update_subscriptions( - hass, mqtt_mock, switch.DOMAIN, config + hass, mqtt_mock, switch.DOMAIN, config, topics ) From bcd2ae78a0ed54522e1d0fa827ec5866698422da Mon Sep 17 00:00:00 2001 From: Tsvi Mostovicz Date: Mon, 26 Oct 2020 16:26:21 +0200 Subject: [PATCH 043/118] Bump libhdate depndency (#42396) * Bump libhdate depndency This version fixes an important bug found by @amitfin in py-libhdate/py-libhdate#78 * Update reqs Co-authored-by: Paulus Schoutsen --- homeassistant/components/jewish_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/jewish_calendar/manifest.json b/homeassistant/components/jewish_calendar/manifest.json index 9f0d55433f..500d98dbe9 100644 --- a/homeassistant/components/jewish_calendar/manifest.json +++ b/homeassistant/components/jewish_calendar/manifest.json @@ -2,6 +2,6 @@ "domain": "jewish_calendar", "name": "Jewish Calendar", "documentation": "https://www.home-assistant.io/integrations/jewish_calendar", - "requirements": ["hdate==0.9.5"], + "requirements": ["hdate==0.9.12"], "codeowners": ["@tsvi"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5686eb6df6..7b4478d9c9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -735,7 +735,7 @@ hass_splunk==0.1.1 hatasmota==0.0.23 # homeassistant.components.jewish_calendar -hdate==0.9.5 +hdate==0.9.12 # homeassistant.components.heatmiser heatmiserV3==1.1.18 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index af83a66436..21d331f3f3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -370,7 +370,7 @@ hass-nabucasa==0.37.1 hatasmota==0.0.23 # homeassistant.components.jewish_calendar -hdate==0.9.5 +hdate==0.9.12 # homeassistant.components.here_travel_time herepy==2.0.0 From 934ac73ae5755edb40aefdd0552228155a0e370a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Oct 2020 13:36:03 +0100 Subject: [PATCH 044/118] Fix CI (#42397) --- homeassistant/helpers/event.py | 2 +- homeassistant/helpers/template.py | 6 +++++- tests/helpers/test_event.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index b4caccf531..2044dbd785 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -988,7 +988,7 @@ def async_track_template_result( action: TrackTemplateResultListener, raise_on_template_error: bool = False, ) -> _TrackTemplateResultInfo: - """Add a listener that fires when a the result of a template changes. + """Add a listener that fires when the result of a template changes. The action will fire with the initial result from the template, and then whenever the output from the template changes. The template will diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 9ffc659395..de131d8dc5 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -146,10 +146,14 @@ def gen_result_wrapper(kls): class TupleWrapper(tuple, ResultWrapper): """Wrap a tuple.""" + # This is all magic to be allowed to subclass a tuple. + def __new__(cls, value: tuple, render_result: str) -> "TupleWrapper": """Create a new tuple class.""" return super().__new__(cls, tuple(value)) + # pylint: disable=super-init-not-called + def __init__(self, value: tuple, render_result: str): """Initialize a new tuple class.""" self.render_result = render_result @@ -398,7 +402,7 @@ class Template: return self._parse_result(render_result) - def _parse_result(self, render_result: str) -> Any: + def _parse_result(self, render_result: str) -> Any: # pylint: disable=no-self-use """Parse the result.""" try: result = literal_eval(render_result) diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 9f6e66048f..8add5cc1cf 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -1771,7 +1771,7 @@ async def test_specifically_referenced_entity_is_not_rate_limited(hass): async def test_track_two_templates_with_different_rate_limits(hass): """Test two templates with different rate limits.""" - template_one = Template("{{ states | count }} ", hass) + template_one = Template("{{ (states | count) + 0 }}", hass) template_five = Template("{{ states | count }}", hass) refresh_runs = { From b7c958719ec1291c325cf6526977a004482f0be3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Oct 2020 16:46:26 +0100 Subject: [PATCH 045/118] Fix MQTT publish from a script with templates (#42398) --- homeassistant/components/mqtt/__init__.py | 6 +- homeassistant/components/snips/__init__.py | 2 +- .../mqtt/test_alarm_control_panel.py | 8 +-- tests/components/mqtt/test_climate.py | 13 ++-- tests/components/mqtt/test_cover.py | 62 +++++++++++++------ tests/components/mqtt/test_init.py | 45 +++++++++++++- tests/components/mqtt/test_light.py | 34 +++++----- 7 files changed, 126 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index d7d11e6f49..09655ababd 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -322,7 +322,7 @@ MQTT_RW_PLATFORM_SCHEMA = MQTT_BASE_PLATFORM_SCHEMA.extend( MQTT_PUBLISH_SCHEMA = vol.Schema( { vol.Required(ATTR_TOPIC): valid_publish_topic, - vol.Exclusive(ATTR_PAYLOAD, CONF_PAYLOAD): object, + vol.Exclusive(ATTR_PAYLOAD, CONF_PAYLOAD): cv.string, vol.Exclusive(ATTR_PAYLOAD_TEMPLATE, CONF_PAYLOAD): cv.string, vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean, @@ -567,7 +567,9 @@ async def async_setup_entry(hass, entry): retain: bool = call.data[ATTR_RETAIN] if payload_template is not None: try: - payload = template.Template(payload_template, hass).async_render() + payload = template.Template(payload_template, hass).async_render( + parse_result=False + ) except template.jinja2.TemplateError as exc: _LOGGER.error( "Unable to publish to %s: rendering payload template of " diff --git a/homeassistant/components/snips/__init__.py b/homeassistant/components/snips/__init__.py index 618190e8e7..11f732a3bd 100644 --- a/homeassistant/components/snips/__init__.py +++ b/homeassistant/components/snips/__init__.py @@ -98,7 +98,7 @@ async def async_setup(hass, config): for site_id in site_ids: payload = json.dumps({"siteId": site_id}) hass.components.mqtt.async_publish( - FEEDBACK_ON_TOPIC, None, qos=0, retain=False + FEEDBACK_ON_TOPIC, "", qos=0, retain=False ) hass.components.mqtt.async_publish( topic, payload, qos=int(state), retain=state diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index b6c162eb87..524448e583 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -397,9 +397,9 @@ async def test_disarm_publishes_mqtt_with_template(hass, mqtt_mock): """ config = copy.deepcopy(DEFAULT_CONFIG_CODE) config[alarm_control_panel.DOMAIN]["code"] = "0123" - config[alarm_control_panel.DOMAIN]["command_template"] = ( - '{"action":"{{ action }}",' '"code":"{{ code }}"}' - ) + config[alarm_control_panel.DOMAIN][ + "command_template" + ] = '{"action":"{{ action }}","code":"{{ code }}"}' assert await async_setup_component( hass, alarm_control_panel.DOMAIN, @@ -409,7 +409,7 @@ async def test_disarm_publishes_mqtt_with_template(hass, mqtt_mock): await common.async_alarm_disarm(hass, "0123") mqtt_mock.async_publish.assert_called_once_with( - "alarm/command", {"action": "DISARM", "code": "0123"}, 0, False + "alarm/command", '{"action":"DISARM","code":"0123"}', 0, False ) diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index d60af211d7..3c629a8201 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -322,7 +322,9 @@ async def test_set_target_temperature(hass, mqtt_mock): await common.async_set_temperature(hass, temperature=47, entity_id=ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("temperature") == 47 - mqtt_mock.async_publish.assert_called_once_with("temperature-topic", 47, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "temperature-topic", "47.0", 0, False + ) # also test directly supplying the operation mode to set_temperature mqtt_mock.async_publish.reset_mock() @@ -333,7 +335,10 @@ async def test_set_target_temperature(hass, mqtt_mock): assert state.state == "cool" assert state.attributes.get("temperature") == 21 mqtt_mock.async_publish.assert_has_calls( - [call("mode-topic", "cool", 0, False), call("temperature-topic", 21, 0, False)] + [ + call("mode-topic", "cool", 0, False), + call("temperature-topic", "21.0", 0, False), + ] ) mqtt_mock.async_publish.reset_mock() @@ -372,8 +377,8 @@ async def test_set_target_temperature_low_high(hass, mqtt_mock): state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("target_temp_low") == 20 assert state.attributes.get("target_temp_high") == 23 - mqtt_mock.async_publish.assert_any_call("temperature-low-topic", 20, 0, False) - mqtt_mock.async_publish.assert_any_call("temperature-high-topic", 23, 0, False) + mqtt_mock.async_publish.assert_any_call("temperature-low-topic", "20.0", 0, False) + mqtt_mock.async_publish.assert_any_call("temperature-high-topic", "23.0", 0, False) async def test_set_target_temperature_low_highpessimistic(hass, mqtt_mock): diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index dd879338a4..629d9674b2 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -702,7 +702,9 @@ async def test_set_position_templated(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("set-position-topic", 38, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "set-position-topic", "38", 0, False + ) async def test_set_position_untemplated(hass, mqtt_mock): @@ -732,7 +734,7 @@ async def test_set_position_untemplated(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("position-topic", 62, 0, False) + mqtt_mock.async_publish.assert_called_once_with("position-topic", "62", 0, False) async def test_set_position_untemplated_custom_percentage_range(hass, mqtt_mock): @@ -764,7 +766,7 @@ async def test_set_position_untemplated_custom_percentage_range(hass, mqtt_mock) blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("position-topic", 62, 0, False) + mqtt_mock.async_publish.assert_called_once_with("position-topic", "62", 0, False) async def test_no_command_topic(hass, mqtt_mock): @@ -896,7 +898,9 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 100, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "100", 0, False + ) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -906,7 +910,7 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 0, 0, False) + mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", "0", 0, False) mqtt_mock.async_publish.reset_mock() # Close tilt status would be received from device when non-optimistic @@ -924,7 +928,9 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 100, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "100", 0, False + ) mqtt_mock.async_publish.reset_mock() # Open tilt status would be received from device when non-optimistic @@ -942,7 +948,7 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 0, 0, False) + mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", "0", 0, False) async def test_tilt_given_value(hass, mqtt_mock): @@ -976,7 +982,9 @@ async def test_tilt_given_value(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 80, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "80", 0, False + ) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -986,7 +994,9 @@ async def test_tilt_given_value(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) mqtt_mock.async_publish.reset_mock() # Close tilt status would be received from device when non-optimistic @@ -1004,7 +1014,9 @@ async def test_tilt_given_value(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 80, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "80", 0, False + ) mqtt_mock.async_publish.reset_mock() # Open tilt status would be received from device when non-optimistic @@ -1022,7 +1034,9 @@ async def test_tilt_given_value(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) async def test_tilt_given_value_optimistic(hass, mqtt_mock): @@ -1062,7 +1076,9 @@ async def test_tilt_given_value_optimistic(hass, mqtt_mock): ] assert current_cover_tilt_position == 80 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 80, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "80", 0, False + ) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -1077,7 +1093,9 @@ async def test_tilt_given_value_optimistic(hass, mqtt_mock): ] assert current_cover_tilt_position == 25 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) async def test_tilt_given_value_altered_range(hass, mqtt_mock): @@ -1119,7 +1137,9 @@ async def test_tilt_given_value_altered_range(hass, mqtt_mock): ] assert current_cover_tilt_position == 50 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -1134,7 +1154,7 @@ async def test_tilt_given_value_altered_range(hass, mqtt_mock): ] assert current_cover_tilt_position == 0 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 0, 0, False) + mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", "0", 0, False) mqtt_mock.async_publish.reset_mock() await hass.services.async_call( @@ -1149,7 +1169,9 @@ async def test_tilt_given_value_altered_range(hass, mqtt_mock): ] assert current_cover_tilt_position == 50 - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) async def test_tilt_via_topic(hass, mqtt_mock): @@ -1353,7 +1375,9 @@ async def test_tilt_position(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 50, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "50", 0, False + ) async def test_tilt_position_altered_range(hass, mqtt_mock): @@ -1389,7 +1413,9 @@ async def test_tilt_position_altered_range(hass, mqtt_mock): blocking=True, ) - mqtt_mock.async_publish.assert_called_once_with("tilt-command-topic", 25, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "25", 0, False + ) async def test_find_percentage_in_range_defaults(hass, mqtt_mock): diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 7b634714a5..7a833a72d5 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -122,7 +122,7 @@ async def test_service_call_with_template_payload_renders_template(hass, mqtt_mo mqtt.async_publish_template(hass, "test/topic", "{{ 1+1 }}") await hass.async_block_till_done() assert mqtt_mock.async_publish.called - assert mqtt_mock.async_publish.call_args[0][1] == 2 + assert mqtt_mock.async_publish.call_args[0][1] == "2" async def test_service_call_with_payload_doesnt_render_template(hass, mqtt_mock): @@ -1497,3 +1497,46 @@ async def test_debug_info_qos_retain(hass, mqtt_mock): "time": start_dt, "topic": "sensor/abc", } in debug_info_data["entities"][0]["subscriptions"][0]["messages"] + + +async def test_publish_json_from_template(hass, mqtt_mock): + """Test the publishing of call to services.""" + test_str = "{'valid': 'python', 'invalid': 'json'}" + + await async_setup_component( + hass, + "script", + { + "script": { + "test_script_payload": { + "sequence": { + "service": "mqtt.publish", + "data": {"topic": "test-topic", "payload": test_str}, + } + }, + "test_script_payload_template": { + "sequence": { + "service": "mqtt.publish", + "data": {"topic": "test-topic", "payload_template": test_str}, + } + }, + } + }, + ) + + await hass.services.async_call("script", "test_script_payload", blocking=True) + await hass.async_block_till_done() + + assert mqtt_mock.async_publish.called + assert mqtt_mock.async_publish.call_args[0][1] == test_str + + mqtt_mock.async_publish.reset_mock() + assert not mqtt_mock.async_publish.called + + await hass.services.async_call( + "script", "test_script_payload_template", blocking=True + ) + await hass.async_block_till_done() + + assert mqtt_mock.async_publish.called + assert mqtt_mock.async_publish.call_args[0][1] == test_str diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 62291fd18b..102d1726cc 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -760,11 +760,11 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): [ call("test_light_rgb/set", "on", 2, False), call("test_light_rgb/rgb/set", "255,128,0", 2, False), - call("test_light_rgb/brightness/set", 50, 2, False), + call("test_light_rgb/brightness/set", "50", 2, False), call("test_light_rgb/hs/set", "359.0,78.0", 2, False), - call("test_light_rgb/white_value/set", 80, 2, False), + call("test_light_rgb/white_value/set", "80", 2, False), call("test_light_rgb/xy/set", "0.14,0.131", 2, False), - call("test_light_rgb/color_temp/set", 125, 2, False), + call("test_light_rgb/color_temp/set", "125", 2, False), ], any_order=True, ) @@ -841,7 +841,7 @@ async def test_sending_mqtt_color_temp_command_with_template(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ call("test_light_color_temp/set", "on", 0, False), - call("test_light_color_temp/color_temp/set", 10, 0, False), + call("test_light_color_temp/color_temp/set", "10", 0, False), ], any_order=True, ) @@ -877,7 +877,7 @@ async def test_on_command_first(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ call("test_light/set", "ON", 0, False), - call("test_light/bright", 50, 0, False), + call("test_light/bright", "50", 0, False), ], ) mqtt_mock.async_publish.reset_mock() @@ -911,7 +911,7 @@ async def test_on_command_last(hass, mqtt_mock): # test_light/set: 'ON' mqtt_mock.async_publish.assert_has_calls( [ - call("test_light/bright", 50, 0, False), + call("test_light/bright", "50", 0, False), call("test_light/set", "ON", 0, False), ], ) @@ -946,7 +946,9 @@ async def test_on_command_brightness(hass, mqtt_mock): # Should get the following MQTT messages. # test_light/bright: 255 - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 255, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "test_light/bright", "255", 0, False + ) mqtt_mock.async_publish.reset_mock() await common.async_turn_off(hass, "light.test") @@ -957,7 +959,7 @@ async def test_on_command_brightness(hass, mqtt_mock): # Turn on w/ brightness await common.async_turn_on(hass, "light.test", brightness=50) - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 50, 0, False) + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", "50", 0, False) mqtt_mock.async_publish.reset_mock() await common.async_turn_off(hass, "light.test") @@ -969,7 +971,7 @@ async def test_on_command_brightness(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ call("test_light/rgb", "255,128,0", 0, False), - call("test_light/bright", 50, 0, False), + call("test_light/bright", "50", 0, False), ], any_order=True, ) @@ -1000,7 +1002,9 @@ async def test_on_command_brightness_scaled(hass, mqtt_mock): # Should get the following MQTT messages. # test_light/bright: 100 - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 100, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "test_light/bright", "100", 0, False + ) mqtt_mock.async_publish.reset_mock() await common.async_turn_off(hass, "light.test") @@ -1011,19 +1015,21 @@ async def test_on_command_brightness_scaled(hass, mqtt_mock): # Turn on w/ brightness await common.async_turn_on(hass, "light.test", brightness=50) - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 20, 0, False) + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", "20", 0, False) mqtt_mock.async_publish.reset_mock() # Turn on w/ max brightness await common.async_turn_on(hass, "light.test", brightness=255) - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 100, 0, False) + mqtt_mock.async_publish.assert_called_once_with( + "test_light/bright", "100", 0, False + ) mqtt_mock.async_publish.reset_mock() # Turn on w/ min brightness await common.async_turn_on(hass, "light.test", brightness=1) - mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 1, 0, False) + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", "1", 0, False) mqtt_mock.async_publish.reset_mock() await common.async_turn_off(hass, "light.test") @@ -1035,7 +1041,7 @@ async def test_on_command_brightness_scaled(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ call("test_light/rgb", "255,128,0", 0, False), - call("test_light/bright", 1, 0, False), + call("test_light/bright", "1", 0, False), ], any_order=True, ) From f716b7714b0a27cc99406bf57a698b4245497375 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 26 Oct 2020 16:01:09 +0100 Subject: [PATCH 046/118] Allow skip parsing template result (#42401) --- homeassistant/components/api/__init__.py | 2 +- .../components/shell_command/__init__.py | 4 ++- homeassistant/components/template/cover.py | 7 ++++- homeassistant/components/template/fan.py | 7 ++++- homeassistant/components/template/light.py | 22 +++++++++------ homeassistant/helpers/condition.py | 5 +++- homeassistant/helpers/template.py | 28 ++++++++++++------- tests/components/shell_command/test_init.py | 2 +- tests/helpers/test_condition.py | 23 +++++++++++++++ tests/helpers/test_template.py | 22 +++++++++++++++ 10 files changed, 98 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 779df78516..72fb263606 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -410,7 +410,7 @@ class APITemplateView(HomeAssistantView): try: data = await request.json() tpl = template.Template(data["template"], request.app["hass"]) - return str(tpl.async_render(data.get("variables"))) + return tpl.async_render(variables=data.get("variables"), parse_result=False) except (ValueError, TemplateError) as ex: return self.json_message( f"Error rendering template: {ex}", HTTP_BAD_REQUEST diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index bce980035d..0d221cb6b0 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -45,7 +45,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: if args_compiled: try: - rendered_args = args_compiled.async_render(service.data) + rendered_args = args_compiled.async_render( + variables=service.data, parse_result=False + ) except TemplateError as ex: _LOGGER.exception("Error rendering command template: %s", ex) return diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 7c35663bb5..cc91bdabd7 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -39,6 +39,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script +from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -258,7 +259,11 @@ class CoverTemplate(TemplateEntity, CoverEntity): self._position = None return - state = str(result).lower() + if isinstance(result, ResultWrapper): + state = result.render_result.lower() + else: + state = str(result).lower() + if state in _VALID_STATES: if state in ("true", STATE_OPEN): self._position = 100 diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index f0b22fb46b..e5b3569e14 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -34,6 +34,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script +from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -367,7 +368,11 @@ class TemplateFan(TemplateEntity, FanEntity): @callback def _update_speed(self, speed): # Validate speed - speed = str(speed) + if isinstance(speed, ResultWrapper): + speed = speed.render_result + else: + speed = str(speed) + if speed in self._speed_list: self._speed = speed elif speed in [STATE_UNAVAILABLE, STATE_UNKNOWN]: diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 5d6c1befd4..84939beeca 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -33,6 +33,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script +from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -405,23 +406,28 @@ class LightTemplate(TemplateEntity, LightEntity): def _update_state(self, result): """Update the state from the template.""" - if isinstance(result, TemplateError): + if isinstance(result, (TemplateError, ResultWrapper)): # This behavior is legacy self._state = False if not self._availability_template: self._available = True return + if isinstance(result, bool): + self._state = result + return + state = str(result).lower() if state in _VALID_STATES: self._state = state in ("true", STATE_ON) - else: - _LOGGER.error( - "Received invalid light is_on state: %s. Expected: %s", - state, - ", ".join(_VALID_STATES), - ) - self._state = None + return + + _LOGGER.error( + "Received invalid light is_on state: %s. Expected: %s", + state, + ", ".join(_VALID_STATES), + ) + self._state = None @callback def _update_temperature(self, render): diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 57271ccaf8..9cfa6c0099 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -461,7 +461,10 @@ def async_template( if isinstance(value, bool): return value - return str(value).lower() == "true" + if isinstance(value, str): + return value.lower() == "true" + + return False def async_template_from_config( diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index de131d8dc5..7584610016 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -4,7 +4,7 @@ import asyncio import base64 import collections.abc from datetime import datetime, timedelta -from functools import wraps +from functools import partial, wraps import json import logging import math @@ -360,28 +360,36 @@ class Template: return extract_entities(self.hass, self.template, variables) - def render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any: + def render( + self, + variables: TemplateVarsType = None, + parse_result: bool = True, + **kwargs: Any, + ) -> Any: """Render given template.""" if self.is_static: - if self.hass.config.legacy_templates: + if self.hass.config.legacy_templates or not parse_result: return self.template return self._parse_result(self.template) - if variables is not None: - kwargs.update(variables) - return run_callback_threadsafe( - self.hass.loop, self.async_render, kwargs + self.hass.loop, + partial(self.async_render, variables, parse_result, **kwargs), ).result() @callback - def async_render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any: + def async_render( + self, + variables: TemplateVarsType = None, + parse_result: bool = True, + **kwargs: Any, + ) -> Any: """Render given template. This method must be run in the event loop. """ if self.is_static: - if self.hass.config.legacy_templates: + if self.hass.config.legacy_templates or not parse_result: return self.template return self._parse_result(self.template) @@ -397,7 +405,7 @@ class Template: render_result = render_result.strip() - if self.hass.config.legacy_templates: + if self.hass.config.legacy_templates or not parse_result: return render_result return self._parse_result(render_result) diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index 440375438c..f5ad37cc61 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -174,7 +174,7 @@ async def test_do_no_run_forever(hass, caplog): assert await async_setup_component( hass, shell_command.DOMAIN, - {shell_command.DOMAIN: {"test_service": "sleep 10000s"}}, + {shell_command.DOMAIN: {"test_service": "sleep 10000"}}, ) await hass.async_block_till_done() diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 001c59e2f2..802ad7699e 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -915,3 +915,26 @@ async def test_condition_template_error(hass, caplog): assert caplog.records[0].message.startswith( "Error during template condition: UndefinedError:" ) + + +async def test_condition_template_invalid_results(hass): + """Test template condition render false with invalid results.""" + test = await condition.async_from_config( + hass, {"condition": "template", "value_template": "{{ 'string' }}"} + ) + assert not test(hass) + + test = await condition.async_from_config( + hass, {"condition": "template", "value_template": "{{ 10.1 }}"} + ) + assert not test(hass) + + test = await condition.async_from_config( + hass, {"condition": "template", "value_template": "{{ 42 }}"} + ) + assert not test(hass) + + test = await condition.async_from_config( + hass, {"condition": "template", "value_template": "{{ [1, 2, 3] }}"} + ) + assert not test(hass) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 86e17f8cb0..750978b84e 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -2653,6 +2653,28 @@ async def test_legacy_templates(hass): ) +async def test_no_result_parsing(hass): + """Test if templates results are not parsed.""" + hass.states.async_set("sensor.temperature", "12") + + assert ( + template.Template("{{ states.sensor.temperature.state }}", hass).async_render( + parse_result=False + ) + == "12" + ) + + assert ( + template.Template("{{ false }}", hass).async_render(parse_result=False) + == "False" + ) + + assert ( + template.Template("{{ [1, 2, 3] }}", hass).async_render(parse_result=False) + == "[1, 2, 3]" + ) + + async def test_is_static_still_ast_evals(hass): """Test is_static still convers to native type.""" tpl = template.Template("[1, 2]", hass) From 3ec83dc29e7ccbc760ffaa9f98da6a2d3b137e95 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Oct 2020 16:38:48 +0100 Subject: [PATCH 047/118] Catch ValueError in google_translate (#42405) --- CODEOWNERS | 1 - .../components/google_translate/manifest.json | 2 +- homeassistant/components/google_translate/tts.py | 11 ++++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 18439b3bb8..87112840f5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -161,7 +161,6 @@ homeassistant/components/goalzero/* @tkdrob homeassistant/components/gogogate2/* @vangorra homeassistant/components/google_assistant/* @home-assistant/cloud homeassistant/components/google_cloud/* @lufton -homeassistant/components/google_translate/* @awarecan homeassistant/components/gpsd/* @fabaff homeassistant/components/gree/* @cmroche homeassistant/components/greeneye_monitor/* @jkeljo diff --git a/homeassistant/components/google_translate/manifest.json b/homeassistant/components/google_translate/manifest.json index 452a5352aa..c949e514d8 100644 --- a/homeassistant/components/google_translate/manifest.json +++ b/homeassistant/components/google_translate/manifest.json @@ -3,5 +3,5 @@ "name": "Google Translate Text-to-Speech", "documentation": "https://www.home-assistant.io/integrations/google_translate", "requirements": ["gTTS-token==1.1.3"], - "codeowners": ["@awarecan"] + "codeowners": [] } diff --git a/homeassistant/components/google_translate/tts.py b/homeassistant/components/google_translate/tts.py index 7988b699c1..66c0000804 100644 --- a/homeassistant/components/google_translate/tts.py +++ b/homeassistant/components/google_translate/tts.py @@ -123,9 +123,14 @@ class GoogleProvider(Provider): data = b"" for idx, part in enumerate(message_parts): - part_token = await self.hass.async_add_executor_job( - token.calculate_token, part - ) + try: + part_token = await self.hass.async_add_executor_job( + token.calculate_token, part + ) + except ValueError as err: + # If token seed fetching fails. + _LOGGER.warning(err) + return None, None url_param = { "ie": "UTF-8", From 287b33eef3ebe3f9f90868f6dc31f497666b16db Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Oct 2020 16:40:43 +0100 Subject: [PATCH 048/118] Do not set up DSM when setting up Nest legacy (#42406) --- homeassistant/components/nest/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/nest/sensor.py b/homeassistant/components/nest/sensor.py index 794e8210f8..6245c5d83d 100644 --- a/homeassistant/components/nest/sensor.py +++ b/homeassistant/components/nest/sensor.py @@ -14,4 +14,6 @@ async def async_setup_entry( """Set up the sensors.""" if DATA_SDM not in entry.data: await async_setup_legacy_entry(hass, entry, async_add_entities) + return + await async_setup_sdm_entry(hass, entry, async_add_entities) From 326d36d30385505bd148e4a0c99ccebebb5a0dd3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 26 Oct 2020 19:29:10 +0100 Subject: [PATCH 049/118] Skip template result parsing in several places (#42408) * Skip template result parsing in several places * Adjust alert integration * Adjust Alexa integration * Adjust apns integration * Adjust arest integration * Adjust dialogflow integration * Adjust generic camera integration * Adjust imap email content integration * Adjust InfluxDB integration * Adjust intent integration * Adjust logbook integration * Adjust HP ILO integration * Adjust manual alarm control panel integration * Adjust manual mqtt alarm control panel integration * Adjust minio integration * Adjust mqtt integration * Adjust notify integration * Adjust persistent notification integration * Adjust rest integration * Adjust rss feed template integration * Adjust slack integration * Adjust Xiaomi integration * Adjust TCP integration * Adjust Telegram Bot integration * Bump CI cache version * Revert "Bump CI cache version" This reverts commit 875efe58cf165d84ce68aa8867b99a169fad4ea5. * Adjust demo tests --- homeassistant/components/alert/__init__.py | 6 +++--- .../components/alexa/flash_briefings.py | 16 ++++++++++++---- homeassistant/components/alexa/intent.py | 2 +- homeassistant/components/apns/notify.py | 2 +- homeassistant/components/arest/sensor.py | 2 +- homeassistant/components/dialogflow/__init__.py | 2 +- homeassistant/components/generic/camera.py | 4 ++-- homeassistant/components/hp_ilo/sensor.py | 4 +++- .../components/imap_email_content/sensor.py | 2 +- homeassistant/components/influxdb/sensor.py | 4 ++-- .../components/intent_script/__init__.py | 7 ++++--- homeassistant/components/logbook/__init__.py | 2 +- .../components/manual/alarm_control_panel.py | 4 +++- .../manual_mqtt/alarm_control_panel.py | 4 +++- homeassistant/components/minio/__init__.py | 2 +- .../components/mqtt/alarm_control_panel.py | 2 +- homeassistant/components/mqtt/cover.py | 2 +- .../components/mqtt/light/schema_template.py | 8 ++++++-- homeassistant/components/notify/__init__.py | 8 ++++---- .../persistent_notification/__init__.py | 4 ++-- homeassistant/components/rest/binary_sensor.py | 4 ++-- homeassistant/components/rest/notify.py | 2 +- homeassistant/components/rest/sensor.py | 4 ++-- homeassistant/components/rest/switch.py | 4 ++-- .../components/rest_command/__init__.py | 11 ++++++++--- .../components/rss_feed_template/__init__.py | 8 +++++--- homeassistant/components/slack/notify.py | 2 +- homeassistant/components/tcp/sensor.py | 4 +++- .../components/telegram_bot/__init__.py | 4 +++- homeassistant/components/xiaomi/camera.py | 2 +- tests/components/demo/test_notify.py | 2 +- 31 files changed, 82 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/alert/__init__.py b/homeassistant/components/alert/__init__.py index 755bf6b3c4..53b1a1248d 100644 --- a/homeassistant/components/alert/__init__.py +++ b/homeassistant/components/alert/__init__.py @@ -276,7 +276,7 @@ class Alert(ToggleEntity): self._send_done_message = True if self._message_template is not None: - message = self._message_template.async_render() + message = self._message_template.async_render(parse_result=False) else: message = self._name @@ -291,7 +291,7 @@ class Alert(ToggleEntity): if self._done_message_template is None: return - message = self._done_message_template.async_render() + message = self._done_message_template.async_render(parse_result=False) await self._send_notification_message(message) @@ -300,7 +300,7 @@ class Alert(ToggleEntity): msg_payload = {ATTR_MESSAGE: message} if self._title_template is not None: - title = self._title_template.async_render() + title = self._title_template.async_render(parse_result=False) msg_payload.update({ATTR_TITLE: title}) if self._data: msg_payload.update({ATTR_DATA: self._data}) diff --git a/homeassistant/components/alexa/flash_briefings.py b/homeassistant/components/alexa/flash_briefings.py index ed3da1d10b..b8f78705e1 100644 --- a/homeassistant/components/alexa/flash_briefings.py +++ b/homeassistant/components/alexa/flash_briefings.py @@ -80,13 +80,17 @@ class AlexaFlashBriefingView(http.HomeAssistantView): output = {} if item.get(CONF_TITLE) is not None: if isinstance(item.get(CONF_TITLE), template.Template): - output[ATTR_TITLE_TEXT] = item[CONF_TITLE].async_render() + output[ATTR_TITLE_TEXT] = item[CONF_TITLE].async_render( + parse_result=False + ) else: output[ATTR_TITLE_TEXT] = item.get(CONF_TITLE) if item.get(CONF_TEXT) is not None: if isinstance(item.get(CONF_TEXT), template.Template): - output[ATTR_MAIN_TEXT] = item[CONF_TEXT].async_render() + output[ATTR_MAIN_TEXT] = item[CONF_TEXT].async_render( + parse_result=False + ) else: output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT) @@ -97,13 +101,17 @@ class AlexaFlashBriefingView(http.HomeAssistantView): if item.get(CONF_AUDIO) is not None: if isinstance(item.get(CONF_AUDIO), template.Template): - output[ATTR_STREAM_URL] = item[CONF_AUDIO].async_render() + output[ATTR_STREAM_URL] = item[CONF_AUDIO].async_render( + parse_result=False + ) else: output[ATTR_STREAM_URL] = item.get(CONF_AUDIO) if item.get(CONF_DISPLAY_URL) is not None: if isinstance(item.get(CONF_DISPLAY_URL), template.Template): - output[ATTR_REDIRECTION_URL] = item[CONF_DISPLAY_URL].async_render() + output[ATTR_REDIRECTION_URL] = item[CONF_DISPLAY_URL].async_render( + parse_result=False + ) else: output[ATTR_REDIRECTION_URL] = item.get(CONF_DISPLAY_URL) diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index c04b493bee..f64031250e 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -271,7 +271,7 @@ class AlexaResponse: self.reprompt = { "type": speech_type.value, - key: text.async_render(self.variables), + key: text.async_render(self.variables, parse_result=False), } def as_dict(self): diff --git a/homeassistant/components/apns/notify.py b/homeassistant/components/apns/notify.py index 59b2a7aa9f..24d666fa59 100644 --- a/homeassistant/components/apns/notify.py +++ b/homeassistant/components/apns/notify.py @@ -229,7 +229,7 @@ class ApnsNotificationService(BaseNotificationService): if isinstance(message, str): rendered_message = message elif isinstance(message, template_helper.Template): - rendered_message = message.render() + rendered_message = message.render(parse_result=False) else: rendered_message = "" diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py index 7e50b1df8f..d213a3d290 100644 --- a/homeassistant/components/arest/sensor.py +++ b/homeassistant/components/arest/sensor.py @@ -78,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def _render(value): try: - return value_template.async_render({"value": value}) + return value_template.async_render({"value": value}, parse_result=False) except TemplateError: _LOGGER.exception("Error parsing value") return value diff --git a/homeassistant/components/dialogflow/__init__.py b/homeassistant/components/dialogflow/__init__.py index b9ba977e42..e623919f09 100644 --- a/homeassistant/components/dialogflow/__init__.py +++ b/homeassistant/components/dialogflow/__init__.py @@ -161,7 +161,7 @@ class DialogflowResponse: assert self.speech is None if isinstance(text, template.Template): - text = text.async_render(self.parameters) + text = text.async_render(self.parameters, parse_result=False) self.speech = text diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 3656e66956..2e798b8cc4 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -121,7 +121,7 @@ class GenericCamera(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" try: - url = self._still_image_url.async_render() + url = self._still_image_url.async_render(parse_result=False) except TemplateError as err: _LOGGER.error("Error parsing template %s: %s", self._still_image_url, err) return self._last_image @@ -178,7 +178,7 @@ class GenericCamera(Camera): return None try: - return self._stream_source.async_render() + return self._stream_source.async_render(parse_result=False) except TemplateError as err: _LOGGER.error("Error parsing template %s: %s", self._stream_source, err) return None diff --git a/homeassistant/components/hp_ilo/sensor.py b/homeassistant/components/hp_ilo/sensor.py index 1cb65292c7..da597acb8b 100644 --- a/homeassistant/components/hp_ilo/sensor.py +++ b/homeassistant/components/hp_ilo/sensor.py @@ -157,7 +157,9 @@ class HpIloSensor(Entity): ilo_data = getattr(self.hp_ilo_data.data, self._ilo_function)() if self._sensor_value_template is not None: - ilo_data = self._sensor_value_template.render(ilo_data=ilo_data) + ilo_data = self._sensor_value_template.render( + ilo_data=ilo_data, parse_result=False + ) self._state = ilo_data diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 21b535450a..04d4ca97c5 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -183,7 +183,7 @@ class EmailContentSensor(Entity): ATTR_DATE: email_message["Date"], ATTR_BODY: EmailContentSensor.get_msg_text(email_message), } - return self._value_template.render(variables) + return self._value_template.render(variables, parse_result=False) def sender_allowed(self, email_message): """Check if the sender is in the allowed senders list.""" diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index 2fcf4baaba..ff9f6f9315 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -268,7 +268,7 @@ class InfluxFluxSensorData: """Get the latest data by querying influx.""" _LOGGER.debug(RENDERING_QUERY_MESSAGE, self.query) try: - rendered_query = self.query.render() + rendered_query = self.query.render(parse_result=False) except TemplateError as ex: _LOGGER.error(RENDERING_QUERY_ERROR_MESSAGE, ex) return @@ -312,7 +312,7 @@ class InfluxQLSensorData: """Get the latest data with a shell command.""" _LOGGER.debug(RENDERING_WHERE_MESSAGE, self.where) try: - where_clause = self.where.render() + where_clause = self.where.render(parse_result=False) except TemplateError as ex: _LOGGER.error(RENDERING_WHERE_ERROR_MESSAGE, ex) return diff --git a/homeassistant/components/intent_script/__init__.py b/homeassistant/components/intent_script/__init__.py index ebbd2bb824..892ea83982 100644 --- a/homeassistant/components/intent_script/__init__.py +++ b/homeassistant/components/intent_script/__init__.py @@ -87,13 +87,14 @@ class ScriptIntentHandler(intent.IntentHandler): if speech is not None: response.async_set_speech( - speech[CONF_TEXT].async_render(slots), speech[CONF_TYPE] + speech[CONF_TEXT].async_render(slots, parse_result=False), + speech[CONF_TYPE], ) if card is not None: response.async_set_card( - card[CONF_TITLE].async_render(slots), - card[CONF_CONTENT].async_render(slots), + card[CONF_TITLE].async_render(slots, parse_result=False), + card[CONF_CONTENT].async_render(slots, parse_result=False), card[CONF_TYPE], ) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 99fdc54603..254d99ed84 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -144,7 +144,7 @@ async def async_setup(hass, config): domain = DOMAIN message.hass = hass - message = message.async_render() + message = message.async_render(parse_result=False) async_log_entry(hass, name, message, domain, entity_id) hass.components.frontend.async_register_built_in_panel( diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py index 168d5b637e..2313bcace1 100644 --- a/homeassistant/components/manual/alarm_control_panel.py +++ b/homeassistant/components/manual/alarm_control_panel.py @@ -385,7 +385,9 @@ class ManualAlarm(alarm.AlarmControlPanelEntity, RestoreEntity): if isinstance(self._code, str): alarm_code = self._code else: - alarm_code = self._code.render(from_state=self._state, to_state=state) + alarm_code = self._code.render( + parse_result=False, from_state=self._state, to_state=state + ) check = not alarm_code or code == alarm_code if not check: _LOGGER.warning("Invalid code given for %s", state) diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py index 10c2a473a2..f11938396a 100644 --- a/homeassistant/components/manual_mqtt/alarm_control_panel.py +++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py @@ -406,7 +406,9 @@ class ManualMQTTAlarm(alarm.AlarmControlPanelEntity): if isinstance(self._code, str): alarm_code = self._code else: - alarm_code = self._code.render(from_state=self._state, to_state=state) + alarm_code = self._code.render( + from_state=self._state, to_state=state, parse_result=False + ) check = not alarm_code or code == alarm_code if not check: _LOGGER.warning("Invalid code given for %s", state) diff --git a/homeassistant/components/minio/__init__.py b/homeassistant/components/minio/__init__.py index 24e7049dd1..178058986c 100644 --- a/homeassistant/components/minio/__init__.py +++ b/homeassistant/components/minio/__init__.py @@ -124,7 +124,7 @@ def setup(hass, config): def _render_service_value(service, key): value = service.data[key] value.hass = hass - return value.async_render() + return value.async_render(parse_result=False) def put_file(service): """Upload file service.""" diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 6d33175e6c..35d0e1fb42 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -338,7 +338,7 @@ class MqttAlarm( """Publish via mqtt.""" command_template = self._config[CONF_COMMAND_TEMPLATE] values = {"action": action, "code": code} - payload = command_template.async_render(**values) + payload = command_template.async_render(**values, parse_result=False) mqtt.async_publish( self.hass, self._config[CONF_COMMAND_TOPIC], diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 20146b0b7d..c3a7813324 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -557,7 +557,7 @@ class MqttCover( position = kwargs[ATTR_POSITION] percentage_position = position if set_position_template is not None: - position = set_position_template.async_render(**kwargs) + position = set_position_template.async_render(parse_result=False, **kwargs) else: position = self.find_in_range_from_percent(position, COVER_PAYLOAD) diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 46a2b9f7ab..44a87f2af2 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -441,7 +441,9 @@ class MqttLightTemplate( mqtt.async_publish( self.hass, self._topics[CONF_COMMAND_TOPIC], - self._templates[CONF_COMMAND_ON_TEMPLATE].async_render(**values), + self._templates[CONF_COMMAND_ON_TEMPLATE].async_render( + parse_result=False, **values + ), self._config[CONF_QOS], self._config[CONF_RETAIN], ) @@ -464,7 +466,9 @@ class MqttLightTemplate( mqtt.async_publish( self.hass, self._topics[CONF_COMMAND_TOPIC], - self._templates[CONF_COMMAND_OFF_TEMPLATE].async_render(**values), + self._templates[CONF_COMMAND_OFF_TEMPLATE].async_render( + parse_result=False, **values + ), self._config[CONF_QOS], self._config[CONF_RETAIN], ) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 016c1aa9e8..08219567ed 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -133,7 +133,7 @@ class BaseNotificationService: if title: title.hass = self.hass - kwargs[ATTR_TITLE] = title.async_render() + kwargs[ATTR_TITLE] = title.async_render(parse_result=False) if self._registered_targets.get(service.service) is not None: kwargs[ATTR_TARGET] = [self._registered_targets[service.service]] @@ -141,7 +141,7 @@ class BaseNotificationService: kwargs[ATTR_TARGET] = service.data.get(ATTR_TARGET) message.hass = self.hass - kwargs[ATTR_MESSAGE] = message.async_render() + kwargs[ATTR_MESSAGE] = message.async_render(parse_result=False) kwargs[ATTR_DATA] = service.data.get(ATTR_DATA) await self.async_send_message(**kwargs) @@ -229,12 +229,12 @@ async def async_setup(hass, config): payload = {} message = service.data[ATTR_MESSAGE] message.hass = hass - payload[ATTR_MESSAGE] = message.async_render() + payload[ATTR_MESSAGE] = message.async_render(parse_result=False) title = service.data.get(ATTR_TITLE) if title: title.hass = hass - payload[ATTR_TITLE] = title.async_render() + payload[ATTR_TITLE] = title.async_render(parse_result=False) await hass.services.async_call( pn.DOMAIN, pn.SERVICE_CREATE, payload, blocking=True diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 9a1f35c947..49a9f4c3be 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -119,7 +119,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: if title is not None: try: title.hass = hass - title = title.async_render() + title = title.async_render(parse_result=False) except TemplateError as ex: _LOGGER.error("Error rendering title %s: %s", title, ex) title = title.template @@ -128,7 +128,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: try: message.hass = hass - message = message.async_render() + message = message.async_render(parse_result=False) except TemplateError as ex: _LOGGER.error("Error rendering message %s: %s", message, ex) message = message.template diff --git a/homeassistant/components/rest/binary_sensor.py b/homeassistant/components/rest/binary_sensor.py index c25750e2a2..ec8b3ba012 100644 --- a/homeassistant/components/rest/binary_sensor.py +++ b/homeassistant/components/rest/binary_sensor.py @@ -84,7 +84,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= if resource_template is not None: resource_template.hass = hass - resource = resource_template.render() + resource = resource_template.render(parse_result=False) if value_template is not None: value_template.hass = hass @@ -189,6 +189,6 @@ class RestBinarySensor(BinarySensorEntity): async def async_update(self): """Get the latest data from REST API and updates the state.""" if self._resource_template is not None: - self.rest.set_url(self._resource_template.render()) + self.rest.set_url(self._resource_template.render(parse_result=False)) await self.rest.async_update() diff --git a/homeassistant/components/rest/notify.py b/homeassistant/components/rest/notify.py index d7ee57b4f8..b8f81b19e9 100644 --- a/homeassistant/components/rest/notify.py +++ b/homeassistant/components/rest/notify.py @@ -163,7 +163,7 @@ class RestNotificationService(BaseNotificationService): key: _data_template_creator(item) for key, item in value.items() } value.hass = self._hass - return value.async_render(kwargs) + return value.async_render(kwargs, parse_result=False) data.update(_data_template_creator(self._data_template)) diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index e9783e3fea..323447ba1b 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -103,7 +103,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= if resource_template is not None: resource_template.hass = hass - resource = resource_template.render() + resource = resource_template.render(parse_result=False) if username and password: if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION: @@ -202,7 +202,7 @@ class RestSensor(Entity): async def async_update(self): """Get the latest data from REST API and update the state.""" if self._resource_template is not None: - self.rest.set_url(self._resource_template.render()) + self.rest.set_url(self._resource_template.render(parse_result=False)) await self.rest.async_update() diff --git a/homeassistant/components/rest/switch.py b/homeassistant/components/rest/switch.py index 865f4d01b3..1b980e12b7 100644 --- a/homeassistant/components/rest/switch.py +++ b/homeassistant/components/rest/switch.py @@ -162,7 +162,7 @@ class RestSwitch(SwitchEntity): async def async_turn_on(self, **kwargs): """Turn the device on.""" - body_on_t = self._body_on.async_render() + body_on_t = self._body_on.async_render(parse_result=False) try: req = await self.set_device_state(body_on_t) @@ -178,7 +178,7 @@ class RestSwitch(SwitchEntity): async def async_turn_off(self, **kwargs): """Turn the device off.""" - body_off_t = self._body_off.async_render() + body_off_t = self._body_off.async_render(parse_result=False) try: req = await self.set_device_state(body_off_t) diff --git a/homeassistant/components/rest_command/__init__.py b/homeassistant/components/rest_command/__init__.py index b1996bdce5..7ba46d3bf5 100644 --- a/homeassistant/components/rest_command/__init__.py +++ b/homeassistant/components/rest_command/__init__.py @@ -93,17 +93,22 @@ async def async_setup(hass, config): payload = None if template_payload: payload = bytes( - template_payload.async_render(variables=service.data), "utf-8" + template_payload.async_render( + variables=service.data, parse_result=False + ), + "utf-8", ) - request_url = template_url.async_render(variables=service.data) + request_url = template_url.async_render( + variables=service.data, parse_result=False + ) headers = None if template_headers: headers = {} for header_name, template_header in template_headers.items(): headers[header_name] = template_header.async_render( - variables=service.data + variables=service.data, parse_result=False ) if content_type: diff --git a/homeassistant/components/rss_feed_template/__init__.py b/homeassistant/components/rss_feed_template/__init__.py index b979e4d526..c9871c8f6b 100644 --- a/homeassistant/components/rss_feed_template/__init__.py +++ b/homeassistant/components/rss_feed_template/__init__.py @@ -83,17 +83,19 @@ class RssView(HomeAssistantView): response += "\n" if self._title is not None: - response += " %s\n" % escape(self._title.async_render()) + response += " %s\n" % escape( + self._title.async_render(parse_result=False) + ) for item in self._items: response += " \n" if "title" in item: response += " " - response += escape(item["title"].async_render()) + response += escape(item["title"].async_render(parse_result=False)) response += "\n" if "description" in item: response += " " - response += escape(item["description"].async_render()) + response += escape(item["description"].async_render(parse_result=False)) response += "\n" response += " \n" diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index 5fff857825..88317b3158 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -118,7 +118,7 @@ def _async_templatize_blocks(hass, value): } tmpl = template.Template(value, hass=hass) - return tmpl.async_render() + return tmpl.async_render(parse_result=False) class SlackNotificationService(BaseNotificationService): diff --git a/homeassistant/components/tcp/sensor.py b/homeassistant/components/tcp/sensor.py index 9eee857fd8..868cd9b855 100644 --- a/homeassistant/components/tcp/sensor.py +++ b/homeassistant/components/tcp/sensor.py @@ -136,7 +136,9 @@ class TcpSensor(Entity): if self._config[CONF_VALUE_TEMPLATE] is not None: try: - self._state = self._config[CONF_VALUE_TEMPLATE].render(value=value) + self._state = self._config[CONF_VALUE_TEMPLATE].render( + parse_result=False, value=value + ) return except TemplateError: _LOGGER.error( diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 7a9d0c18e9..60e33414b1 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -329,7 +329,9 @@ async def async_setup(hass, config): else: attribute_templ.hass = hass try: - data[attribute] = attribute_templ.async_render() + data[attribute] = attribute_templ.async_render( + parse_result=False + ) except TemplateError as exc: _LOGGER.error( "TemplateError in %s: %s -> %s", diff --git a/homeassistant/components/xiaomi/camera.py b/homeassistant/components/xiaomi/camera.py index 45466be2cc..97a0aca20f 100644 --- a/homeassistant/components/xiaomi/camera.py +++ b/homeassistant/components/xiaomi/camera.py @@ -142,7 +142,7 @@ class XiaomiCamera(Camera): """Return a still image response from the camera.""" try: - host = self.host.async_render() + host = self.host.async_render(parse_result=False) except TemplateError as exc: _LOGGER.error("Error parsing template %s: %s", self.host, exc) return self._last_image diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py index eac5e6a600..893b9d57e6 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -108,7 +108,7 @@ async def test_sending_templated_message(hass, events): await hass.async_block_till_done() last_event = events[-1] assert last_event.data[notify.ATTR_TITLE] == "temperature" - assert last_event.data[notify.ATTR_MESSAGE] == 10 + assert last_event.data[notify.ATTR_MESSAGE] == "10" async def test_method_forwards_correct_data(hass, events): From 13b8bc629095f872886e1f604bc42fbe8e9f3dfd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 26 Oct 2020 20:29:20 +0100 Subject: [PATCH 050/118] Add missing config flow translation key for Nest (#42423) --- homeassistant/components/nest/strings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nest/strings.json b/homeassistant/components/nest/strings.json index 943afd254c..6882e8f55e 100644 --- a/homeassistant/components/nest/strings.json +++ b/homeassistant/components/nest/strings.json @@ -25,7 +25,8 @@ "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", - "authorize_url_fail": "Unknown error generating an authorize url." + "authorize_url_fail": "Unknown error generating an authorize url.", + "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]" }, "create_entry": { "default": "[%key:common::config_flow::create_entry::authenticated%]" From 2893972c69ee6f95962a81e87216e2ba6132b3eb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 26 Oct 2020 19:30:18 +0000 Subject: [PATCH 051/118] Bumped version to 0.117.0b5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index d51fa29a2f..cebee253df 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0b5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 93e71ef21827f01543b30eb00ce1ce1f2b1aff3b Mon Sep 17 00:00:00 2001 From: Angelo Gagliano <25516409+TheGardenMonkey@users.noreply.github.com> Date: Tue, 27 Oct 2020 05:02:16 -0400 Subject: [PATCH 052/118] Add retry for unavailable static tplink devices after HA starts (#42247) --- homeassistant/components/tplink/common.py | 30 +++++++++++++++++++++++ homeassistant/components/tplink/light.py | 21 ++++++---------- homeassistant/components/tplink/switch.py | 20 ++++++--------- tests/components/tplink/test_light.py | 30 +++++++++++++++++++++++ 4 files changed, 74 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/tplink/common.py b/homeassistant/components/tplink/common.py index ad4c4ff63f..a8ba48e393 100644 --- a/homeassistant/components/tplink/common.py +++ b/homeassistant/components/tplink/common.py @@ -13,6 +13,8 @@ from pyHS100 import ( from homeassistant.helpers.typing import HomeAssistantType +from .const import DOMAIN as TPLINK_DOMAIN + _LOGGER = logging.getLogger(__name__) @@ -127,3 +129,31 @@ def get_static_devices(config_data) -> SmartDevices: "Failed to setup device %s due to %s; not retrying", host, sde ) return SmartDevices(lights, switches) + + +def add_available_devices(hass, device_type, device_class, async_add_entities): + """Get sysinfo for all devices.""" + + devices = hass.data[TPLINK_DOMAIN][device_type] + + if f"{device_type}_remaining" in hass.data[TPLINK_DOMAIN]: + devices = hass.data[TPLINK_DOMAIN][f"{device_type}_remaining"] + + entities_ready = [] + entities_unavailable = [] + for device in devices: + try: + device.get_sysinfo() + entities_ready.append(device_class(device)) + except SmartDeviceException as ex: + entities_unavailable.append(device) + _LOGGER.warning( + "Unable to communicate with device %s: %s", + device.host, + ex, + ) + + hass.data[TPLINK_DOMAIN][f"{device_type}_remaining"] = entities_unavailable + + if entities_ready: + async_add_entities(entities_ready, update_before_add=True) diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 21dc382f48..e2acf3fcd9 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -16,7 +16,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR_TEMP, LightEntity, ) -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, PlatformNotReady import homeassistant.helpers.device_registry as dr from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.color import ( @@ -26,6 +26,7 @@ from homeassistant.util.color import ( import homeassistant.util.dt as dt_util from . import CONF_LIGHT, DOMAIN as TPLINK_DOMAIN +from .common import add_available_devices PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=5) @@ -60,20 +61,12 @@ SLEEP_TIME = 2 async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_entities): """Set up lights.""" - devices = hass.data[TPLINK_DOMAIN][CONF_LIGHT] - entities = [] + await hass.async_add_executor_job( + add_available_devices, hass, CONF_LIGHT, TPLinkSmartBulb, async_add_entities + ) - await hass.async_add_executor_job(get_devices_sysinfo, devices) - for device in devices: - entities.append(TPLinkSmartBulb(device)) - - async_add_entities(entities, update_before_add=True) - - -def get_devices_sysinfo(devices): - """Get sysinfo for all devices.""" - for device in devices: - device.get_sysinfo() + if hass.data[TPLINK_DOMAIN][f"{CONF_LIGHT}_remaining"]: + raise PlatformNotReady def brightness_to_percentage(byt): diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 236607f4cd..b7e31e82e7 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -11,10 +11,12 @@ from homeassistant.components.switch import ( SwitchEntity, ) from homeassistant.const import ATTR_VOLTAGE +from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.device_registry as dr from homeassistant.helpers.typing import HomeAssistantType from . import CONF_SWITCH, DOMAIN as TPLINK_DOMAIN +from .common import add_available_devices PARALLEL_UPDATES = 0 @@ -29,20 +31,12 @@ SLEEP_TIME = 2 async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_entities): """Set up switches.""" - devices = hass.data[TPLINK_DOMAIN][CONF_SWITCH] - entities = [] + await hass.async_add_executor_job( + add_available_devices, hass, CONF_SWITCH, SmartPlugSwitch, async_add_entities + ) - await hass.async_add_executor_job(get_devices_sysinfo, devices) - for device in devices: - entities.append(SmartPlugSwitch(device)) - - async_add_entities(entities, update_before_add=True) - - -def get_devices_sysinfo(devices): - """Get sysinfo for all devices.""" - for device in devices: - device.get_sysinfo() + if hass.data[TPLINK_DOMAIN][f"{CONF_SWITCH}_remaining"]: + raise PlatformNotReady class SmartPlugSwitch(SwitchEntity): diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index 56de30b195..4987f4f26c 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -577,3 +577,33 @@ async def test_update_failure( in caplog.text ) assert "Device 123.123.123.123|light1 responded after " in caplog.text + + +async def test_async_setup_entry_unavailable( + hass: HomeAssistant, light_mock_data: LightMockData, caplog +): + """Test unavailable devices trigger a later retry.""" + caplog.clear() + caplog.set_level(logging.WARNING) + + with patch( + "homeassistant.components.tplink.common.SmartDevice.get_sysinfo", + side_effect=SmartDeviceException, + ): + await async_setup_component(hass, HA_DOMAIN, {}) + await hass.async_block_till_done() + + await async_setup_component( + hass, + tplink.DOMAIN, + { + tplink.DOMAIN: { + CONF_DISCOVERY: False, + CONF_LIGHT: [{CONF_HOST: "123.123.123.123"}], + } + }, + ) + + await hass.async_block_till_done() + assert "Unable to communicate with device 123.123.123.123" in caplog.text + assert len(hass.data[tplink.DOMAIN][f"{CONF_LIGHT}_remaining"]) == 1 From e3d29429d38f1bd0c5ef05eb911b4e2817b953c2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 26 Oct 2020 22:12:21 +0100 Subject: [PATCH 053/118] Make sure to clean up httpx session on failed connection to Axis device (#42428) --- homeassistant/components/axis/device.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index 94aa149c09..79204bf300 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -280,12 +280,15 @@ async def get_device(hass, host, port, username, password): except axis.Unauthorized as err: LOGGER.warning("Connected to device at %s but not registered.", host) + await device.vapix.close() raise AuthenticationRequired from err except (asyncio.TimeoutError, axis.RequestError) as err: LOGGER.error("Error connecting to the Axis device at %s", host) + await device.vapix.close() raise CannotConnect from err except axis.AxisException as err: LOGGER.exception("Unknown Axis communication error occurred") + await device.vapix.close() raise AuthenticationRequired from err From af31aa7995ce2254952587c0fe39ad900e3f97c3 Mon Sep 17 00:00:00 2001 From: Tsvi Mostovicz Date: Tue, 27 Oct 2020 15:56:45 +0200 Subject: [PATCH 054/118] Fix DLNA DMR media receiver when using Python 3.9 (#42430) --- homeassistant/components/dlna_dmr/media_player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index acbd69138d..2b01ad2a4a 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -168,7 +168,7 @@ async def async_setup_platform( requester = AiohttpSessionRequester(session, True) # ensure event handler has been started - with await hass.data[DLNA_DMR_DATA]["lock"]: + async with hass.data[DLNA_DMR_DATA]["lock"]: server_host = config.get(CONF_LISTEN_IP) if server_host is None: server_host = get_local_ip() @@ -220,7 +220,7 @@ class DlnaDmrDevice(MediaPlayerEntity): async def _async_on_hass_stop(self, event): """Event handler on Home Assistant stop.""" - with await self.hass.data[DLNA_DMR_DATA]["lock"]: + async with self.hass.data[DLNA_DMR_DATA]["lock"]: await self._device.async_unsubscribe_services() async def async_update(self): From 29e7aa753cafe24ed67a240364df630ddf720648 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Mon, 26 Oct 2020 17:10:30 -0400 Subject: [PATCH 055/118] Remove title string from Xbox translations (#42431) --- homeassistant/components/xbox/strings.json | 1 - homeassistant/components/xbox/translations/en.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/xbox/strings.json b/homeassistant/components/xbox/strings.json index ccece2d7f7..accd677594 100644 --- a/homeassistant/components/xbox/strings.json +++ b/homeassistant/components/xbox/strings.json @@ -1,5 +1,4 @@ { - "title": "xbox", "config": { "step": { "pick_implementation": { diff --git a/homeassistant/components/xbox/translations/en.json b/homeassistant/components/xbox/translations/en.json index ae5465f889..0bb1266bde 100644 --- a/homeassistant/components/xbox/translations/en.json +++ b/homeassistant/components/xbox/translations/en.json @@ -13,6 +13,5 @@ "title": "Pick Authentication Method" } } - }, - "title": "xbox" + } } \ No newline at end of file From 9fa97473cb6693001f513ab9f619cd5033aea238 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 26 Oct 2020 17:26:03 -0600 Subject: [PATCH 056/118] Bump simplisafe-python to 9.6.0 (#42437) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 0968235257..0f209b366e 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.5.1"], + "requirements": ["simplisafe-python==9.6.0"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7b4478d9c9..7a6ea6537a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2017,7 +2017,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.5.1 +simplisafe-python==9.6.0 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 21d331f3f3..1e7d4830d7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -959,7 +959,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.5.1 +simplisafe-python==9.6.0 # homeassistant.components.slack slackclient==2.5.0 From 91e8593fb67bd8c8863bbbf273e0171bc84be189 Mon Sep 17 00:00:00 2001 From: rajlaud <50647620+rajlaud@users.noreply.github.com> Date: Mon, 26 Oct 2020 23:12:03 -0500 Subject: [PATCH 057/118] Improve Squeezebox media browser performance (#42439) * Bump pysqueezebox version to better handle large libraries for media browser * Merge changes into requirements --- homeassistant/components/squeezebox/browse_media.py | 2 +- homeassistant/components/squeezebox/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/squeezebox/browse_media.py b/homeassistant/components/squeezebox/browse_media.py index 3e5523e847..bab24e39cb 100644 --- a/homeassistant/components/squeezebox/browse_media.py +++ b/homeassistant/components/squeezebox/browse_media.py @@ -65,7 +65,7 @@ CONTENT_TYPE_TO_CHILD_TYPE = { "Genres": MEDIA_TYPE_GENRE, } -BROWSE_LIMIT = 500 +BROWSE_LIMIT = 1000 async def build_item_response(player, payload): diff --git a/homeassistant/components/squeezebox/manifest.json b/homeassistant/components/squeezebox/manifest.json index af358b832f..a0d0d1ef50 100644 --- a/homeassistant/components/squeezebox/manifest.json +++ b/homeassistant/components/squeezebox/manifest.json @@ -6,7 +6,7 @@ "@rajlaud" ], "requirements": [ - "pysqueezebox==0.5.1" + "pysqueezebox==0.5.4" ], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 7a6ea6537a..aa488f262d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1694,7 +1694,7 @@ pysonos==0.0.35 pyspcwebgw==0.4.0 # homeassistant.components.squeezebox -pysqueezebox==0.5.1 +pysqueezebox==0.5.4 # homeassistant.components.stiebel_eltron pystiebeleltron==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1e7d4830d7..24907d0721 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -835,7 +835,7 @@ pysonos==0.0.35 pyspcwebgw==0.4.0 # homeassistant.components.squeezebox -pysqueezebox==0.5.1 +pysqueezebox==0.5.4 # homeassistant.components.syncthru pysyncthru==0.7.0 From 2697056ba9b2ea89897262b8657ec6de3f812bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gabriel?= Date: Tue, 27 Oct 2020 05:03:07 -0300 Subject: [PATCH 058/118] Add device info default values to Panasonic Viera (#42441) --- .../components/panasonic_viera/__init__.py | 4 +- .../components/panasonic_viera/const.py | 3 + .../panasonic_viera/media_player.py | 16 ++-- .../panasonic_viera/test_config_flow.py | 76 ++++++++++++++++--- tests/components/panasonic_viera/test_init.py | 6 +- 5 files changed, 85 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 8ebf5c8c4b..f51c6d9d37 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -245,7 +245,9 @@ class Remote: """Return device info.""" if self._control is None: return None - return await self._handle_errors(self._control.get_device_info) + device_info = await self._handle_errors(self._control.get_device_info) + _LOGGER.debug("Fetched device info: %s", str(device_info)) + return device_info async def _handle_errors(self, func, *args): """Handle errors from func, set available and reconnect if needed.""" diff --git a/homeassistant/components/panasonic_viera/const.py b/homeassistant/components/panasonic_viera/const.py index 36b6136044..a2e3fc2eec 100644 --- a/homeassistant/components/panasonic_viera/const.py +++ b/homeassistant/components/panasonic_viera/const.py @@ -18,4 +18,7 @@ ATTR_MANUFACTURER = "manufacturer" ATTR_MODEL_NUMBER = "modelNumber" ATTR_UDN = "UDN" +DEFAULT_MANUFACTURER = "Panasonic" +DEFAULT_MODEL_NUMBER = "Panasonic Viera" + ERROR_INVALID_PIN_CODE = "invalid_pin_code" diff --git a/homeassistant/components/panasonic_viera/media_player.py b/homeassistant/components/panasonic_viera/media_player.py index 316385f7d9..c59c77b569 100644 --- a/homeassistant/components/panasonic_viera/media_player.py +++ b/homeassistant/components/panasonic_viera/media_player.py @@ -26,6 +26,8 @@ from .const import ( ATTR_MODEL_NUMBER, ATTR_REMOTE, ATTR_UDN, + DEFAULT_MANUFACTURER, + DEFAULT_MODEL_NUMBER, DOMAIN, ) @@ -69,11 +71,11 @@ class PanasonicVieraTVEntity(MediaPlayerEntity): self._device_info = device_info @property - def unique_id(self) -> str: + def unique_id(self): """Return the unique ID of the device.""" - if self._device_info is not None: - return self._device_info[ATTR_UDN] - return None + if self._device_info is None: + return None + return self._device_info[ATTR_UDN] @property def device_info(self): @@ -83,8 +85,10 @@ class PanasonicVieraTVEntity(MediaPlayerEntity): return { "name": self._name, "identifiers": {(DOMAIN, self._device_info[ATTR_UDN])}, - "manufacturer": self._device_info[ATTR_MANUFACTURER], - "model": self._device_info[ATTR_MODEL_NUMBER], + "manufacturer": self._device_info.get( + ATTR_MANUFACTURER, DEFAULT_MANUFACTURER + ), + "model": self._device_info.get(ATTR_MODEL_NUMBER, DEFAULT_MODEL_NUMBER), } @property diff --git a/tests/components/panasonic_viera/test_config_flow.py b/tests/components/panasonic_viera/test_config_flow.py index f3d1f1cc8f..15e6b73202 100644 --- a/tests/components/panasonic_viera/test_config_flow.py +++ b/tests/components/panasonic_viera/test_config_flow.py @@ -12,6 +12,8 @@ from homeassistant.components.panasonic_viera.const import ( CONF_APP_ID, CONF_ENCRYPTION_KEY, CONF_ON_ACTION, + DEFAULT_MANUFACTURER, + DEFAULT_MODEL_NUMBER, DEFAULT_NAME, DEFAULT_PORT, DOMAIN, @@ -37,13 +39,14 @@ def panasonic_viera_setup_fixture(): def get_mock_remote( host="1.2.3.4", + request_error=None, authorize_error=None, encrypted=False, app_id=None, encryption_key=None, name=DEFAULT_NAME, - manufacturer="mock-manufacturer", - model_number="mock-model-number", + manufacturer=DEFAULT_MANUFACTURER, + model_number=DEFAULT_MODEL_NUMBER, unique_id="mock-unique-id", ): """Return a mock remote.""" @@ -54,7 +57,8 @@ def get_mock_remote( mock_remote.enc_key = encryption_key def request_pin_code(name=None): - return + if request_error is not None: + raise request_error mock_remote.request_pin_code = request_pin_code @@ -110,8 +114,8 @@ async def test_flow_non_encrypted(hass): CONF_ON_ACTION: None, ATTR_DEVICE_INFO: { ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: "mock-manufacturer", - ATTR_MODEL_NUMBER: "mock-model-number", + ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, + ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, ATTR_UDN: "mock-unique-id", }, } @@ -164,6 +168,56 @@ async def test_flow_unknown_abort(hass): assert result["reason"] == "unknown" +async def test_flow_encrypted_not_connected_pin_code_request(hass): + """Test flow with encryption and PIN code request connection error abortion during pairing request step.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + mock_remote = get_mock_remote(encrypted=True, request_error=TimeoutError) + + with patch( + "homeassistant.components.panasonic_viera.config_flow.RemoteControl", + return_value=mock_remote, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + ) + + assert result["type"] == "abort" + assert result["reason"] == "cannot_connect" + + +async def test_flow_encrypted_unknown_pin_code_request(hass): + """Test flow with encryption and PIN code request unknown error abortion during pairing request step.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + mock_remote = get_mock_remote(encrypted=True, request_error=Exception) + + with patch( + "homeassistant.components.panasonic_viera.config_flow.RemoteControl", + return_value=mock_remote, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + ) + + assert result["type"] == "abort" + assert result["reason"] == "unknown" + + async def test_flow_encrypted_valid_pin_code(hass): """Test flow with encryption and valid PIN code.""" @@ -208,8 +262,8 @@ async def test_flow_encrypted_valid_pin_code(hass): CONF_ENCRYPTION_KEY: "test-encryption-key", ATTR_DEVICE_INFO: { ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: "mock-manufacturer", - ATTR_MODEL_NUMBER: "mock-model-number", + ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, + ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, ATTR_UDN: "mock-unique-id", }, } @@ -392,8 +446,8 @@ async def test_imported_flow_non_encrypted(hass): CONF_ON_ACTION: "test-on-action", ATTR_DEVICE_INFO: { ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: "mock-manufacturer", - ATTR_MODEL_NUMBER: "mock-model-number", + ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, + ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, ATTR_UDN: "mock-unique-id", }, } @@ -442,8 +496,8 @@ async def test_imported_flow_encrypted_valid_pin_code(hass): CONF_ENCRYPTION_KEY: "test-encryption-key", ATTR_DEVICE_INFO: { ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: "mock-manufacturer", - ATTR_MODEL_NUMBER: "mock-model-number", + ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, + ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, ATTR_UDN: "mock-unique-id", }, } diff --git a/tests/components/panasonic_viera/test_init.py b/tests/components/panasonic_viera/test_init.py index 70b8db656e..3ac6b7e12d 100644 --- a/tests/components/panasonic_viera/test_init.py +++ b/tests/components/panasonic_viera/test_init.py @@ -8,6 +8,8 @@ from homeassistant.components.panasonic_viera.const import ( CONF_APP_ID, CONF_ENCRYPTION_KEY, CONF_ON_ACTION, + DEFAULT_MANUFACTURER, + DEFAULT_MODEL_NUMBER, DEFAULT_NAME, DEFAULT_PORT, DOMAIN, @@ -33,8 +35,8 @@ MOCK_ENCRYPTION_DATA = { MOCK_DEVICE_INFO = { ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: "mock-manufacturer", - ATTR_MODEL_NUMBER: "mock-model-number", + ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, + ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, ATTR_UDN: "mock-unique-id", } From 8da732a89ddb5fc1a565d08f7ee7430c3e92e359 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 27 Oct 2020 11:04:11 +0100 Subject: [PATCH 059/118] Fix hassio-version (#42449) --- azure-pipelines-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index 4668e62892..2420c2e44a 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -143,8 +143,8 @@ stages: version="$(homeassistantRelease)" - git clone https://github.com/home-assistant/hassio-version - cd hassio-version + git clone https://github.com/home-assistant/version + cd version dev_version="$(jq --raw-output '.homeassistant.default' dev.json)" beta_version="$(jq --raw-output '.homeassistant.default' beta.json)" From 1962a7c7ceb496f7ac3e96ba9122f93164e0f03a Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 27 Oct 2020 14:50:20 +0100 Subject: [PATCH 060/118] Fix DSM Surveillance Station when only home mode is enabled (#42456) --- homeassistant/components/synology_dsm/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index c77ea1d249..44a68b4c18 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -316,6 +316,8 @@ class SynoApi: ) self._with_surveillance_station = bool( self._fetching_entities.get(SynoSurveillanceStation.CAMERA_API_KEY) + ) or bool( + self._fetching_entities.get(SynoSurveillanceStation.HOME_MODE_API_KEY) ) # Reset not used API, information is not reset since it's used in device_info From 45c1c2acac34168fde500e88550535d18d7beeab Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 27 Oct 2020 15:08:56 +0100 Subject: [PATCH 061/118] Fix initial state of pi4ioe (#42459) --- homeassistant/components/pi4ioe5v9xxxx/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/pi4ioe5v9xxxx/switch.py b/homeassistant/components/pi4ioe5v9xxxx/switch.py index 6bac673459..81de76c086 100644 --- a/homeassistant/components/pi4ioe5v9xxxx/switch.py +++ b/homeassistant/components/pi4ioe5v9xxxx/switch.py @@ -55,7 +55,7 @@ class Pi4ioe5v9Switch(SwitchEntity): self._name = name or DEVICE_DEFAULT_NAME self._pin = pin self._invert_logic = invert_logic - self._state = False + self._state = not invert_logic @property def name(self): From ba6acc286c3e8de14478887d7833087f68498aaf Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 27 Oct 2020 23:01:13 +0100 Subject: [PATCH 062/118] Bump hatasmota to 0.0.24 (#42472) --- homeassistant/components/tasmota/discovery.py | 3 + .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/tasmota/test_binary_sensor.py | 24 ++++++ .../components/tasmota/test_device_trigger.py | 50 ++++++++++++ tests/components/tasmota/test_light.py | 77 +++++++++++++++++-- 7 files changed, 152 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/tasmota/discovery.py b/homeassistant/components/tasmota/discovery.py index 6d224a9870..06a8833323 100644 --- a/homeassistant/components/tasmota/discovery.py +++ b/homeassistant/components/tasmota/discovery.py @@ -29,6 +29,9 @@ TASMOTA_DISCOVERY_INSTANCE = "tasmota_discovery_instance" def clear_discovery_hash(hass, discovery_hash): """Clear entry in ALREADY_DISCOVERED list.""" + if ALREADY_DISCOVERED not in hass.data: + # Discovery is shutting down + return del hass.data[ALREADY_DISCOVERED][discovery_hash] diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 8cc3006c63..991e38e6a9 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.0.23"], + "requirements": ["hatasmota==0.0.24"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index aa488f262d..1a51d5313f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -732,7 +732,7 @@ hass-nabucasa==0.37.1 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.0.23 +hatasmota==0.0.24 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 24907d0721..ca30214d58 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ hangups==0.4.11 hass-nabucasa==0.37.1 # homeassistant.components.tasmota -hatasmota==0.0.23 +hatasmota==0.0.24 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/tests/components/tasmota/test_binary_sensor.py b/tests/components/tasmota/test_binary_sensor.py index 69d49a6ca8..52ab88b0ec 100644 --- a/tests/components/tasmota/test_binary_sensor.py +++ b/tests/components/tasmota/test_binary_sensor.py @@ -92,6 +92,30 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): assert state.state == STATE_OFF +async def test_friendly_names(hass, mqtt_mock, setup_tasmota): + """Test state update via MQTT.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config["rl"][0] = 1 + config["swc"][0] = 1 + config["swc"][1] = 1 + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.tasmota_binary_sensor_1") + assert state.state == "unavailable" + assert state.attributes.get("friendly_name") == "Tasmota binary_sensor 1" + + state = hass.states.get("binary_sensor.beer") + assert state.state == "unavailable" + assert state.attributes.get("friendly_name") == "Beer" + + async def test_off_delay(hass, mqtt_mock, setup_tasmota): """Test off_delay option.""" config = copy.deepcopy(DEFAULT_CONFIG) diff --git a/tests/components/tasmota/test_device_trigger.py b/tests/components/tasmota/test_device_trigger.py index 9d6d1cac79..35f3b4be9d 100644 --- a/tests/components/tasmota/test_device_trigger.py +++ b/tests/components/tasmota/test_device_trigger.py @@ -830,3 +830,53 @@ async def test_attach_unknown_remove_device_from_registry( # Remove the device device_reg.async_remove_device(device_entry.id) await hass.async_block_till_done() + + +async def test_attach_remove_config_entry(hass, mqtt_mock, setup_tasmota, device_reg): + """Test trigger cleanup when removing a Tasmota config entry.""" + # Discover a device with device trigger + config = copy.deepcopy(DEFAULT_CONFIG) + config["swc"][0] = 0 + mac = config["mac"] + + mqtt_mock.async_subscribe.reset_mock() + + async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", json.dumps(config)) + await hass.async_block_till_done() + + device_entry = device_reg.async_get_device(set(), {("mac", mac)}) + + calls = [] + + def callback(trigger, context): + calls.append(trigger["trigger"]["description"]) + + await async_attach_trigger( + hass, + { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "00000049A3BC_switch_1_TOGGLE", + "type": "button_short_press", + "subtype": "switch_1", + }, + callback, + None, + ) + + # Fake short press. + async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/SWITCH1T", '{"TRIG":"TOGGLE"}') + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0] == "event 'tasmota_event'" + + # Remove the Tasmota config entry + config_entries = hass.config_entries.async_entries("tasmota") + await hass.config_entries.async_remove(config_entries[0].entry_id) + await hass.async_block_till_done() + + # Verify the triggers are no longer active + async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/SWITCH1T", '{"TRIG":"TOGGLE"}') + await hass.async_block_till_done() + assert len(calls) == 1 diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index f9d839516e..48b1dec723 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -482,7 +482,7 @@ async def test_sending_mqtt_commands_on_off(hass, mqtt_mock, setup_tasmota): # Turn the light on and verify MQTT message is sent await common.async_turn_on(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 ON", 0, False + "tasmota_49A3BC/cmnd/Power1", "ON", 0, False ) mqtt_mock.async_publish.reset_mock() @@ -493,7 +493,7 @@ async def test_sending_mqtt_commands_on_off(hass, mqtt_mock, setup_tasmota): # Turn the light off and verify MQTT message is sent await common.async_turn_off(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 OFF", 0, False + "tasmota_49A3BC/cmnd/Power1", "OFF", 0, False ) mqtt_mock.async_publish.reset_mock() @@ -581,6 +581,57 @@ async def test_sending_mqtt_commands_rgbww(hass, mqtt_mock, setup_tasmota): mqtt_mock.async_publish.reset_mock() +async def test_sending_mqtt_commands_power_unlinked(hass, mqtt_mock, setup_tasmota): + """Test the sending MQTT commands to a light with unlinked dimlevel and power.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config["rl"][0] = 2 + config["lt_st"] = 1 # 1 channel light (dimmer) + config["so"]["20"] = 1 # Update of Dimmer/Color/CT without turning power on + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") + state = hass.states.get("light.test") + assert state.state == STATE_OFF + await hass.async_block_till_done() + await hass.async_block_till_done() + mqtt_mock.async_publish.reset_mock() + + # Turn the light on and verify MQTT message is sent + await common.async_turn_on(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 ON", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Tasmota is not optimistic, the state should still be off + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + # Turn the light off and verify MQTT message is sent + await common.async_turn_off(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 OFF", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Turn the light on and verify MQTT messages are sent; POWER should be sent + await common.async_turn_on(hass, "light.test", brightness=192) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/Backlog", + "NoDelay;Fade 0;NoDelay;Dimmer 75;NoDelay;Power1 ON", + 0, + False, + ) + mqtt_mock.async_publish.reset_mock() + + async def test_transition(hass, mqtt_mock, setup_tasmota): """Test transition commands.""" config = copy.deepcopy(DEFAULT_CONFIG) @@ -733,7 +784,7 @@ async def test_split_light2(hass, mqtt_mock, setup_tasmota): async def _test_unlinked_light(hass, mqtt_mock, config, num_switches): - """Test multi-channel light split to single-channel dimmers.""" + """Test rgbww light split to rgb+ww.""" mac = config["mac"] num_lights = 2 @@ -775,7 +826,7 @@ async def _test_unlinked_light(hass, mqtt_mock, config, num_switches): async def test_unlinked_light(hass, mqtt_mock, setup_tasmota): - """Test multi-channel light split to rgb+ww.""" + """Test rgbww light split to rgb+ww.""" config = copy.deepcopy(DEFAULT_CONFIG) config["rl"][0] = 2 config["rl"][1] = 2 @@ -786,7 +837,7 @@ async def test_unlinked_light(hass, mqtt_mock, setup_tasmota): async def test_unlinked_light2(hass, mqtt_mock, setup_tasmota): - """Test multi-channel light split to single-channel dimmers.""" + """Test rgbww light split to rgb+ww.""" config = copy.deepcopy(DEFAULT_CONFIG) config["rl"][0] = 1 config["rl"][1] = 1 @@ -899,6 +950,22 @@ async def test_discovery_removal_relay_as_light(hass, mqtt_mock, caplog, setup_t ) +async def test_discovery_removal_relay_as_light2( + hass, mqtt_mock, caplog, setup_tasmota +): + """Test removal of discovered relay as light.""" + config1 = copy.deepcopy(DEFAULT_CONFIG) + config1["rl"][0] = 1 + config1["so"]["30"] = 1 # Enforce Home Assistant auto-discovery as light + config2 = copy.deepcopy(DEFAULT_CONFIG) + config2["rl"][0] = 0 + config2["so"]["30"] = 0 # Disable Home Assistant auto-discovery as light + + await help_test_discovery_removal( + hass, mqtt_mock, caplog, light.DOMAIN, config1, config2 + ) + + async def test_discovery_update_unchanged_light(hass, mqtt_mock, caplog, setup_tasmota): """Test update of discovered light.""" config = copy.deepcopy(DEFAULT_CONFIG) From 2effbb66048518cfc0db467fa83929d204e97585 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 27 Oct 2020 23:00:39 +0100 Subject: [PATCH 063/118] Update frontend to 20201021.3 (#42486) --- 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 f02e9a4ec3..ee27bb08d7 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==20201021.2"], + "requirements": ["home-assistant-frontend==20201021.3"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 41211fafb7..0b5fb019cd 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.37.1 -home-assistant-frontend==20201021.2 +home-assistant-frontend==20201021.3 httpx==0.16.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.2 diff --git a/requirements_all.txt b/requirements_all.txt index 1a51d5313f..cab2659319 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -759,7 +759,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20201021.2 +home-assistant-frontend==20201021.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ca30214d58..5d3df98687 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -385,7 +385,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20201021.2 +home-assistant-frontend==20201021.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From cb453d3ae1209125459af35d03a3ff4138b07f4c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 27 Oct 2020 22:51:58 +0100 Subject: [PATCH 064/118] Make result wrappers more robust (#42488) --- homeassistant/helpers/template.py | 16 ++++++++++------ tests/helpers/test_template.py | 13 ++++++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 7584610016..a314fcb07e 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -127,7 +127,7 @@ def is_template_string(maybe_template: str) -> bool: class ResultWrapper: """Result wrapper class to store render result.""" - render_result: str + render_result: Optional[str] def gen_result_wrapper(kls): @@ -136,8 +136,8 @@ def gen_result_wrapper(kls): class Wrapper(kls, ResultWrapper): """Wrapper of a kls that can store render_result.""" - def __init__(self, value: kls, render_result: str) -> None: - super().__init__(value) + def __init__(self, *args: tuple, render_result: Optional[str] = None) -> None: + super().__init__(*args) self.render_result = render_result return Wrapper @@ -148,13 +148,15 @@ class TupleWrapper(tuple, ResultWrapper): # This is all magic to be allowed to subclass a tuple. - def __new__(cls, value: tuple, render_result: str) -> "TupleWrapper": + def __new__( + cls, value: tuple, *, render_result: Optional[str] = None + ) -> "TupleWrapper": """Create a new tuple class.""" return super().__new__(cls, tuple(value)) # pylint: disable=super-init-not-called - def __init__(self, value: tuple, render_result: str): + def __init__(self, value: tuple, *, render_result: Optional[str] = None): """Initialize a new tuple class.""" self.render_result = render_result @@ -416,7 +418,9 @@ class Template: result = literal_eval(render_result) if type(result) in RESULT_WRAPPERS: - result = RESULT_WRAPPERS[type(result)](result, render_result) + result = RESULT_WRAPPERS[type(result)]( + result, render_result=render_result + ) # If the literal_eval result is a string, use the original # render, by not returning right here. The evaluation of strings diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 750978b84e..856fc1495e 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -5,6 +5,7 @@ import random import pytest import pytz +import voluptuous as vol from homeassistant.components import group from homeassistant.config import async_process_ha_core_config @@ -2684,14 +2685,16 @@ async def test_is_static_still_ast_evals(hass): async def test_result_wrappers(hass): """Test result wrappers.""" - for text, native in ( - ("[1, 2]", [1, 2]), - ("{1, 2}", {1, 2}), - ("(1, 2)", (1, 2)), - ('{"hello": True}', {"hello": True}), + for text, native, orig_type, schema in ( + ("[1, 2]", [1, 2], list, vol.Schema([int])), + ("{1, 2}", {1, 2}, set, vol.Schema({int})), + ("(1, 2)", (1, 2), tuple, vol.ExactSequence([int, int])), + ('{"hello": True}', {"hello": True}, dict, vol.Schema({"hello": bool})), ): tpl = template.Template(text, hass) result = tpl.async_render() + assert isinstance(result, orig_type) assert isinstance(result, template.ResultWrapper) assert result == native assert result.render_result == text + schema(result) # should not raise From 7dcebbe89d219e4301b164bb6b64d62eb14ab9bc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 27 Oct 2020 22:59:49 +0100 Subject: [PATCH 065/118] Bump cryptography to 3.2 (#42489) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0b5fb019cd..10cb7858ab 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -8,7 +8,7 @@ attrs==19.3.0 bcrypt==3.1.7 certifi>=2020.6.20 ciso8601==2.1.3 -cryptography==2.9.2 +cryptography==3.2.0 defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 diff --git a/requirements.txt b/requirements.txt index 5bee6b7f94..1586fdddee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ httpx==0.16.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.2 PyJWT==1.7.1 -cryptography==2.9.2 +cryptography==3.2.0 pip>=8.0.3 python-slugify==4.0.1 pytz>=2020.1 diff --git a/setup.py b/setup.py index 490a7da56b..e501ac6cf1 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ REQUIRES = [ "jinja2>=2.11.2", "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. - "cryptography==2.9.2", + "cryptography==3.2.0", "pip>=8.0.3", "python-slugify==4.0.1", "pytz>=2020.1", From 40be42299df640629e092d551da44ad0d0b5c5bf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 27 Oct 2020 23:22:59 +0100 Subject: [PATCH 066/118] Improve mqtt test (#42490) --- homeassistant/helpers/template.py | 2 +- tests/components/mqtt/test_init.py | 8 ++++++-- tests/helpers/test_template.py | 10 ++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index a314fcb07e..0614b3c3b4 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -428,7 +428,7 @@ class Template: # output; use the original render instead of the evaluated one. if not isinstance(result, str): return result - except (ValueError, SyntaxError, MemoryError): + except (ValueError, TypeError, SyntaxError, MemoryError): pass return render_result diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 7a833a72d5..39ea8bc92e 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1502,6 +1502,7 @@ async def test_debug_info_qos_retain(hass, mqtt_mock): async def test_publish_json_from_template(hass, mqtt_mock): """Test the publishing of call to services.""" test_str = "{'valid': 'python', 'invalid': 'json'}" + test_str_tpl = "{'valid': '{{ \"python\" }}', 'invalid': 'json'}" await async_setup_component( hass, @@ -1511,13 +1512,16 @@ async def test_publish_json_from_template(hass, mqtt_mock): "test_script_payload": { "sequence": { "service": "mqtt.publish", - "data": {"topic": "test-topic", "payload": test_str}, + "data": {"topic": "test-topic", "payload": test_str_tpl}, } }, "test_script_payload_template": { "sequence": { "service": "mqtt.publish", - "data": {"topic": "test-topic", "payload_template": test_str}, + "data": { + "topic": "test-topic", + "payload_template": test_str_tpl, + }, } }, } diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 856fc1495e..9ac546c6a7 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -2698,3 +2698,13 @@ async def test_result_wrappers(hass): assert result == native assert result.render_result == text schema(result) # should not raise + + +async def test_parse_result(hass): + """Test parse result.""" + for tpl, result in ( + ('{{ "{{}}" }}', "{{}}"), + ("not-something", "not-something"), + ("2a", "2a"), + ): + assert template.Template(tpl, hass).async_render() == result From a88e011bba29831925147358d3070e29275b865c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 27 Oct 2020 23:24:54 +0100 Subject: [PATCH 067/118] Fix race in Tasmota discovery (#42492) --- homeassistant/components/tasmota/__init__.py | 29 ++++++++----------- homeassistant/components/tasmota/discovery.py | 7 ++--- .../components/tasmota/test_device_trigger.py | 2 +- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py index 5754b98b71..a82d95474c 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -19,12 +19,10 @@ from homeassistant.components.mqtt.subscription import ( ) from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType from . import device_automation, discovery from .const import CONF_DISCOVERY_PREFIX, DATA_REMOVE_DISCOVER_COMPONENT, PLATFORMS -from .discovery import TASMOTA_DISCOVERY_DEVICE _LOGGER = logging.getLogger(__name__) @@ -55,13 +53,11 @@ async def async_setup_entry(hass, entry): tasmota_mqtt = TasmotaMQTTClient(_publish, _subscribe_topics, _unsubscribe_topics) - async def async_discover_device(config, mac): - """Discover and add a Tasmota device.""" - await async_setup_device(hass, mac, config, entry, tasmota_mqtt) + device_registry = await hass.helpers.device_registry.async_get_registry() - hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format("device") - ] = async_dispatcher_connect(hass, TASMOTA_DISCOVERY_DEVICE, async_discover_device) + def async_discover_device(config, mac): + """Discover and add a Tasmota device.""" + async_setup_device(hass, mac, config, entry, tasmota_mqtt, device_registry) async def start_platforms(): await device_automation.async_setup_entry(hass, entry) @@ -73,7 +69,9 @@ async def async_setup_entry(hass, entry): ) discovery_prefix = entry.data[CONF_DISCOVERY_PREFIX] - await discovery.async_start(hass, discovery_prefix, entry, tasmota_mqtt) + await discovery.async_start( + hass, discovery_prefix, entry, tasmota_mqtt, async_discover_device + ) hass.async_create_task(start_platforms()) return True @@ -97,7 +95,6 @@ async def async_unload_entry(hass, entry): # disable discovery await discovery.async_stop(hass) hass.data.pop(DEVICE_MACS) - hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format("device")]() hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format("device_automation"))() for component in PLATFORMS: hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format(component))() @@ -105,9 +102,8 @@ async def async_unload_entry(hass, entry): return True -async def _remove_device(hass, config_entry, mac, tasmota_mqtt): +def _remove_device(hass, config_entry, mac, tasmota_mqtt, device_registry): """Remove device from device registry.""" - device_registry = await hass.helpers.device_registry.async_get_registry() device = device_registry.async_get_device(set(), {(CONNECTION_NETWORK_MAC, mac)}) if device is None: @@ -118,9 +114,8 @@ async def _remove_device(hass, config_entry, mac, tasmota_mqtt): clear_discovery_topic(mac, config_entry.data[CONF_DISCOVERY_PREFIX], tasmota_mqtt) -async def _update_device(hass, config_entry, config): +def _update_device(hass, config_entry, config, device_registry): """Add or update device registry.""" - device_registry = await hass.helpers.device_registry.async_get_registry() config_entry_id = config_entry.entry_id device_info = { "connections": {(CONNECTION_NETWORK_MAC, config[CONF_MAC])}, @@ -135,9 +130,9 @@ async def _update_device(hass, config_entry, config): hass.data[DEVICE_MACS][device.id] = config[CONF_MAC] -async def async_setup_device(hass, mac, config, config_entry, tasmota_mqtt): +def async_setup_device(hass, mac, config, config_entry, tasmota_mqtt, device_registry): """Set up the Tasmota device.""" if not config: - await _remove_device(hass, config_entry, mac, tasmota_mqtt) + _remove_device(hass, config_entry, mac, tasmota_mqtt, device_registry) else: - await _update_device(hass, config_entry, config) + _update_device(hass, config_entry, config, device_registry) diff --git a/homeassistant/components/tasmota/discovery.py b/homeassistant/components/tasmota/discovery.py index 06a8833323..2313a8327c 100644 --- a/homeassistant/components/tasmota/discovery.py +++ b/homeassistant/components/tasmota/discovery.py @@ -21,7 +21,6 @@ from .const import DOMAIN, PLATFORMS _LOGGER = logging.getLogger(__name__) ALREADY_DISCOVERED = "tasmota_discovered_components" -TASMOTA_DISCOVERY_DEVICE = "tasmota_discovery_device" TASMOTA_DISCOVERY_ENTITY_NEW = "tasmota_discovery_entity_new_{}" TASMOTA_DISCOVERY_ENTITY_UPDATED = "tasmota_discovery_entity_updated_{}_{}_{}_{}" TASMOTA_DISCOVERY_INSTANCE = "tasmota_discovery_instance" @@ -41,7 +40,7 @@ def set_discovery_hash(hass, discovery_hash): async def async_start( - hass: HomeAssistantType, discovery_topic, config_entry, tasmota_mqtt + hass: HomeAssistantType, discovery_topic, config_entry, tasmota_mqtt, setup_device ) -> bool: """Start Tasmota device discovery.""" @@ -95,9 +94,7 @@ async def async_start( _LOGGER.debug("Received discovery data for tasmota device: %s", mac) tasmota_device_config = tasmota_get_device_config(payload) - async_dispatcher_send( - hass, TASMOTA_DISCOVERY_DEVICE, tasmota_device_config, mac - ) + setup_device(tasmota_device_config, mac) if not payload: return diff --git a/tests/components/tasmota/test_device_trigger.py b/tests/components/tasmota/test_device_trigger.py index 35f3b4be9d..b027b2c095 100644 --- a/tests/components/tasmota/test_device_trigger.py +++ b/tests/components/tasmota/test_device_trigger.py @@ -832,7 +832,7 @@ async def test_attach_unknown_remove_device_from_registry( await hass.async_block_till_done() -async def test_attach_remove_config_entry(hass, mqtt_mock, setup_tasmota, device_reg): +async def test_attach_remove_config_entry(hass, device_reg, mqtt_mock, setup_tasmota): """Test trigger cleanup when removing a Tasmota config entry.""" # Discover a device with device trigger config = copy.deepcopy(DEFAULT_CONFIG) From 5a460e609fd7fd0bc045f9b88a004bd83479bc30 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 27 Oct 2020 22:26:04 +0000 Subject: [PATCH 068/118] Bumped version to 0.117.0b6 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index cebee253df..a1ca478364 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0b5" +PATCH_VERSION = "0b6" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 68d2938c6b0c32101099ae4ac304b519d3cd8928 Mon Sep 17 00:00:00 2001 From: Angelo Gagliano <25516409+TheGardenMonkey@users.noreply.github.com> Date: Wed, 28 Oct 2020 04:51:53 -0400 Subject: [PATCH 069/118] Move async_add_entities back to event loop for tplink component (#42454) --- homeassistant/components/tplink/common.py | 12 +++++------- homeassistant/components/tplink/light.py | 7 +++++-- homeassistant/components/tplink/switch.py | 7 +++++-- tests/components/tplink/test_light.py | 11 +++++++++-- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/tplink/common.py b/homeassistant/components/tplink/common.py index a8ba48e393..1aca4bf7ed 100644 --- a/homeassistant/components/tplink/common.py +++ b/homeassistant/components/tplink/common.py @@ -131,7 +131,7 @@ def get_static_devices(config_data) -> SmartDevices: return SmartDevices(lights, switches) -def add_available_devices(hass, device_type, device_class, async_add_entities): +def add_available_devices(hass, device_type, device_class): """Get sysinfo for all devices.""" devices = hass.data[TPLINK_DOMAIN][device_type] @@ -140,20 +140,18 @@ def add_available_devices(hass, device_type, device_class, async_add_entities): devices = hass.data[TPLINK_DOMAIN][f"{device_type}_remaining"] entities_ready = [] - entities_unavailable = [] + devices_unavailable = [] for device in devices: try: device.get_sysinfo() entities_ready.append(device_class(device)) except SmartDeviceException as ex: - entities_unavailable.append(device) + devices_unavailable.append(device) _LOGGER.warning( "Unable to communicate with device %s: %s", device.host, ex, ) - hass.data[TPLINK_DOMAIN][f"{device_type}_remaining"] = entities_unavailable - - if entities_ready: - async_add_entities(entities_ready, update_before_add=True) + hass.data[TPLINK_DOMAIN][f"{device_type}_remaining"] = devices_unavailable + return entities_ready diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index e2acf3fcd9..ceb0944efe 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -61,10 +61,13 @@ SLEEP_TIME = 2 async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_entities): """Set up lights.""" - await hass.async_add_executor_job( - add_available_devices, hass, CONF_LIGHT, TPLinkSmartBulb, async_add_entities + entities = await hass.async_add_executor_job( + add_available_devices, hass, CONF_LIGHT, TPLinkSmartBulb ) + if entities: + async_add_entities(entities, update_before_add=True) + if hass.data[TPLINK_DOMAIN][f"{CONF_LIGHT}_remaining"]: raise PlatformNotReady diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index b7e31e82e7..23000fe7b5 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -31,10 +31,13 @@ SLEEP_TIME = 2 async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_entities): """Set up switches.""" - await hass.async_add_executor_job( - add_available_devices, hass, CONF_SWITCH, SmartPlugSwitch, async_add_entities + entities = await hass.async_add_executor_job( + add_available_devices, hass, CONF_SWITCH, SmartPlugSwitch ) + if entities: + async_add_entities(entities, update_before_add=True) + if hass.data[TPLINK_DOMAIN][f"{CONF_SWITCH}_remaining"]: raise PlatformNotReady diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index 4987f4f26c..48812f8fb2 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -1,4 +1,5 @@ """Tests for light platform.""" +from datetime import timedelta import logging from typing import Callable, NamedTuple @@ -30,8 +31,10 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow from tests.async_mock import Mock, PropertyMock, patch +from tests.common import async_fire_time_changed class LightMockData(NamedTuple): @@ -605,5 +608,9 @@ async def test_async_setup_entry_unavailable( ) await hass.async_block_till_done() - assert "Unable to communicate with device 123.123.123.123" in caplog.text - assert len(hass.data[tplink.DOMAIN][f"{CONF_LIGHT}_remaining"]) == 1 + assert not hass.states.get("light.light1") + + future = utcnow() + timedelta(seconds=30) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert hass.states.get("light.light1") From 5b857765664bb74b6b66d05f096dc5156cecdebf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Oct 2020 09:11:08 +0100 Subject: [PATCH 070/118] Fix string representation of template result wrappers (#42494) --- homeassistant/components/template/cover.py | 6 +----- homeassistant/components/template/fan.py | 6 +----- homeassistant/components/template/light.py | 3 +-- homeassistant/helpers/template.py | 17 +++++++++++++++++ tests/helpers/test_template.py | 6 ++++++ 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index cc91bdabd7..93ffd2fd98 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -39,7 +39,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -259,10 +258,7 @@ class CoverTemplate(TemplateEntity, CoverEntity): self._position = None return - if isinstance(result, ResultWrapper): - state = result.render_result.lower() - else: - state = str(result).lower() + state = str(result).lower() if state in _VALID_STATES: if state in ("true", STATE_OPEN): diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index e5b3569e14..fa67f60dac 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -34,7 +34,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -368,10 +367,7 @@ class TemplateFan(TemplateEntity, FanEntity): @callback def _update_speed(self, speed): # Validate speed - if isinstance(speed, ResultWrapper): - speed = speed.render_result - else: - speed = str(speed) + speed = str(speed) if speed in self._speed_list: self._speed = speed diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 84939beeca..42493136b4 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -33,7 +33,6 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -406,7 +405,7 @@ class LightTemplate(TemplateEntity, LightEntity): def _update_state(self, result): """Update the state from the template.""" - if isinstance(result, (TemplateError, ResultWrapper)): + if isinstance(result, TemplateError): # This behavior is legacy self._state = False if not self._availability_template: diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 0614b3c3b4..d6c89f28e6 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -140,6 +140,16 @@ def gen_result_wrapper(kls): super().__init__(*args) self.render_result = render_result + def __str__(self) -> str: + if self.render_result is None: + # Can't get set repr to work + if kls is set: + return str(set(self)) + + return kls.__str__(self) + + return self.render_result + return Wrapper @@ -160,6 +170,13 @@ class TupleWrapper(tuple, ResultWrapper): """Initialize a new tuple class.""" self.render_result = render_result + def __str__(self) -> str: + """Return string representation.""" + if self.render_result is None: + return super().__str__() + + return self.render_result + RESULT_WRAPPERS: Dict[Type, Type] = { kls: gen_result_wrapper(kls) for kls in (list, dict, set) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 9ac546c6a7..e3eafda52f 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -2698,6 +2698,12 @@ async def test_result_wrappers(hass): assert result == native assert result.render_result == text schema(result) # should not raise + # Result with render text stringifies to original text + assert str(result) == text + # Result without render text stringifies same as original type + assert str(template.RESULT_WRAPPERS[orig_type](native)) == str( + orig_type(native) + ) async def test_parse_result(hass): From a34d06e363e1fd1ef9cd8d0fb0baa66ca9932ac7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Oct 2020 16:58:16 +0100 Subject: [PATCH 071/118] Default legacy templates to true (#42511) * Default legacy templates to true * Disable legacy_templates in tests Co-authored-by: Franck Nijhof --- homeassistant/core.py | 2 +- tests/common.py | 1 + tests/test_core.py | 198 +++++++++++++++++++++++------------------- 3 files changed, 113 insertions(+), 88 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 53dc75472c..ca4a27eef3 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1530,7 +1530,7 @@ class Config: self.safe_mode: bool = False # Use legacy template behavior - self.legacy_templates: bool = False + self.legacy_templates: bool = True def distance(self, lat: float, lon: float) -> Optional[float]: """Calculate distance from Home Assistant. diff --git a/tests/common.py b/tests/common.py index 8f4ceb5754..0b56d10188 100644 --- a/tests/common.py +++ b/tests/common.py @@ -207,6 +207,7 @@ async def async_test_home_assistant(loop): hass.config.units = METRIC_SYSTEM hass.config.media_dirs = {"local": get_test_config_dir("media")} hass.config.skip_pip = True + hass.config.legacy_templates = False hass.config_entries = config_entries.ConfigEntries(hass, {}) hass.config_entries._entries = [] diff --git a/tests/test_core.py b/tests/test_core.py index 9c5ab20597..119c0269ba 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -885,108 +885,132 @@ class TestServiceRegistry(unittest.TestCase): self.hass.block_till_done() -class TestConfig(unittest.TestCase): - """Test configuration methods.""" +def test_config_defaults(): + """Test config defaults.""" + hass = Mock() + config = ha.Config(hass) + assert config.hass is hass + assert config.latitude == 0 + assert config.longitude == 0 + assert config.elevation == 0 + assert config.location_name == "Home" + assert config.time_zone == dt_util.UTC + assert config.internal_url is None + assert config.external_url is None + assert config.config_source == "default" + assert config.skip_pip is False + assert config.components == set() + assert config.api is None + assert config.config_dir is None + assert config.allowlist_external_dirs == set() + assert config.allowlist_external_urls == set() + assert config.media_dirs == {} + assert config.safe_mode is False + assert config.legacy_templates is True - # pylint: disable=invalid-name - def setUp(self): - """Set up things to be run when tests are started.""" - self.config = ha.Config(None) - assert self.config.config_dir is None - def test_path_with_file(self): - """Test get_config_path method.""" - self.config.config_dir = "/test/ha-config" - assert self.config.path("test.conf") == "/test/ha-config/test.conf" +def test_config_path_with_file(): + """Test get_config_path method.""" + config = ha.Config(None) + config.config_dir = "/test/ha-config" + assert config.path("test.conf") == "/test/ha-config/test.conf" - def test_path_with_dir_and_file(self): - """Test get_config_path method.""" - self.config.config_dir = "/test/ha-config" - assert self.config.path("dir", "test.conf") == "/test/ha-config/dir/test.conf" - def test_as_dict(self): - """Test as dict.""" - self.config.config_dir = "/test/ha-config" - self.config.hass = MagicMock() - type(self.config.hass.state).value = PropertyMock(return_value="RUNNING") - expected = { - "latitude": 0, - "longitude": 0, - "elevation": 0, - CONF_UNIT_SYSTEM: METRIC_SYSTEM.as_dict(), - "location_name": "Home", - "time_zone": "UTC", - "components": set(), - "config_dir": "/test/ha-config", - "whitelist_external_dirs": set(), - "allowlist_external_dirs": set(), - "allowlist_external_urls": set(), - "version": __version__, - "config_source": "default", - "safe_mode": False, - "state": "RUNNING", - "external_url": None, - "internal_url": None, - } +def test_config_path_with_dir_and_file(): + """Test get_config_path method.""" + config = ha.Config(None) + config.config_dir = "/test/ha-config" + assert config.path("dir", "test.conf") == "/test/ha-config/dir/test.conf" - assert expected == self.config.as_dict() - def test_is_allowed_path(self): - """Test is_allowed_path method.""" - with TemporaryDirectory() as tmp_dir: - # The created dir is in /tmp. This is a symlink on OS X - # causing this test to fail unless we resolve path first. - self.config.allowlist_external_dirs = {os.path.realpath(tmp_dir)} +def test_config_as_dict(): + """Test as dict.""" + config = ha.Config(None) + config.config_dir = "/test/ha-config" + config.hass = MagicMock() + type(config.hass.state).value = PropertyMock(return_value="RUNNING") + expected = { + "latitude": 0, + "longitude": 0, + "elevation": 0, + CONF_UNIT_SYSTEM: METRIC_SYSTEM.as_dict(), + "location_name": "Home", + "time_zone": "UTC", + "components": set(), + "config_dir": "/test/ha-config", + "whitelist_external_dirs": set(), + "allowlist_external_dirs": set(), + "allowlist_external_urls": set(), + "version": __version__, + "config_source": "default", + "safe_mode": False, + "state": "RUNNING", + "external_url": None, + "internal_url": None, + } - test_file = os.path.join(tmp_dir, "test.jpg") - with open(test_file, "w") as tmp_file: - tmp_file.write("test") + assert expected == config.as_dict() - valid = [test_file, tmp_dir, os.path.join(tmp_dir, "notfound321")] - for path in valid: - assert self.config.is_allowed_path(path) - self.config.allowlist_external_dirs = {"/home", "/var"} +def test_config_is_allowed_path(): + """Test is_allowed_path method.""" + config = ha.Config(None) + with TemporaryDirectory() as tmp_dir: + # The created dir is in /tmp. This is a symlink on OS X + # causing this test to fail unless we resolve path first. + config.allowlist_external_dirs = {os.path.realpath(tmp_dir)} - invalid = [ - "/hass/config/secure", - "/etc/passwd", - "/root/secure_file", - "/var/../etc/passwd", - test_file, - ] - for path in invalid: - assert not self.config.is_allowed_path(path) + test_file = os.path.join(tmp_dir, "test.jpg") + with open(test_file, "w") as tmp_file: + tmp_file.write("test") - with pytest.raises(AssertionError): - self.config.is_allowed_path(None) + valid = [test_file, tmp_dir, os.path.join(tmp_dir, "notfound321")] + for path in valid: + assert config.is_allowed_path(path) - def test_is_allowed_external_url(self): - """Test is_allowed_external_url method.""" - self.config.allowlist_external_urls = [ - "http://x.com/", - "https://y.com/bla/", - "https://z.com/images/1.jpg/", - ] - - valid = [ - "http://x.com/1.jpg", - "http://x.com", - "https://y.com/bla/", - "https://y.com/bla/2.png", - "https://z.com/images/1.jpg", - ] - for url in valid: - assert self.config.is_allowed_external_url(url) + config.allowlist_external_dirs = {"/home", "/var"} invalid = [ - "https://a.co", - "https://y.com/bla_wrong", - "https://y.com/bla/../image.jpg", - "https://z.com/images", + "/hass/config/secure", + "/etc/passwd", + "/root/secure_file", + "/var/../etc/passwd", + test_file, ] - for url in invalid: - assert not self.config.is_allowed_external_url(url) + for path in invalid: + assert not config.is_allowed_path(path) + + with pytest.raises(AssertionError): + config.is_allowed_path(None) + + +def test_config_is_allowed_external_url(): + """Test is_allowed_external_url method.""" + config = ha.Config(None) + config.allowlist_external_urls = [ + "http://x.com/", + "https://y.com/bla/", + "https://z.com/images/1.jpg/", + ] + + valid = [ + "http://x.com/1.jpg", + "http://x.com", + "https://y.com/bla/", + "https://y.com/bla/2.png", + "https://z.com/images/1.jpg", + ] + for url in valid: + assert config.is_allowed_external_url(url) + + invalid = [ + "https://a.co", + "https://y.com/bla_wrong", + "https://y.com/bla/../image.jpg", + "https://z.com/images", + ] + for url in invalid: + assert not config.is_allowed_external_url(url) async def test_event_on_update(hass): From 529b849efed8046530254ad9082e18d3b91590fe Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 28 Oct 2020 18:08:23 +0100 Subject: [PATCH 072/118] Bumped version to 0.117.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a1ca478364..fdf4b26567 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0b6" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 0959777b8166690088dc8abe438fbbb63e630fcb Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Thu, 29 Oct 2020 09:22:36 +0100 Subject: [PATCH 073/118] Fix adding Virtual DSM system in synology_dsm (#42523) --- .../components/synology_dsm/config_flow.py | 1 - .../synology_dsm/test_config_flow.py | 48 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 2267d0b099..314bab5480 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -275,7 +275,6 @@ def _login_and_fetch_syno_info(api, otp_code): if ( not api.information.serial or api.utilisation.cpu_user_load is None - or not api.storage.disks_ids or not api.storage.volumes_ids or not api.network.macs ): diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 5a1a86ef67..fd060ce352 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -81,6 +81,20 @@ def mock_controller_service_2sa(): yield service_mock +@pytest.fixture(name="service_vdsm") +def mock_controller_service_vdsm(): + """Mock a successful service.""" + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM" + ) as service_mock: + service_mock.return_value.information.serial = SERIAL + service_mock.return_value.utilisation.cpu_user_load = 1 + service_mock.return_value.storage.disks_ids = [] + service_mock.return_value.storage.volumes_ids = ["volume_1"] + service_mock.return_value.network.macs = MACS + yield service_mock + + @pytest.fixture(name="service_failed") def mock_controller_service_failed(): """Mock a failed service.""" @@ -196,6 +210,40 @@ async def test_user_2sa(hass: HomeAssistantType, service_2sa: MagicMock): assert result["data"].get(CONF_VOLUMES) is None +async def test_user_vdsm(hass: HomeAssistantType, service_vdsm: MagicMock): + """Test user config.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=None + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + # test with all provided + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={ + CONF_HOST: HOST, + CONF_PORT: PORT, + CONF_SSL: SSL, + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == SERIAL + assert result["title"] == HOST + assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_PORT] == PORT + assert result["data"][CONF_SSL] == SSL + assert result["data"][CONF_USERNAME] == USERNAME + assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_MAC] == MACS + assert result["data"].get("device_token") is None + assert result["data"].get(CONF_DISKS) is None + assert result["data"].get(CONF_VOLUMES) is None + + async def test_import(hass: HomeAssistantType, service: MagicMock): """Test import step.""" # import with minimum setup From 048db9cb7da8a579551a380669f8c07dd5fd858a Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Fri, 30 Oct 2020 09:11:25 +0100 Subject: [PATCH 074/118] Enable polling for DSMR derivative entity (#42524) --- homeassistant/components/dsmr/sensor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 411088c509..7b9f9bab6f 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -320,6 +320,16 @@ class DerivativeDSMREntity(DSMREntity): """Return the calculated current hourly rate.""" return self._state + @property + def force_update(self): + """Disable force update.""" + return False + + @property + def should_poll(self): + """Enable polling.""" + return True + async def async_update(self): """Recalculate hourly rate if timestamp has changed. From 1ab2d55a7ab7ea5d60170ea2e297cc18c0a4c3f1 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Wed, 28 Oct 2020 23:06:01 +0000 Subject: [PATCH 075/118] Bump aiohomekit to 0.2.54 (#42532) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 1fb4c05c59..efe842bad0 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", "requirements": [ - "aiohomekit==0.2.53" + "aiohomekit==0.2.54" ], "zeroconf": [ "_hap._tcp.local." diff --git a/requirements_all.txt b/requirements_all.txt index cab2659319..aa7b58fbd8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -178,7 +178,7 @@ aioguardian==1.0.1 aioharmony==0.2.6 # homeassistant.components.homekit_controller -aiohomekit==0.2.53 +aiohomekit==0.2.54 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5d3df98687..e6831b8422 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -109,7 +109,7 @@ aioguardian==1.0.1 aioharmony==0.2.6 # homeassistant.components.homekit_controller -aiohomekit==0.2.53 +aiohomekit==0.2.54 # homeassistant.components.emulated_hue # homeassistant.components.http From c604595f987106a8448637370f6f1f9527280a02 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Thu, 29 Oct 2020 11:50:39 +0100 Subject: [PATCH 076/118] Fix RMV giving wrong data and ignoring given parameters (#42561) --- homeassistant/components/rmvtransport/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index 76e75d77a5..4a619437d5 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -263,10 +263,10 @@ class RMVDepartureData: if not dest_found: continue - elif self._lines and journey["number"] not in self._lines: + if self._lines and journey["number"] not in self._lines: continue - elif journey["minutes"] < self._time_offset: + if journey["minutes"] < self._time_offset: continue for attr in ["direction", "departure_time", "product", "minutes"]: From c57d697df530d2df3c04d11ca99af2d8fc8f0909 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 30 Oct 2020 00:14:07 +0100 Subject: [PATCH 077/118] Update frontend to 20201021.4 (#42590) --- 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 ee27bb08d7..0957c6fd77 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==20201021.3"], + "requirements": ["home-assistant-frontend==20201021.4"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 10cb7858ab..203bbb6202 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.37.1 -home-assistant-frontend==20201021.3 +home-assistant-frontend==20201021.4 httpx==0.16.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.2 diff --git a/requirements_all.txt b/requirements_all.txt index aa7b58fbd8..0a0f919eaf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -759,7 +759,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20201021.3 +home-assistant-frontend==20201021.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e6831b8422..1dd47f41cb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -385,7 +385,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20201021.3 +home-assistant-frontend==20201021.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From aa52ade5a06d52e895b34d76a279170b6a660a8c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Oct 2020 20:09:41 +0100 Subject: [PATCH 078/118] Fix MQTT template light (#42598) --- homeassistant/components/mqtt/light/schema_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 44a87f2af2..faf987881b 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -252,7 +252,7 @@ class MqttLightTemplate( except ValueError: _LOGGER.warning("Invalid color value received") - if self._templates[CONF_COLOR_TEMP_TEMPLATE] is not None: + if self._templates[CONF_WHITE_VALUE_TEMPLATE] is not None: try: self._white_value = int( self._templates[ From d26bceb110bf9bbf656826aeaaee6c10cdab4a2a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 30 Oct 2020 00:13:49 +0100 Subject: [PATCH 079/118] Bump hatasmota to 0.0.25 (#42605) --- .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_sensor.py | 91 +++++++++++-------- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 991e38e6a9..b087e13ece 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.0.24"], + "requirements": ["hatasmota==0.0.25"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 0a0f919eaf..0c8f9b4fed 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -732,7 +732,7 @@ hass-nabucasa==0.37.1 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.0.24 +hatasmota==0.0.25 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1dd47f41cb..c92af14b83 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ hangups==0.4.11 hass-nabucasa==0.37.1 # homeassistant.components.tasmota -hatasmota==0.0.24 +hatasmota==0.0.25 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index ab8498ed04..e5d55b75a3 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -105,12 +105,12 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): ) await hass.async_block_till_done() - state = hass.states.get("sensor.dht11_temperature") + state = hass.states.get("sensor.tasmota_dht11_temperature") assert state.state == "unavailable" assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") - state = hass.states.get("sensor.dht11_temperature") + state = hass.states.get("sensor.tasmota_dht11_temperature") assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -118,7 +118,7 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", '{"DHT11":{"Temperature":20.5}}' ) - state = hass.states.get("sensor.dht11_temperature") + state = hass.states.get("sensor.tasmota_dht11_temperature") assert state.state == "20.5" # Test polled state update @@ -127,7 +127,7 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): "tasmota_49A3BC/stat/STATUS8", '{"StatusSNS":{"DHT11":{"Temperature":20.0}}}', ) - state = hass.states.get("sensor.dht11_temperature") + state = hass.states.get("sensor.tasmota_dht11_temperature") assert state.state == "20.0" @@ -150,12 +150,12 @@ async def test_nested_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): ) await hass.async_block_till_done() - state = hass.states.get("sensor.tx23_speed_act") + state = hass.states.get("sensor.tasmota_tx23_speed_act") assert state.state == "unavailable" assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") - state = hass.states.get("sensor.tx23_speed_act") + state = hass.states.get("sensor.tasmota_tx23_speed_act") assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -163,7 +163,7 @@ async def test_nested_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", '{"TX23":{"Speed":{"Act":"12.3"}}}' ) - state = hass.states.get("sensor.tx23_speed_act") + state = hass.states.get("sensor.tasmota_tx23_speed_act") assert state.state == "12.3" # Test polled state update @@ -172,7 +172,7 @@ async def test_nested_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): "tasmota_49A3BC/stat/STATUS8", '{"StatusSNS":{"TX23":{"Speed":{"Act":"23.4"}}}}', ) - state = hass.states.get("sensor.tx23_speed_act") + state = hass.states.get("sensor.tasmota_tx23_speed_act") assert state.state == "23.4" @@ -195,12 +195,12 @@ async def test_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): ) await hass.async_block_till_done() - state = hass.states.get("sensor.energy_totaltariff_1") + state = hass.states.get("sensor.tasmota_energy_totaltariff_1") assert state.state == "unavailable" assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") - state = hass.states.get("sensor.energy_totaltariff_1") + state = hass.states.get("sensor.tasmota_energy_totaltariff_1") assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -208,7 +208,7 @@ async def test_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/SENSOR", '{"ENERGY":{"TotalTariff":[1.2,3.4]}}' ) - state = hass.states.get("sensor.energy_totaltariff_1") + state = hass.states.get("sensor.tasmota_energy_totaltariff_1") assert state.state == "3.4" # Test polled state update @@ -217,7 +217,7 @@ async def test_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): "tasmota_49A3BC/stat/STATUS8", '{"StatusSNS":{"ENERGY":{"TotalTariff":[5.6,7.8]}}}', ) - state = hass.states.get("sensor.energy_totaltariff_1") + state = hass.states.get("sensor.tasmota_energy_totaltariff_1") assert state.state == "7.8" @@ -297,15 +297,15 @@ async def test_attributes(hass, mqtt_mock, setup_tasmota): ) await hass.async_block_till_done() - state = hass.states.get("sensor.dht11_temperature") + state = hass.states.get("sensor.tasmota_dht11_temperature") assert state.attributes.get("device_class") == "temperature" - assert state.attributes.get("friendly_name") == "DHT11 Temperature" + assert state.attributes.get("friendly_name") == "Tasmota DHT11 Temperature" assert state.attributes.get("icon") is None assert state.attributes.get("unit_of_measurement") == "C" - state = hass.states.get("sensor.beer_CarbonDioxide") + state = hass.states.get("sensor.tasmota_beer_CarbonDioxide") assert state.attributes.get("device_class") is None - assert state.attributes.get("friendly_name") == "Beer CarbonDioxide" + assert state.attributes.get("friendly_name") == "Tasmota Beer CarbonDioxide" assert state.attributes.get("icon") == "mdi:molecule-co2" assert state.attributes.get("unit_of_measurement") == "ppm" @@ -329,15 +329,15 @@ async def test_nested_sensor_attributes(hass, mqtt_mock, setup_tasmota): ) await hass.async_block_till_done() - state = hass.states.get("sensor.tx23_speed_act") + state = hass.states.get("sensor.tasmota_tx23_speed_act") assert state.attributes.get("device_class") is None - assert state.attributes.get("friendly_name") == "TX23 Speed Act" + assert state.attributes.get("friendly_name") == "Tasmota TX23 Speed Act" assert state.attributes.get("icon") is None assert state.attributes.get("unit_of_measurement") == "km/h" - state = hass.states.get("sensor.tx23_dir_avg") + state = hass.states.get("sensor.tasmota_tx23_dir_avg") assert state.attributes.get("device_class") is None - assert state.attributes.get("friendly_name") == "TX23 Dir Avg" + assert state.attributes.get("friendly_name") == "Tasmota TX23 Dir Avg" assert state.attributes.get("icon") is None assert state.attributes.get("unit_of_measurement") == " " @@ -367,15 +367,15 @@ async def test_indexed_sensor_attributes(hass, mqtt_mock, setup_tasmota): ) await hass.async_block_till_done() - state = hass.states.get("sensor.dummy1_temperature_0") + state = hass.states.get("sensor.tasmota_dummy1_temperature_0") assert state.attributes.get("device_class") == "temperature" - assert state.attributes.get("friendly_name") == "Dummy1 Temperature 0" + assert state.attributes.get("friendly_name") == "Tasmota Dummy1 Temperature 0" assert state.attributes.get("icon") is None assert state.attributes.get("unit_of_measurement") == "C" - state = hass.states.get("sensor.dummy2_carbondioxide_1") + state = hass.states.get("sensor.tasmota_dummy2_carbondioxide_1") assert state.attributes.get("device_class") is None - assert state.attributes.get("friendly_name") == "Dummy2 CarbonDioxide 1" + assert state.attributes.get("friendly_name") == "Tasmota Dummy2 CarbonDioxide 1" assert state.attributes.get("icon") == "mdi:molecule-co2" assert state.attributes.get("unit_of_measurement") == "ppm" @@ -396,15 +396,15 @@ async def test_enable_status_sensor(hass, mqtt_mock, setup_tasmota): await hass.async_block_till_done() await hass.async_block_till_done() - state = hass.states.get("sensor.tasmota_status") + state = hass.states.get("sensor.tasmota_signal") assert state is None - entry = entity_reg.async_get("sensor.tasmota_status") + entry = entity_reg.async_get("sensor.tasmota_signal") assert entry.disabled assert entry.disabled_by == "integration" # Enable the status sensor updated_entry = entity_reg.async_update_entity( - "sensor.tasmota_status", disabled_by=None + "sensor.tasmota_signal", disabled_by=None ) assert updated_entry != entry assert updated_entry.disabled is False @@ -428,12 +428,12 @@ async def test_enable_status_sensor(hass, mqtt_mock, setup_tasmota): ) await hass.async_block_till_done() - state = hass.states.get("sensor.tasmota_status") + state = hass.states.get("sensor.tasmota_signal") assert state.state == "unavailable" assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") - state = hass.states.get("sensor.tasmota_status") + state = hass.states.get("sensor.tasmota_signal") assert state.state == STATE_UNKNOWN assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -451,7 +451,7 @@ async def test_availability_when_connection_lost( sensor.DOMAIN, config, sensor_config, - "dht11_temperature", + "tasmota_dht11_temperature", ) @@ -460,7 +460,12 @@ async def test_availability(hass, mqtt_mock, setup_tasmota): config = copy.deepcopy(DEFAULT_CONFIG) sensor_config = copy.deepcopy(DEFAULT_SENSOR_CONFIG) await help_test_availability( - hass, mqtt_mock, sensor.DOMAIN, config, sensor_config, "dht11_temperature" + hass, + mqtt_mock, + sensor.DOMAIN, + config, + sensor_config, + "tasmota_dht11_temperature", ) @@ -469,7 +474,12 @@ async def test_availability_discovery_update(hass, mqtt_mock, setup_tasmota): config = copy.deepcopy(DEFAULT_CONFIG) sensor_config = copy.deepcopy(DEFAULT_SENSOR_CONFIG) await help_test_availability_discovery_update( - hass, mqtt_mock, sensor.DOMAIN, config, sensor_config, "dht11_temperature" + hass, + mqtt_mock, + sensor.DOMAIN, + config, + sensor_config, + "tasmota_dht11_temperature", ) @@ -506,8 +516,8 @@ async def test_discovery_removal_sensor(hass, mqtt_mock, caplog, setup_tasmota): config, sensor_config1, {}, - "dht11_temperature", - "DHT11 Temperature", + "tasmota_dht11_temperature", + "Tasmota DHT11 Temperature", ) @@ -528,8 +538,8 @@ async def test_discovery_update_unchanged_sensor( config, discovery_update, sensor_config, - "dht11_temperature", - "DHT11 Temperature", + "tasmota_dht11_temperature", + "Tasmota DHT11 Temperature", ) @@ -559,7 +569,7 @@ async def test_entity_id_update_subscriptions(hass, mqtt_mock, setup_tasmota): config, topics, sensor_config, - "dht11_temperature", + "tasmota_dht11_temperature", ) @@ -568,5 +578,10 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock, setup_tasmota) config = copy.deepcopy(DEFAULT_CONFIG) sensor_config = copy.deepcopy(DEFAULT_SENSOR_CONFIG) await help_test_entity_id_update_discovery_update( - hass, mqtt_mock, sensor.DOMAIN, config, sensor_config, "dht11_temperature" + hass, + mqtt_mock, + sensor.DOMAIN, + config, + sensor_config, + "tasmota_dht11_temperature", ) From 26766d68aaf7cc6293a65b0aa6ccaff185a55652 Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Thu, 29 Oct 2020 19:49:13 -0400 Subject: [PATCH 080/118] Update greeclimate to 0.9.2 (#42616) Fixes issue with erroneous await in UDP recv queue --- homeassistant/components/gree/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/gree/manifest.json b/homeassistant/components/gree/manifest.json index ea04eb12f5..c5fb412f1d 100644 --- a/homeassistant/components/gree/manifest.json +++ b/homeassistant/components/gree/manifest.json @@ -3,6 +3,6 @@ "name": "Gree Climate", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/gree", - "requirements": ["greeclimate==0.9.0"], + "requirements": ["greeclimate==0.9.2"], "codeowners": ["@cmroche"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0c8f9b4fed..8d0a2be17d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -696,7 +696,7 @@ gpiozero==1.5.1 gps3==0.33.3 # homeassistant.components.gree -greeclimate==0.9.0 +greeclimate==0.9.2 # homeassistant.components.greeneye_monitor greeneye_monitor==2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c92af14b83..f3e61a25d4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -352,7 +352,7 @@ google-cloud-pubsub==0.39.1 google-nest-sdm==0.1.6 # homeassistant.components.gree -greeclimate==0.9.0 +greeclimate==0.9.2 # homeassistant.components.griddy griddypower==0.1.0 From 2dcd57bf38789bda3ced4509d718c21b8f1fd128 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 30 Oct 2020 08:31:58 +0000 Subject: [PATCH 081/118] Bumped version to 0.117.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index fdf4b26567..95ce48ca88 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 750bb11895bde00f14c9506be5944a69b4e7b555 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 29 Oct 2020 22:34:06 +0100 Subject: [PATCH 082/118] Fix broken time trigger test (#42606) --- tests/components/config/test_core.py | 35 ++++++++++--------- tests/components/hassio/test_init.py | 3 +- .../homeassistant/triggers/test_time.py | 1 - 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index 1f379727b4..72e655dbb6 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -61,22 +61,23 @@ async def test_websocket_core_update(hass, client): assert hass.config.external_url != "https://www.example.com" assert hass.config.internal_url != "http://example.com" - await client.send_json( - { - "id": 5, - "type": "config/core/update", - "latitude": 60, - "longitude": 50, - "elevation": 25, - "location_name": "Huis", - CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, - "time_zone": "America/New_York", - "external_url": "https://www.example.com", - "internal_url": "http://example.local", - } - ) + with patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz: + await client.send_json( + { + "id": 5, + "type": "config/core/update", + "latitude": 60, + "longitude": 50, + "elevation": 25, + "location_name": "Huis", + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, + "time_zone": "America/New_York", + "external_url": "https://www.example.com", + "internal_url": "http://example.local", + } + ) - msg = await client.receive_json() + msg = await client.receive_json() assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT @@ -86,11 +87,11 @@ async def test_websocket_core_update(hass, client): assert hass.config.elevation == 25 assert hass.config.location_name == "Huis" assert hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL - assert hass.config.time_zone.zone == "America/New_York" assert hass.config.external_url == "https://www.example.com" assert hass.config.internal_url == "http://example.local" - dt_util.set_default_time_zone(ORIG_TIME_ZONE) + assert len(mock_set_tz.mock_calls) == 1 + assert mock_set_tz.mock_calls[0][1][0].zone == "America/New_York" async def test_websocket_core_update_not_admin(hass, hass_ws_client, hass_admin_user): diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 56792295fe..62b4a4adbd 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -186,7 +186,8 @@ async def test_setup_core_push_timezone(hass, aioclient_mock): assert aioclient_mock.call_count == 7 assert aioclient_mock.mock_calls[2][2]["timezone"] == "testzone" - await hass.config.async_update(time_zone="America/New_York") + with patch("homeassistant.util.dt.set_default_time_zone"): + await hass.config.async_update(time_zone="America/New_York") await hass.async_block_till_done() assert aioclient_mock.mock_calls[-1][2]["timezone"] == "America/New_York" diff --git a/tests/components/homeassistant/triggers/test_time.py b/tests/components/homeassistant/triggers/test_time.py index db10e83662..91fd57beed 100644 --- a/tests/components/homeassistant/triggers/test_time.py +++ b/tests/components/homeassistant/triggers/test_time.py @@ -74,7 +74,6 @@ async def test_if_fires_using_at_input_datetime(hass, calls, has_date, has_time) "input_datetime", {"input_datetime": {"trigger": {"has_date": has_date, "has_time": has_time}}}, ) - now = dt_util.now() trigger_dt = now.replace( From faf73e304a26f164e294bedd0a91136a68261690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Oldag?= Date: Sun, 1 Nov 2020 15:23:42 +0100 Subject: [PATCH 083/118] Bump pwmled to v1.6.6 (#42607) --- homeassistant/components/rpi_gpio_pwm/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rpi_gpio_pwm/manifest.json b/homeassistant/components/rpi_gpio_pwm/manifest.json index cde9f24843..2467673e95 100644 --- a/homeassistant/components/rpi_gpio_pwm/manifest.json +++ b/homeassistant/components/rpi_gpio_pwm/manifest.json @@ -2,6 +2,6 @@ "domain": "rpi_gpio_pwm", "name": "pigpio Daemon PWM LED", "documentation": "https://www.home-assistant.io/integrations/rpi_gpio_pwm", - "requirements": ["pwmled==1.5.3"], + "requirements": ["pwmled==1.6.6"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 8d0a2be17d..4418da89d7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1182,7 +1182,7 @@ pushbullet.py==0.11.0 pushover_complete==1.1.1 # homeassistant.components.rpi_gpio_pwm -pwmled==1.5.3 +pwmled==1.6.6 # homeassistant.components.august py-august==0.25.0 From 6d54ed14b4b736830b492e5777bc24cbed787dd4 Mon Sep 17 00:00:00 2001 From: airthusiast <67556031+airthusiast@users.noreply.github.com> Date: Fri, 30 Oct 2020 18:50:59 +0100 Subject: [PATCH 084/118] Fix Fibaro HC2 climate device missing temperature (#42627) --- homeassistant/components/fibaro/climate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py index 6a7a6f145d..58fde1e370 100644 --- a/homeassistant/components/fibaro/climate.py +++ b/homeassistant/components/fibaro/climate.py @@ -321,7 +321,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): """Return the current temperature.""" if self._temp_sensor_device: device = self._temp_sensor_device.fibaro_device - if device.properties.heatingThermostatSetpoint: + if "heatingThermostatSetpoint" in device.properties: return float(device.properties.heatingThermostatSetpoint) return float(device.properties.value) return None @@ -331,7 +331,7 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): """Return the temperature we try to reach.""" if self._target_temp_device: device = self._target_temp_device.fibaro_device - if device.properties.heatingThermostatSetpointFuture: + if "heatingThermostatSetpointFuture" in device.properties: return float(device.properties.heatingThermostatSetpointFuture) return float(device.properties.targetLevel) return None From 2430c5ea22a4b203fed8b3b8e07287fdcdabe2c6 Mon Sep 17 00:00:00 2001 From: Malte Franken Date: Fri, 30 Oct 2020 22:23:07 +1100 Subject: [PATCH 085/118] Fix geo_rss_events import statement (#42629) * bump integration library version * fix import statement --- homeassistant/components/geo_rss_events/manifest.json | 2 +- homeassistant/components/geo_rss_events/sensor.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/geo_rss_events/manifest.json b/homeassistant/components/geo_rss_events/manifest.json index 77d38d58ad..4a434aed8d 100644 --- a/homeassistant/components/geo_rss_events/manifest.json +++ b/homeassistant/components/geo_rss_events/manifest.json @@ -2,6 +2,6 @@ "domain": "geo_rss_events", "name": "GeoRSS", "documentation": "https://www.home-assistant.io/integrations/geo_rss_events", - "requirements": ["georss_generic_client==0.3"], + "requirements": ["georss_generic_client==0.4"], "codeowners": ["@exxamalte"] } diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index 5a11136fd4..c75234f5f2 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -9,7 +9,7 @@ from datetime import timedelta import logging from georss_client import UPDATE_OK, UPDATE_OK_NO_DATA -from georss_client.generic_feed import GenericFeed +from georss_generic_client import GenericFeed import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/requirements_all.txt b/requirements_all.txt index 4418da89d7..b93fd48849 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -638,7 +638,7 @@ geojson_client==0.4 geopy==1.21.0 # homeassistant.components.geo_rss_events -georss_generic_client==0.3 +georss_generic_client==0.4 # homeassistant.components.ign_sismologia georss_ign_sismologia_client==0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f3e61a25d4..482025a58e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -315,7 +315,7 @@ geojson_client==0.4 geopy==1.21.0 # homeassistant.components.geo_rss_events -georss_generic_client==0.3 +georss_generic_client==0.4 # homeassistant.components.ign_sismologia georss_ign_sismologia_client==0.2 From 92f9a77770fab371118f6af30f4004f65d6362e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 30 Oct 2020 15:20:42 +0100 Subject: [PATCH 086/118] Bump pycfdns to 1.2.1 (#42634) --- homeassistant/components/cloudflare/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cloudflare/manifest.json b/homeassistant/components/cloudflare/manifest.json index cab768953b..e2f55b13a7 100644 --- a/homeassistant/components/cloudflare/manifest.json +++ b/homeassistant/components/cloudflare/manifest.json @@ -2,7 +2,7 @@ "domain": "cloudflare", "name": "Cloudflare", "documentation": "https://www.home-assistant.io/integrations/cloudflare", - "requirements": ["pycfdns==1.1.1"], + "requirements": ["pycfdns==1.2.1"], "codeowners": ["@ludeeus", "@ctalkington"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index b93fd48849..3484a7a385 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1288,7 +1288,7 @@ pybotvac==0.0.17 pycarwings2==2.9 # homeassistant.components.cloudflare -pycfdns==1.1.1 +pycfdns==1.2.1 # homeassistant.components.channels pychannels==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 482025a58e..b48e066568 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -636,7 +636,7 @@ pyblackbird==0.5 pybotvac==0.0.17 # homeassistant.components.cloudflare -pycfdns==1.1.1 +pycfdns==1.2.1 # homeassistant.components.cast pychromecast==7.5.1 From 5f28c82837b273f0d6dd174e31f8de5e4ba404a4 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 30 Oct 2020 19:04:11 +0100 Subject: [PATCH 087/118] Make sure Tasmota status sensors are disabled (#42643) --- homeassistant/components/tasmota/sensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 076ca457a2..bdaac51f09 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -1,6 +1,7 @@ """Support for Tasmota sensors.""" from typing import Optional +from hatasmota import status_sensor from hatasmota.const import ( SENSOR_AMBIENT, SENSOR_APPARENT_POWERUSAGE, @@ -162,7 +163,7 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity): def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" # Hide status sensors to not overwhelm users - if self._tasmota_entity.quantity == SENSOR_STATUS_SIGNAL: + if self._tasmota_entity.quantity in status_sensor.SENSORS: return False return True From 562ccbbe251f5ece061eab80ff9979c1dbc792be Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Sat, 31 Oct 2020 09:15:11 -0400 Subject: [PATCH 088/118] attempt to renew subscription immediately to stop endless loop if it fails after setup (#42651) --- homeassistant/components/onvif/event.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index eb61cbb4b7..eaf2323604 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -74,6 +74,14 @@ class EventManager: async def async_start(self) -> bool: """Start polling events.""" if await self.device.create_pullpoint_subscription(): + # Create subscription manager + self._subscription = self.device.create_subscription_service( + "PullPointSubscription" + ) + + # Renew immediately + await self.async_renew() + # Initialize events pullpoint = self.device.create_pullpoint_service() try: @@ -87,11 +95,6 @@ class EventManager: # Parse event initialization await self.async_parse_messages(response.NotificationMessage) - # Create subscription manager - self._subscription = self.device.create_subscription_service( - "PullPointSubscription" - ) - self.started = True return True From a93ae418a2d86dca3db5ebaf63592b9a4294364d Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sun, 1 Nov 2020 11:05:55 -0500 Subject: [PATCH 089/118] Bump up ZHA dependencies (#42679) * Bump up ZHA dependencies * Fix tests because of zigpy request signature change --- homeassistant/components/zha/core/gateway.py | 2 +- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zha/test_cover.py | 37 ++++++++++---------- tests/components/zha/test_discover.py | 1 + tests/components/zha/test_light.py | 13 ++++--- tests/components/zha/test_switch.py | 4 +-- 8 files changed, 33 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index bb57d7f03f..bdfcf1b24f 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -637,7 +637,7 @@ class ZHAGateway: _LOGGER.debug("Shutting down ZHA ControllerApplication") for unsubscribe in self._unsubs: unsubscribe() - await self.application_controller.shutdown() + await self.application_controller.pre_shutdown() @callback diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 01c24bdc6f..be8619811d 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -9,7 +9,7 @@ "zha-quirks==0.0.45", "zigpy-cc==0.5.2", "zigpy-deconz==0.11.0", - "zigpy==0.26.0", + "zigpy==0.27.0", "zigpy-xbee==0.13.0", "zigpy-zigate==0.6.2", "zigpy-znp==0.2.2" diff --git a/requirements_all.txt b/requirements_all.txt index 3484a7a385..93942625b6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2355,7 +2355,7 @@ zigpy-zigate==0.6.2 zigpy-znp==0.2.2 # homeassistant.components.zha -zigpy==0.26.0 +zigpy==0.27.0 # homeassistant.components.zoneminder zm-py==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b48e066568..425838fe4e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1124,4 +1124,4 @@ zigpy-zigate==0.6.2 zigpy-znp==0.2.2 # homeassistant.components.zha -zigpy==0.26.0 +zigpy==0.27.0 diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index b295543b3e..783637d26d 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -32,7 +32,7 @@ from .common import ( send_attributes_report, ) -from tests.async_mock import AsyncMock, MagicMock, call, patch +from tests.async_mock import AsyncMock, MagicMock, patch from tests.common import async_capture_events, mock_coro, mock_restore_cache @@ -144,9 +144,10 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 - assert cluster.request.call_args == call( - False, 0x1, (), expect_reply=True, manufacturer=None, tsn=None - ) + assert cluster.request.call_args[0][0] is False + assert cluster.request.call_args[0][1] == 0x01 + assert cluster.request.call_args[0][2] == () + assert cluster.request.call_args[1]["expect_reply"] is True # open from UI with patch( @@ -156,9 +157,10 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 - assert cluster.request.call_args == call( - False, 0x0, (), expect_reply=True, manufacturer=None, tsn=None - ) + assert cluster.request.call_args[0][0] is False + assert cluster.request.call_args[0][1] == 0x00 + assert cluster.request.call_args[0][2] == () + assert cluster.request.call_args[1]["expect_reply"] is True # set position UI with patch( @@ -171,15 +173,11 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): blocking=True, ) assert cluster.request.call_count == 1 - assert cluster.request.call_args == call( - False, - 0x5, - (zigpy.types.uint8_t,), - 53, - expect_reply=True, - manufacturer=None, - tsn=None, - ) + assert cluster.request.call_args[0][0] is False + assert cluster.request.call_args[0][1] == 0x05 + assert cluster.request.call_args[0][2] == (zigpy.types.uint8_t,) + assert cluster.request.call_args[0][3] == 53 + assert cluster.request.call_args[1]["expect_reply"] is True # stop from UI with patch( @@ -189,9 +187,10 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): DOMAIN, SERVICE_STOP_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 - assert cluster.request.call_args == call( - False, 0x2, (), expect_reply=True, manufacturer=None, tsn=None - ) + assert cluster.request.call_args[0][0] is False + assert cluster.request.call_args[0][1] == 0x02 + assert cluster.request.call_args[0][2] == () + assert cluster.request.call_args[1]["expect_reply"] is True # test rejoin await async_test_rejoin(hass, zigpy_cover_device, [cluster], (1,)) diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index 5589a7d94a..b5da98dc01 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -113,6 +113,7 @@ async def test_devices( 0, expect_reply=True, manufacturer=None, + tries=1, tsn=None, ) diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 642504384c..ea1b8487b7 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -323,7 +323,7 @@ async def async_test_on_off_from_hass(hass, cluster, entity_id): assert cluster.request.call_count == 1 assert cluster.request.await_count == 1 assert cluster.request.call_args == call( - False, ON, (), expect_reply=True, manufacturer=None, tsn=None + False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None ) await async_test_off_from_hass(hass, cluster, entity_id) @@ -340,7 +340,7 @@ async def async_test_off_from_hass(hass, cluster, entity_id): assert cluster.request.call_count == 1 assert cluster.request.await_count == 1 assert cluster.request.call_args == call( - False, OFF, (), expect_reply=True, manufacturer=None, tsn=None + False, OFF, (), expect_reply=True, manufacturer=None, tries=1, tsn=None ) @@ -360,7 +360,7 @@ async def async_test_level_on_off_from_hass( assert level_cluster.request.call_count == 0 assert level_cluster.request.await_count == 0 assert on_off_cluster.request.call_args == call( - False, ON, (), expect_reply=True, manufacturer=None, tsn=None + False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None ) on_off_cluster.request.reset_mock() level_cluster.request.reset_mock() @@ -373,7 +373,7 @@ async def async_test_level_on_off_from_hass( assert level_cluster.request.call_count == 1 assert level_cluster.request.await_count == 1 assert on_off_cluster.request.call_args == call( - False, ON, (), expect_reply=True, manufacturer=None, tsn=None + False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None ) assert level_cluster.request.call_args == call( False, @@ -383,6 +383,7 @@ async def async_test_level_on_off_from_hass( 100.0, expect_reply=True, manufacturer=None, + tries=1, tsn=None, ) on_off_cluster.request.reset_mock() @@ -396,7 +397,7 @@ async def async_test_level_on_off_from_hass( assert level_cluster.request.call_count == 1 assert level_cluster.request.await_count == 1 assert on_off_cluster.request.call_args == call( - False, ON, (), expect_reply=True, manufacturer=None, tsn=None + False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None ) assert level_cluster.request.call_args == call( False, @@ -406,6 +407,7 @@ async def async_test_level_on_off_from_hass( 1, expect_reply=True, manufacturer=None, + tries=1, tsn=None, ) on_off_cluster.request.reset_mock() @@ -445,6 +447,7 @@ async def async_test_flash_from_hass(hass, cluster, entity_id, flash): 0, expect_reply=True, manufacturer=None, + tries=1, tsn=None, ) diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index aab8dafef4..80412d95fb 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -136,7 +136,7 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device): ) assert len(cluster.request.mock_calls) == 1 assert cluster.request.call_args == call( - False, ON, (), expect_reply=True, manufacturer=None, tsn=None + False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None ) # turn off from HA @@ -150,7 +150,7 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device): ) assert len(cluster.request.mock_calls) == 1 assert cluster.request.call_args == call( - False, OFF, (), expect_reply=True, manufacturer=None, tsn=None + False, OFF, (), expect_reply=True, manufacturer=None, tries=1, tsn=None ) # test joining a new switch to the network and HA From 769c5c1779071b9502ff52a63f924d2ea0ced88a Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 31 Oct 2020 16:05:59 -0500 Subject: [PATCH 090/118] Fix canary camera entity inheritance (#42691) --- homeassistant/components/canary/camera.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 608dfe02b8..a90eb5a282 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -78,6 +78,7 @@ class CanaryCamera(CoordinatorEntity, Camera): def __init__(self, hass, coordinator, location_id, device, timeout, ffmpeg_args): """Initialize a Canary security camera.""" super().__init__(coordinator) + Camera.__init__(self) self._ffmpeg = hass.data[DATA_FFMPEG] self._ffmpeg_arguments = ffmpeg_args self._location_id = location_id From 43a990350b8413762ccc34e62c19ee19540fe8dd Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Sun, 1 Nov 2020 12:25:34 -0500 Subject: [PATCH 091/118] Use pylutron_caseta 0.7.1 (#42701) --- homeassistant/components/lutron_caseta/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lutron_caseta/manifest.json b/homeassistant/components/lutron_caseta/manifest.json index f80e34a6b5..366c9cb7f1 100644 --- a/homeassistant/components/lutron_caseta/manifest.json +++ b/homeassistant/components/lutron_caseta/manifest.json @@ -3,7 +3,7 @@ "name": "Lutron Caséta", "documentation": "https://www.home-assistant.io/integrations/lutron_caseta", "requirements": [ - "pylutron-caseta==0.7.0" + "pylutron-caseta==0.7.1" ], "codeowners": [ "@swails" diff --git a/requirements_all.txt b/requirements_all.txt index 93942625b6..0113e7e81b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1487,7 +1487,7 @@ pylitejet==0.1 pyloopenergy==0.2.1 # homeassistant.components.lutron_caseta -pylutron-caseta==0.7.0 +pylutron-caseta==0.7.1 # homeassistant.components.lutron pylutron==0.2.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 425838fe4e..81c138dbda 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -730,7 +730,7 @@ pylibrespot-java==0.1.0 pylitejet==0.1 # homeassistant.components.lutron_caseta -pylutron-caseta==0.7.0 +pylutron-caseta==0.7.1 # homeassistant.components.mailgun pymailgunner==1.4 From aecc3476e84fcdd87675c381c9eea01247f2a338 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 1 Nov 2020 20:00:21 +0000 Subject: [PATCH 092/118] Bumped version to 0.117.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 95ce48ca88..44c3669db9 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 64707d89ddc592bf00acd779a4550886c90f5e8e Mon Sep 17 00:00:00 2001 From: Andreas Billmeier Date: Wed, 4 Nov 2020 10:06:35 +0100 Subject: [PATCH 093/118] Please set exact cryptography version cryptography==3.2 (#42611) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 203bbb6202..01c2a30d2d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -8,7 +8,7 @@ attrs==19.3.0 bcrypt==3.1.7 certifi>=2020.6.20 ciso8601==2.1.3 -cryptography==3.2.0 +cryptography==3.2 defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 diff --git a/requirements.txt b/requirements.txt index 1586fdddee..498b948e47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ httpx==0.16.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.2 PyJWT==1.7.1 -cryptography==3.2.0 +cryptography==3.2 pip>=8.0.3 python-slugify==4.0.1 pytz>=2020.1 diff --git a/setup.py b/setup.py index e501ac6cf1..a1794f7880 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ REQUIRES = [ "jinja2>=2.11.2", "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. - "cryptography==3.2.0", + "cryptography==3.2", "pip>=8.0.3", "python-slugify==4.0.1", "pytz>=2020.1", From dc0d27ec18202c3c5798d040400ba5a50692608d Mon Sep 17 00:00:00 2001 From: Andreas Billmeier Date: Wed, 4 Nov 2020 10:17:40 +0100 Subject: [PATCH 094/118] Fix broken maxcube component (#42674) --- homeassistant/components/maxcube/binary_sensor.py | 2 +- homeassistant/components/maxcube/climate.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/maxcube/binary_sensor.py b/homeassistant/components/maxcube/binary_sensor.py index 06b5345697..376076352a 100644 --- a/homeassistant/components/maxcube/binary_sensor.py +++ b/homeassistant/components/maxcube/binary_sensor.py @@ -16,7 +16,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): name = f"{cube.room_by_id(device.room_id).name} {device.name}" # Only add Window Shutters - if cube.is_windowshutter(device): + if device.is_windowshutter(): devices.append(MaxCubeShutter(handler, name, device.rf_address)) if devices: diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index e222784ca5..c17cc988c1 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -70,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in cube.devices: name = f"{cube.room_by_id(device.room_id).name} {device.name}" - if cube.is_thermostat(device) or cube.is_wallthermostat(device): + if device.is_thermostat() or device.is_wallthermostat(): devices.append(MaxCubeClimate(handler, name, device.rf_address)) if devices: @@ -180,11 +180,11 @@ class MaxCubeClimate(ClimateEntity): device = cube.device_by_rf(self._rf_address) valve = 0 - if cube.is_thermostat(device): + if device.is_thermostat(): valve = device.valve_position - elif cube.is_wallthermostat(device): + elif device.is_wallthermostat(): for device in cube.devices_by_room(cube.room_by_id(device.room_id)): - if cube.is_thermostat(device) and device.valve_position > 0: + if device.is_thermostat() and device.valve_position > 0: valve = device.valve_position break else: @@ -287,7 +287,7 @@ class MaxCubeClimate(ClimateEntity): cube = self._cubehandle.cube device = cube.device_by_rf(self._rf_address) - if not cube.is_thermostat(device): + if not device.is_thermostat(): return {} return {ATTR_VALVE_POSITION: device.valve_position} From 74d8569aab8efd9b6b4d00ee8601631de0529eab Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 2 Nov 2020 07:08:36 -0500 Subject: [PATCH 095/118] Fix Vizio host string for zeroconf discovery (#42738) --- homeassistant/components/vizio/config_flow.py | 8 +++-- tests/components/vizio/test_config_flow.py | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index f3b7720a33..b6d6d9bfb0 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -348,9 +348,11 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle zeroconf discovery.""" assert self.hass - discovery_info[ - CONF_HOST - ] = f"{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}" + # If host already has port, no need to add it again + if ":" not in discovery_info[CONF_HOST]: + discovery_info[ + CONF_HOST + ] = f"{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}" # Set default name to discovered device name by stripping zeroconf service # (`type`) from `name` diff --git a/tests/components/vizio/test_config_flow.py b/tests/components/vizio/test_config_flow.py index 5cbc2fa556..32469fabd0 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -27,6 +27,7 @@ from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PIN, + CONF_PORT, ) from homeassistant.helpers.typing import HomeAssistantType @@ -777,6 +778,35 @@ async def test_zeroconf_flow_already_configured( assert result["reason"] == "already_configured" +async def test_zeroconf_flow_with_port_in_host( + hass: HomeAssistantType, + vizio_connect: pytest.fixture, + vizio_bypass_setup: pytest.fixture, + vizio_guess_device_type: pytest.fixture, +) -> None: + """Test entity is already configured during zeroconf setup when port is in host.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_SPEAKER_CONFIG, + options={CONF_VOLUME_STEP: VOLUME_STEP}, + unique_id=UNIQUE_ID, + ) + entry.add_to_hass(hass) + + # Try rediscovering same device, this time with port already in host + discovery_info = MOCK_ZEROCONF_SERVICE_INFO.copy() + discovery_info[ + CONF_HOST + ] = f"{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info + ) + + # Flow should abort because device is already setup + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + async def test_zeroconf_dupe_fail( hass: HomeAssistantType, vizio_connect: pytest.fixture, From beaaa1478c4c59612708476d7b14e3f836bf5255 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Mon, 2 Nov 2020 15:13:53 +0100 Subject: [PATCH 096/118] Bump bimmer_connected to 0.7.10: fix login issues, add (PH)EV services (#42747) Co-authored-by: rikroe --- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index bd76294c8e..9e49703c43 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.7.8"], + "requirements": ["bimmer_connected==0.7.10"], "dependencies": [], "codeowners": ["@gerard33", "@rikroe"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0113e7e81b..34dea8659f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -342,7 +342,7 @@ beautifulsoup4==4.9.1 bellows==0.20.3 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.8 +bimmer_connected==0.7.10 # homeassistant.components.bizkaibus bizkaibus==0.1.1 From b7d0a21a8bd2848b18423b02e01d1d3f02014163 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 2 Nov 2020 10:51:03 -0700 Subject: [PATCH 097/118] Bump pyairvisual to 5.0.4 (#42760) --- homeassistant/components/airvisual/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airvisual/manifest.json b/homeassistant/components/airvisual/manifest.json index d11f3ab69a..351c725110 100644 --- a/homeassistant/components/airvisual/manifest.json +++ b/homeassistant/components/airvisual/manifest.json @@ -3,6 +3,6 @@ "name": "AirVisual", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/airvisual", - "requirements": ["pyairvisual==5.0.3"], + "requirements": ["pyairvisual==5.0.4"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 34dea8659f..9b13ab3a93 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1252,7 +1252,7 @@ pyaehw4a1==0.3.9 pyaftership==0.1.2 # homeassistant.components.airvisual -pyairvisual==5.0.3 +pyairvisual==5.0.4 # homeassistant.components.almond pyalmond==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 81c138dbda..8b20708f03 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -615,7 +615,7 @@ py_nextbusnext==0.1.4 pyaehw4a1==0.3.9 # homeassistant.components.airvisual -pyairvisual==5.0.3 +pyairvisual==5.0.4 # homeassistant.components.almond pyalmond==0.0.2 From e6cc35cff2fdc4861d3ba9d7fa138ed085696c95 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 2 Nov 2020 14:57:53 -0700 Subject: [PATCH 098/118] Fix incorrect property usage for SimpliSafe (#42770) --- .../components/simplisafe/alarm_control_panel.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 11f794eef5..7634f1cce8 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -159,7 +159,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): try: await self._system.set_off() except SimplipyError as err: - LOGGER.error('Error while disarming "%s": %s', self._system.name, err) + LOGGER.error('Error while disarming "%s": %s', self._system.system_id, err) return self._state = STATE_ALARM_DISARMED @@ -172,7 +172,9 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): try: await self._system.set_home() except SimplipyError as err: - LOGGER.error('Error while arming "%s" (home): %s', self._system.name, err) + LOGGER.error( + 'Error while arming "%s" (home): %s', self._system.system_id, err + ) return self._state = STATE_ALARM_ARMED_HOME @@ -185,7 +187,9 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): try: await self._system.set_away() except SimplipyError as err: - LOGGER.error('Error while arming "%s" (away): %s', self._system.name, err) + LOGGER.error( + 'Error while arming "%s" (away): %s', self._system.system_id, err + ) return self._state = STATE_ALARM_ARMING From a7bdd2aafe64ead72e3ef96be7d525ed68e96582 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 4 Nov 2020 10:03:32 +0100 Subject: [PATCH 099/118] Force color or white mode exclusivity for Tasmota lights (#42772) --- homeassistant/components/tasmota/light.py | 5 +++++ tests/components/tasmota/test_light.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/homeassistant/components/tasmota/light.py b/homeassistant/components/tasmota/light.py index 1a41e7373f..a680d873f9 100644 --- a/homeassistant/components/tasmota/light.py +++ b/homeassistant/components/tasmota/light.py @@ -128,6 +128,11 @@ class TasmotaLight( white_value = float(attributes["white_value"]) percent_white = white_value / TASMOTA_BRIGHTNESS_MAX self._white_value = percent_white * 255 + if self._white_value == 0: + self._color_temp = None + self._white_value = None + if self._white_value is not None and self._white_value > 0: + self._hs = None self.async_write_ha_state() @property diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index 48b1dec723..9210c577a5 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -432,6 +432,8 @@ async def test_controlling_state_via_mqtt_rgbww(hass, mqtt_mock, setup_tasmota): state = hass.states.get("light.test") assert state.state == STATE_ON assert state.attributes.get("white_value") == 127.5 + # Setting white > 0 should clear the color + assert not state.attributes.get("rgb_color") async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON","CT":300}' @@ -440,6 +442,15 @@ async def test_controlling_state_via_mqtt_rgbww(hass, mqtt_mock, setup_tasmota): assert state.state == STATE_ON assert state.attributes.get("color_temp") == 300 + async_fire_mqtt_message( + hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON","White":0}' + ) + state = hass.states.get("light.test") + assert state.state == STATE_ON + # Setting white to 0 should clear the white_value and color_temp + assert not state.attributes.get("white_value") + assert not state.attributes.get("color_temp") + async_fire_mqtt_message( hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON","Scheme":3}' ) From d967ecc8c69295f802d4c763581ff341c8fcbc4e Mon Sep 17 00:00:00 2001 From: cgtobi Date: Tue, 3 Nov 2020 00:17:37 +0100 Subject: [PATCH 100/118] Bump pyatmo to 4.2.0 (#42774) --- homeassistant/components/netatmo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json index 12c9220c36..824b835b01 100644 --- a/homeassistant/components/netatmo/manifest.json +++ b/homeassistant/components/netatmo/manifest.json @@ -3,7 +3,7 @@ "name": "Netatmo", "documentation": "https://www.home-assistant.io/integrations/netatmo", "requirements": [ - "pyatmo==4.1.0" + "pyatmo==4.2.0" ], "after_dependencies": [ "cloud", diff --git a/requirements_all.txt b/requirements_all.txt index 9b13ab3a93..91ef29800b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1264,7 +1264,7 @@ pyarlo==0.2.3 pyatag==0.3.4.4 # homeassistant.components.netatmo -pyatmo==4.1.0 +pyatmo==4.2.0 # homeassistant.components.atome pyatome==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8b20708f03..9bd8137a1e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -627,7 +627,7 @@ pyarlo==0.2.3 pyatag==0.3.4.4 # homeassistant.components.netatmo -pyatmo==4.1.0 +pyatmo==4.2.0 # homeassistant.components.blackbird pyblackbird==0.5 From 43486917bfd947549dfe1130bc08702a8922b224 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 3 Nov 2020 00:20:02 +0100 Subject: [PATCH 101/118] Fix broken 2fa authentication in synology_dsm (#42775) --- homeassistant/components/synology_dsm/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 44a68b4c18..a9c2f47e6b 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -258,10 +258,9 @@ class SynoApi: self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), + device_token=self._entry.data.get("device_token"), ) - await self._hass.async_add_executor_job( - self.dsm.login, self._entry.data.get("device_token") - ) + await self._hass.async_add_executor_job(self.dsm.login) self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) From 0dc926a13112a6211d30d6e13e1457e788a1fc96 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Wed, 4 Nov 2020 10:04:30 +0100 Subject: [PATCH 102/118] Bump bimmer_connected to 0.7.11 (#42788) --- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index 9e49703c43..6a886fc9ef 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.7.10"], + "requirements": ["bimmer_connected==0.7.11"], "dependencies": [], "codeowners": ["@gerard33", "@rikroe"] } diff --git a/requirements_all.txt b/requirements_all.txt index 91ef29800b..4121b1ad03 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -342,7 +342,7 @@ beautifulsoup4==4.9.1 bellows==0.20.3 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.10 +bimmer_connected==0.7.11 # homeassistant.components.bizkaibus bizkaibus==0.1.1 From 32652fde488832be7674bd3cb1e631dd0c0ca392 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Tue, 3 Nov 2020 23:01:52 +0100 Subject: [PATCH 103/118] Filter rfxtrx replace devices in option flow on existing config entry (#42800) * Fix function _can_replace_device * Rearrange --- homeassistant/components/rfxtrx/config_flow.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 624b7ade69..f81fdde3c4 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -360,13 +360,17 @@ class OptionsFlow(config_entries.OptionsFlow): """Check if device can be replaced with selected device.""" device_data = self._get_device_data(entry_id) event_code = device_data[CONF_EVENT_CODE] - rfx_obj = get_rfx_object(event_code) - if ( - rfx_obj.device.packettype == self._selected_device_object.device.packettype - and rfx_obj.device.subtype == self._selected_device_object.device.subtype - and self._selected_device_event_code != event_code - ): - return True + + if event_code is not None: + rfx_obj = get_rfx_object(event_code) + if ( + rfx_obj.device.packettype + == self._selected_device_object.device.packettype + and rfx_obj.device.subtype + == self._selected_device_object.device.subtype + and self._selected_device_event_code != event_code + ): + return True return False From e4f88a02f7a41f46dc02222e30269ae43d65fc4f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 3 Nov 2020 21:52:47 -1000 Subject: [PATCH 104/118] Fix rest sensors with resource templates (#42818) --- homeassistant/components/rest/binary_sensor.py | 4 ++-- homeassistant/components/rest/sensor.py | 4 ++-- tests/components/rest/test_binary_sensor.py | 2 +- tests/components/rest/test_sensor.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/rest/binary_sensor.py b/homeassistant/components/rest/binary_sensor.py index ec8b3ba012..c19bfe307d 100644 --- a/homeassistant/components/rest/binary_sensor.py +++ b/homeassistant/components/rest/binary_sensor.py @@ -84,7 +84,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= if resource_template is not None: resource_template.hass = hass - resource = resource_template.render(parse_result=False) + resource = resource_template.async_render(parse_result=False) if value_template is not None: value_template.hass = hass @@ -189,6 +189,6 @@ class RestBinarySensor(BinarySensorEntity): async def async_update(self): """Get the latest data from REST API and updates the state.""" if self._resource_template is not None: - self.rest.set_url(self._resource_template.render(parse_result=False)) + self.rest.set_url(self._resource_template.async_render(parse_result=False)) await self.rest.async_update() diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 323447ba1b..826160604b 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -103,7 +103,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= if resource_template is not None: resource_template.hass = hass - resource = resource_template.render(parse_result=False) + resource = resource_template.async_render(parse_result=False) if username and password: if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION: @@ -202,7 +202,7 @@ class RestSensor(Entity): async def async_update(self): """Get the latest data from REST API and update the state.""" if self._resource_template is not None: - self.rest.set_url(self._resource_template.render(parse_result=False)) + self.rest.set_url(self._resource_template.async_render(parse_result=False)) await self.rest.async_update() diff --git a/tests/components/rest/test_binary_sensor.py b/tests/components/rest/test_binary_sensor.py index 276dc293f8..1f2c88f427 100644 --- a/tests/components/rest/test_binary_sensor.py +++ b/tests/components/rest/test_binary_sensor.py @@ -116,7 +116,7 @@ async def test_setup_minimum_resource_template(hass): { "binary_sensor": { "platform": "rest", - "resource_template": "http://localhost", + "resource_template": "{% set url = 'http://localhost' %}{{ url }}", } }, ) diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index 6a7e444ced..d841f69e45 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -102,7 +102,7 @@ async def test_setup_minimum_resource_template(hass): { "sensor": { "platform": "rest", - "resource_template": "http://localhost", + "resource_template": "{% set url = 'http://localhost' %}{{ url }}", } }, ) From 45dbd498524727bd4a45f1690010ca195b0b9476 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 4 Nov 2020 10:04:45 +0100 Subject: [PATCH 105/118] Fix evohome scheduling coroutines (#42821) --- homeassistant/components/evohome/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index f429f5bd2f..818744f7d0 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -431,7 +431,7 @@ class EvoBroker: return if refresh: - self.hass.helpers.event.async_call_later(1, self.async_update()) + self.hass.helpers.event.async_call_later(1, self.async_update) return result From f3c7b77afa6e9e679a68582fe552f0cb25039033 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 4 Nov 2020 09:24:41 +0000 Subject: [PATCH 106/118] Bumped version to 0.117.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 44c3669db9..2360191b14 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 36b099592eff2e0773fe88fe73d2ebe8bdc8f2cb Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Wed, 4 Nov 2020 10:43:33 -0500 Subject: [PATCH 107/118] Update greeclimate to 0.9.5 (#42796) Provides better discovery across subnets in some cases --- homeassistant/components/gree/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/gree/manifest.json b/homeassistant/components/gree/manifest.json index c5fb412f1d..3480d84d97 100644 --- a/homeassistant/components/gree/manifest.json +++ b/homeassistant/components/gree/manifest.json @@ -3,6 +3,6 @@ "name": "Gree Climate", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/gree", - "requirements": ["greeclimate==0.9.2"], + "requirements": ["greeclimate==0.9.5"], "codeowners": ["@cmroche"] -} +} \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 4121b1ad03..c5249892b1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -696,7 +696,7 @@ gpiozero==1.5.1 gps3==0.33.3 # homeassistant.components.gree -greeclimate==0.9.2 +greeclimate==0.9.5 # homeassistant.components.greeneye_monitor greeneye_monitor==2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9bd8137a1e..6a172830d5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -352,7 +352,7 @@ google-cloud-pubsub==0.39.1 google-nest-sdm==0.1.6 # homeassistant.components.gree -greeclimate==0.9.2 +greeclimate==0.9.5 # homeassistant.components.griddy griddypower==0.1.0 From d4b68454a3967d3be3f4c385f0a73b5d4d265ac8 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Wed, 4 Nov 2020 11:30:46 +0100 Subject: [PATCH 108/118] Cleanup dispatchers when unloading rfxtrx (#42803) * Cleanup dispatchers * Fix name * Refactor code * Fix typo in function description * Fix pylint error * Change function definition * Move cleanup_callbacks data to domain --- homeassistant/components/rfxtrx/__init__.py | 20 +++++++++++++++++-- .../components/rfxtrx/binary_sensor.py | 8 ++------ homeassistant/components/rfxtrx/const.py | 4 ++++ homeassistant/components/rfxtrx/cover.py | 6 ++---- homeassistant/components/rfxtrx/light.py | 6 ++---- homeassistant/components/rfxtrx/sensor.py | 6 ++---- homeassistant/components/rfxtrx/switch.py | 6 ++---- 7 files changed, 32 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 9c0615fdb5..5f8d14710e 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -49,6 +49,9 @@ from .const import ( CONF_OFF_DELAY, CONF_REMOVE_DEVICE, CONF_SIGNAL_REPETITIONS, + DATA_CLEANUP_CALLBACKS, + DATA_LISTENER, + DATA_RFXOBJECT, DEVICE_PACKET_TYPE_LIGHTING4, EVENT_RFXTRX_EVENT, SERVICE_SEND, @@ -93,8 +96,6 @@ DATA_TYPES = OrderedDict( ) _LOGGER = logging.getLogger(__name__) -DATA_RFXOBJECT = "rfxobject" -DATA_LISTENER = "ha_stop" def _bytearray_string(data): @@ -188,6 +189,8 @@ async def async_setup_entry(hass, entry: config_entries.ConfigEntry): """Set up the RFXtrx component.""" hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][DATA_CLEANUP_CALLBACKS] = [] + await async_setup_internal(hass, entry) for domain in DOMAINS: @@ -212,12 +215,17 @@ async def async_unload_entry(hass, entry: config_entries.ConfigEntry): hass.services.async_remove(DOMAIN, SERVICE_SEND) + for cleanup_callback in hass.data[DOMAIN][DATA_CLEANUP_CALLBACKS]: + cleanup_callback() + listener = hass.data[DOMAIN][DATA_LISTENER] listener() rfx_object = hass.data[DOMAIN][DATA_RFXOBJECT] await hass.async_add_executor_job(rfx_object.close_connection) + hass.data.pop(DOMAIN) + return True @@ -428,6 +436,14 @@ def get_device_id(device, data_bits=None): return (f"{device.packettype:x}", f"{device.subtype:x}", id_string) +def connect_auto_add(hass, entry_data, callback_fun): + """Connect to dispatcher for automatic add.""" + if entry_data[CONF_AUTOMATIC_ADD]: + hass.data[DOMAIN][DATA_CLEANUP_CALLBACKS].append( + hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, callback_fun) + ) + + class RfxtrxEntity(RestoreEntity): """Represents a Rfxtrx device. diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py index 7fe89e747b..78eb49740d 100644 --- a/homeassistant/components/rfxtrx/binary_sensor.py +++ b/homeassistant/components/rfxtrx/binary_sensor.py @@ -19,11 +19,10 @@ from homeassistant.core import callback from homeassistant.helpers import event as evt from . import ( - CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_OFF_DELAY, - SIGNAL_EVENT, RfxtrxEntity, + connect_auto_add, find_possible_pt2262_device, get_device_id, get_pt2262_cmd, @@ -147,10 +146,7 @@ async def async_setup_entry( async_add_entities([sensor]) # Subscribe to main RFXtrx events - if discovery_info[CONF_AUTOMATIC_ADD]: - hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_EVENT, binary_sensor_update - ) + connect_auto_add(hass, discovery_info, binary_sensor_update) class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity): diff --git a/homeassistant/components/rfxtrx/const.py b/homeassistant/components/rfxtrx/const.py index 404d344cc7..28aec12564 100644 --- a/homeassistant/components/rfxtrx/const.py +++ b/homeassistant/components/rfxtrx/const.py @@ -33,3 +33,7 @@ SERVICE_SEND = "send" DEVICE_PACKET_TYPE_LIGHTING4 = 0x13 EVENT_RFXTRX_EVENT = "rfxtrx_event" + +DATA_RFXOBJECT = "rfxobject" +DATA_LISTENER = "ha_stop" +DATA_CLEANUP_CALLBACKS = "cleanup_callbacks" diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index 86950308f5..dfbaa60f58 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -6,12 +6,11 @@ from homeassistant.const import CONF_DEVICES, STATE_OPEN from homeassistant.core import callback from . import ( - CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS, - SIGNAL_EVENT, RfxtrxCommandEntity, + connect_auto_add, get_device_id, get_rfx_object, ) @@ -81,8 +80,7 @@ async def async_setup_entry( async_add_entities([entity]) # Subscribe to main RFXtrx events - if discovery_info[CONF_AUTOMATIC_ADD]: - hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, cover_update) + connect_auto_add(hass, discovery_info, cover_update) class RfxtrxCover(RfxtrxCommandEntity, CoverEntity): diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index 33ee5ea474..fd790581ed 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -12,12 +12,11 @@ from homeassistant.const import CONF_DEVICES, STATE_ON from homeassistant.core import callback from . import ( - CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS, - SIGNAL_EVENT, RfxtrxCommandEntity, + connect_auto_add, get_device_id, get_rfx_object, ) @@ -95,8 +94,7 @@ async def async_setup_entry( async_add_entities([entity]) # Subscribe to main RFXtrx events - if discovery_info[CONF_AUTOMATIC_ADD]: - hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, light_update) + connect_auto_add(hass, discovery_info, light_update) class RfxtrxLight(RfxtrxCommandEntity, LightEntity): diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index 81e0c60e05..c897e16411 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -20,11 +20,10 @@ from homeassistant.const import ( from homeassistant.core import callback from . import ( - CONF_AUTOMATIC_ADD, CONF_DATA_BITS, DATA_TYPES, - SIGNAL_EVENT, RfxtrxEntity, + connect_auto_add, get_device_id, get_rfx_object, ) @@ -127,8 +126,7 @@ async def async_setup_entry( async_add_entities([entity]) # Subscribe to main RFXtrx events - if discovery_info[CONF_AUTOMATIC_ADD]: - hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, sensor_update) + connect_auto_add(hass, discovery_info, sensor_update) class RfxtrxSensor(RfxtrxEntity): diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index 5306921079..96c066d5f3 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -8,13 +8,12 @@ from homeassistant.const import CONF_DEVICES, STATE_ON from homeassistant.core import callback from . import ( - CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS, DOMAIN, - SIGNAL_EVENT, RfxtrxCommandEntity, + connect_auto_add, get_device_id, get_rfx_object, ) @@ -92,8 +91,7 @@ async def async_setup_entry( async_add_entities([entity]) # Subscribe to main RFXtrx events - if discovery_info[CONF_AUTOMATIC_ADD]: - hass.helpers.dispatcher.async_dispatcher_connect(SIGNAL_EVENT, switch_update) + connect_auto_add(hass, discovery_info, switch_update) class RfxtrxSwitch(RfxtrxCommandEntity, SwitchEntity): From 6bacda3cee276d287878da52a03ab51f6d4ae865 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 4 Nov 2020 15:35:54 +0100 Subject: [PATCH 109/118] Call coordinator.shutdown() when ConfigEntryNotReady (#42833) --- homeassistant/components/brother/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/brother/__init__.py b/homeassistant/components/brother/__init__.py index f7e5fbdb0d..8c5cdb2d7e 100644 --- a/homeassistant/components/brother/__init__.py +++ b/homeassistant/components/brother/__init__.py @@ -34,6 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator.async_refresh() if not coordinator.last_update_success: + coordinator.shutdown() raise ConfigEntryNotReady hass.data.setdefault(DOMAIN, {}) From df6ee2e51f49d096664d2cb1fa94d69847ada132 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 4 Nov 2020 15:47:28 +0000 Subject: [PATCH 110/118] Bumped version to 0.117.4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2360191b14..8911ed2c5f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 2f1d30fa8509dac21c08a4caa18d336311b00d81 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 4 Nov 2020 21:59:08 +0100 Subject: [PATCH 111/118] Bump hatasmota to 0.0.25.1 (#42850) --- .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_common.py | 38 +++++++++++++++++++ tests/components/tasmota/test_discovery.py | 25 +++++++++++- 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index b087e13ece..a0414b6450 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.0.25"], + "requirements": ["hatasmota==0.0.25.1"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index c5249892b1..7504b10e9e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -732,7 +732,7 @@ hass-nabucasa==0.37.1 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.0.25 +hatasmota==0.0.25.1 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6a172830d5..2e74a82e8c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ hangups==0.4.11 hass-nabucasa==0.37.1 # homeassistant.components.tasmota -hatasmota==0.0.25 +hatasmota==0.0.25.1 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/tests/components/tasmota/test_common.py b/tests/components/tasmota/test_common.py index 5c18f2f401..665249f312 100644 --- a/tests/components/tasmota/test_common.py +++ b/tests/components/tasmota/test_common.py @@ -58,6 +58,44 @@ DEFAULT_CONFIG = { } +DEFAULT_CONFIG_9_0_0_4 = { + "ip": "192.168.15.10", + "dn": "Tasmota", + "fn": ["Test", "Beer", "Milk", "Four", None], + "hn": "tasmota_49A3BC-0956", + "if": 0, # iFan + "lk": 1, # RGB + white channels linked to a single light + "mac": "00000049A3BC", + "md": "Sonoff Basic", + "ofln": "Offline", + "onln": "Online", + "state": ["OFF", "ON", "TOGGLE", "HOLD"], + "sw": "8.4.0.2", + "swn": [None, None, None, None, None], + "t": "tasmota_49A3BC", + "ft": "%topic%/%prefix%/", + "tp": ["cmnd", "stat", "tele"], + "rl": [0, 0, 0, 0, 0, 0, 0, 0], + "swc": [-1, -1, -1, -1, -1, -1, -1, -1], + "btn": [0, 0, 0, 0], + "so": { + "4": 0, # Return MQTT response as RESULT or %COMMAND% + "11": 0, # Swap button single and double press functionality + "13": 0, # Allow immediate action on single button press + "17": 1, # Show Color string as hex or comma-separated + "20": 0, # Update of Dimmer/Color/CT without turning power on + "30": 0, # Enforce Home Assistant auto-discovery as light + "68": 0, # Multi-channel PWM instead of a single light + "73": 0, # Enable Buttons decoupling and send multi-press and hold MQTT messages + "82": 0, # Reduce the CT range from 153..500 to 200.380 + "114": 0, # Enable sending switch MQTT messages + }, + "ty": 0, # Tuya MCU + "lt_st": 0, + "ver": 1, +} + + async def help_test_availability_when_connection_lost( hass, mqtt_client_mock, diff --git a/tests/components/tasmota/test_discovery.py b/tests/components/tasmota/test_discovery.py index 607676615a..ba288ef93a 100644 --- a/tests/components/tasmota/test_discovery.py +++ b/tests/components/tasmota/test_discovery.py @@ -6,7 +6,7 @@ from homeassistant.components.tasmota.const import DEFAULT_PREFIX from homeassistant.components.tasmota.discovery import ALREADY_DISCOVERED from .conftest import setup_tasmota_helper -from .test_common import DEFAULT_CONFIG +from .test_common import DEFAULT_CONFIG, DEFAULT_CONFIG_9_0_0_4 from tests.async_mock import patch from tests.common import async_fire_mqtt_message @@ -132,6 +132,29 @@ async def test_device_discover( assert device_entry.sw_version == config["sw"] +async def test_device_discover_deprecated( + hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota +): + """Test setting up a device with deprecated discovery message.""" + config = copy.deepcopy(DEFAULT_CONFIG_9_0_0_4) + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + # Verify device and registry entries are created + device_entry = device_reg.async_get_device(set(), {("mac", mac)}) + assert device_entry is not None + assert device_entry.manufacturer == "Tasmota" + assert device_entry.model == config["md"] + assert device_entry.name == config["dn"] + assert device_entry.sw_version == config["sw"] + + async def test_device_update( hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota ): From ba48fd6c517a1f52d4d5b0ceedf7c93b89659dd1 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 4 Nov 2020 11:41:08 -0700 Subject: [PATCH 112/118] Clean up SimpliSafe binary sensor implementation (#42841) * Clean up SimpliSafe binary sensor implementation * Cleanup * Constant name --- .../components/simplisafe/binary_sensor.py | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/simplisafe/binary_sensor.py b/homeassistant/components/simplisafe/binary_sensor.py index 263bbf51b1..67a4af4752 100644 --- a/homeassistant/components/simplisafe/binary_sensor.py +++ b/homeassistant/components/simplisafe/binary_sensor.py @@ -23,25 +23,25 @@ SUPPORTED_BATTERY_SENSOR_TYPES = [ EntityTypes.temperature, ] -SUPPORTED_SENSOR_TYPES = [ - EntityTypes.entry, +SUPPORTED_TRIGGERED_SENSOR_TYPES = [ EntityTypes.carbon_monoxide, - EntityTypes.smoke, + EntityTypes.entry, EntityTypes.leak, + EntityTypes.smoke, ] -HA_SENSOR_TYPES = { - EntityTypes.entry: DEVICE_CLASS_DOOR, +DEVICE_CLASSES = { EntityTypes.carbon_monoxide: DEVICE_CLASS_GAS, - EntityTypes.smoke: DEVICE_CLASS_SMOKE, + EntityTypes.entry: DEVICE_CLASS_DOOR, EntityTypes.leak: DEVICE_CLASS_MOISTURE, + EntityTypes.smoke: DEVICE_CLASS_SMOKE, } SENSOR_MODELS = { - EntityTypes.entry: "Entry Sensor", EntityTypes.carbon_monoxide: "Carbon Monoxide Detector", - EntityTypes.smoke: "Smoke Detector", + EntityTypes.entry: "Entry Sensor", EntityTypes.leak: "Water Sensor", + EntityTypes.smoke: "Smoke Detector", } @@ -56,10 +56,10 @@ async def async_setup_entry(hass, entry, async_add_entities): continue for sensor in system.sensors.values(): - if sensor.type in SUPPORTED_SENSOR_TYPES: - sensors.append(SimpliSafeBinarySensor(simplisafe, system, sensor)) + if sensor.type in SUPPORTED_TRIGGERED_SENSOR_TYPES: + sensors.append(TriggeredBinarySensor(simplisafe, system, sensor)) if sensor.type in SUPPORTED_BATTERY_SENSOR_TYPES: - sensors.append(SimpliSafeSensorBattery(simplisafe, system, sensor)) + sensors.append(BatteryBinarySensor(simplisafe, system, sensor)) async_add_entities(sensors) @@ -70,6 +70,17 @@ class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity): def __init__(self, simplisafe, system, sensor): """Initialize.""" super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) + self._device_info["identifiers"] = {(DOMAIN, sensor.serial)} + self._device_info["model"] = SENSOR_MODELS[sensor.type] + self._device_info["name"] = sensor.name + + +class TriggeredBinarySensor(SimpliSafeBinarySensor): + """Define a binary sensor related to whether an entity has been triggered.""" + + def __init__(self, simplisafe, system, sensor): + """Initialize.""" + super().__init__(simplisafe, system, sensor) self._system = system self._sensor = sensor self._is_on = False @@ -77,16 +88,7 @@ class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity): @property def device_class(self): """Return type of sensor.""" - return HA_SENSOR_TYPES[self._sensor.type] - - @property - def device_info(self): - """Return device registry information for this entity.""" - info = super().device_info - info["identifiers"] = {(DOMAIN, self._sensor.serial)} - info["model"] = SENSOR_MODELS[self._sensor.type] - info["name"] = self._sensor.name - return info + return DEVICE_CLASSES[self._sensor.type] @property def is_on(self): @@ -99,19 +101,15 @@ class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity): self._is_on = self._sensor.triggered -class SimpliSafeSensorBattery(SimpliSafeEntity, BinarySensorEntity): +class BatteryBinarySensor(SimpliSafeEntity, BinarySensorEntity): """Define a SimpliSafe battery binary sensor entity.""" def __init__(self, simplisafe, system, sensor): """Initialize.""" - super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) + super().__init__(simplisafe, system, sensor) self._sensor = sensor self._is_low = False - self._device_info["identifiers"] = {(DOMAIN, sensor.serial)} - self._device_info["model"] = SENSOR_MODELS[sensor.type] - self._device_info["name"] = sensor.name - @property def device_class(self): """Return type of sensor.""" From 943cc243b5e8af4b0ed0d18b92e8ef369dbd718a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 4 Nov 2020 12:34:14 -0700 Subject: [PATCH 113/118] Fix missing sensor exceptions in SimpliSafe (#42845) * Fix missing sensor exceptions in SimpliSafe * Entity types * Don't overdo it --- .../components/simplisafe/__init__.py | 12 +++++ .../components/simplisafe/binary_sensor.py | 52 ++++++------------- homeassistant/components/simplisafe/sensor.py | 11 ++-- 3 files changed, 31 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index a3f56a9e1a..1389c1d1f0 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -724,3 +724,15 @@ class SimpliSafeEntity(CoordinatorEntity): @callback def async_update_from_websocket_event(self, event): """Update the entity with the provided websocket event.""" + + +class SimpliSafeBaseSensor(SimpliSafeEntity): + """Define a SimpliSafe base (binary) sensor.""" + + def __init__(self, simplisafe, system, sensor): + """Initialize.""" + super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) + self._device_info["identifiers"] = {(DOMAIN, sensor.serial)} + self._device_info["model"] = sensor.type.name + self._device_info["name"] = sensor.name + self._sensor = sensor diff --git a/homeassistant/components/simplisafe/binary_sensor.py b/homeassistant/components/simplisafe/binary_sensor.py index 67a4af4752..aa0132cb33 100644 --- a/homeassistant/components/simplisafe/binary_sensor.py +++ b/homeassistant/components/simplisafe/binary_sensor.py @@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import ( ) from homeassistant.core import callback -from . import SimpliSafeEntity +from . import SimpliSafeBaseSensor from .const import DATA_CLIENT, DOMAIN, LOGGER SUPPORTED_BATTERY_SENSOR_TYPES = [ @@ -23,27 +23,13 @@ SUPPORTED_BATTERY_SENSOR_TYPES = [ EntityTypes.temperature, ] -SUPPORTED_TRIGGERED_SENSOR_TYPES = [ - EntityTypes.carbon_monoxide, - EntityTypes.entry, - EntityTypes.leak, - EntityTypes.smoke, -] - -DEVICE_CLASSES = { +TRIGGERED_SENSOR_TYPES = { EntityTypes.carbon_monoxide: DEVICE_CLASS_GAS, EntityTypes.entry: DEVICE_CLASS_DOOR, EntityTypes.leak: DEVICE_CLASS_MOISTURE, EntityTypes.smoke: DEVICE_CLASS_SMOKE, } -SENSOR_MODELS = { - EntityTypes.carbon_monoxide: "Carbon Monoxide Detector", - EntityTypes.entry: "Entry Sensor", - EntityTypes.leak: "Water Sensor", - EntityTypes.smoke: "Smoke Detector", -} - async def async_setup_entry(hass, entry, async_add_entities): """Set up SimpliSafe binary sensors based on a config entry.""" @@ -56,39 +42,34 @@ async def async_setup_entry(hass, entry, async_add_entities): continue for sensor in system.sensors.values(): - if sensor.type in SUPPORTED_TRIGGERED_SENSOR_TYPES: - sensors.append(TriggeredBinarySensor(simplisafe, system, sensor)) + if sensor.type in TRIGGERED_SENSOR_TYPES: + sensors.append( + TriggeredBinarySensor( + simplisafe, + system, + sensor, + TRIGGERED_SENSOR_TYPES[sensor.type], + ) + ) if sensor.type in SUPPORTED_BATTERY_SENSOR_TYPES: sensors.append(BatteryBinarySensor(simplisafe, system, sensor)) async_add_entities(sensors) -class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity): - """Define a SimpliSafe binary sensor entity.""" - - def __init__(self, simplisafe, system, sensor): - """Initialize.""" - super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) - self._device_info["identifiers"] = {(DOMAIN, sensor.serial)} - self._device_info["model"] = SENSOR_MODELS[sensor.type] - self._device_info["name"] = sensor.name - - -class TriggeredBinarySensor(SimpliSafeBinarySensor): +class TriggeredBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity): """Define a binary sensor related to whether an entity has been triggered.""" - def __init__(self, simplisafe, system, sensor): + def __init__(self, simplisafe, system, sensor, device_class): """Initialize.""" super().__init__(simplisafe, system, sensor) - self._system = system - self._sensor = sensor + self._device_class = device_class self._is_on = False @property def device_class(self): """Return type of sensor.""" - return DEVICE_CLASSES[self._sensor.type] + return self._device_class @property def is_on(self): @@ -101,13 +82,12 @@ class TriggeredBinarySensor(SimpliSafeBinarySensor): self._is_on = self._sensor.triggered -class BatteryBinarySensor(SimpliSafeEntity, BinarySensorEntity): +class BatteryBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity): """Define a SimpliSafe battery binary sensor entity.""" def __init__(self, simplisafe, system, sensor): """Initialize.""" super().__init__(simplisafe, system, sensor) - self._sensor = sensor self._is_low = False @property diff --git a/homeassistant/components/simplisafe/sensor.py b/homeassistant/components/simplisafe/sensor.py index 5909471c45..7e927ee942 100644 --- a/homeassistant/components/simplisafe/sensor.py +++ b/homeassistant/components/simplisafe/sensor.py @@ -4,7 +4,7 @@ from simplipy.entity import EntityTypes from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT from homeassistant.core import callback -from . import SimpliSafeEntity +from . import SimpliSafeBaseSensor from .const import DATA_CLIENT, DOMAIN, LOGGER @@ -25,19 +25,14 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors) -class SimplisafeFreezeSensor(SimpliSafeEntity): +class SimplisafeFreezeSensor(SimpliSafeBaseSensor): """Define a SimpliSafe freeze sensor entity.""" def __init__(self, simplisafe, system, sensor): """Initialize.""" - super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) - self._sensor = sensor + super().__init__(simplisafe, system, sensor) self._state = None - self._device_info["identifiers"] = {(DOMAIN, sensor.serial)} - self._device_info["model"] = "Freeze Sensor" - self._device_info["name"] = sensor.name - @property def device_class(self): """Return type of sensor.""" From 7c084975b72d52e6015ce6103949b01a8573b5f3 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 4 Nov 2020 18:59:54 -0700 Subject: [PATCH 114/118] Fix missing/incorrect SimpliSafe entities (#42846) * Entity types * Don't overdo it * Fix missing/incorrect SimpliSafe entities --- .../components/simplisafe/__init__.py | 20 ++++++++++++++++--- .../components/simplisafe/binary_sensor.py | 6 +++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 1389c1d1f0..f71e69d9aa 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -3,6 +3,7 @@ import asyncio from uuid import UUID from simplipy import API +from simplipy.entity import EntityTypes from simplipy.errors import EndpointUnavailable, InvalidCredentialsError, SimplipyError from simplipy.websocket import ( EVENT_CAMERA_MOTION_DETECTED, @@ -590,6 +591,13 @@ class SimpliSafeEntity(CoordinatorEntity): else: self._serial = system.serial + try: + sensor_type = EntityTypes( + simplisafe.initial_event_to_use[system.system_id].get("sensorType") + ) + except ValueError: + sensor_type = EntityTypes.unknown + self._attrs = { ATTR_LAST_EVENT_INFO: simplisafe.initial_event_to_use[system.system_id].get( "info" @@ -597,9 +605,7 @@ class SimpliSafeEntity(CoordinatorEntity): ATTR_LAST_EVENT_SENSOR_NAME: simplisafe.initial_event_to_use[ system.system_id ].get("sensorName"), - ATTR_LAST_EVENT_SENSOR_TYPE: simplisafe.initial_event_to_use[ - system.system_id - ].get("sensorType"), + ATTR_LAST_EVENT_SENSOR_TYPE: sensor_type.name, ATTR_LAST_EVENT_TIMESTAMP: simplisafe.initial_event_to_use[ system.system_id ].get("eventTimestamp"), @@ -736,3 +742,11 @@ class SimpliSafeBaseSensor(SimpliSafeEntity): self._device_info["model"] = sensor.type.name self._device_info["name"] = sensor.name self._sensor = sensor + self._sensor_type_human_name = " ".join( + [w.title() for w in self._sensor.type.name.split("_")] + ) + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._system.address} {self._name} {self._sensor_type_human_name}" diff --git a/homeassistant/components/simplisafe/binary_sensor.py b/homeassistant/components/simplisafe/binary_sensor.py index aa0132cb33..2a4f430e9c 100644 --- a/homeassistant/components/simplisafe/binary_sensor.py +++ b/homeassistant/components/simplisafe/binary_sensor.py @@ -6,6 +6,8 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_DOOR, DEVICE_CLASS_GAS, DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, BinarySensorEntity, ) @@ -18,7 +20,7 @@ SUPPORTED_BATTERY_SENSOR_TYPES = [ EntityTypes.carbon_monoxide, EntityTypes.entry, EntityTypes.leak, - EntityTypes.lock, + EntityTypes.lock_keypad, EntityTypes.smoke, EntityTypes.temperature, ] @@ -26,7 +28,9 @@ SUPPORTED_BATTERY_SENSOR_TYPES = [ TRIGGERED_SENSOR_TYPES = { EntityTypes.carbon_monoxide: DEVICE_CLASS_GAS, EntityTypes.entry: DEVICE_CLASS_DOOR, + EntityTypes.glass_break: DEVICE_CLASS_SAFETY, EntityTypes.leak: DEVICE_CLASS_MOISTURE, + EntityTypes.motion: DEVICE_CLASS_MOTION, EntityTypes.smoke: DEVICE_CLASS_SMOKE, } From b65c92ce912ef8c65b00c6d3eea9f8ea0570f70c Mon Sep 17 00:00:00 2001 From: cgtobi Date: Thu, 5 Nov 2020 14:16:51 +0100 Subject: [PATCH 115/118] Fix Netatmo public weather sensor bug (#42858) --- .../components/netatmo/netatmo_entity_base.py | 8 ++--- homeassistant/components/netatmo/sensor.py | 32 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/netatmo/netatmo_entity_base.py b/homeassistant/components/netatmo/netatmo_entity_base.py index 6bae7d5416..db2f8877f6 100644 --- a/homeassistant/components/netatmo/netatmo_entity_base.py +++ b/homeassistant/components/netatmo/netatmo_entity_base.py @@ -45,10 +45,10 @@ class NetatmoBase(Entity): data_class["name"], signal_name, self.async_update_callback, - LAT_NE=data_class["LAT_NE"], - LON_NE=data_class["LON_NE"], - LAT_SW=data_class["LAT_SW"], - LON_SW=data_class["LON_SW"], + lat_ne=data_class["lat_ne"], + lon_ne=data_class["lon_ne"], + lat_sw=data_class["lat_sw"], + lon_sw=data_class["lon_sw"], ) else: diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 84e18ba724..2e810ba227 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -202,10 +202,10 @@ async def async_setup_entry(hass, entry, async_add_entities): PUBLICDATA_DATA_CLASS_NAME, signal_name, None, - LAT_NE=area.lat_ne, - LON_NE=area.lon_ne, - LAT_SW=area.lat_sw, - LON_SW=area.lon_sw, + lat_ne=area.lat_ne, + lon_ne=area.lon_ne, + lat_sw=area.lat_sw, + lon_sw=area.lon_sw, ) for sensor_type in SUPPORTED_PUBLIC_SENSOR_TYPES: new_entities.append( @@ -473,10 +473,10 @@ class NetatmoPublicSensor(NetatmoBase): self._data_classes.append( { "name": PUBLICDATA_DATA_CLASS_NAME, - "LAT_NE": area.lat_ne, - "LON_NE": area.lon_ne, - "LAT_SW": area.lat_sw, - "LON_SW": area.lon_sw, + "lat_ne": area.lat_ne, + "lon_ne": area.lon_ne, + "lat_sw": area.lat_sw, + "lon_sw": area.lon_sw, "area_name": area.area_name, SIGNAL_NAME: self._signal_name, } @@ -563,10 +563,10 @@ class NetatmoPublicSensor(NetatmoBase): self._data_classes = [ { "name": PUBLICDATA_DATA_CLASS_NAME, - "LAT_NE": area.lat_ne, - "LON_NE": area.lon_ne, - "LAT_SW": area.lat_sw, - "LON_SW": area.lon_sw, + "lat_ne": area.lat_ne, + "lon_ne": area.lon_ne, + "lat_sw": area.lat_sw, + "lon_sw": area.lon_sw, "area_name": area.area_name, SIGNAL_NAME: self._signal_name, } @@ -577,10 +577,10 @@ class NetatmoPublicSensor(NetatmoBase): PUBLICDATA_DATA_CLASS_NAME, self._signal_name, self.async_update_callback, - LAT_NE=area.lat_ne, - LON_NE=area.lon_ne, - LAT_SW=area.lat_sw, - LON_SW=area.lon_sw, + lat_ne=area.lat_ne, + lon_ne=area.lon_ne, + lat_sw=area.lat_sw, + lon_sw=area.lon_sw, ) @callback From 7d0f5fa31a8400332656b28a8fd7e00cd3becf6a Mon Sep 17 00:00:00 2001 From: Andreas Billmeier Date: Thu, 5 Nov 2020 09:45:29 +0100 Subject: [PATCH 116/118] Revert "Fix broken maxcube component" (#42859) This reverts commit 898f50386ff509ffb7b60a6e3f61eafc96ad484d. --- homeassistant/components/maxcube/binary_sensor.py | 2 +- homeassistant/components/maxcube/climate.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/maxcube/binary_sensor.py b/homeassistant/components/maxcube/binary_sensor.py index 376076352a..06b5345697 100644 --- a/homeassistant/components/maxcube/binary_sensor.py +++ b/homeassistant/components/maxcube/binary_sensor.py @@ -16,7 +16,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): name = f"{cube.room_by_id(device.room_id).name} {device.name}" # Only add Window Shutters - if device.is_windowshutter(): + if cube.is_windowshutter(device): devices.append(MaxCubeShutter(handler, name, device.rf_address)) if devices: diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index c17cc988c1..e222784ca5 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -70,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in cube.devices: name = f"{cube.room_by_id(device.room_id).name} {device.name}" - if device.is_thermostat() or device.is_wallthermostat(): + if cube.is_thermostat(device) or cube.is_wallthermostat(device): devices.append(MaxCubeClimate(handler, name, device.rf_address)) if devices: @@ -180,11 +180,11 @@ class MaxCubeClimate(ClimateEntity): device = cube.device_by_rf(self._rf_address) valve = 0 - if device.is_thermostat(): + if cube.is_thermostat(device): valve = device.valve_position - elif device.is_wallthermostat(): + elif cube.is_wallthermostat(device): for device in cube.devices_by_room(cube.room_by_id(device.room_id)): - if device.is_thermostat() and device.valve_position > 0: + if cube.is_thermostat(device) and device.valve_position > 0: valve = device.valve_position break else: @@ -287,7 +287,7 @@ class MaxCubeClimate(ClimateEntity): cube = self._cubehandle.cube device = cube.device_by_rf(self._rf_address) - if not device.is_thermostat(): + if not cube.is_thermostat(device): return {} return {ATTR_VALVE_POSITION: device.valve_position} From dfed834617736a4130a4a3c1da85e310cd62582e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 5 Nov 2020 13:45:58 +0000 Subject: [PATCH 117/118] Bumped version to 0.117.5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 8911ed2c5f..7b66011629 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 117 -PATCH_VERSION = "4" +PATCH_VERSION = "5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) From 8895c65dd497c3630dd193dc95c4ce0b4a45d960 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Thu, 5 Nov 2020 16:32:02 +0100 Subject: [PATCH 118/118] Bump bimmer_connected to 0.7.12 (#42875) Co-authored-by: rikroe --- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index 6a886fc9ef..85b521caa3 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.7.11"], + "requirements": ["bimmer_connected==0.7.12"], "dependencies": [], "codeowners": ["@gerard33", "@rikroe"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7504b10e9e..c490cb121e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -342,7 +342,7 @@ beautifulsoup4==4.9.1 bellows==0.20.3 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.11 +bimmer_connected==0.7.12 # homeassistant.components.bizkaibus bizkaibus==0.1.1